Files
boocode/openspec/changes/v2-3-provider-lifecycle/design.md
indifferentketchup 93d3f86c2b v2.2-paseo-providers: Paseo provider stack + v2.2.1 pane-scoped chat fixes
Ship Paseo-equivalent provider snapshot, AgentComposerBar, ACP dispatch
rewrite with streaming/persist, permission prompts, and agent commands.
Follow-up: pane-scoped chat resolution, CoderMessageList tool timeline,
WS user-delta replace, and inference orphan tool_call stripping.
Archive openspec v2-2; update CHANGELOG and CURRENT.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 15:18:31 +00:00

28 KiB
Raw Blame History

v2.3 Provider lifecycle — design

Detailed implementation plan for Paseo-style provider registration, readiness probing, and enable/disable toggles in BooCoder.

Audience: Sam + future agents implementing the batch.
Paseo reference: /opt/forks/paseo/packages/server/src/server/agent/ (registry, snapshot manager, generic ACP), /opt/forks/paseo/packages/app/src/screens/settings/providers-section.tsx (UI behavior).


1. Current state vs target

1.1 BooCode today (v2.2)

┌─────────────────┐     startup      ┌──────────────────┐
│ provider-       │ ───────────────► │ available_agents │  (which, version, models JSONB)
│ registry.ts     │   agent-probe    │ (Postgres)       │
│ (7 hardcoded)   │                  └────────┬─────────┘
└────────┬────────┘                           │
         │                                    │
         ▼                                    ▼
┌─────────────────┐   cache miss    ┌──────────────────┐
│ getProvider     │ ──────────────► │ probeAcpProvider │  (full ACP session, 30s)
│ Snapshot()      │   per agent     │ per installed    │
└────────┬────────┘                 └──────────────────┘
         │
         ▼
  Omit uninstalled ──► AgentComposerBar never sees them
  No enabled flag
  status: ready | error only

Key files:

File Role
apps/coder/src/services/provider-registry.ts Static PROVIDERS[]
apps/coder/src/services/agent-probe.ts Boot which + DB upsert
apps/coder/src/services/provider-snapshot.ts Cache + cold probe + merge
apps/coder/src/services/acp-spawn.ts Per-agent argv switch
apps/coder/src/routes/providers.ts snapshot + refresh
apps/web/src/components/AgentComposerBar.tsx Picker UI

1.2 Target (Paseo-aligned, BooCode-native)

┌──────────────────┐
│ Built-in registry│──┐
│ (provider-       │  │
│  registry.ts)    │  │    merge at boot + on config reload
└──────────────────┘  │
                      ▼
┌──────────────────┐  ┌──────────────────┐
│ /data/coder-     │─►│ ResolvedProvider   │
│ providers.json   │  │ Registry (in-mem)  │
└──────────────────┘  └────────┬───────────┘
                               │
         ┌─────────────────────┼─────────────────────┐
         ▼                     ▼                     ▼
  agent-probe (fast)    getProviderSnapshot      dispatch
  which + version       tier-1: isAvailable      generic ACP for
  → available_agents    tier-2: cold ACP         config entries
                        enabled filter
                               │
                               ▼
                    Always emit entry per registered provider
                    loading → ready | unavailable | error

Principles copied from Paseo (docs/providers.md in fork):

  1. Registration ≠ installation — config lists what you want; probe tells you whats ready.
  2. Warm until refresh — no TTL re-probe on picker open; explicit POST /api/providers/refresh only.
  3. Disabled skips probeenabled: falseunavailable without spawning.
  4. Config reload replaces registry — no redeploy to add an ACP wrapper.

2. Config file: /data/coder-providers.json

2.1 Location and loading

Env var Default Notes
CODER_PROVIDERS_PATH /data/coder-providers.json Same bind-mount pattern as SKILLS_ROOT, MCP_CONFIG_PATH
  • BooCoder runs on host systemd — path resolves to /opt/boocode/data/coder-providers.json in dev (add to repo as data/coder-providers.json + .env.host).
  • Missing file → {} (built-ins only, all enabled).
  • Invalid JSON → log error, fall back to {} (do not crash boot).
  • Reload: on POST /api/providers/config success, or SIGHUP optional later; v1: restart boocoder.service after manual edit is acceptable for solo use.

2.2 Schema (Zod)

New file: apps/coder/src/services/provider-config.ts

