Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/protocol/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ type ResponsesInputItem =
// Tool results carry a content array; the Responses API wants a string. Join
// the text parts; non-text content is not representable here and is dropped
// with a marker so the model is not misled into thinking it went missing
// silently.
// silently. The wire has no error flag, so a failed result is wrapped in
// `<error>` as Interchange's Chat Completions adapter does.
function toolResultText(
block: Extract<ContentBlock, { type: "tool_result" }>,
): string {
Expand All @@ -51,7 +52,8 @@ function toolResultText(
if (c.type === "text") parts.push(c.text);
else parts.push(`[unsupported ${c.type} content omitted]`);
}
return parts.join("");
const text = parts.join("\n");
return block.isError ? `<error>\n${text}\n</error>` : text;
}

// Grounded on the OpenAI Responses API document input shape: a base64
Expand Down
36 changes: 36 additions & 0 deletions src/responses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,42 @@ describe("Responses request builder — tool-item dedupe", () => {
});
});

describe("Responses request builder — tool-result encoding", () => {
// The wire has no error flag; without the `<error>` wrapper the model
// cannot tell a failed tool from a successful one.
test.each([
{ name: "success", isError: false, output: "a\nb" },
{ name: "error", isError: true, output: "<error>\na\nb\n</error>" },
])("encodes a $name result like the Chat adapter", ({ isError, output }) => {
const adapter = createOpenAIResponsesAdapter(source, {});
const request = adapter.buildRequest(
[
{
role: "user",
timestamp: 0,
content: [
{
type: "tool_result",
callId: "call_1",
isError,
content: [
{ type: "text", text: "a" },
{ type: "text", text: "b" },
],
},
],
},
],
"model",
{},
);
const outputs = inputItemsOf(request).filter(
(item) => item["type"] === "function_call_output",
);
expect(outputs[0]?.["output"]).toBe(output);
});
});

describe("Responses request builder — un-replayable reasoning", () => {
const history = (model: string, signature: string): ConversationTurn[] => [
{ role: "user", timestamp: 0, content: [{ type: "text", text: "hi" }] },
Expand Down
Loading