From 3580e29862fef1e9f3a8aaea6d855fe3eaa9f8b9 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 3 Jul 2026 20:55:45 +0000 Subject: [PATCH 1/3] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/link-assistant/agent/issues/275 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..520ebbd --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-07-03T20:55:45.140Z for PR creation at branch issue-275-0cfd401f04e4 for issue https://github.com/link-assistant/agent/issues/275 \ No newline at end of file From 92572861086de350fd004808220eeccf5d764471 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 3 Jul 2026 21:08:02 +0000 Subject: [PATCH 2/3] fix compaction cascade model logging --- js/src/cli/model-config.js | 23 +++++-- js/tests/compaction-model.ts | 115 +++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 5 deletions(-) diff --git a/js/src/cli/model-config.js b/js/src/cli/model-config.js index bd06b7d..aa957b5 100644 --- a/js/src/cli/model-config.js +++ b/js/src/cli/model-config.js @@ -302,7 +302,10 @@ async function resolveCompactionModelEntry( // Short name resolution const { Provider } = await import('../provider/provider.ts'); - const resolved = await Provider.parseModelWithResolution(modelArg); + const resolved = await Provider.resolveShortModelName(modelArg); + if (!resolved) { + throw new Error('ProviderModelNotFoundError'); + } return { providerID: resolved.providerID, modelID: resolved.modelID, @@ -336,10 +339,17 @@ async function parseCompactionModelConfig( // Check for --compaction-models (cascade) first — it overrides --compaction-model const cliCompactionModelsArg = getCompactionModelsFromProcessArgv(); + const defaultCompactionModels = getDefaultCompactionModels(defaultOptions); + const compactionModelsSource = + cliCompactionModelsArg || + (argv['compaction-models'] && + argv['compaction-models'] !== defaultCompactionModels) + ? 'cli' + : 'default'; const compactionModelsArg = cliCompactionModelsArg ?? argv['compaction-models'] ?? - getDefaultCompactionModels(defaultOptions); + defaultCompactionModels; // Parse the links notation sequence into an array of model names const modelNames = parseLinksNotationSequence(compactionModelsArg); @@ -360,8 +370,11 @@ async function parseCompactionModelConfig( useSameModel: resolved.useSameModel, }); } catch (err) { - // If a model can't be resolved, log and skip it - Log.Default.warn(() => ({ + const logSkip = + compactionModelsSource === 'default' + ? Log.Default.debug + : Log.Default.warn; + logSkip(() => ({ message: 'skipping unresolvable compaction model in cascade', model: name, error: err?.message, @@ -374,7 +387,7 @@ async function parseCompactionModelConfig( models: compactionModels.map((m) => m.useSameModel ? 'same' : `${m.providerID}/${m.modelID}` ), - source: cliCompactionModelsArg ? 'cli' : 'default', + source: compactionModelsSource, })); // Use the first model as the primary compaction model (for backward compatibility) diff --git a/js/tests/compaction-model.ts b/js/tests/compaction-model.ts index 4982f7b..1e2ff00 100644 --- a/js/tests/compaction-model.ts +++ b/js/tests/compaction-model.ts @@ -371,3 +371,118 @@ describe('CompactionModelConfig with cascade', () => { expect(config.compactionModels).toBeUndefined(); }); }); + +describe('compaction model cascade logging', () => { + test('does not emit provider error logs for unresolvable default cascade entries', async () => { + const configContent = JSON.stringify({ + $schema: 'https://opencode.ai/config.json', + disabled_providers: [ + 'amazon-bedrock', + 'anthropic', + 'azure', + 'claude-oauth', + 'github-copilot', + 'github-copilot-enterprise', + 'google', + 'google-vertex', + 'google-vertex-anthropic', + 'groq', + 'kilo', + 'link-assistant', + 'link-assistant/cache', + 'mistral', + 'opencode', + 'openai', + 'openrouter', + 'xai', + ], + provider: { + formalai: { + name: 'local', + npm: '@ai-sdk/openai-compatible', + options: { + baseURL: 'http://127.0.0.1:8080/api/openai/v1', + apiKey: '{env:FORMAL_AI_API_KEY}', + }, + models: { + 'formal-ai': { + name: 'formal-ai', + }, + }, + }, + }, + model: 'formalai/formal-ai', + }); + + const script = ` + import { parseModelConfig } from './src/cli/model-config.js'; + import { initConfig, resetConfig } from './src/config/config.ts'; + import { Log } from './src/util/log.ts'; + import { Instance } from './src/project/instance.ts'; + + process.argv = ['bun', 'agent', '--model', 'formalai/formal-ai', '--verbose']; + resetConfig(); + initConfig(process.argv); + await Log.init({ print: true, level: 'DEBUG', compactJson: true }); + await Instance.provide({ + directory: process.cwd(), + fn: async () => { + await parseModelConfig( + { model: 'formalai/formal-ai' }, + () => {}, + () => {} + ); + }, + }); + await Instance.disposeAll(); + `; + + const proc = Bun.spawn({ + cmd: ['bun', '--eval', script], + cwd: process.cwd(), + stdout: 'pipe', + stderr: 'pipe', + env: { + ...process.env, + FORMAL_AI_API_KEY: 'formal-ai', + LINK_ASSISTANT_AGENT_CONFIG_CONTENT: configContent, + }, + }); + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + + if (exitCode !== 0) { + throw new Error(`parseModelConfig child process failed: ${stderr}`); + } + + const logs = stdout + .split('\n') + .filter(Boolean) + .flatMap((line) => { + try { + return [JSON.parse(line)]; + } catch { + return []; + } + }); + const skippedCascadeModels = logs.filter( + (log) => + log.message === 'skipping unresolvable compaction model in cascade' + ); + + expect(skippedCascadeModels.length).toBeGreaterThan(0); + expect(skippedCascadeModels.every((log) => log.level === 'debug')).toBe( + true + ); + expect( + logs.some( + (log) => + log.level === 'error' && + log.message === 'model not found - refusing to silently fallback' + ) + ).toBe(false); + }); +}); From 0b8bc3b1b96ab08d369182d70aaea69a273ba62d Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 3 Jul 2026 21:10:02 +0000 Subject: [PATCH 3/3] add changeset for compaction logging fix --- js/.changeset/quiet-compaction-cascade.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 js/.changeset/quiet-compaction-cascade.md diff --git a/js/.changeset/quiet-compaction-cascade.md b/js/.changeset/quiet-compaction-cascade.md new file mode 100644 index 0000000..61bc8fc --- /dev/null +++ b/js/.changeset/quiet-compaction-cascade.md @@ -0,0 +1,5 @@ +--- +'@link-assistant/agent': patch +--- + +Fix compaction cascade logging so default fallback model misses no longer emit provider error logs for single-provider configurations.