Skip to content

Commit 222d635

Browse files
fix(parser): require explicit provider factories
The provider registry should make new agent integrations choose an explicit facade path instead of silently inheriting the migration fallback. Claude.ai and ChatGPT are import-only parsers, so they now use a small ProviderBase-backed import-only provider with no source discovery while registry tests fail if any registered agent falls through to the legacy factory.
1 parent 83f8b73 commit 222d635

3 files changed

Lines changed: 125 additions & 78 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package parser
2+
3+
import "context"
4+
5+
type importOnlyProviderFactory struct {
6+
def AgentDef
7+
}
8+
9+
func newImportOnlyProviderFactory(def AgentDef) ProviderFactory {
10+
return importOnlyProviderFactory{def: cloneAgentDef(def)}
11+
}
12+
13+
func (f importOnlyProviderFactory) Definition() AgentDef {
14+
return cloneAgentDef(f.def)
15+
}
16+
17+
func (f importOnlyProviderFactory) Capabilities() Capabilities {
18+
return Capabilities{}
19+
}
20+
21+
func (f importOnlyProviderFactory) NewProvider(cfg ProviderConfig) Provider {
22+
cfg = cfg.Clone()
23+
return &importOnlyProvider{
24+
ProviderBase: ProviderBase{
25+
Def: cloneAgentDef(f.def),
26+
Config: cfg,
27+
},
28+
}
29+
}
30+
31+
type importOnlyProvider struct {
32+
ProviderBase
33+
}
34+
35+
func (p *importOnlyProvider) Parse(
36+
context.Context,
37+
ParseRequest,
38+
) (ParseOutcome, error) {
39+
return ParseOutcome{}, p.unsupported(ProviderFeatureParse)
40+
}

