* test: reproduce config-set rejecting model_overrides.<agent-id> (#3227) * fix(config): allow model_overrides.<agent-id> in config-set (#3227) * changeset: pr=3253 for #3227
This commit is contained in:
5
.changeset/brave-wolves-rally.md
Normal file
5
.changeset/brave-wolves-rally.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Fixed
|
||||||
|
pr: 3253
|
||||||
|
---
|
||||||
|
**`gsd-sdk query config-set model_overrides.<agent-id>` now accepted** — was rejected with "Unknown config key" despite the override mechanism working. Sibling fix to #3162.
|
||||||
@@ -107,6 +107,10 @@ const DYNAMIC_KEY_PATTERNS = [
|
|||||||
{ topLevel: 'dynamic_routing',
|
{ topLevel: 'dynamic_routing',
|
||||||
test: (k) => /^dynamic_routing\.(enabled|escalate_on_failure|max_escalations|tier_models\.(light|standard|heavy))$/.test(k),
|
test: (k) => /^dynamic_routing\.(enabled|escalate_on_failure|max_escalations|tier_models\.(light|standard|heavy))$/.test(k),
|
||||||
description: 'dynamic_routing.<enabled|escalate_on_failure|max_escalations|tier_models.<light|standard|heavy>>' },
|
description: 'dynamic_routing.<enabled|escalate_on_failure|max_escalations|tier_models.<light|standard|heavy>>' },
|
||||||
|
// #3227 — per-agent model overrides: model_overrides.<agent-id>
|
||||||
|
// Full model IDs (e.g. "openai/o3") and tier aliases (opus/sonnet/haiku/inherit)
|
||||||
|
// are both accepted. Value validation is handled by the resolver at read time.
|
||||||
|
{ topLevel: 'model_overrides', test: (k) => /^model_overrides\.[a-zA-Z0-9_-]+$/.test(k), description: 'model_overrides.<agent-id>' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -133,6 +133,12 @@ export const DYNAMIC_KEY_PATTERNS: readonly DynamicKeyPattern[] = [
|
|||||||
description: 'dynamic_routing.<enabled|escalate_on_failure|max_escalations|tier_models.<light|standard|heavy>>',
|
description: 'dynamic_routing.<enabled|escalate_on_failure|max_escalations|tier_models.<light|standard|heavy>>',
|
||||||
test: (k) => /^dynamic_routing\.(enabled|escalate_on_failure|max_escalations|tier_models\.(light|standard|heavy))$/.test(k),
|
test: (k) => /^dynamic_routing\.(enabled|escalate_on_failure|max_escalations|tier_models\.(light|standard|heavy))$/.test(k),
|
||||||
},
|
},
|
||||||
|
// #3227 — per-agent model overrides: model_overrides.<agent-id>
|
||||||
|
{
|
||||||
|
source: '^model_overrides\\.[a-zA-Z0-9_-]+$',
|
||||||
|
description: 'model_overrides.<agent-id>',
|
||||||
|
test: (k) => /^model_overrides\.[a-zA-Z0-9_-]+$/.test(k),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Returns true if keyPath is a valid config key (exact, runtime-state, or dynamic pattern). */
|
/** Returns true if keyPath is a valid config key (exact, runtime-state, or dynamic pattern). */
|
||||||
|
|||||||
119
tests/bug-3227-config-set-model-overrides.test.cjs
Normal file
119
tests/bug-3227-config-set-model-overrides.test.cjs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression test for bug #3227 — config-set rejects model_overrides.<agent-id>.
|
||||||
|
*
|
||||||
|
* `gsd-sdk query config-set model_overrides.gsd-plan-checker opus` was
|
||||||
|
* rejected with "Unknown config key" because `model_overrides.<agent-id>` was
|
||||||
|
* missing from DYNAMIC_KEY_PATTERNS in both the CJS schema and the SDK schema.
|
||||||
|
*
|
||||||
|
* The override mechanism itself worked correctly (resolve-model returned the
|
||||||
|
* override after a direct file edit). Only the write path was gated wrong.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { describe, test } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const { createTempProject, cleanup, runGsdTools } = require('./helpers.cjs');
|
||||||
|
const { DYNAMIC_KEY_PATTERNS, isValidConfigKey } = require('../get-shit-done/bin/lib/config-schema.cjs');
|
||||||
|
|
||||||
|
describe('#3227 — config-set accepts model_overrides.<agent-id>', () => {
|
||||||
|
test('isValidConfigKey accepts model_overrides.gsd-plan-checker', () => {
|
||||||
|
assert.ok(
|
||||||
|
isValidConfigKey('model_overrides.gsd-plan-checker'),
|
||||||
|
'model_overrides.gsd-plan-checker must be accepted by isValidConfigKey'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isValidConfigKey accepts model_overrides with various agent-id formats', () => {
|
||||||
|
const validKeys = [
|
||||||
|
'model_overrides.gsd-executor',
|
||||||
|
'model_overrides.gsd-planner',
|
||||||
|
'model_overrides.gsd-codebase-mapper',
|
||||||
|
'model_overrides.my_custom_agent',
|
||||||
|
'model_overrides.agent123',
|
||||||
|
];
|
||||||
|
for (const key of validKeys) {
|
||||||
|
assert.ok(isValidConfigKey(key), `isValidConfigKey must accept ${key}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isValidConfigKey rejects bare model_overrides (no agent-id)', () => {
|
||||||
|
assert.ok(
|
||||||
|
!isValidConfigKey('model_overrides'),
|
||||||
|
'bare model_overrides must be rejected (use model_overrides.<agent-id>)'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DYNAMIC_KEY_PATTERNS includes an entry for model_overrides', () => {
|
||||||
|
const hasPattern = DYNAMIC_KEY_PATTERNS.some(
|
||||||
|
(p) => p.description && p.description.includes('model_overrides')
|
||||||
|
);
|
||||||
|
assert.ok(hasPattern, 'DYNAMIC_KEY_PATTERNS must have an entry covering model_overrides.<agent-id>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config-set model_overrides.gsd-plan-checker opus succeeds via gsd-tools.cjs', (t) => {
|
||||||
|
const tmpDir = createTempProject();
|
||||||
|
t.after(() => cleanup(tmpDir));
|
||||||
|
|
||||||
|
const result = runGsdTools(
|
||||||
|
['config-set', 'model_overrides.gsd-plan-checker', 'opus'],
|
||||||
|
tmpDir
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
result.success,
|
||||||
|
[
|
||||||
|
'config-set model_overrides.gsd-plan-checker opus should succeed,',
|
||||||
|
'got:',
|
||||||
|
'stdout: ' + result.output,
|
||||||
|
'stderr: ' + result.error,
|
||||||
|
].join('\n')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config-set model_overrides.gsd-plan-checker opus writes to config.json', (t) => {
|
||||||
|
const tmpDir = createTempProject();
|
||||||
|
t.after(() => cleanup(tmpDir));
|
||||||
|
|
||||||
|
runGsdTools(['config-set', 'model_overrides.gsd-plan-checker', 'opus'], tmpDir);
|
||||||
|
|
||||||
|
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
||||||
|
assert.ok(fs.existsSync(configPath), '.planning/config.json must exist after config-set');
|
||||||
|
|
||||||
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
assert.ok(
|
||||||
|
config.model_overrides !== undefined &&
|
||||||
|
config.model_overrides['gsd-plan-checker'] === 'opus',
|
||||||
|
[
|
||||||
|
'Expected model_overrides["gsd-plan-checker"]: "opus" in config.json,',
|
||||||
|
'got: ' + JSON.stringify(config),
|
||||||
|
].join('\n')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config-get model_overrides.gsd-plan-checker returns opus after config-set', (t) => {
|
||||||
|
const tmpDir = createTempProject();
|
||||||
|
t.after(() => cleanup(tmpDir));
|
||||||
|
|
||||||
|
runGsdTools(['config-set', 'model_overrides.gsd-plan-checker', 'opus'], tmpDir);
|
||||||
|
|
||||||
|
const getResult = runGsdTools(
|
||||||
|
['config-get', 'model_overrides.gsd-plan-checker'],
|
||||||
|
tmpDir
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
getResult.success,
|
||||||
|
[
|
||||||
|
'config-get model_overrides.gsd-plan-checker should succeed,',
|
||||||
|
'got:',
|
||||||
|
'stdout: ' + getResult.output,
|
||||||
|
'stderr: ' + getResult.error,
|
||||||
|
].join('\n')
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
getResult.output.includes('opus'),
|
||||||
|
'config-get output should contain "opus", got: ' + getResult.output
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user