refactor: extract tool call status normalization
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
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 { deriveCodexToolDetail, normalizeCodexFilePath } from "./tool-call-detail-parser.js";
|
||||||
import { isSpeakToolName } from "../../tool-name-normalization.js";
|
import { isSpeakToolName } from "../../tool-name-normalization.js";
|
||||||
|
|
||||||
@@ -9,9 +13,6 @@ interface CodexMapperOptions {
|
|||||||
cwd?: string | null;
|
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 CodexCommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
||||||
|
|
||||||
const CodexToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]);
|
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 {
|
function buildMcpToolName(server: string | undefined, tool: string): string {
|
||||||
const trimmedTool = tool.trim();
|
const trimmedTool = tool.trim();
|
||||||
if (!trimmedTool) {
|
if (!trimmedTool) {
|
||||||
@@ -633,7 +606,7 @@ function mapCommandExecutionItem(
|
|||||||
|
|
||||||
const name = "shell";
|
const name = "shell";
|
||||||
const error = item.error ?? null;
|
const error = item.error ?? null;
|
||||||
const status = resolveStatus(item.status, error, output);
|
const status = normalizeToolCallStatus(item.status, error, output);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callId: item.id,
|
callId: item.id,
|
||||||
@@ -808,7 +781,7 @@ function mapFileChangeItem(
|
|||||||
|
|
||||||
const name = "apply_patch";
|
const name = "apply_patch";
|
||||||
const error = item.error ?? null;
|
const error = item.error ?? null;
|
||||||
const status = resolveStatus(item.status, error, output);
|
const status = normalizeToolCallStatus(item.status, error, output);
|
||||||
const firstFile = files[0];
|
const firstFile = files[0];
|
||||||
const firstTextFields = resolveFileChangeTextFields(firstFile);
|
const firstTextFields = resolveFileChangeTextFields(firstFile);
|
||||||
const hasFirstTextFields = Object.keys(firstTextFields).length > 0;
|
const hasFirstTextFields = Object.keys(firstTextFields).length > 0;
|
||||||
@@ -846,7 +819,7 @@ function mapMcpToolCallItem(
|
|||||||
const input = item.arguments ?? null;
|
const input = item.arguments ?? null;
|
||||||
const output = item.result ?? null;
|
const output = item.result ?? null;
|
||||||
const error = item.error ?? null;
|
const error = item.error ?? null;
|
||||||
const status = resolveStatus(item.status, error, output);
|
const status = normalizeToolCallStatus(item.status, error, output);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callId: item.id,
|
callId: item.id,
|
||||||
@@ -866,7 +839,7 @@ function mapWebSearchItem(
|
|||||||
const output = item.action ?? null;
|
const output = item.action ?? null;
|
||||||
const name = "web_search";
|
const name = "web_search";
|
||||||
const error = item.error ?? null;
|
const error = item.error ?? null;
|
||||||
const status = resolveStatus(item.status ?? "completed", error, output);
|
const status = normalizeToolCallStatus(item.status ?? "completed", error, output);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callId: item.id,
|
callId: item.id,
|
||||||
@@ -943,7 +916,11 @@ export function mapCodexRolloutToolCall(params: {
|
|||||||
input: normalizedInput,
|
input: normalizedInput,
|
||||||
output: parsed.data.output ?? null,
|
output: parsed.data.output ?? null,
|
||||||
error: parsed.data.error ?? 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,
|
cwd: params.cwd ?? null,
|
||||||
});
|
});
|
||||||
if (!pass1.success) {
|
if (!pass1.success) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
import type { ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||||
|
import { normalizeToolCallStatus } from "../tool-call-mapper-utils.js";
|
||||||
import { deriveOpencodeToolDetail } from "./tool-call-detail-parser.js";
|
import { deriveOpencodeToolDetail } from "./tool-call-detail-parser.js";
|
||||||
|
|
||||||
interface OpencodeToolCallParams {
|
interface OpencodeToolCallParams {
|
||||||
@@ -13,10 +14,6 @@ interface OpencodeToolCallParams {
|
|||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 OpencodeToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]);
|
||||||
|
|
||||||
const OpencodeRawToolCallSchema = z
|
const OpencodeRawToolCallSchema = z
|
||||||
@@ -37,24 +34,8 @@ const OpencodeNormalizedToolCallPass1Schema = OpencodeRawToolCallSchema.transfor
|
|||||||
const error = raw.error ?? null;
|
const error = raw.error ?? null;
|
||||||
const callId =
|
const callId =
|
||||||
typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId.trim() : null;
|
typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId.trim() : null;
|
||||||
let status: z.infer<typeof OpencodeToolCallStatusSchema>;
|
const rawStatus = typeof raw.status === "string" ? raw.status : undefined;
|
||||||
|
const status = normalizeToolCallStatus(rawStatus, error, output);
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callId,
|
callId,
|
||||||
|
|||||||
@@ -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 {
|
interface ReadChunkLike {
|
||||||
text?: string;
|
text?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user