fix: resolve ProviderModelNotFoundError on non-Claude runtimes (#1156)

Extend resolve_model_ids to accept "omit" value: returns empty string
so non-Claude runtimes (OpenCode, Codex, Gemini, etc.) use their
configured default model instead of unresolvable Claude aliases.

- resolve_model_ids: "omit" short-circuits before alias resolution
- model_overrides still respected (checked first) for explicit IDs
- Installer sets resolve_model_ids: "omit" in ~/.gsd/defaults.json
  for non-Claude runtimes during install
- 4 new tests covering omit behavior and override passthrough
- Fix websearch test mocks for fs.writeSync output change (#1276)

Closes #1156

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-03-21 00:57:20 -04:00
parent 3f2c0a16e0
commit 02254db611
4 changed files with 63 additions and 8 deletions

View File

@@ -1195,15 +1195,16 @@ describe('websearch command', () => {
const { cmdWebsearch } = require('../get-shit-done/bin/lib/commands.cjs');
let origFetch;
let origApiKey;
let origStdoutWrite;
let origWriteSync;
let captured;
beforeEach(() => {
origFetch = global.fetch;
origApiKey = process.env.BRAVE_API_KEY;
origStdoutWrite = process.stdout.write;
origWriteSync = fs.writeSync;
captured = '';
process.stdout.write = (chunk) => { captured += chunk; return true; };
// output() uses fs.writeSync(1, data) since #1276 — mock it to capture output
fs.writeSync = (fd, data) => { if (fd === 1) captured += data; return Buffer.byteLength(String(data)); };
});
afterEach(() => {
@@ -1213,7 +1214,7 @@ describe('websearch command', () => {
} else {
delete process.env.BRAVE_API_KEY;
}
process.stdout.write = origStdoutWrite;
fs.writeSync = origWriteSync;
});
test('returns available=false when BRAVE_API_KEY is unset', async () => {

View File

@@ -281,6 +281,31 @@ describe('resolveModelInternal', () => {
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'opus');
});
});
describe('resolve_model_ids: "omit"', () => {
test('returns empty string for known agents', () => {
writeConfig({ resolve_model_ids: 'omit' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), '');
});
test('returns empty string for unknown agents', () => {
writeConfig({ resolve_model_ids: 'omit' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-nonexistent'), '');
});
test('still respects model_overrides even when omit', () => {
writeConfig({
resolve_model_ids: 'omit',
model_overrides: { 'gsd-planner': 'openai/gpt-5.4' },
});
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), 'openai/gpt-5.4');
});
test('returns empty string with inherit profile', () => {
writeConfig({ resolve_model_ids: 'omit', model_profile: 'inherit' });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-planner'), '');
});
});
});
// ─── escapeRegex ───────────────────────────────────────────────────────────────