1// Package agenttest provides test-only constructors for wiring a real
2// production agent.Coordinator without booting a full app.App. It is
3// imported only from _test.go files (e.g. internal/backend integration
4// tests) and is never referenced by production code, so it is compiled
5// only under tests and never ships in the production binary or API.
6package agenttest
7
8import (
9 "context"
10
11 "charm.land/catwalk/pkg/catwalk"
12 "charm.land/fantasy/providers/openaicompat"
13 "github.com/charmbracelet/crush/internal/agent"
14 "github.com/charmbracelet/crush/internal/config"
15 "github.com/charmbracelet/crush/internal/message"
16 "github.com/charmbracelet/crush/internal/permission"
17 "github.com/charmbracelet/crush/internal/session"
18)
19
20// NewCoordinator builds a real agent.Coordinator through the production
21// agent.NewCoordinator constructor so the RunAccepted / BeginAccepted /
22// run path (including UpdateModels) is the actual code under test.
23//
24// It installs a minimal config with a single openai-compatible provider
25// whose model resolves offline. run rebuilds the model on every call, so
26// the provider must construct without network I/O; the cancel-on-entry
27// path this helper is built to exercise returns before any model call,
28// so no request is ever issued. The coder agent's allowed-tools list is
29// cleared to keep tool construction cheap and free of sub-agent wiring.
30//
31// The optional coordinator dependencies (history, filetracker, LSP,
32// notify, runComplete, skills) are nil: run guards the publisher fields
33// and the cancel-on-entry path never touches the others.
34func NewCoordinator(
35 ctx context.Context,
36 workingDir string,
37 sessions session.Service,
38 messages message.Service,
39) (agent.Coordinator, error) {
40 cfg, err := config.Init(workingDir, "", false)
41 if err != nil {
42 return nil, err
43 }
44
45 const (
46 providerID = "test-openai-compat"
47 modelID = "test-model"
48 )
49 cfg.Config().Providers.Set(providerID, config.ProviderConfig{
50 ID: providerID,
51 Name: "Test",
52 Type: openaicompat.Name,
53 BaseURL: "http://127.0.0.1:0/v1",
54 APIKey: "test",
55 Models: []catwalk.Model{{ID: modelID, DefaultMaxTokens: 4096}},
56 })
57 selected := config.SelectedModel{Provider: providerID, Model: modelID}
58 cfg.Config().Models[config.SelectedModelTypeLarge] = selected
59 cfg.Config().Models[config.SelectedModelTypeSmall] = selected
60
61 // Keep buildTools light: no sub-agent or agentic-fetch construction.
62 coderCfg := cfg.Config().Agents[config.AgentCoder]
63 coderCfg.AllowedTools = nil
64 cfg.Config().Agents[config.AgentCoder] = coderCfg
65
66 return agent.NewCoordinator(
67 ctx,
68 cfg,
69 sessions,
70 messages,
71 permission.NewPermissionService(workingDir, true, nil),
72 nil,
73 nil,
74 nil,
75 nil,
76 nil,
77 nil,
78 )
79}