const ProviderOverrideSchema = z.object({
  extends: z.enum(['acp']).optional(), // v2.3: only 'acp' for custom; built-ins omit extends
  label: z.string().min(1).optional(),
  description: z.string().optional(),
  command: z.array(z.string().min(1)).min(1).optional(), // [binary, ...args]
  env: z.record(z.string()).optional(),
  enabled: z.boolean().optional(), // default true
  order: z.number().int().optional(), // UI sort key
  models: z.array(z.object({ id: z.string(), label: z.string() })).optional(),
  additionalModels: z.array(z.object({ id: z.string(), label: z.string() })).optional(),
});

const CoderProvidersFileSchema = z.object({
  providers: z.record(ProviderOverrideSchema).default({}),
});

Rules:

Case Behavior
Built-in id (e.g. goose) Override merges: enabled, label, command (replace spawn), env
New id + extends: "acp" New registry entry; requires label + command
New id without extends Reject at load with log (v2.3)
enabled: false on built-in Stays in registry; snapshot enabled: false, status unavailable
Custom id collision with built-in Config wins for overrides only; cannot redefine boocode transport

2.3 Example file (ship in data/coder-providers.json)

{
  "providers": {
    "goose": { "enabled": true },
    "copilot": { "enabled": false },
    "amp-acp": {
      "extends": "acp",
      "label": "Amp",
      "description": "ACP wrapper for Amp",
      "command": ["amp-acp"],
      "enabled": true
    }
  }
}

2.4 Paseo parity notes

Paseo uses ~/.paseo/config.json under agents.providers with the same fields (extends, command, enabled, models, …). We intentionally use a repo-adjacent data file instead of dotfile — matches AGENTS.md / skills layout and survives container/host split (coder reads host path).


3. Resolved provider registry

3.1 New module: provider-config-registry.ts

Responsibility: Single in-memory source of truth after merge.

export interface ResolvedProviderDef extends ProviderDef {
  id: string;
  enabled: boolean;
  isBuiltin: boolean;
  isCustomAcp: boolean;
  /** Full argv for spawn: [binary, ...args] */
  launchCommand: [string, ...string[]] | null;
  env: Record<string, string> | undefined;
  configLabel?: string;
  configDescription?: string;
}

export function buildResolvedRegistry(
  builtins: ProviderDef[],
  config: CoderProvidersFile,
): Map<string, ResolvedProviderDef>;

export function loadProviderConfig(path: string): CoderProvidersFile;
export function reloadProviderConfig(): void; // called after PATCH

Merge algorithm (mirror Paseo buildProviderRegistry / addDerivedProviders):

  1. For each built-in in PROVIDERS:
    • Apply config override if present
    • enabled = override.enabled !== false
    • launchCommand = override.command ?? default from acp-spawn + install_path at dispatch time
  2. For each config key not in built-ins:
    • Require extends: "acp", label, command
    • Insert as isCustomAcp: true, transport: 'acp', modelSource: 'probe'
  3. boocode always enabled; ignore enabled: false with warn log

Consumers: agent-probe, provider-snapshot, dispatcher, acp-dispatch, routes.

3.2 agent-probe changes

File: apps/coder/src/services/agent-probe.ts

  • Iterate getResolvedProviderIds() instead of PROBED_AGENT_NAMES only.
  • For custom ACP: probe command[0] via which (not agent name).
  • Upsert available_agents for custom ids (new rows).
  • Store label, transport: 'acp' from resolved def.
  • Skip probe entirely when enabled: false (optional: delete row or keep stale — keep row, set install_path null on disable refresh).

3.3 Schema migration (optional column)

File: apps/coder/src/schema.sql

ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT true;
ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS source TEXT DEFAULT 'builtin';
-- source: 'builtin' | 'config'

Mirror enabled from config on each probe pass. Custom providers get source = 'config'.

Alternative (simpler v2.3.0): dont add DB column; read enabled only from in-memory registry at snapshot time. DB holds install facts only. Prefer this for phase 1; add column if settings page needs to show state after coder restart without re-reading JSON.


4. Snapshot lifecycle

4.1 Type changes

Files: apps/coder/src/services/provider-types.ts, apps/web/src/api/types.ts

export type ProviderSnapshotStatus = 'loading' | 'ready' | 'unavailable' | 'error';

