Skip to content

Commit c817fe5

Browse files
OpenCode 2: restore a failed tool call in the host's finished result shape
The fold restore wrote a failed call's result as { result: { error, content }, resultType: "error" }, which is ToolResultPart.make's input, not its output. OpenCode 2.2 validates the request against the finished schema, so a single restored failed call made every request fail with "Schema validation failed". It now writes { type: "error", value: { error, content } }, the shape the host itself sends for a failed call. Co-authored-by: Alfonso <alfonso-magic-context@users.noreply.github.com>
1 parent 3a512e7 commit c817fe5

2 files changed

Lines changed: 12 additions & 7 deletions

File tree

‎packages/plugin/src/v2/fold/restore.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,16 @@ export function restoreRow(
254254
providerMetadata: (part.executed === true ? sameProvider : reuse)
255255
? metadata(native)
256256
: undefined,
257+
// A finished tool-result part, in the shape `ToolResultPart.make` returns:
258+
// `{ type, value }`. `{ result, resultType }` is make's input, and
259+
// OpenCode 2.2's request schema rejects it, failing every request that
260+
// carries a restored failed call.
257261
result:
258262
state.status === "error"
259-
? { error: state.error, content: toolContent }
263+
? { type: "error", value: { error: state.error, content: toolContent } }
260264
: single?.type === "text"
261265
? { type: "text", value: single.text }
262266
: { type: "content", value: toolContent },
263-
...(state.status === "error" ? { resultType: "error" } : {}),
264267
};
265268
if (part.executed === true) content.push(result);
266269
else results.push({ role: "tool", content: [result] });

‎packages/plugin/src/v2/hooks/payload.test.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="bun-types" />
22

33
import { describe, expect, it, spyOn } from "bun:test";
4-
import { Message } from "@opencode/ai/schema/messages";
4+
import { Message, ToolResultValue } from "@opencode/ai/schema/messages";
55
import { estimateMessageTokens } from "../../hooks/magic-context/final-wire-token-estimate";
66
import type { MessageLike } from "../../hooks/magic-context/tag-messages";
77
import {
@@ -243,10 +243,12 @@ describe("adaptPayload", () => {
243243
state: { status: "error", output: JSON.stringify(failure) },
244244
});
245245
payload.commit();
246-
expect(callsAndResults(context.messages).results[0]).toMatchObject({
247-
resultType: "error",
248-
result: failure,
249-
});
246+
const restored = callsAndResults(context.messages).results[0];
247+
// A restored failed call must already be in the host's finished shape;
248+
// OpenCode 2.2 validates the request against it and refuses otherwise.
249+
expect(restored.result).toEqual({ type: "error", value: failure });
250+
expect("resultType" in restored).toBe(false);
251+
expect(ToolResultValue.is(restored.result)).toBe(true);
250252
});
251253

252254
it("keeps error drop marker bytes and call pairing", () => {

0 commit comments

Comments
 (0)