fix: preserve agent instructions on adapter switch
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
75c7eb3868
commit
a315838d43
2 changed files with 75 additions and 2 deletions
|
|
@ -197,4 +197,43 @@ describe("agent instructions bundle routes", () => {
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("preserves managed instructions config when switching adapters", async () => {
|
||||||
|
mockAgentService.getById.mockResolvedValue({
|
||||||
|
...makeAgent(),
|
||||||
|
adapterType: "codex_local",
|
||||||
|
adapterConfig: {
|
||||||
|
instructionsBundleMode: "managed",
|
||||||
|
instructionsRootPath: "/tmp/agent-1",
|
||||||
|
instructionsEntryFile: "AGENTS.md",
|
||||||
|
instructionsFilePath: "/tmp/agent-1/AGENTS.md",
|
||||||
|
model: "gpt-5.4",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await request(createApp())
|
||||||
|
.patch("/api/agents/11111111-1111-4111-8111-111111111111?companyId=company-1")
|
||||||
|
.send({
|
||||||
|
adapterType: "claude_local",
|
||||||
|
adapterConfig: {
|
||||||
|
model: "claude-sonnet-4",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status, JSON.stringify(res.body)).toBe(200);
|
||||||
|
expect(mockAgentService.update).toHaveBeenCalledWith(
|
||||||
|
"11111111-1111-4111-8111-111111111111",
|
||||||
|
expect.objectContaining({
|
||||||
|
adapterType: "claude_local",
|
||||||
|
adapterConfig: expect.objectContaining({
|
||||||
|
model: "claude-sonnet-4",
|
||||||
|
instructionsBundleMode: "managed",
|
||||||
|
instructionsRootPath: "/tmp/agent-1",
|
||||||
|
instructionsEntryFile: "AGENTS.md",
|
||||||
|
instructionsFilePath: "/tmp/agent-1/AGENTS.md",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,13 @@ export function agentRoutes(db: Db) {
|
||||||
};
|
};
|
||||||
const DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES = new Set(Object.keys(DEFAULT_INSTRUCTIONS_PATH_KEYS));
|
const DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES = new Set(Object.keys(DEFAULT_INSTRUCTIONS_PATH_KEYS));
|
||||||
const KNOWN_INSTRUCTIONS_PATH_KEYS = new Set(["instructionsFilePath", "agentsMdPath"]);
|
const KNOWN_INSTRUCTIONS_PATH_KEYS = new Set(["instructionsFilePath", "agentsMdPath"]);
|
||||||
|
const KNOWN_INSTRUCTIONS_BUNDLE_KEYS = [
|
||||||
|
"instructionsBundleMode",
|
||||||
|
"instructionsRootPath",
|
||||||
|
"instructionsEntryFile",
|
||||||
|
"instructionsFilePath",
|
||||||
|
"agentsMdPath",
|
||||||
|
] as const;
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
const svc = agentService(db);
|
const svc = agentService(db);
|
||||||
|
|
@ -303,6 +310,24 @@ export function agentRoutes(db: Db) {
|
||||||
return trimmed.length > 0 ? trimmed : null;
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preserveInstructionsBundleConfig(
|
||||||
|
existingAdapterConfig: Record<string, unknown>,
|
||||||
|
nextAdapterConfig: Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
const nextKeys = new Set(Object.keys(nextAdapterConfig));
|
||||||
|
if (KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => nextKeys.has(key))) {
|
||||||
|
return nextAdapterConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const merged = { ...nextAdapterConfig };
|
||||||
|
for (const key of KNOWN_INSTRUCTIONS_BUNDLE_KEYS) {
|
||||||
|
if (merged[key] === undefined && existingAdapterConfig[key] !== undefined) {
|
||||||
|
merged[key] = existingAdapterConfig[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
function parseBooleanLike(value: unknown): boolean | null {
|
function parseBooleanLike(value: unknown): boolean | null {
|
||||||
if (typeof value === "boolean") return value;
|
if (typeof value === "boolean") return value;
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
|
|
@ -1710,9 +1735,18 @@ export function agentRoutes(db: Db) {
|
||||||
Object.prototype.hasOwnProperty.call(patchData, "adapterType") ||
|
Object.prototype.hasOwnProperty.call(patchData, "adapterType") ||
|
||||||
Object.prototype.hasOwnProperty.call(patchData, "adapterConfig");
|
Object.prototype.hasOwnProperty.call(patchData, "adapterConfig");
|
||||||
if (touchesAdapterConfiguration) {
|
if (touchesAdapterConfiguration) {
|
||||||
const rawEffectiveAdapterConfig = Object.prototype.hasOwnProperty.call(patchData, "adapterConfig")
|
const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {};
|
||||||
|
const changingAdapterType =
|
||||||
|
typeof patchData.adapterType === "string" && patchData.adapterType !== existing.adapterType;
|
||||||
|
let rawEffectiveAdapterConfig = Object.prototype.hasOwnProperty.call(patchData, "adapterConfig")
|
||||||
? (asRecord(patchData.adapterConfig) ?? {})
|
? (asRecord(patchData.adapterConfig) ?? {})
|
||||||
: (asRecord(existing.adapterConfig) ?? {});
|
: existingAdapterConfig;
|
||||||
|
if (changingAdapterType) {
|
||||||
|
rawEffectiveAdapterConfig = preserveInstructionsBundleConfig(
|
||||||
|
existingAdapterConfig,
|
||||||
|
rawEffectiveAdapterConfig,
|
||||||
|
);
|
||||||
|
}
|
||||||
const effectiveAdapterConfig = applyCreateDefaultsByAdapterType(
|
const effectiveAdapterConfig = applyCreateDefaultsByAdapterType(
|
||||||
requestedAdapterType,
|
requestedAdapterType,
|
||||||
rawEffectiveAdapterConfig,
|
rawEffectiveAdapterConfig,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue