fix(ai): filter empty error assistant messages in transformMessages

When 429/500 errors occur during tool execution, empty assistant messages
with stopReason='error' get persisted. These break the tool_use -> tool_result
chain for Claude/Gemini APIs.

Added centralized filtering in transformMessages to skip assistant messages
with empty content and no tool calls. Provider-level filters remain for
defense-in-depth.
This commit is contained in:
Mario Zechner 2026-01-16 22:34:58 +01:00
parent d2f9ab110c
commit fbb74bb29e
11 changed files with 125 additions and 8 deletions

View file

@ -105,4 +105,36 @@ describe("SettingsManager", () => {
expect(savedSettings.defaultThinkingLevel).toBe("high");
});
});
describe("shellCommandPrefix", () => {
it("should load shellCommandPrefix from settings", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ shellCommandPrefix: "shopt -s expand_aliases" }));
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getShellCommandPrefix()).toBe("shopt -s expand_aliases");
});
it("should return undefined when shellCommandPrefix is not set", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ theme: "dark" }));
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getShellCommandPrefix()).toBeUndefined();
});
it("should preserve shellCommandPrefix when saving unrelated settings", () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ shellCommandPrefix: "shopt -s expand_aliases" }));
const manager = SettingsManager.create(projectDir, agentDir);
manager.setTheme("light");
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
expect(savedSettings.shellCommandPrefix).toBe("shopt -s expand_aliases");
expect(savedSettings.theme).toBe("light");
});
});
});

View file

@ -299,6 +299,31 @@ describe("Coding Agent Tools", () => {
await expect(bashWithBadShell.execute("test-call-12", { command: "echo test" })).rejects.toThrow(/ENOENT/);
});
it("should prepend command prefix when configured", async () => {
const bashWithPrefix = createBashTool(testDir, {
commandPrefix: "export TEST_VAR=hello",
});
const result = await bashWithPrefix.execute("test-prefix-1", { command: "echo $TEST_VAR" });
expect(getTextOutput(result).trim()).toBe("hello");
});
it("should include output from both prefix and command", async () => {
const bashWithPrefix = createBashTool(testDir, {
commandPrefix: "echo prefix-output",
});
const result = await bashWithPrefix.execute("test-prefix-2", { command: "echo command-output" });
expect(getTextOutput(result).trim()).toBe("prefix-output\ncommand-output");
});
it("should work without command prefix", async () => {
const bashWithoutPrefix = createBashTool(testDir, {});
const result = await bashWithoutPrefix.execute("test-prefix-3", { command: "echo no-prefix" });
expect(getTextOutput(result).trim()).toBe("no-prefix");
});
});
describe("grep tool", () => {