# agent: dedupe transcript scanning in opencode and copilot-cli

`06f522b`·

Soph·2w ago·2 files·+39 added/-75 removed

opencode's ExtractModifiedFilesFromOffset and ExtractModifiedFiles repeated the assistant-message/tool-part file collection loop — extract modifiedFilesFromMessages and call it from both. copilot-cli's summary and model extraction repeated the scan-newest-first/unmarshal/return pattern three times — extract lastEventField[T] and express the model-change-then-tool-telemetry fallback as two calls. Covered by the existing transcript extraction tests in both packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

## Sessions

11253d4586b3View transcript

## Changes

2

- cmd/entire/cli/agent

- copilotcli
    - Mtranscript.go+29/-48
  - opencode
    - Mtranscript.go+10/-27

```
168 unmodified lines

// lastEventField scans events newest-first for entries of eventType,
// returning the first non-empty value extract produces (skipping entries
// whose data doesn't unmarshal).
func lastEventField[T any](events []copilotEvent, eventType string, extract func(T) string) string {
	for i := len(events) - 1; i >= 0; i-- {
		if events[i].Type != eventType {
			continue
		}

var data T
		if err := json.Unmarshal(events[i].Data, &data); err != nil {
			continue
		}

if v := extract(data); v != "" {
			return v
		}
	}

return ""
}

// extractSummaryFromEvents returns the content of the last assistant.message event.
func extractSummaryFromEvents(events []copilotEvent) string {
	for i := len(events) - 1; i >= 0; i-- {
		if events[i].Type != eventTypeAssistantMsg {
			continue
		}

var data assistantMessageData
		if err := json.Unmarshal(events[i].Data, &data); err != nil {
			continue
		}

if data.Content != "" {
			return data.Content
		}
	}

return ""
}

// extractModelFromEvents returns the model from transcript events.
// First checks session.model_change events, then falls back to the model field
// in tool.execution_complete events (Copilot CLI includes model per tool call).
func extractModelFromEvents(events []copilotEvent) string {
	// Primary: session.model_change (explicit model declaration)
	for i := len(events) - 1; i >= 0; i-- {
		if events[i].Type != eventTypeModelChange {
			continue
		}

var data modelChangeData
		if err := json.Unmarshal(events[i].Data, &data); err != nil {
			continue
		}

if data.NewModel != "" {
			return data.NewModel
		}
	}
	if model := lastEventField(events, eventTypeModelChange,
		func(d modelChangeData) string { return d.NewModel }); model != "" {
		return model
	}

// Fallback: tool.execution_complete events include a model field
	for i := len(events) - 1; i >= 0; i-- {
		if events[i].Type != eventTypeToolExecDone {
			continue
		}

var data toolExecCompleteData
		if err := json.Unmarshal(events[i].Data, &data); err != nil {
			continue
		}

if data.Model != "" {
			return data.Model
		}
	}

return ""
}

// sessionShutdownData is the data payload for session.shutdown events.
```

Mcmd/entire/cli/agent/copilotcli/transcript.go+29/-48

```
102 unmodified lines

// modifiedFilesFromMessages collects unique file paths touched by
// file-modification tool calls in msgs[startOffset:].
func modifiedFilesFromMessages(msgs []ExportMessage, startOffset int) []string {
	seen := make(map[string]bool)
	var files []string

for i := startOffset; i < len(session.Messages); i++ {
		msg := session.Messages[i]

}
	return files
}

// ExtractModifiedFiles extracts modified file paths from raw export JSON transcript bytes.
```