internal/parser/provider.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ func providerFactoryForDef(def AgentDef) ProviderFactory {
354354
return newAmpProviderFactory(def)
355355
case AgentClaude:
356356
return newClaudeProviderFactory(def)
357+
case AgentClaudeAI:
358+
return newImportOnlyProviderFactory(def)
357359
case AgentCommandCode:
358360
return newCommandCodeProviderFactory(def)
359361
case AgentCodex:
@@ -366,6 +368,8 @@ func providerFactoryForDef(def AgentDef) ProviderFactory {
366368
return newCortexProviderFactory(def)
367369
case AgentCursor:
368370
return newCursorProviderFactory(def)
371+
case AgentChatGPT:
372+
return newImportOnlyProviderFactory(def)
369373
case AgentDeepSeekTUI:
370374
return newDeepSeekTUIProviderFactory(def)
371375
case AgentForge:

internal/parser/provider_test.go

Lines changed: 81 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -148,105 +148,120 @@ func TestProviderRegistryMirrorsAgentRegistry(t *testing.T) {
148148
}
149149
}
150150

151-
func TestLegacyProviderCapabilitiesMatchBaseDefaults(t *testing.T) {
152-
legacyAgent := legacyProviderTestAgent(t)
153-
def, ok := AgentByType(legacyAgent)
154-
require.True(t, ok)
155-
provider, ok := NewProvider(legacyAgent, ProviderConfig{
156-
Roots: []string{t.TempDir()},
157-
Machine: "devbox",
158-
})
159-
require.True(t, ok)
160-
require.NotNil(t, provider)
161-
162-
assert.Equal(t, Capabilities{}, provider.Capabilities())
163-
164-
ctx := context.Background()
165-
discovered, err := provider.Discover(ctx)
166-
require.NoError(t, err)
167-
assert.Empty(t, discovered)
151+
func TestProviderRegistryHasNoLegacyFallback(t *testing.T) {
152+
for _, def := range Registry {
153+
factory := providerFactoryForDef(def)
154+
_, legacyFactory := factory.(legacyProviderFactory)
155+
assert.Falsef(t, legacyFactory, "%s uses legacy provider factory", def.Type)
156+
provider := factory.NewProvider(ProviderConfig{
157+
Roots: []string{t.TempDir()},
158+
Machine: "devbox",
159+
})
160+
_, legacyProvider := provider.(*legacyProvider)
161+
assert.Falsef(t, legacyProvider, "%s uses legacy provider", def.Type)
162+
}
163+
}
168164

169-
plan, err := provider.WatchPlan(ctx)
170-
require.NoError(t, err)
171-
assert.Empty(t, plan.Roots)
165+
func TestImportOnlyProviderCapabilitiesMatchBaseDefaults(t *testing.T) {
166+
for _, agent := range []AgentType{AgentClaudeAI, AgentChatGPT} {
167+
def, ok := AgentByType(agent)
168+
require.True(t, ok)
169+
provider, ok := NewProvider(agent, ProviderConfig{
170+
Roots: []string{t.TempDir()},
171+
Machine: "devbox",
172+
})
173+
require.True(t, ok)
174+
require.NotNil(t, provider)
175+
_, importOnly := provider.(*importOnlyProvider)
176+
require.Truef(t, importOnly, "%s provider is not import-only", agent)
172177

173-
changed, err := provider.SourcesForChangedPath(ctx, ChangedPathRequest{
174-
Path: "/tmp/session.jsonl",
175-
EventKind: "write",
176-
WatchRoot: "/tmp",
177-
})
178-
require.NoError(t, err)
179-
assert.Empty(t, changed)
178+
assert.Equal(t, Capabilities{}, provider.Capabilities())
180179

181-
source, found, err := provider.FindSource(ctx, FindSourceRequest{
182-
RawSessionID: "session",
183-
FullSessionID: def.IDPrefix + "session",
184-
StoredFilePath: "/tmp/session.jsonl",
185-
FingerprintKey: "/tmp/session.jsonl",
186-
})
187-
require.NoError(t, err)
188-
assert.False(t, found)
189-
assert.Empty(t, source)
180+
ctx := context.Background()
181+
discovered, err := provider.Discover(ctx)
182+
require.NoError(t, err)
183+
assert.Empty(t, discovered)
190184

191-
_, err = provider.Fingerprint(ctx, SourceRef{
192-
Provider: legacyAgent,
193-
Key: "session",
194-
DisplayPath: "/tmp/session.jsonl",
195-
FingerprintKey: "/tmp/session.jsonl",
196-
})
197-
require.Error(t, err)
198-
assert.True(t, errors.Is(err, ErrUnsupportedProviderFeature))
185+
plan, err := provider.WatchPlan(ctx)
186+
require.NoError(t, err)
187+
assert.Empty(t, plan.Roots)
199188

200-
incremental, status, err := provider.ParseIncremental(ctx, IncrementalRequest{
201-
Source: SourceRef{Provider: legacyAgent, Key: "session"},
202-
Fingerprint: SourceFingerprint{Key: "/tmp/session.jsonl"},
203-
SessionID: def.IDPrefix + "session",
204-
StartOrdinal: 1,
205-
Machine: "devbox",
206-
})
207-
require.NoError(t, err)
208-
assert.Equal(t, IncrementalUnsupported, status)
209-
assert.Empty(t, incremental)
189+
changed, err := provider.SourcesForChangedPath(ctx, ChangedPathRequest{
190+
Path: "/tmp/session.jsonl",
191+
EventKind: "write",
192+
WatchRoot: "/tmp",
193+
})
194+
require.NoError(t, err)
195+
assert.Empty(t, changed)
196+
197+
source, found, err := provider.FindSource(ctx, FindSourceRequest{
198+
RawSessionID: "session",
199+
FullSessionID: def.IDPrefix + "session",
200+
StoredFilePath: "/tmp/session.jsonl",
201+
FingerprintKey: "/tmp/session.jsonl",
202+
})
203+
require.NoError(t, err)
204+
assert.False(t, found)
205+
assert.Empty(t, source)
206+
207+
_, err = provider.Fingerprint(ctx, SourceRef{
208+
Provider: agent,
209+
Key: "session",
210+
DisplayPath: "/tmp/session.jsonl",
211+
FingerprintKey: "/tmp/session.jsonl",
212+
})
213+
require.Error(t, err)
214+
assert.True(t, errors.Is(err, ErrUnsupportedProviderFeature))
215+
216+
incremental, status, err := provider.ParseIncremental(ctx, IncrementalRequest{
217+
Source: SourceRef{Provider: agent, Key: "session"},
218+
Fingerprint: SourceFingerprint{Key: "/tmp/session.jsonl"},
219+
SessionID: def.IDPrefix + "session",
220+
StartOrdinal: 1,
221+
Machine: "devbox",
222+
})
223+
require.NoError(t, err)
224+
assert.Equal(t, IncrementalUnsupported, status)
225+
assert.Empty(t, incremental)
226+
}
210227
}
211228

212229
func TestProviderFactoryLookupAndConfigSnapshot(t *testing.T) {
213230
cfg := ProviderConfig{
214231
Roots: []string{"/tmp/one", "/tmp/two"},
215232
Machine: "devbox",
216233
}
217-
legacyAgent := legacyProviderTestAgent(t)
218234

219-
factory, ok := ProviderFactoryByType(legacyAgent)
235+
factory, ok := ProviderFactoryByType(AgentClaudeAI)
220236
require.True(t, ok)
221-
assert.Equal(t, legacyAgent, factory.Definition().Type)
237+
assert.Equal(t, AgentClaudeAI, factory.Definition().Type)
222238

223-
provider, ok := NewProvider(legacyAgent, cfg)
239+
provider, ok := NewProvider(AgentClaudeAI, cfg)
224240
require.True(t, ok)
225241
require.NotNil(t, provider)
226242

227243
cfg.Roots[0] = "/tmp/mutated"
228-
legacy, ok := provider.(*legacyProvider)
244+
importOnly, ok := provider.(*importOnlyProvider)
229245
require.True(t, ok)
230-
assert.Equal(t, []string{"/tmp/one", "/tmp/two"}, legacy.Config.Roots)
231-
assert.Equal(t, "devbox", legacy.Config.Machine)
246+
assert.Equal(t, []string{"/tmp/one", "/tmp/two"}, importOnly.Config.Roots)
247+
assert.Equal(t, "devbox", importOnly.Config.Machine)
232248

233249
_, ok = ProviderFactoryByType("missing")
234250
assert.False(t, ok)
235251
_, ok = NewProvider("missing", cfg)
236252
assert.False(t, ok)
237253
}
238254

239-
func TestLegacyProviderParseReturnsUnsupported(t *testing.T) {
240-
legacyAgent := legacyProviderTestAgent(t)
241-
provider, ok := NewProvider(legacyAgent, ProviderConfig{
255+
func TestImportOnlyProviderParseReturnsUnsupported(t *testing.T) {
256+
provider, ok := NewProvider(AgentChatGPT, ProviderConfig{
242257
Roots: []string{t.TempDir()},
243258
Machine: "devbox",
244259
})
245260
require.True(t, ok)
246261

247262
outcome, err := provider.Parse(context.Background(), ParseRequest{
248263
Source: SourceRef{
249-
Provider: legacyAgent,
264+
Provider: AgentChatGPT,
250265
Key: "source",
251266
DisplayPath: "/tmp/source.jsonl",
252267
FingerprintKey: "/tmp/source.jsonl",
@@ -262,22 +277,10 @@ func TestLegacyProviderParseReturnsUnsupported(t *testing.T) {
262277
assert.True(t, errors.Is(err, ErrUnsupportedProviderFeature))
263278
var unsupported UnsupportedProviderFeatureError
264279
require.ErrorAs(t, err, &unsupported)
265-
assert.Equal(t, legacyAgent, unsupported.Provider)
280+
assert.Equal(t, AgentChatGPT, unsupported.Provider)
266281
assert.Equal(t, ProviderFeatureParse, unsupported.Feature)
267282
}
268283

269-
func legacyProviderTestAgent(t *testing.T) AgentType {
270-
t.Helper()
271-
for _, def := range Registry {
272-
factory := providerFactoryForDef(def)
273-
if _, ok := factory.(legacyProviderFactory); ok {
274-
return def.Type
275-
}
276-
}
277-
t.Fatal("expected at least one legacy provider for fallback tests")
278-
return ""
279-
}
280-
281284
func assertAgentDefMetadataEqual(t *testing.T, want, got AgentDef) {
282285
t.Helper()
283286

0 commit comments

Comments
 (0)