-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathattach_transcript.go
More file actions
80 lines (70 loc) · 2.44 KB
/
Copy pathattach_transcript.go
File metadata and controls
80 lines (70 loc) · 2.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package cli
import (
"encoding/json"
"github.com/GrayCodeAI/trace/cli/agent"
"github.com/GrayCodeAI/trace/cli/agent/geminicli"
"github.com/GrayCodeAI/trace/cli/transcript"
)
// transcriptMetadata holds metadata extracted from a single transcript parse pass.
type transcriptMetadata struct {
FirstPrompt string
TurnCount int
Model string
}
// extractTranscriptMetadata parses transcript bytes once and extracts the first user prompt,
// user turn count, and model name. Supports both JSONL (Claude Code, Cursor, OpenCode) and
// Gemini JSON format.
func extractTranscriptMetadata(data []byte) transcriptMetadata {
var meta transcriptMetadata
// Try JSONL format first (Claude Code, Cursor, OpenCode, etc.)
lines, err := transcript.ParseFromBytes(data)
if err == nil {
for _, line := range lines {
if line.Type == transcript.TypeUser {
if prompt := transcript.ExtractUserContent(line.Message); prompt != "" {
meta.TurnCount++
if meta.FirstPrompt == "" {
meta.FirstPrompt = prompt
}
}
}
if line.Type == transcript.TypeAssistant && meta.Model == "" {
var msg struct {
Model string `json:"model"`
}
if json.Unmarshal(line.Message, &msg) == nil && msg.Model != "" {
meta.Model = msg.Model
}
}
}
if meta.TurnCount > 0 || meta.Model != "" {
return meta
}
}
// Fallback: try Gemini JSON format {"messages": [...]}
if prompts, gemErr := geminicli.ExtractAllUserPrompts(data); gemErr == nil && len(prompts) > 0 {
meta.FirstPrompt = prompts[0]
meta.TurnCount = len(prompts)
}
return meta
}
// extractTranscriptMetadataForAgent augments the generic attach parser with
// agent-native prompt and model extraction when available. Native extractors
// are authoritative because they understand format-specific nesting and
// conversation branches (Pi, Codex, Droid, etc.); failures remain best-effort
// and preserve whatever the generic parser found.
func extractTranscriptMetadataForAgent(ag agent.Agent, sessionRef string, data []byte) transcriptMetadata {
meta := extractTranscriptMetadata(data)
if extractor, ok := agent.AsPromptExtractor(ag); ok {
if prompts, err := extractor.ExtractPrompts(sessionRef, 0); err == nil && len(prompts) > 0 {
meta.FirstPrompt = prompts[0]
meta.TurnCount = len(prompts)
}
}
if extractor, ok := agent.AsModelExtractor(ag); ok {
if model, err := extractor.ExtractModel(data); err == nil && model != "" {
meta.Model = model
}
}
return meta
}