Skip to content

Commit 338e329

Browse files
feat(parser): migrate commandcode and iflow providers
Command Code and iFlow both fit the directory JSONL source shape, so moving them together proves the helper against real providers without mixing in nested layouts like Qwen or composite providers like WorkBuddy. The providers keep source discovery, changed-path classification, persisted lookup, fingerprinting, and parse normalization behind concrete facade implementations while preserving the legacy parser functions for current runtime callers.
1 parent 55cfa0e commit 338e329

8 files changed

Lines changed: 596 additions & 4 deletions
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package parser
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
var _ Provider = (*commandCodeProvider)(nil)
11+
12+
type commandCodeProviderFactory struct {
13+
def AgentDef
14+
}
15+
16+
func newCommandCodeProviderFactory(def AgentDef) ProviderFactory {
17+
return commandCodeProviderFactory{def: cloneAgentDef(def)}
18+
}
19+
20+
func (f commandCodeProviderFactory) Definition() AgentDef {
21+
return cloneAgentDef(f.def)
22+
}
23+
24+
func (f commandCodeProviderFactory) Capabilities() Capabilities {
25+
return commandCodeProviderCapabilities()
26+
}
27+
28+
func (f commandCodeProviderFactory) NewProvider(cfg ProviderConfig) Provider {
29+
cfg = cfg.Clone()
30+
return &commandCodeProvider{
31+
ProviderBase: ProviderBase{
32+
Def: cloneAgentDef(f.def),
33+
Caps: commandCodeProviderCapabilities(),
34+
Config: cfg,
35+
},
36+
sources: newCommandCodeSourceSet(cfg.Roots),
37+
}
38+
}
39+
40+
type commandCodeProvider struct {
41+
ProviderBase
42+
sources DirectoryJSONLSourceSet
43+
}
44+
45+
func (p *commandCodeProvider) Discover(ctx context.Context) ([]SourceRef, error) {
46+
return p.sources.Discover(ctx)
47+
}
48+
49+
func (p *commandCodeProvider) WatchPlan(ctx context.Context) (WatchPlan, error) {
50+
return p.sources.WatchPlan(ctx)
51+
}
52+
53+
func (p *commandCodeProvider) SourcesForChangedPath(
54+
ctx context.Context,
55+
req ChangedPathRequest,
56+
) ([]SourceRef, error) {
57+
return p.sources.SourcesForChangedPath(ctx, req)
58+
}
59+
60+
func (p *commandCodeProvider) FindSource(
61+
ctx context.Context,
62+
req FindSourceRequest,
63+
) (SourceRef, bool, error) {
64+
return p.sources.FindSource(ctx, providerFindRequestWithRawSessionID(p.Def, req))
65+
}
66+
67+
func (p *commandCodeProvider) Fingerprint(
68+
ctx context.Context,
69+
source SourceRef,
70+
) (SourceFingerprint, error) {
71+
return p.sources.Fingerprint(ctx, source)
72+
}
73+
74+
func (p *commandCodeProvider) Parse(
75+
ctx context.Context,
76+
req ParseRequest,
77+
) (ParseOutcome, error) {
78+
if err := ctx.Err(); err != nil {
79+
return ParseOutcome{}, err
80+
}
81+
path, ok := p.sources.pathFromSource(req.Source)
82+
if !ok {
83+
return ParseOutcome{}, fmt.Errorf("commandcode source path unavailable")
84+
}
85+
machine := firstNonEmptyJSONLString(req.Machine, p.Config.Machine)
86+
sess, msgs, err := ParseCommandCodeSession(path, machine)
87+
if err != nil {
88+
return ParseOutcome{}, err
89+
}
90+
if sess == nil {
91+
return ParseOutcome{
92+
ResultSetComplete: true,
93+
SkipReason: SkipNoSession,
94+
}, nil
95+
}
96+
if req.Fingerprint.Hash != "" {
97+
sess.File.Hash = req.Fingerprint.Hash
98+
}
99+
return ParseOutcome{
100+
Results: []ParseResultOutcome{{
101+
Result: ParseResult{
102+
Session: *sess,
103+
Messages: msgs,
104+
},
105+
DataVersion: DataVersionCurrent,
106+
}},
107+
ResultSetComplete: true,
108+
}, nil
109+
}
110+
111+
func newCommandCodeSourceSet(roots []string) DirectoryJSONLSourceSet {
112+
return NewDirectoryJSONLSourceSet(
113+
AgentCommandCode,
114+
roots,
115+
JSONLSourceSetOptions{
116+
IncludePath: isCommandCodeSourcePath,
117+
ProjectHint: func(root, path string) string { return "" },
118+
SessionIDFromPath: commandCodeSessionIDFromPath,
119+
},
120+
)
121+
}
122+
123+
func isCommandCodeSourcePath(root, path string) bool {
124+
name := filepath.Base(path)
125+
if !strings.HasSuffix(name, ".jsonl") ||
126+
strings.HasSuffix(name, ".checkpoints.jsonl") ||
127+
strings.HasSuffix(name, ".prompts.jsonl") {
128+
return false
129+
}
130+
return IsValidSessionID(strings.TrimSuffix(name, ".jsonl"))
131+
}
132+
133+
func commandCodeSessionIDFromPath(root, path string) string {
134+
name := filepath.Base(path)
135+
if !isCommandCodeSourcePath(root, path) {
136+
return ""
137+
}
138+
return strings.TrimSuffix(name, ".jsonl")
139+
}
140+
141+
func commandCodeProviderCapabilities() Capabilities {
142+
return Capabilities{
143+
Source: jsonlFileProviderSourceCapabilities(),
144+
Content: ContentCapabilities{
145+
FirstMessage: CapabilitySupported,
146+
SessionName: CapabilitySupported,
147+
Cwd: CapabilitySupported,
148+
GitBranch: CapabilitySupported,
149+
Thinking: CapabilitySupported,
150+
ToolCalls: CapabilitySupported,
151+
ToolResults: CapabilitySupported,
152+
MalformedLineCount: CapabilitySupported,
153+
},
154+
}
155+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package parser
2+
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestCommandCodeProviderFactoryReplacesLegacyAdapter(t *testing.T) {
14+
factory, ok := ProviderFactoryByType(AgentCommandCode)
15+
require.True(t, ok)
16+
_, legacyFactory := factory.(legacyProviderFactory)
17+
assert.False(t, legacyFactory)
18+
19+
provider, ok := NewProvider(AgentCommandCode, ProviderConfig{
20+
Roots: []string{t.TempDir()},
21+
Machine: "devbox",
22+
})
23+
require.True(t, ok)
24+
_, legacyProvider := provider.(*legacyProvider)
25+
assert.False(t, legacyProvider)
26+
}
27+
28+
func TestCommandCodeProviderSourceMethods(t *testing.T) {
29+
root := t.TempDir()
30+
projectDir := filepath.Join(root, "users-alice-code-sample-project")
31+
sourcePath := filepath.Join(projectDir, "sess_123.jsonl")
32+
writeSourceFile(t, sourcePath, commandCodeProviderFixture())
33+
writeSourceFile(t, filepath.Join(projectDir, "sess_123.checkpoints.jsonl"), "{}\n")
34+
writeSourceFile(t, filepath.Join(projectDir, "sess_123.prompts.jsonl"), "{}\n")
35+
36+
provider, ok := NewProvider(AgentCommandCode, ProviderConfig{
37+
Roots: []string{root},
38+
Machine: "devbox",
39+
})
40+
require.True(t, ok)
41+
42+
discovered, err := provider.Discover(context.Background())
43+
require.NoError(t, err)
44+
require.Len(t, discovered, 1)
45+
assert.Equal(t, AgentCommandCode, discovered[0].Provider)
46+
assert.Equal(t, sourcePath, discovered[0].DisplayPath)
47+
assert.Empty(t, discovered[0].ProjectHint)
48+
49+
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
50+
FullSessionID: "host~commandcode:sess_123",
51+
})
52+
require.NoError(t, err)
53+
require.True(t, ok)
54+
assert.Equal(t, sourcePath, found.DisplayPath)
55+
56+
found, ok, err = provider.FindSource(context.Background(), FindSourceRequest{
57+
FingerprintKey: sourcePath,
58+
})
59+
require.NoError(t, err)
60+
require.True(t, ok)
61+
assert.Equal(t, sourcePath, found.DisplayPath)
62+
63+
require.NoError(t, os.Remove(sourcePath))
64+
changed, err := provider.SourcesForChangedPath(
65+
context.Background(),
66+
ChangedPathRequest{Path: sourcePath, EventKind: "remove", WatchRoot: root},
67+
)
68+
require.NoError(t, err)
69+
require.Len(t, changed, 1)
70+
assert.Equal(t, sourcePath, changed[0].DisplayPath)
71+
}
72+
73+
func TestCommandCodeProviderParse(t *testing.T) {
74+
root := t.TempDir()
75+
sourcePath := filepath.Join(root, "project", "sess_123.jsonl")
76+
writeSourceFile(t, sourcePath, commandCodeProviderFixture())
77+
78+
provider, ok := NewProvider(AgentCommandCode, ProviderConfig{
79+
Roots: []string{root},
80+
Machine: "devbox",
81+
})
82+
require.True(t, ok)
83+
sources, err := provider.Discover(context.Background())
84+
require.NoError(t, err)
85+
require.Len(t, sources, 1)
86+
87+
outcome, err := provider.Parse(context.Background(), ParseRequest{
88+
Source: sources[0],
89+
Fingerprint: SourceFingerprint{
90+
Key: sourcePath,
91+
Hash: "abc123",
92+
},
93+
})
94+
require.NoError(t, err)
95+
require.True(t, outcome.ResultSetComplete)
96+
require.Len(t, outcome.Results, 1)
97+
assert.Equal(t, DataVersionCurrent, outcome.Results[0].DataVersion)
98+
assert.Equal(t, "commandcode:sess_123", outcome.Results[0].Result.Session.ID)
99+
assert.Equal(t, "devbox", outcome.Results[0].Result.Session.Machine)
100+
assert.Equal(t, "abc123", outcome.Results[0].Result.Session.File.Hash)
101+
assert.Len(t, outcome.Results[0].Result.Messages, 2)
102+
}
103+
104+
func commandCodeProviderFixture() string {
105+
return `{"id":"m1","timestamp":"2026-06-01T10:00:00Z","sessionId":"sess_123","role":"user","content":[{"type":"text","text":"Inspect server logs"}],"gitBranch":"feature/command-code","metadata":{"version":2,"cwd":"/Users/alice/code/sample-project"}}
106+
{"id":"m2","timestamp":"2026-06-01T10:00:03Z","sessionId":"sess_123","role":"assistant","content":[{"type":"text","text":"The error is in the startup path."}],"gitBranch":"feature/command-code","metadata":{"version":2}}`
107+
}

internal/parser/directory_jsonl_source_set.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func NewDirectoryJSONLSourceSet(
3131
}
3232
if options.ProjectHint == nil {
3333
options.ProjectHint = func(root, path string) string {
34-
return filepath.Base(filepath.Dir(path))
34+
return directoryJSONLProjectFromPath(path)
3535
}
3636
}
3737
return DirectoryJSONLSourceSet{
@@ -49,3 +49,7 @@ func isDirectoryJSONLPath(root, path string) bool {
4949
parts[0] != "" && parts[0] != "." && parts[0] != ".." &&
5050
parts[1] != "" && parts[1] != "." && parts[1] != ".."
5151
}
52+
53+
func directoryJSONLProjectFromPath(path string) string {
54+
return filepath.Base(filepath.Dir(path))
55+
}

0 commit comments

Comments
 (0)