fix: surface real tool error string in opencode log handler (#736)
opencode's `ToolStateError` carries the failure reason on `state.error`, not `state.output`. our log handler was reading `state.output` and falling back to `(no error message)`, so every tool failure logged a useless line. type the state as a discriminated union (mirrors @opencode-ai/sdk) so the field misread becomes a compile error. operator-facing only: the model already received the real error via opencode's tool-result envelope (verified by running webfetch against a known-404 URL — model reported "Error: Request failed with status code: 404" verbatim). closes #662
This commit is contained in:
committed by
pullfrog[bot]
parent
1f4c3031be
commit
8d6460da1c
+30
-22
@@ -307,6 +307,17 @@ interface OpenCodeStepFinishEvent {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* tool-part state, mirroring opencode's `ToolState` (anomalyco/opencode
|
||||
* `session/message-v2.ts`). error parts carry the reason on `error`,
|
||||
* completed parts on `output` — reading the wrong field is what caused
|
||||
* the silent `(no error message)` log in #662.
|
||||
*/
|
||||
type ToolState =
|
||||
| { status: "pending" | "running"; input?: unknown }
|
||||
| { status: "completed"; input?: unknown; output: string }
|
||||
| { status: "error"; input?: unknown; error: string };
|
||||
|
||||
interface OpenCodeToolUseEvent {
|
||||
type: "tool_use";
|
||||
timestamp?: number;
|
||||
@@ -315,7 +326,7 @@ interface OpenCodeToolUseEvent {
|
||||
id?: string;
|
||||
callID?: string;
|
||||
tool?: string;
|
||||
state?: { status?: string; input?: unknown; output?: string };
|
||||
state?: ToolState;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -324,7 +335,7 @@ interface OpenCodeToolResultEvent {
|
||||
type: "tool_result";
|
||||
timestamp?: number;
|
||||
sessionID?: string;
|
||||
part?: { callID?: string; state?: { status?: string; output?: string } };
|
||||
part?: { callID?: string; state?: ToolState };
|
||||
tool_id?: string;
|
||||
status?: "success" | "error";
|
||||
output?: string;
|
||||
@@ -703,11 +714,9 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
|
||||
// status="error" through the same `tool_use` event the CLI's run-loop
|
||||
// (and our injected plugin for subagent parts) emits — without this
|
||||
// branch the only signal in the user's logs is `» <tool>(...)` with
|
||||
// no indication the call failed. error info lives in `state.output`
|
||||
// (an error string set by the tool layer).
|
||||
// no indication the call failed.
|
||||
if (event.part?.state?.status === "error") {
|
||||
const errorMsg = event.part.state.output ?? "(no error message)";
|
||||
log.info(withLabel(label, `» tool call failed: ${errorMsg}`));
|
||||
log.info(withLabel(label, `» tool call failed: ${event.part.state.error}`));
|
||||
}
|
||||
|
||||
// agent's explicit MCP report_progress takes priority over todo tracking
|
||||
@@ -723,8 +732,14 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
|
||||
},
|
||||
tool_result: (event: OpenCodeToolResultEvent) => {
|
||||
const toolId = event.part?.callID || event.tool_id;
|
||||
const status = event.part?.state?.status || event.status || "unknown";
|
||||
const output = event.part?.state?.output || event.output;
|
||||
const state = event.part?.state;
|
||||
const status = state?.status ?? event.status ?? "unknown";
|
||||
const payload =
|
||||
state?.status === "completed"
|
||||
? state.output
|
||||
: state?.status === "error"
|
||||
? state.error
|
||||
: event.output;
|
||||
const label = eventLabel(event);
|
||||
|
||||
timerFor(label).markToolResult();
|
||||
@@ -743,12 +758,12 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
|
||||
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
||||
if (toolId && taskDispatchByCallID.has(toolId)) {
|
||||
const dispatch = taskDispatchByCallID.get(toolId);
|
||||
if (dispatch) emitSubagentFinished(dispatch, status, output, "exact");
|
||||
if (dispatch) emitSubagentFinished(dispatch, status, payload, "exact");
|
||||
} else {
|
||||
const callIDIsKnownNonTask = toolId ? knownNonTaskCallIDs.has(toolId) : false;
|
||||
if (!callIDIsKnownNonTask && pendingTaskDispatches.length > 0) {
|
||||
const dispatch = pendingTaskDispatches[0]!;
|
||||
emitSubagentFinished(dispatch, status, output, "fifo");
|
||||
emitSubagentFinished(dispatch, status, payload, "fifo");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -765,13 +780,8 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
|
||||
`» ${params.label} tool_result${stepContext}: id=${toolId}, status=${status}, duration=${Math.round(toolDuration)}ms`
|
||||
)
|
||||
);
|
||||
if (output) {
|
||||
log.debug(
|
||||
withLabel(
|
||||
label,
|
||||
` output: ${typeof output === "string" ? output : JSON.stringify(output)}`
|
||||
)
|
||||
);
|
||||
if (payload) {
|
||||
log.debug(withLabel(label, ` output: ${payload}`));
|
||||
}
|
||||
if (toolDuration > 5000) {
|
||||
log.info(
|
||||
@@ -784,11 +794,9 @@ async function runOpenCode(params: RunParams): Promise<AgentResult> {
|
||||
}
|
||||
}
|
||||
if (status === "error") {
|
||||
const errorMsg = typeof output === "string" ? output : JSON.stringify(output);
|
||||
log.info(withLabel(label, `» tool call failed: ${errorMsg}`));
|
||||
} else if (output) {
|
||||
const outputStr = typeof output === "string" ? output : JSON.stringify(output);
|
||||
log.debug(withLabel(label, `tool output: ${outputStr}`));
|
||||
log.info(withLabel(label, `» tool call failed: ${payload ?? "(no error message)"}`));
|
||||
} else if (payload) {
|
||||
log.debug(withLabel(label, `tool output: ${payload}`));
|
||||
}
|
||||
},
|
||||
error: (event: OpenCodeErrorEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user