fix Codebuddy model discovery from CLI help (#6617)

Co-authored-by: zzjjzz-zz <210207242+zzjjzz-zz@users.noreply.github.com>
This commit is contained in:
zzjjzz-zz
2026-08-11 09:00:14 +00:00
committed by GitHub
co-authored by zzjjzz-zz
parent e78bb22613
commit b193677347
2 changed files with 74 additions and 41 deletions
+32 -31
View File
@@ -13,36 +13,30 @@ import type { RuntimeAgentDef } from '../types.js';
// Key differences from the claude adapter:
// • Binary names: `codebuddy` / `cbc` (not `claude` / `openclaude`).
// • `--effort` flag for reasoning control (minimal/low/medium/high/xhigh/max).
// • Richer multi-provider model list (GLM, Kimi, MiniMax, Claude, GPT,
// Gemini, DeepSeek) exposed through `--model <id>`.
// • A multi-provider model list advertised in `--help` and selected through
// `--model <id>`.
// • `--mcp-config <fileOrString>` for MCP server injection (the daemon can
// also write `.mcp.json` to the project cwd, same as Claude Code).
const CODEBUDDY_FALLBACK_MODELS = [
DEFAULT_MODEL_OPTION,
// GLM family (Zhipu AI)
{ id: 'glm-5.1-ioa', label: 'glm-5.1-ioa' },
{ id: 'glm-5v-turbo-ioa', label: 'glm-5v-turbo-ioa' },
// Claude family (via Codebuddy proxy)
{ id: 'claude-opus-4.8-1m', label: 'claude-opus-4.8-1m' },
{ id: 'claude-opus-4.8', label: 'claude-opus-4.8' },
{ id: 'claude-sonnet-4.6-1m', label: 'claude-sonnet-4.6-1m' },
{ id: 'claude-haiku-4.5', label: 'claude-haiku-4.5' },
// GPT family
{ id: 'gpt-5.5', label: 'gpt-5.5' },
{ id: 'gpt-5.4', label: 'gpt-5.4' },
{ id: 'gpt-5.3-codex', label: 'gpt-5.3-codex' },
// Gemini family
{ id: 'gemini-3.5-flash', label: 'gemini-3.5-flash' },
// DeepSeek family
{ id: 'deepseek-v4-pro-ioa', label: 'deepseek-v4-pro-ioa' },
{ id: 'deepseek-v4-flash-ioa', label: 'deepseek-v4-flash-ioa' },
// Kimi family
{ id: 'kimi-k2.6-ioa', label: 'kimi-k2.6-ioa' },
// MiniMax family
{ id: 'minimax-m3-ioa', label: 'minimax-m3-ioa' },
{ id: 'minimax-m2.7-ioa', label: 'minimax-m2.7-ioa' },
];
function parseCodebuddyHelpModels(stdout: string) {
const advertised = stdout.match(
/--model <model>[\s\S]*?Currently supported:\s*\(([^)]*)\)/i,
)?.[1];
if (!advertised) return null;
const modelIds = [...new Set(
advertised
.split(',')
.map((modelId) => modelId.trim())
.filter((modelId) => modelId.length > 0 && modelId !== DEFAULT_MODEL_OPTION.id),
)];
if (modelIds.length === 0) return null;
return [
DEFAULT_MODEL_OPTION,
...modelIds.map((modelId) => ({ id: modelId, label: modelId })),
];
}
export const codebuddyAgentDef = {
id: 'codebuddy',
@@ -59,10 +53,17 @@ export const codebuddyAgentDef = {
'--include-partial-messages': 'partialMessages',
'--add-dir': 'addDir',
},
fallbackModels: CODEBUDDY_FALLBACK_MODELS,
// Codebuddy CLI does not ship a `models` subcommand; the supported model
// ids are advertised in `--help` output. Fallback list above covers the
// current set; users can type custom ids through the "Custom" input.
// Codebuddy has no `models` subcommand, but the installed build advertises
// its supported ids in root help. Parse that version-local catalog instead
// of surfacing a static list that drifts as Codebuddy updates.
listModels: {
args: ['--help'],
parse: parseCodebuddyHelpModels,
timeoutMs: 10_000,
},
// If an older build omits the catalog or help changes shape, fail closed to
// Codebuddy's configured default rather than offering unverified ids.
fallbackModels: [DEFAULT_MODEL_OPTION],
// Codebuddy CLI --effort supports exactly 6 levels:
// minimal, low, medium, high, xhigh, max
// We additionally expose a synthetic `default` sentinel as the FIRST
+42 -10
View File
@@ -124,16 +124,10 @@ describe('codebuddy definition metadata', () => {
expect(codebuddyAgentDef.externalMcpInjection).toBe('claude-mcp-json');
});
it('exposes fallback models with multi-provider coverage', () => {
const modelIds = codebuddyAgentDef.fallbackModels.map((m) => m.id);
expect(modelIds).toContain('default');
expect(modelIds).toContain('glm-5.1-ioa');
expect(modelIds).toContain('claude-opus-4.8');
expect(modelIds).toContain('gpt-5.5');
expect(modelIds).toContain('gemini-3.5-flash');
expect(modelIds).toContain('deepseek-v4-pro-ioa');
expect(modelIds).toContain('kimi-k2.6-ioa');
expect(modelIds).toContain('minimax-m3-ioa');
it('fails closed to the CLI-configured default when live model discovery is unavailable', () => {
expect(codebuddyAgentDef.fallbackModels).toEqual([
{ id: 'default', label: 'Default (CLI config)' },
]);
});
it('probes -p subcommand for capability flags', () => {
@@ -148,6 +142,44 @@ describe('codebuddy definition metadata', () => {
});
});
describe('codebuddy model discovery', () => {
it('parses the model ids advertised by the installed CLI help', () => {
const help = `
Options:
--model <model> Model for the current session. Please provide the model ID. Currently supported: (hy3, glm-5.2, glm-5.1,
glm-5v-turbo, minimax-m3, kimi-k3-1, deepseek-v4-pro)
--text-to-image-model <model> Model for text-to-image generation
`;
expect(codebuddyAgentDef.listModels?.args).toEqual(['--help']);
expect(codebuddyAgentDef.listModels?.parse(help)).toEqual([
{ id: 'default', label: 'Default (CLI config)' },
{ id: 'hy3', label: 'hy3' },
{ id: 'glm-5.2', label: 'glm-5.2' },
{ id: 'glm-5.1', label: 'glm-5.1' },
{ id: 'glm-5v-turbo', label: 'glm-5v-turbo' },
{ id: 'minimax-m3', label: 'minimax-m3' },
{ id: 'kimi-k3-1', label: 'kimi-k3-1' },
{ id: 'deepseek-v4-pro', label: 'deepseek-v4-pro' },
]);
});
it('deduplicates advertised model ids while preserving CLI order', () => {
const help = '--model <model> Currently supported: (hy3, glm-5.2, hy3)';
expect(codebuddyAgentDef.listModels?.parse(help)?.map((model) => model.id)).toEqual([
'default',
'hy3',
'glm-5.2',
]);
});
it('returns null when the CLI does not advertise a usable model catalog', () => {
expect(codebuddyAgentDef.listModels?.parse('--model <model> Model for the session')).toBeNull();
expect(codebuddyAgentDef.listModels?.parse('--model <model> Currently supported: ()')).toBeNull();
});
});
describe('codebuddy reasoning round-trip', () => {
it('declares reasoningOptions so the daemon sanitizer does not drop effort', () => {
expect(codebuddyAgentDef.reasoningOptions).toBeDefined();