export interface ProviderSnapshotEntry {
  name: string;
  label: string;
  description?: string;
  transport: string;
  status: ProviderSnapshotStatus;
  enabled: boolean;
  installed: boolean; // binary found on last fast probe
  models: ProviderModel[];
  modes: ProviderMode[];
  defaultModeId: string | null;
  commands: AgentCommand[];
  error?: string;
  fetchedAt?: string; // ISO — when tier-2 probe completed
}

Restore unavailable (removed in stale cleanup — intentional regression for this batch).

4.2 buildProviderEntry rewrite

File: apps/coder/src/services/provider-snapshot.ts

Stop returning null for uninstalled. Always return an entry for every resolved registry id.

for each resolvedProvider:
  if !enabled:
    return { status: 'unavailable', enabled: false, installed: false, models: [], ... }

  if native boocode:
    return { status: 'ready', enabled: true, installed: true, models: llamaSwap, ... }

  fast = agentRow?.install_path != null  // or isCommandAvailable(launchCommand[0])

  if !fast:
    return { status: 'unavailable', enabled: true, installed: false, models: [], modes: manifest, commands: manifest }

  if tier2_skipped:  // see §4.3
    return { status: 'ready', enabled: true, installed: true, models: from DB, modes: manifest or DB, ... }

  cold ACP probe:
    ok → ready + models/modes/commands merge
    fail → error + error message

4.3 Two-tier probe (implements deferred work §2)

Tier 1 — fast (always on cold read if enabled + installed):

async function isProviderAvailable(resolved: ResolvedProviderDef, agentRow: AgentRow): Promise<boolean> {
  if (resolved.isNative) return true;
  if (agentRow?.install_path) return true;
  if (resolved.launchCommand) return isCommandAvailable(resolved.launchCommand[0]);
  return false;
}

New util: apps/coder/src/services/command-availability.tswhich-style check (lift idea from Paseo utils/executable.ts, ~20 lines, no full port).

Tier 2 — slow (ACP session):

Run only when:

Condition Action
force === true (POST /refresh) Always cold probe installed enabled providers
last_probed_at older than PROVIDER_PROBE_TTL_MS (default 24h, env override) Cold probe
DB models empty AND installed Cold probe
Otherwise Use available_agents.models + manifest modes/commands

Env: PROVIDER_PROBE_TTL_MS default 86400000 (24h). Paseo uses warm-forever until refresh; 24h is a homelab compromise so stale model lists self-heal.

Paseo contract (adopt explicitly):

  • Opening AgentComposerBar does not call refresh or force probe.
  • POST /api/providers/refresh clears cache + forces tier-2 for home cwd.
  • Document in BOOCODER.md.

4.4 Loading state

On cache miss, before async probe completes:

  1. Return entries with status: 'loading' immediately (sync).
  2. Singleflight inflight map (already exists) — on completion, flip to terminal status + emit…

Tier 2 optional: WS frame provider_snapshot_updated — defer to follow-up; v2.3 can rely on client polling 2s while any entry loading (CoderPane already polls when WS disconnected; extend: poll while snapshot has loading).

4.5 Cache keys

Keep cwd-keyed cache (resolvedCwd = cwd ?? homedir()). Settings UI uses snapshot with no cwd or explicit cwd=~ — same as Paseo home-directory snapshot for provider management.


5. Generic ACP dispatch

5.1 Problem

acp-spawn.ts switch grows with every agent. Custom config entries cannot dispatch today.

5.2 Solution

File: apps/coder/src/services/acp-spawn.ts

export function resolveLaunchSpec(
  resolved: ResolvedProviderDef,
  installPath: string | null,
): { binary: string; args: string[]; env?: Record<string, string> } | null {
  if (resolved.launchCommand) {
    return {
      binary: resolved.launchCommand[0],
      args: resolved.launchCommand.slice(1),
      env: resolved.env,
    };
  }
  // built-in fallback
  const args = resolveAcpSpawnArgs(resolved.id);
  if (!args || !installPath) return null;
  return { binary: installPath, args, env: resolved.env };
}

File: apps/coder/src/services/acp-dispatch.ts

  • Replace resolveAcpSpawnArgs(agent) + spawn(installPath, args) with resolveLaunchSpec(resolved, installPath).
  • Merge env into spawn env: { ...process.env, ...spec.env }.
  • Dispatcher loads resolved def by task.agent name.

Do not port Paseo GenericACPAgentClient class — keep procedural dispatch + existing acp-stream.ts.


6. HTTP API

File: apps/coder/src/routes/providers.ts

