abd5b13d52
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
87 lines
4.2 KiB
TypeScript
87 lines
4.2 KiB
TypeScript
/**
|
|
* Agent config route: the per-tool `call_description` field on toolsBuiltin rows. The
|
|
* default config writes `call_description: true` (plus the `description` property in
|
|
* parameters) on the four command/subagent tools; PUT round-trips a flipped `false` into
|
|
* system_config.yaml (preserving the rest of the file); a non-boolean value is a 400.
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import fs from "node:fs/promises";
|
|
import { systemConfigPath } from "@prismshadow/penguin-core";
|
|
import type { ToolDefinitionConfig } from "@prismshadow/penguin-core";
|
|
import type { AgentConfigResponse, ProjectCreateResponse } from "../src/api/types.js";
|
|
import { apiClient, createTestApp, provisionUser } from "./helpers.js";
|
|
import type { TestApp } from "./helpers.js";
|
|
|
|
describe("agent config: per-tool call_description", () => {
|
|
let t: TestApp;
|
|
let owner: ReturnType<typeof apiClient>;
|
|
let projectId: string;
|
|
let configPath: string;
|
|
|
|
beforeEach(async () => {
|
|
t = await createTestApp();
|
|
const a = await provisionUser(t.app, "owner_cd");
|
|
owner = apiClient(t.app, a.cookie);
|
|
const created = (await (
|
|
await owner.post("/api/projects", { projectId: "owner_cd-calldesc", name: "cd project" })
|
|
).json()) as ProjectCreateResponse;
|
|
projectId = created.project.projectId;
|
|
configPath = `/api/projects/${projectId}/agents/default_agent/config`;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await t.cleanup();
|
|
});
|
|
|
|
it("defaults carry call_description: true and the description property; PUT false round-trips", async () => {
|
|
const initial = (await (await owner.get(configPath)).json()) as AgentConfigResponse;
|
|
const names = initial.config.toolsBuiltin.map((tool) => tool.name);
|
|
// File tools lead the default toolset; the renamed shell tool follows.
|
|
expect(names.slice(0, 4)).toEqual(["read_file", "edit_file", "write_file", "exec_command"]);
|
|
const propsOf = (tool: ToolDefinitionConfig): Record<string, unknown> =>
|
|
(tool.parameters as { properties: Record<string, unknown> }).properties;
|
|
for (const name of ["exec_command", "input_command", "run_subagent", "input_subagent"]) {
|
|
const tool = initial.config.toolsBuiltin.find((row) => row.name === name)!;
|
|
expect(tool.call_description).toBe(true);
|
|
expect(propsOf(tool)["description"]).toBeDefined();
|
|
}
|
|
for (const name of ["read_file", "edit_file", "write_file"]) {
|
|
const tool = initial.config.toolsBuiltin.find((row) => row.name === name)!;
|
|
expect(tool.call_description).toBeUndefined();
|
|
expect(propsOf(tool)["description"]).toBeUndefined();
|
|
}
|
|
|
|
// Flip exec_command's toggle off via the whole-table PUT.
|
|
const tools = initial.config.toolsBuiltin.map((row) =>
|
|
row.name === "exec_command" ? { ...row, call_description: false } : row,
|
|
);
|
|
const putRes = await owner.put(configPath, { config: { toolsBuiltin: tools } });
|
|
expect(putRes.status).toBe(200);
|
|
const updated = (await putRes.json()) as AgentConfigResponse;
|
|
expect(
|
|
updated.config.toolsBuiltin.find((row) => row.name === "exec_command")!.call_description,
|
|
).toBe(false);
|
|
// Written into the YAML itself; the rest of the config is preserved.
|
|
const yaml = await fs.readFile(systemConfigPath(t.root, projectId, "default_agent"), "utf8");
|
|
expect(yaml).toContain("call_description: false");
|
|
expect(updated.config.systemPrompt).toBe(initial.config.systemPrompt);
|
|
// The property stays declared in the stored parameters (filtering happens at assembly, not in the config).
|
|
expect(
|
|
propsOf(updated.config.toolsBuiltin.find((row) => row.name === "exec_command")!)[
|
|
"description"
|
|
],
|
|
).toBeDefined();
|
|
});
|
|
|
|
it("rejects a non-boolean call_description with 400", async () => {
|
|
const initial = (await (await owner.get(configPath)).json()) as AgentConfigResponse;
|
|
const tools = initial.config.toolsBuiltin.map((row) =>
|
|
row.name === "exec_command" ? { ...row, call_description: "yes" } : row,
|
|
);
|
|
const res = await owner.put(configPath, { config: { toolsBuiltin: tools } });
|
|
expect(res.status).toBe(400);
|
|
const body = (await res.json()) as { error?: { message?: string } };
|
|
expect(JSON.stringify(body)).toContain("call_description");
|
|
});
|
|
});
|