From e52c2fb2b7fb1d646ea040b178e89eeb01c5d2c3 Mon Sep 17 00:00:00 2001 From: Ruziyev Timur Date: Tue, 28 Jul 2026 00:25:02 +0500 Subject: [PATCH] fix(config): handle 'local-agent' provider in getLLMConfig (#118) * fix(config): handle 'local-agent' provider in getLLMConfig Routing a mission through a connected local CLI agent (Claude Code / Codex / Hermes) sets the provider to `local-agent`, but getLLMConfig's switch had no case for it, so it fell through to `default` and threw `Unknown provider: local-agent`. This aborted every keyless mission with a REFUSED, even though `local-agent` is a registered provider wired to LocalAgentAdapter everywhere else. Add a `case 'local-agent'` mirroring `codex`: keyless (no apiKey/baseUrl), with the connected agent id (codex|claude|hermes) carried in `model` and a sensible default. Verified getLLMConfig('local-agent','claude') now resolves to {provider:'local-agent', model:'claude'} instead of throwing. Co-Authored-By: Claude Opus 4.8 * test(config): regression coverage for local-agent provider (#118) Addresses review feedback: the fix had no test, so removing the `case 'local-agent'` would leave CI green while re-breaking keyless missions. Adds focused assertions in src/__tests__/local-agent-provider.test.ts: - local-agent + model `claude` resolves to provider/model with no apiKey/baseUrl - any supported agent id (codex|claude|hermes) passed as model is preserved - omitting the model falls back to the default agent id - getLLMConfig('local-agent') no longer throws - AVAILABLE_MODELS['local-agent'] surfaces the connected-agent ids Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: hoptoad-off Co-authored-by: Claude Opus 4.8 --- src/__tests__/local-agent-provider.test.ts | 44 ++++++++++++++++++++++ src/config/index.ts | 6 +++ 2 files changed, 50 insertions(+) create mode 100644 src/__tests__/local-agent-provider.test.ts diff --git a/src/__tests__/local-agent-provider.test.ts b/src/__tests__/local-agent-provider.test.ts new file mode 100644 index 0000000..46b5de1 --- /dev/null +++ b/src/__tests__/local-agent-provider.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; +import { config, AVAILABLE_MODELS } from '../config/index.js'; + +// Regression guard for #118: getLLMConfig had no `case 'local-agent'`, so it fell +// through to `default` and threw `Unknown provider: local-agent`, aborting every +// keyless (connected-agent) mission. These assertions fail if that case is removed. +describe("local-agent provider wiring (#118)", () => { + it('resolves a keyless config for local-agent with the agent id carried in model', () => { + const cfg = config.getLLMConfig('local-agent', 'claude'); + expect(cfg.provider).toBe('local-agent'); + expect(cfg.model).toBe('claude'); + // Keyless: the connected CLI agent uses its own login — no API key or base URL. + expect(cfg.apiKey).toBeUndefined(); + expect(cfg.baseUrl).toBeUndefined(); + }); + + it('preserves any supported agent id passed as the model', () => { + for (const agent of ['codex', 'claude', 'hermes']) { + const cfg = config.getLLMConfig('local-agent', agent); + expect(cfg.provider).toBe('local-agent'); + expect(cfg.model).toBe(agent); + expect(cfg.apiKey).toBeUndefined(); + expect(cfg.baseUrl).toBeUndefined(); + } + }); + + it('falls back to the default agent id when no model is given', () => { + const cfg = config.getLLMConfig('local-agent'); + expect(cfg.provider).toBe('local-agent'); + expect(cfg.model).toBe('claude'); + expect(cfg.apiKey).toBeUndefined(); + expect(cfg.baseUrl).toBeUndefined(); + }); + + it('does not throw "Unknown provider" for local-agent', () => { + expect(() => config.getLLMConfig('local-agent')).not.toThrow(); + }); + + it('surfaces the connected-agent ids as available local-agent models', () => { + expect(AVAILABLE_MODELS['local-agent']?.map(m => m.id)).toEqual( + expect.arrayContaining(['codex', 'claude', 'hermes']), + ); + }); +}); diff --git a/src/config/index.ts b/src/config/index.ts index c5d7d75..3ae06b1 100755 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -867,6 +867,12 @@ class ConfigManager { case 'codex': actualModel = model || this.config.get('codex').defaultModel; break; + case 'local-agent': + // Keyless backbone: the mission is routed through a connected local CLI agent + // (Claude Code / Codex / Hermes), each using its own login — no API key or base + // URL. The chosen agent id (codex|claude|hermes) travels in the `model` field. + actualModel = model || 'claude'; + break; case 'mock': actualModel = 'mock-model'; break;