Merge pull request 'feat(cli): add agentOverrides and modelOverrides to config key validation' (#554) from fix/532-config-key-validation into main
This commit is contained in:
@@ -618,5 +618,65 @@ defaultModel: default
|
|||||||
rmSync(tempDir, { recursive: true, force: true });
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("agentOverrides — accepts valid 3-segment path", async () => {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
|
||||||
|
try {
|
||||||
|
createTestConfig(tempDir, sampleConfig);
|
||||||
|
await cmdConfigSet(tempDir, "agentOverrides.solve-issue.planner", "claude-code");
|
||||||
|
const value = await cmdConfigGet(tempDir, "agentOverrides.solve-issue.planner");
|
||||||
|
expect(value).toBe("claude-code");
|
||||||
|
} finally {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("agentOverrides — rejects incomplete path (2 segments)", async () => {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
|
||||||
|
try {
|
||||||
|
createTestConfig(tempDir, sampleConfig);
|
||||||
|
await expect(cmdConfigSet(tempDir, "agentOverrides.solve-issue", "hermes")).rejects.toThrow(
|
||||||
|
/incomplete path|must specify a field/i,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("modelOverrides — accepts valid 2-segment path", async () => {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
|
||||||
|
try {
|
||||||
|
createTestConfig(tempDir, sampleConfig);
|
||||||
|
await cmdConfigSet(tempDir, "modelOverrides.extract", "gpt4");
|
||||||
|
const value = await cmdConfigGet(tempDir, "modelOverrides.extract");
|
||||||
|
expect(value).toBe("gpt4");
|
||||||
|
} finally {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("modelOverrides — rejects incomplete path (1 segment only)", async () => {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
|
||||||
|
try {
|
||||||
|
createTestConfig(tempDir, sampleConfig);
|
||||||
|
await expect(cmdConfigSet(tempDir, "modelOverrides", "gpt4")).rejects.toThrow(
|
||||||
|
/incomplete path|must specify a field/i,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects unknown top-level key (regression)", async () => {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), "test-config-"));
|
||||||
|
try {
|
||||||
|
createTestConfig(tempDir, sampleConfig);
|
||||||
|
await expect(cmdConfigSet(tempDir, "randomKey", "value")).rejects.toThrow(
|
||||||
|
/Unknown config key/,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { parse, stringify } from "yaml";
|
|||||||
/**
|
/**
|
||||||
* Valid configuration key schema
|
* Valid configuration key schema
|
||||||
*/
|
*/
|
||||||
const VALID_CONFIG_KEYS: Record<string, { nested: boolean; knownFields?: string[] }> = {
|
const VALID_CONFIG_KEYS: Record<
|
||||||
|
string,
|
||||||
|
{ nested: boolean; knownFields?: string[]; minDepth?: number }
|
||||||
|
> = {
|
||||||
providers: {
|
providers: {
|
||||||
nested: true,
|
nested: true,
|
||||||
knownFields: ["baseUrl", "apiKey"],
|
knownFields: ["baseUrl", "apiKey"],
|
||||||
@@ -18,6 +21,17 @@ const VALID_CONFIG_KEYS: Record<string, { nested: boolean; knownFields?: string[
|
|||||||
nested: true,
|
nested: true,
|
||||||
knownFields: ["command", "args"],
|
knownFields: ["command", "args"],
|
||||||
},
|
},
|
||||||
|
agentOverrides: {
|
||||||
|
nested: true,
|
||||||
|
// agentOverrides.<workflowName>.<roleName> = agentAlias (string value)
|
||||||
|
// No knownFields — workflow/role names are user-defined
|
||||||
|
},
|
||||||
|
modelOverrides: {
|
||||||
|
nested: true,
|
||||||
|
minDepth: 2,
|
||||||
|
// modelOverrides.<scenario> = modelAlias (string value)
|
||||||
|
// No knownFields — scenarios are user-defined
|
||||||
|
},
|
||||||
defaultAgent: { nested: false },
|
defaultAgent: { nested: false },
|
||||||
defaultModel: { nested: false },
|
defaultModel: { nested: false },
|
||||||
};
|
};
|
||||||
@@ -43,8 +57,9 @@ function validateConfigKey(path: string[]): void {
|
|||||||
throw new Error(`${topLevel} is a scalar key and cannot have nested properties`);
|
throw new Error(`${topLevel} is a scalar key and cannot have nested properties`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nested keys must have at least 3 segments (e.g., providers.myProvider.baseUrl)
|
// Nested keys must have at least minDepth segments (default 3)
|
||||||
if (schema.nested && path.length < 3) {
|
const minDepth = schema.minDepth ?? 3;
|
||||||
|
if (schema.nested && path.length < minDepth) {
|
||||||
const fields = schema.knownFields?.join(", ") ?? "";
|
const fields = schema.knownFields?.join(", ") ?? "";
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Incomplete path for ${topLevel}. Must specify a field (e.g., ${topLevel}.<name>.<field>). Valid fields: ${fields}`,
|
`Incomplete path for ${topLevel}. Must specify a field (e.g., ${topLevel}.<name>.<field>). Valid fields: ${fields}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user