Method Path Body Response
GET /api/providers/snapshot?cwd= ProviderSnapshotEntry[] (unchanged path)
POST /api/providers/refresh { providers?: string[] } optional { refreshed: number } — if providers set, refresh subset only (Paseo pattern)
GET /api/providers/config { providers: Record<string, ProviderOverride> }
PATCH /api/providers/config partial providers map merged file written, registry reload, { ok: true }
GET /api/providers/:id/diagnostic { diagnostic: string } Tier 2

PATCH semantics: shallow merge at top level per provider id (same as Paseo patchConfig). Writing enabled: false triggers registry reload + snapshot reconcile (mark unavailable without probe).

Proxy: BooChat server may proxy /api/coder/providers/* — check apps/server/src/index.ts coder proxy prefix; add config routes if missing.

Web client: apps/web/src/api/client.ts

coder: {
  snapshot: ...
  refreshProviders: (providers?: string[]) => ...
  getProviderConfig: () => ...
  patchProviderConfig: (patch) => ...
  getProviderDiagnostic: (id) => ...
}

7. Web UI

7.1 Settings: Provider management drawer

New: apps/web/src/components/coder/ProviderSettingsDrawer.tsx (or section under existing settings)

Behavior lifted from Paseo providers-section.tsx:

UI element Action
Row per registered provider Label, status dot, model count
Switch PATCH config { [id]: { enabled } }
Refresh icon POST /api/providers/refresh
Add provider Opens catalog modal
Row click Diagnostic sheet (optional phase 2)

Status labels: Disabled · Loading · Available · Not installed · Error

Entry point: link from AgentComposerBar (gear icon) or CoderPane header.

7.2 AgentComposerBar filter

File: apps/web/src/components/AgentComposerBar.tsx

const selectable = entries.filter(
  (e) => e.enabled && e.status === 'ready' && e.models.length > 0
);
// boocode: allow ready with empty models if llama-swap down? keep current fallback

Show subtitle when current provider becomes unavailable (toast + reset to boocode).

7.3 Add provider modal

New: apps/web/src/data/acp-provider-catalog.ts

Curated entries (start with 510 you might install):

id command installLink
amp-acp ["amp-acp"] github amp-acp
cline ["npx","-y","cline@…","--acp"] cline.bot
pi-acp from fork

Copy structure from Paseo acp-provider-catalog.ts + buildAcpProviderConfigPatch — trim versions aggressively; pin only when youve verified on homelab.

Modal: search, Install → patchProviderConfig(buildPatch(entry))refreshProviders([entry.id]).

Do not port: React Native components, remote SVG icon pipeline — use lucide fallback icon.

7.4 Loading UX

While any entry status === 'loading', show spinner in composer provider dropdown; optional 2s poll until terminal state (reuse CoderPane poll pattern).


8. Diagnostics (Tier 2 in batch — lightweight)

Paseo getDiagnostic() runs version probe + short ACP initialize. For solo debugging:

File: apps/coder/src/services/provider-diagnostic.ts

export async function getProviderDiagnostic(
  resolved: ResolvedProviderDef,
  agentRow: AgentRow | undefined,
  cwd: string,
): Promise<string> {
  // Plaintext report:
  // - enabled, installed, binary path
  // - last_probed_at, model count from DB
  // - optional: 8s ACP initialize probe (reuse acp-probe with shorter timeout)
}

No need for Paseo diagnostic-utils.ts formatting library — a template string is fine.


9. Testing strategy

Test File
Config load + merge provider-config-registry.test.ts
Snapshot: disabled → unavailable, no probe mock call extend provider-snapshot.test.ts
Snapshot: uninstalled → unavailable, installed true/false same
Tier-2 skip when fresh DB models same
force refresh calls probe same
PATCH config writes file routes/providers.test.ts (optional integration)
resolveLaunchSpec custom command acp-spawn.test.ts

Run: pnpm -C apps/coder test, npx tsc -p apps/web/tsconfig.app.json --noEmit.

Smoke:

curl http://100.114.205.53:9502/api/providers/snapshot
curl -X PATCH http://100.114.205.53:9502/api/providers/config -d '{"providers":{"goose":{"enabled":false}}}'
curl -X POST http://100.114.205.53:9502/api/providers/refresh

10. Implementation phases

Phase 1 — Config + registry (backend only)

  • provider-config.ts, provider-config-registry.ts
  • data/coder-providers.json + CODER_PROVIDERS_PATH
  • Wire agent-probe to resolved ids
  • Unit tests

Exit: custom entry in JSON → row in available_agents after restart.

Phase 2 — Snapshot lifecycle

  • Types: loading, unavailable, enabled
  • Rewrite buildProviderEntry (never omit)
  • Tier-1 fast availability
  • Tier-2 skip when DB fresh
  • Restore warm-cache + force refresh semantics

Exit: disabled goose visible in API as unavailable; picker filters it out.

Phase 3 — Generic dispatch

  • resolveLaunchSpec
  • Dispatcher passes resolved def
  • Smoke: dispatch task for config-only provider (amp-acp if installed)

Phase 4 — HTTP config API

  • GET/PATCH config
  • Reload registry on PATCH
  • Subset refresh

Phase 5 — Web UI

  • Provider settings drawer + toggle
  • AgentComposerBar filter
  • Catalog modal (minimal list)

Phase 6 — Docs + deploy

  • BOOCODER.md section: Provider config
  • CHANGELOG.md entry
  • docs/DEFERRED-WORK.md — mark cold-probe item resolved
  • pnpm -C apps/coder build && sudo systemctl restart boocoder

11. Tier 2 follow-ups (document, dont build in v2.3)

Item Paseo source When
WS provider_snapshot_updated ProviderSnapshotManager EventEmitter When loading poll feels hacky
MCP list_providers / inspect_provider mcp-server.ts When BooCoder MCP orchestration matures
Profile overrides (extends: "claude") provider-registry.ts derived providers When you run Z.AI / multi-endpoint
order field UI sort config schema When catalog >10 entries
Per-workspace snapshot in picker cwd param Already partial — verify project path passed from CoderPane

12. Tier 3 reference — what Paseo has and why we dont port it

This section is reference only. These are large subsystems in /opt/forks/paseo that solve problems BooCode doesnt have at solo scale, or that BooCode already solved differently.

12.1 ACPAgentClient base class (~2,800 lines)

Path: packages/server/src/server/agent/providers/acp-agent.ts

What it does: Full ACP lifecycle — spawn, initialize, session/new, streaming, permissions, tool calls, MCP injection, revert, persisted agent import, probe sessions.

Why Paseo needs it: Paseo is the primary runtime for dozens of providers; one abstraction reduces duplication across copilot, cursor, generic ACP, etc.

Why BooCode skips it: acp-dispatch.ts + acp-stream.ts + acp-probe.ts already cover dispatch and probe as scripts (~400 lines total). Replacing with the class hierarchy is a multi-week rewrite with high regression risk on v2.2 dispatch that works on homelab.

What we take instead: Patterns only — isAvailable() = resolve binary; permission waiter (already shipped); derive models/modes (already shipped).


12.2 Per-provider client classes (claude, codex, opencode, pi, copilot, cursor…)

Paths: packages/server/src/server/agent/providers/*/agent.ts, codex-app-server-agent.ts (5,000+ lines)

