Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/agentsview/session_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func newSessionExportCommand() *cobra.Command {
// conversations, so streaming the whole file would disclose
// unrelated conversations. Filter to the requested conversation.
if tracePath, conversationID, ok :=
parser.ParseVisualStudioCopilotVirtualPath(storedPath); ok {
parser.SplitVisualStudioCopilotVirtualPath(storedPath); ok {
err := parser.WriteVisualStudioCopilotConversationJSONL(
cmd.OutOrStdout(), tracePath, conversationID,
)
Expand Down
67 changes: 62 additions & 5 deletions cmd/agentsview/token_use.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,19 @@ func resolveRawSessionID(

// Canonical disk probe: if the input starts with a known
// agent prefix, trust that interpretation first and strip
// before calling FindSourceFunc (which rejects IDs with
// before resolving the source (which rejects IDs with
// colons via IsValidSessionID).
for _, def := range parser.Registry {
if def.IDPrefix == "" || !def.FileBased ||
def.FindSourceFunc == nil {
!agentHasDiskSourceLookup(def) {
continue
}
if !strings.HasPrefix(input, def.IDPrefix) {
continue
}
bareID := strings.TrimPrefix(input, def.IDPrefix)
for _, dir := range agentDirs[def.Type] {
if def.FindSourceFunc(dir, bareID) != "" {
if findAgentSourceFile(def, dir, bareID) != "" {
return input, true
}
}
Expand All @@ -110,11 +110,11 @@ func resolveRawSessionID(
// colon-bearing raw IDs (Kimi, OpenClaw, Kiro IDE) may
// match.
for _, def := range parser.Registry {
if !def.FileBased || def.FindSourceFunc == nil {
if !def.FileBased || !agentHasDiskSourceLookup(def) {
continue
}
for _, dir := range agentDirs[def.Type] {
if def.FindSourceFunc(dir, input) != "" {
if findAgentSourceFile(def, dir, input) != "" {
return def.IDPrefix + input, true
}
}
Expand All @@ -123,6 +123,63 @@ func resolveRawSessionID(
return input, false
}

// agentHasDiskSourceLookup reports whether a session source can be located on
// disk by raw ID for the agent: via the legacy AgentDef FindSourceFunc hook, or
// via a provider-authoritative provider's FindSource for agents whose lookup was
// folded onto the provider (e.g. Codex).
func agentHasDiskSourceLookup(def parser.AgentDef) bool {
if def.FindSourceFunc != nil {
return true
}
if parser.ProviderMigrationModes()[def.Type] !=
parser.ProviderMigrationProviderAuthoritative {
return false
}
_, ok := parser.ProviderFactoryByType(def.Type)
return ok
}

// findAgentSourceFile resolves a raw agent session ID to an on-disk source path
// under dir, using the legacy FindSourceFunc when present and otherwise the
// provider's FindSource (RawSessionID lookup). Returns "" when no source
// resolves or the agent has no on-disk lookup.
func findAgentSourceFile(def parser.AgentDef, dir, rawID string) string {
if def.FindSourceFunc != nil {
return def.FindSourceFunc(dir, rawID)
}
factory, ok := parser.ProviderFactoryByType(def.Type)
if !ok {
return ""
}
provider := factory.NewProvider(parser.ProviderConfig{Roots: []string{dir}})
source, found, err := provider.FindSource(
context.Background(),
parser.FindSourceRequest{RawSessionID: rawID},
)
if err != nil || !found {
return ""
}
if path, ok := providerSourcePath(source); ok {
return path
}
return ""
}

// providerSourcePath extracts the on-disk path a provider SourceRef points to,
// preferring the display path and falling back to the fingerprint key or key.
func providerSourcePath(source parser.SourceRef) (string, bool) {
for _, candidate := range []string{
source.DisplayPath,
source.FingerprintKey,
source.Key,
} {
if candidate != "" {
return candidate, true
}
}
return "", false
}

// usageExitCode classifies a SessionUsage into an exit code: 2 when
// the session is not in the DB, 0 when token data OR cost is present,
// 3 when the session exists but has neither. Cost-only sessions
Expand Down
40 changes: 30 additions & 10 deletions internal/parser/codex.go
Original file line number Diff line number Diff line change
Expand Up @@ -1299,12 +1299,31 @@ func IsCodexExecSessionFile(path string) bool {
return false
}

// ParseCodexSession parses a Codex JSONL session file.
// The includeExec parameter is retained for backward
// compatibility; exec-originated sessions are now always
// parsed and imported.
// ParseCodexSession and ParseCodexSessionFrom are the exported seam used by the
// S3 sync path (internal/sync), which buffers an s3:// Codex object to a temp
// file and parses it through the legacy processCodex. The Codex provider owns
// these bodies as receiver methods that use no receiver state, so the wrappers
// invoke them on a zero-value provider. They are removed once S3 support folds
// into the JSONL source sets.
func ParseCodexSession(
path, machine string, includeExec bool,
) (*ParsedSession, []ParsedMessage, error) {
return (&codexProvider{}).parseSession(path, machine, includeExec)
}

func ParseCodexSessionFrom(
path string, offset int64, startOrdinal int, includeExec bool,
) ([]ParsedMessage, time.Time, int64, error) {
return (&codexProvider{}).parseSessionFrom(path, offset, startOrdinal, includeExec)
}

// parseSession parses a Codex JSONL session file into a session and its
// messages. The includeExec parameter is retained for backward compatibility;
// exec-originated sessions are now always parsed and imported. This is the
// provider-owned parse entrypoint; the package-level free function was folded
// onto the provider.
func (p *codexProvider) parseSession(
path, machine string, includeExec bool,
) (*ParsedSession, []ParsedMessage, error) {
info, err := os.Stat(path)
if err != nil {
Expand Down Expand Up @@ -1669,12 +1688,13 @@ func CodexTranscriptConsumedSize(path string) (int64, error) {
return readJSONLFrom(path, 0, func(line string) {})
}

// ParseCodexSessionFrom parses only new lines from a Codex
// JSONL file starting at the given byte offset. Returns only
// the newly parsed messages (with ordinals starting at
// startOrdinal) and the latest timestamp seen. Used for
// incremental re-parsing of large append-only session files.
func ParseCodexSessionFrom(
// parseSessionFrom parses only new lines from a Codex JSONL file starting at
// the given byte offset. Returns only the newly parsed messages (with ordinals
// starting at startOrdinal) and the latest timestamp seen. Used for incremental
// re-parsing of large append-only session files. This is the provider-owned
// incremental parse entrypoint; the package-level free function was folded onto
// the provider.
func (p *codexProvider) parseSessionFrom(
path string,
offset int64,
startOrdinal int,
Expand Down
Loading