diff --git a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts index 601fe347c..5a5ab86a6 100644 --- a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts @@ -1,7 +1,11 @@ import { z } from "zod"; import type { ToolCallTimelineItem } from "../../agent-sdk-types.js"; -import { extractCodexShellOutput, truncateDiffText } from "../tool-call-mapper-utils.js"; +import { + extractCodexShellOutput, + normalizeToolCallStatus, + truncateDiffText, +} from "../tool-call-mapper-utils.js"; import { deriveCodexToolDetail, normalizeCodexFilePath } from "./tool-call-detail-parser.js"; import { isSpeakToolName } from "../../tool-name-normalization.js"; @@ -9,9 +13,6 @@ interface CodexMapperOptions { cwd?: string | null; } -const FAILED_STATUSES = new Set(["failed", "error", "errored", "rejected", "denied"]); -const CANCELED_STATUSES = new Set(["canceled", "cancelled", "interrupted", "aborted"]); -const COMPLETED_STATUSES = new Set(["completed", "complete", "done", "success", "succeeded"]); const CodexCommandValueSchema = z.union([z.string(), z.array(z.string())]); const CodexToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]); @@ -552,34 +553,6 @@ function hasRenderableEditDetail(detail: ToolCallTimelineItem["detail"]): boolea ); } -function resolveStatus( - rawStatus: string | undefined, - error: unknown, - output: unknown, -): ToolCallTimelineItem["status"] { - if (error !== undefined && error !== null) { - return "failed"; - } - - if (typeof rawStatus === "string") { - const normalized = rawStatus.trim().toLowerCase(); - if (normalized.length > 0) { - if (FAILED_STATUSES.has(normalized)) { - return "failed"; - } - if (CANCELED_STATUSES.has(normalized)) { - return "canceled"; - } - if (COMPLETED_STATUSES.has(normalized)) { - return "completed"; - } - return "running"; - } - } - - return output !== null && output !== undefined ? "completed" : "running"; -} - function buildMcpToolName(server: string | undefined, tool: string): string { const trimmedTool = tool.trim(); if (!trimmedTool) { @@ -633,7 +606,7 @@ function mapCommandExecutionItem( const name = "shell"; const error = item.error ?? null; - const status = resolveStatus(item.status, error, output); + const status = normalizeToolCallStatus(item.status, error, output); return { callId: item.id, @@ -808,7 +781,7 @@ function mapFileChangeItem( const name = "apply_patch"; const error = item.error ?? null; - const status = resolveStatus(item.status, error, output); + const status = normalizeToolCallStatus(item.status, error, output); const firstFile = files[0]; const firstTextFields = resolveFileChangeTextFields(firstFile); const hasFirstTextFields = Object.keys(firstTextFields).length > 0; @@ -846,7 +819,7 @@ function mapMcpToolCallItem( const input = item.arguments ?? null; const output = item.result ?? null; const error = item.error ?? null; - const status = resolveStatus(item.status, error, output); + const status = normalizeToolCallStatus(item.status, error, output); return { callId: item.id, @@ -866,7 +839,7 @@ function mapWebSearchItem( const output = item.action ?? null; const name = "web_search"; const error = item.error ?? null; - const status = resolveStatus(item.status ?? "completed", error, output); + const status = normalizeToolCallStatus(item.status ?? "completed", error, output); return { callId: item.id, @@ -943,7 +916,11 @@ export function mapCodexRolloutToolCall(params: { input: normalizedInput, output: parsed.data.output ?? null, error: parsed.data.error ?? null, - status: resolveStatus("completed", parsed.data.error ?? null, parsed.data.output ?? null), + status: normalizeToolCallStatus( + "completed", + parsed.data.error ?? null, + parsed.data.output ?? null, + ), cwd: params.cwd ?? null, }); if (!pass1.success) { diff --git a/packages/server/src/server/agent/providers/opencode/tool-call-mapper.ts b/packages/server/src/server/agent/providers/opencode/tool-call-mapper.ts index 29702b2da..679e65cfb 100644 --- a/packages/server/src/server/agent/providers/opencode/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/opencode/tool-call-mapper.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import type { ToolCallTimelineItem } from "../../agent-sdk-types.js"; +import { normalizeToolCallStatus } from "../tool-call-mapper-utils.js"; import { deriveOpencodeToolDetail } from "./tool-call-detail-parser.js"; interface OpencodeToolCallParams { @@ -13,10 +14,6 @@ interface OpencodeToolCallParams { metadata?: Record; } -const FAILED_STATUSES = new Set(["error", "failed", "failure"]); -const CANCELED_STATUSES = new Set(["canceled", "cancelled", "aborted", "interrupted"]); -const COMPLETED_STATUSES = new Set(["complete", "completed", "success", "succeeded", "done"]); - const OpencodeToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]); const OpencodeRawToolCallSchema = z @@ -37,24 +34,8 @@ const OpencodeNormalizedToolCallPass1Schema = OpencodeRawToolCallSchema.transfor const error = raw.error ?? null; const callId = typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId.trim() : null; - let status: z.infer; - - if (error !== null) { - status = "failed"; - } else if (typeof raw.status === "string") { - const normalized = raw.status.trim().toLowerCase(); - if (FAILED_STATUSES.has(normalized)) { - status = "failed"; - } else if (CANCELED_STATUSES.has(normalized)) { - status = "canceled"; - } else if (COMPLETED_STATUSES.has(normalized)) { - status = "completed"; - } else { - status = "running"; - } - } else { - status = output !== null ? "completed" : "running"; - } + const rawStatus = typeof raw.status === "string" ? raw.status : undefined; + const status = normalizeToolCallStatus(rawStatus, error, output); return { callId, diff --git a/packages/server/src/server/agent/providers/tool-call-mapper-utils.ts b/packages/server/src/server/agent/providers/tool-call-mapper-utils.ts index 66cb68070..042e757ca 100644 --- a/packages/server/src/server/agent/providers/tool-call-mapper-utils.ts +++ b/packages/server/src/server/agent/providers/tool-call-mapper-utils.ts @@ -1,3 +1,44 @@ +export type NormalizedToolCallStatus = "running" | "completed" | "failed" | "canceled"; + +const FAILED_STATUS_VOCAB = new Set([ + "failed", + "failure", + "error", + "errored", + "rejected", + "denied", +]); +const CANCELED_STATUS_VOCAB = new Set(["canceled", "cancelled", "interrupted", "aborted"]); +const COMPLETED_STATUS_VOCAB = new Set(["completed", "complete", "done", "success", "succeeded"]); + +export function normalizeToolCallStatus( + rawStatus: string | undefined | null, + error: unknown, + output: unknown, +): NormalizedToolCallStatus { + if (error !== undefined && error !== null) { + return "failed"; + } + + if (typeof rawStatus === "string") { + const normalized = rawStatus.trim().toLowerCase(); + if (normalized.length > 0) { + if (FAILED_STATUS_VOCAB.has(normalized)) { + return "failed"; + } + if (CANCELED_STATUS_VOCAB.has(normalized)) { + return "canceled"; + } + if (COMPLETED_STATUS_VOCAB.has(normalized)) { + return "completed"; + } + return "running"; + } + } + + return output !== null && output !== undefined ? "completed" : "running"; +} + interface ReadChunkLike { text?: string; content?: string;