What they do: Native SDK/RPC integration — not just CLI spawn. Codex uses app-server RPC; Claude uses Claude Agent SDK; OpenCode manages a sidecar server.

Why Paseo needs it: Deep integration — voice, revert, persisted sessions, feature toggles, OAuth diagnostics.

Why BooCode skips it: BooCode delegates to existing CLIs in worktrees. No embedded SDKs. PTY path for claude/qwen is stdin pipe; ACP path uses @agentclientprotocol/sdk at dispatch boundary only.

Lift risk: Importing codex-app-server-agent would drag thousands of lines + unknown deps.


12.3 ProviderSnapshotManager class (full port)

Path: packages/server/src/server/agent/provider-snapshot-manager.ts

What it does: Per-cwd Maps, loading states, singleflight, event emitter, reconcile on registry replace, settings vs workspace refresh split.

Why not full port: BooCodes provider-snapshot.ts is ~250 lines and already has cache + inflight. Selective lift: loading status, reconcile on config reload, subset refresh — not a class-for-class rewrite.


12.4 React Native settings app (packages/app)

Paths: providers-section.tsx, add-provider-modal.tsx, use-providers-snapshot.ts

What it does: Mobile/desktop cross-platform provider UI with Unistyles, native Switch, adaptive sheets.

Why BooCode skips it: BooChat is React web + Tailwind. Port interaction design (toggle, status dots, add flow), not components.


12.5 Daemon config system (patchConfig, migrations, Zod wire messages)

Path: packages/server/src/shared/messages.ts (4000+ lines), daemon config patch RPC

