Files
pi_harness/packages/ai/test/openai-responses-empty-tool-result.test.ts
T
Samwise Wang 279f53b098 fix(ai): use "(no tool output)" placeholder for empty tool results without images (#6290)
OpenAI Completions and Responses providers unconditionally replaced empty
tool result text with "(see attached image)", even when the result had no
image content. This caused the model to hallucinate image attachments for
commands that produce no output (e.g. curl -s with SSL errors, grep with
no matches, true/false).

Now matches the Google provider behavior: "(see attached image)" is only
used when images are actually present; empty results get "(no tool output)".

Co-authored-by: tzwm <tzwm@users.noreply.github.com>
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
2026-07-06 21:42:56 +02:00

59 lines
1.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { convertResponsesMessages } from "../src/api/openai-responses-shared.ts";
import { getModel } from "../src/compat.ts";
import type { AssistantMessage, Context, ToolResultMessage, Usage } from "../src/types.ts";
const usage: Usage = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
function buildEmptyToolResult(toolCallId: string, timestamp: number): ToolResultMessage {
return {
role: "toolResult",
toolCallId,
toolName: "bash",
content: [{ type: "text", text: "" }],
isError: false,
timestamp,
};
}
describe("OpenAI Responses convertResponsesMessages empty tool result", () => {
it("uses '(no tool output)' placeholder for empty tool results without images", () => {
const model = getModel("openai", "gpt-4o-mini");
const now = Date.now();
const assistant: AssistantMessage = {
role: "assistant",
content: [{ type: "toolCall", id: "tool-1", name: "bash", arguments: { command: "true" } }],
api: model.api,
provider: model.provider,
model: model.id,
usage,
stopReason: "toolUse",
timestamp: now,
};
const context: Context = {
messages: [
{ role: "user", content: "Run the command", timestamp: now - 1 },
assistant,
buildEmptyToolResult("tool-1", now + 1),
],
};
const input = convertResponsesMessages(model, context, new Set(["openai", "openai-codex", "opencode"]));
const functionCallOutput = input.find((item) => item.type === "function_call_output") as
| { type: "function_call_output"; output: string }
| undefined;
expect(functionCallOutput).toBeTruthy();
expect(functionCallOutput?.output).toBe("(no tool output)");
expect(functionCallOutput?.output).not.toContain("see attached image");
});
});