What it does: Every settings change is a typed WS/HTTP patch to daemon with validation, persistence, broadcast.

Why BooCode simplifies: Single-user — PATCH writes JSON file + reloads in-process Map. No multi-client sync requirement. If BooChat and CLI both edit, last-write-wins on file is acceptable.


12.6 Full ACP catalog (30+ providers, version-pinned npx)

Path: packages/app/src/data/acp-provider-catalog.ts (~400 lines)

Why trim: Maintenance burden — every upstream version bump is a PR in Paseo. Solo homelab: 510 entries you actually install, update when you install.


12.7 Voice provider stack

Path: packages/server/src/server/speech/*

Why skip: BooCode has no voice surface; unrelated to coder provider lifecycle.


12.8 Workspace git service inside agents

Path: Codex client integration with WorkspaceGitService

Why skip: BooCode worktrees (worktrees.ts) are explicit per-task; agents run in worktree cwd. Different architecture.


12.9 OpenCode server manager sidecar

Path: packages/server/src/server/agent/providers/opencode/server-manager.ts

What it does: Manages long-lived OpenCode server process.

Why skip: BooCode spawns opencode acp per dispatch — stateless, simpler, good enough for single user.


12.10 Pi RPC agent + session import from JSONL

Paths: packages/server/src/server/agent/providers/pi/agent.ts (1,500+ lines)

Why skip until needed: Only lift if you add pi as a built-in with import/revert requirements. Otherwise generic ACP + extends: "acp" + pi-acp catalog entry suffices.


12.11 Summary table

Paseo subsystem Lines (approx) BooCode v2.3 approach
ACPAgentClient 2,800 Keep acp-dispatch
Codex app server agent 5,500 Don't import
Provider registry merge 700 New 200-line module
Snapshot manager 490 Extend existing snapshot
Generic ACP agent 300 resolveLaunchSpec only
RN providers UI 400 Web drawer ~200 lines
MCP list_providers 200 Defer
Config wire protocol 4,000+ JSON file PATCH

Rule of thumb for solo project: Lift data models and lifecycle rules, not class hierarchies.


13. Risk register

Risk Mitigation
Custom npx provider slow cold start Show loading; subset refresh; dont block picker on whole snapshot
Config file edit while coder running PATCH API primary; manual edit requires restart (document)
enabled: false but task in flight Allow running task to finish; block new sends (picker filter)
Type drift web/coder Update both provider-types.ts and api/types.ts; optional zod parity test
Security: arbitrary command in config Single-user trusted path; same trust as AGENTS.md — no app-layer auth
Re-enabling cold probe slowness on refresh Expected; refresh is explicit user action

14. File map (new + touched)

Action Path
New apps/coder/src/services/provider-config.ts
New apps/coder/src/services/provider-config-registry.ts
New apps/coder/src/services/command-availability.ts
New apps/coder/src/services/provider-diagnostic.ts
New apps/coder/src/services/__tests__/provider-config-registry.test.ts
New data/coder-providers.json
New apps/web/src/data/acp-provider-catalog.ts
New apps/web/src/components/coder/ProviderSettingsDrawer.tsx
New apps/web/src/components/coder/AddProviderModal.tsx
Edit apps/coder/src/services/provider-snapshot.ts
Edit apps/coder/src/services/agent-probe.ts
Edit apps/coder/src/services/acp-spawn.ts
Edit apps/coder/src/services/acp-dispatch.ts
Edit apps/coder/src/services/dispatcher.ts
Edit apps/coder/src/routes/providers.ts
Edit apps/coder/src/config.tsCODER_PROVIDERS_PATH
Edit apps/coder/.env.host
Edit apps/coder/src/services/provider-types.ts
Edit apps/web/src/api/types.ts
Edit apps/web/src/api/client.ts
Edit apps/web/src/components/AgentComposerBar.tsx
Edit BOOCODER.md
Edit docs/DEFERRED-WORK.md

15. Attribution

Design patterns from Paseo (/opt/forks/paseo), especially:

  • provider-registry.ts — merge built-ins + config + enabled
  • provider-snapshot-manager.ts — loading/unavailable/ready lifecycle
  • provider-launch-config.ts — override schema
  • providers-section.tsx — settings UX
  • public-docs/custom-providers.md — config file semantics

BooCode implementation remains original code — no copy-paste of Paseo sources required; licensing treated as irrelevant per project owner directive.