refactor(server): collapse tool-call mapper pass2 zod dance

Replace the duplicated 3-stage Zod pipeline (pass1 -> pass2 envelope
union -> pass2 discriminated union) in each provider mapper with a plain
resolveToolKind(name) function over readonly name sets. Opencode's
toolKind was unused; Claude/Codex used it only as a switch discriminator.
This commit is contained in:
Mohamed Boudra
2026-05-01 22:53:14 +07:00
parent 14d78327c3
commit 9800203a48
3 changed files with 160 additions and 443 deletions
@@ -13,6 +13,7 @@ interface MapperParams {
}
const ClaudeToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]);
type ClaudeToolCallStatus = z.infer<typeof ClaudeToolCallStatusSchema>;
const ClaudeRawToolCallSchema = z
.object({
@@ -26,185 +27,58 @@ const ClaudeRawToolCallSchema = z
})
.passthrough();
const ClaudeToolCallPass1Schema = ClaudeRawToolCallSchema.transform((raw) => ({
callId: typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId : null,
name: raw.name.trim(),
input: raw.input ?? null,
output: raw.output ?? null,
metadata: raw.metadata,
error: raw.error ?? null,
status: raw.status,
}));
type ClaudeToolKind =
| "shell"
| "read"
| "write"
| "edit"
| "search"
| "fetch"
| "speak"
| "unknown";
const ClaudeShellToolNameSchema = z.union([
z.literal("Bash"),
z.literal("bash"),
z.literal("shell"),
z.literal("exec_command"),
]);
const ClaudeReadToolNameSchema = z.union([
z.literal("Read"),
z.literal("read"),
z.literal("read_file"),
z.literal("view_file"),
]);
const ClaudeWriteToolNameSchema = z.union([
z.literal("Write"),
z.literal("write"),
z.literal("write_file"),
z.literal("create_file"),
]);
const ClaudeEditToolNameSchema = z.union([
z.literal("Edit"),
z.literal("MultiEdit"),
z.literal("multi_edit"),
z.literal("edit"),
z.literal("apply_patch"),
z.literal("apply_diff"),
z.literal("str_replace_editor"),
]);
const ClaudeSearchToolNameSchema = z.union([
z.literal("WebSearch"),
z.literal("web_search"),
z.literal("search"),
z.literal("Grep"),
z.literal("grep"),
z.literal("Glob"),
z.literal("glob"),
]);
const ClaudeFetchToolNameSchema = z.union([
z.literal("WebFetch"),
z.literal("web_fetch"),
z.literal("WebFetchTool"),
z.literal("web_fetch_tool"),
z.literal("webfetch"),
]);
const ClaudeSpeakToolNameSchema = z
.string()
.min(1)
.refine((name) => isSpeakToolName(name.trim()));
const ClaudeToolKindSchema = z.enum([
"shell",
"read",
"write",
const SHELL_NAMES: ReadonlySet<string> = new Set(["Bash", "bash", "shell", "exec_command"]);
const READ_NAMES: ReadonlySet<string> = new Set(["Read", "read", "read_file", "view_file"]);
const WRITE_NAMES: ReadonlySet<string> = new Set(["Write", "write", "write_file", "create_file"]);
const EDIT_NAMES: ReadonlySet<string> = new Set([
"Edit",
"MultiEdit",
"multi_edit",
"edit",
"apply_patch",
"apply_diff",
"str_replace_editor",
]);
const SEARCH_NAMES: ReadonlySet<string> = new Set([
"WebSearch",
"web_search",
"search",
"fetch",
"speak",
"unknown",
"Grep",
"grep",
"Glob",
"glob",
]);
const FETCH_NAMES: ReadonlySet<string> = new Set([
"WebFetch",
"web_fetch",
"WebFetchTool",
"web_fetch_tool",
"webfetch",
]);
const ClaudeToolCallPass2BaseSchema = z.object({
callId: z.string().min(1),
name: z.string().min(1),
input: z.unknown().nullable(),
output: z.unknown().nullable(),
metadata: z.record(z.string(), z.unknown()).optional(),
error: z.unknown().nullable(),
status: ClaudeToolCallStatusSchema,
toolKind: ClaudeToolKindSchema,
});
function resolveClaudeToolKind(name: string): ClaudeToolKind {
if (SHELL_NAMES.has(name)) return "shell";
if (READ_NAMES.has(name)) return "read";
if (WRITE_NAMES.has(name)) return "write";
if (EDIT_NAMES.has(name)) return "edit";
if (SEARCH_NAMES.has(name)) return "search";
if (FETCH_NAMES.has(name)) return "fetch";
if (isSpeakToolName(name)) return "speak";
return "unknown";
}
const ClaudeToolCallPass2InputSchema = ClaudeToolCallPass2BaseSchema.omit({
toolKind: true,
});
const ClaudeToolCallPass2EnvelopeSchema = z.union([
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeShellToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "shell" as const,
})),
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeReadToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "read" as const,
})),
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeWriteToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "write" as const,
})),
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeEditToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "edit" as const,
})),
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeSearchToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "search" as const,
})),
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeFetchToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "fetch" as const,
})),
ClaudeToolCallPass2InputSchema.extend({
name: ClaudeSpeakToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: "speak" as const,
toolKind: "speak" as const,
})),
ClaudeToolCallPass2InputSchema.transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "unknown" as const,
})),
]);
const ClaudeToolCallPass2Schema = z.discriminatedUnion("toolKind", [
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("shell"),
name: ClaudeShellToolNameSchema,
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("read"),
name: ClaudeReadToolNameSchema,
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("write"),
name: ClaudeWriteToolNameSchema,
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("edit"),
name: ClaudeEditToolNameSchema,
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("search"),
name: ClaudeSearchToolNameSchema,
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("fetch"),
name: ClaudeFetchToolNameSchema,
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("speak"),
name: z.literal("speak"),
}),
ClaudeToolCallPass2BaseSchema.extend({
toolKind: z.literal("unknown"),
}),
]);
type ClaudeToolCallPass2 = z.infer<typeof ClaudeToolCallPass2Schema>;
function resolveDetailName(normalized: ClaudeToolCallPass2): string {
switch (normalized.toolKind) {
function resolveDetailName(toolKind: ClaudeToolKind, name: string): string {
switch (toolKind) {
case "shell":
return "shell";
case "read":
@@ -215,65 +89,56 @@ function resolveDetailName(normalized: ClaudeToolCallPass2): string {
return "apply_patch";
case "search":
case "fetch":
return normalized.name;
return name;
case "speak":
return "speak";
default:
return normalized.name;
return name;
}
}
function toToolCallTimelineItem(normalized: ClaudeToolCallPass2): ToolCallTimelineItem {
const name = normalized.toolKind === "speak" ? ("speak" as const) : normalized.name;
const detailName = resolveDetailName(normalized);
const detail = deriveClaudeToolDetail(detailName, normalized.input, normalized.output);
if (normalized.status === "failed") {
return {
type: "tool_call",
callId: normalized.callId,
name,
detail,
status: "failed",
error: normalized.error ?? { message: "Tool call failed" },
...(normalized.metadata ? { metadata: normalized.metadata } : {}),
};
}
return {
type: "tool_call",
callId: normalized.callId,
name,
detail,
status: normalized.status,
error: null,
...(normalized.metadata ? { metadata: normalized.metadata } : {}),
};
}
function mapClaudeToolCall(
params: MapperParams,
status: z.infer<typeof ClaudeToolCallStatusSchema>,
status: ClaudeToolCallStatus,
error: unknown | null,
): ToolCallTimelineItem | null {
const pass1 = ClaudeToolCallPass1Schema.safeParse({
...params,
status,
error,
});
if (!pass1.success) {
const parsed = ClaudeRawToolCallSchema.safeParse({ ...params, status, error });
if (!parsed.success) {
return null;
}
const raw = parsed.data;
const callId = typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId : null;
if (callId === null) {
return null;
}
const pass2Envelope = ClaudeToolCallPass2EnvelopeSchema.safeParse(pass1.data);
if (!pass2Envelope.success) {
return null;
}
const trimmedName = raw.name.trim();
const toolKind = resolveClaudeToolKind(trimmedName);
const name = toolKind === "speak" ? "speak" : trimmedName;
const input = raw.input ?? null;
const output = raw.output ?? null;
const detail = deriveClaudeToolDetail(resolveDetailName(toolKind, name), input, output);
const pass2 = ClaudeToolCallPass2Schema.safeParse(pass2Envelope.data);
if (!pass2.success) {
return null;
if (raw.status === "failed") {
return {
type: "tool_call",
callId,
name,
detail,
status: "failed",
error: raw.error ?? { message: "Tool call failed" },
...(raw.metadata ? { metadata: raw.metadata } : {}),
};
}
return toToolCallTimelineItem(pass2.data);
return {
type: "tool_call",
callId,
name,
detail,
status: raw.status,
error: null,
...(raw.metadata ? { metadata: raw.metadata } : {}),
};
}
export function mapClaudeRunningToolCall(params: MapperParams): ToolCallTimelineItem | null {
@@ -16,6 +16,7 @@ interface CodexMapperOptions {
const CodexCommandValueSchema = z.union([z.string(), z.array(z.string())]);
const CodexToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]);
type CodexToolCallStatus = z.infer<typeof CodexToolCallStatusSchema>;
const CodexRolloutToolCallParamsSchema = z
.object({
@@ -38,117 +39,44 @@ interface CodexNormalizedToolCallEnvelope {
cwd?: string | null;
}
const CodexNormalizedToolCallPass1Schema = z
.object({
callId: z.string().min(1),
name: z.string().min(1),
input: z.unknown().nullable(),
output: z.unknown().nullable(),
status: CodexToolCallStatusSchema,
error: z.unknown().nullable(),
metadata: z.record(z.string(), z.unknown()).optional(),
cwd: z.string().nullable().optional(),
})
.passthrough();
type CodexToolKind = "shell" | "read" | "write" | "edit" | "search" | "speak" | "unknown";
const CodexShellToolNameSchema = z.union([
z.literal("Bash"),
z.literal("shell"),
z.literal("bash"),
z.literal("exec"),
z.literal("exec_command"),
z.literal("command"),
]);
const CodexReadToolNameSchema = z.union([z.literal("read"), z.literal("read_file")]);
const CodexWriteToolNameSchema = z.union([
z.literal("write"),
z.literal("write_file"),
z.literal("create_file"),
]);
const CodexEditToolNameSchema = z.union([
z.literal("edit"),
z.literal("apply_patch"),
z.literal("apply_diff"),
]);
const CodexSearchToolNameSchema = z.union([z.literal("search"), z.literal("web_search")]);
const CodexSpeakToolNameSchema = z
.string()
.min(1)
.refine((name) => isSpeakToolName(name.trim()));
const CodexToolKindSchema = z.enum([
const CODEX_SHELL_NAMES: ReadonlySet<string> = new Set([
"Bash",
"shell",
"read",
"write",
"edit",
"search",
"speak",
"unknown",
"bash",
"exec",
"exec_command",
"command",
]);
const CODEX_READ_NAMES: ReadonlySet<string> = new Set(["read", "read_file"]);
const CODEX_WRITE_NAMES: ReadonlySet<string> = new Set(["write", "write_file", "create_file"]);
const CODEX_EDIT_NAMES: ReadonlySet<string> = new Set(["edit", "apply_patch", "apply_diff"]);
const CODEX_SEARCH_NAMES: ReadonlySet<string> = new Set(["search", "web_search"]);
const CodexToolCallPass2BaseSchema = CodexNormalizedToolCallPass1Schema.extend({
toolKind: CodexToolKindSchema,
});
function resolveCodexToolKind(name: string): CodexToolKind {
if (CODEX_SHELL_NAMES.has(name)) return "shell";
if (CODEX_READ_NAMES.has(name)) return "read";
if (CODEX_WRITE_NAMES.has(name)) return "write";
if (CODEX_EDIT_NAMES.has(name)) return "edit";
if (CODEX_SEARCH_NAMES.has(name)) return "search";
if (isSpeakToolName(name)) return "speak";
return "unknown";
}
const CodexToolCallPass2EnvelopeSchema = z.union([
CodexNormalizedToolCallPass1Schema.extend({
name: CodexShellToolNameSchema,
}).transform((envelope) => ({ ...envelope, toolKind: "shell" as const })),
CodexNormalizedToolCallPass1Schema.extend({
name: CodexReadToolNameSchema,
}).transform((envelope) => ({ ...envelope, toolKind: "read" as const })),
CodexNormalizedToolCallPass1Schema.extend({
name: CodexWriteToolNameSchema,
}).transform((envelope) => ({ ...envelope, toolKind: "write" as const })),
CodexNormalizedToolCallPass1Schema.extend({
name: CodexEditToolNameSchema,
}).transform((envelope) => ({ ...envelope, toolKind: "edit" as const })),
CodexNormalizedToolCallPass1Schema.extend({
name: CodexSearchToolNameSchema,
}).transform((envelope) => ({ ...envelope, toolKind: "search" as const })),
CodexNormalizedToolCallPass1Schema.extend({
name: CodexSpeakToolNameSchema,
}).transform((envelope) => ({ ...envelope, toolKind: "speak" as const })),
CodexNormalizedToolCallPass1Schema.transform((envelope) => ({
...envelope,
name: envelope.name.trim(),
toolKind: "unknown" as const,
})),
]);
interface CodexResolvedToolCall {
callId: string;
name: string;
toolKind: CodexToolKind;
input: unknown;
output: unknown;
status: CodexToolCallStatus;
error: unknown;
metadata?: Record<string, unknown>;
cwd: string | null;
}
const CodexNormalizedToolCallPass2Schema = z.discriminatedUnion("toolKind", [
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("shell"),
name: CodexShellToolNameSchema,
}),
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("read"),
name: CodexReadToolNameSchema,
}),
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("write"),
name: CodexWriteToolNameSchema,
}),
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("edit"),
name: CodexEditToolNameSchema,
}),
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("search"),
name: CodexSearchToolNameSchema,
}),
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("speak"),
name: CodexSpeakToolNameSchema,
}),
CodexToolCallPass2BaseSchema.extend({
toolKind: z.literal("unknown"),
}),
]);
type CodexNormalizedToolCallPass2 = z.infer<typeof CodexNormalizedToolCallPass2Schema>;
function toToolCallTimelineItem(envelope: CodexNormalizedToolCallPass2): ToolCallTimelineItem {
function toToolCallTimelineItem(envelope: CodexResolvedToolCall): ToolCallTimelineItem {
const name = envelope.toolKind === "speak" ? ("speak" as const) : envelope.name;
const parsedDetail = deriveCodexToolDetail({
name,
@@ -574,15 +502,24 @@ function toNullableObject(value: Record<string, unknown>): Record<string, unknow
function toToolCallFromNormalizedEnvelope(
envelope: CodexNormalizedToolCallEnvelope,
): ToolCallTimelineItem | null {
const pass2Envelope = CodexToolCallPass2EnvelopeSchema.safeParse(envelope);
if (!pass2Envelope.success) {
if (envelope.callId.length === 0 || envelope.name.length === 0 || !envelope.status) {
return null;
}
const parsed = CodexNormalizedToolCallPass2Schema.safeParse(pass2Envelope.data);
if (!parsed.success) {
const trimmedName = envelope.name.trim();
if (trimmedName.length === 0) {
return null;
}
return toToolCallTimelineItem(parsed.data);
return toToolCallTimelineItem({
callId: envelope.callId,
name: trimmedName,
toolKind: resolveCodexToolKind(trimmedName),
input: envelope.input ?? null,
output: envelope.output ?? null,
status: envelope.status,
error: envelope.error ?? null,
...(envelope.metadata ? { metadata: envelope.metadata } : {}),
cwd: envelope.cwd ?? null,
});
}
function mapCommandExecutionItem(
@@ -905,13 +842,20 @@ export function mapCodexRolloutToolCall(params: {
}
const normalizedName = parsed.data.name.trim();
if (normalizedName.length === 0) {
return null;
}
const callId = typeof parsed.data.callId === "string" ? parsed.data.callId.trim() : "";
if (callId.length === 0) {
return null;
}
const normalizedInput =
normalizedName === "apply_patch" || normalizedName === "apply_diff"
? normalizeRolloutEditInput(parsed.data.input ?? null)
: (parsed.data.input ?? null);
const pass1 = CodexNormalizedToolCallPass1Schema.safeParse({
callId: typeof parsed.data.callId === "string" ? parsed.data.callId.trim() : "",
return toToolCallFromNormalizedEnvelope({
callId,
name: normalizedName,
input: normalizedInput,
output: parsed.data.output ?? null,
@@ -923,14 +867,4 @@ export function mapCodexRolloutToolCall(params: {
),
cwd: params.cwd ?? null,
});
if (!pass1.success) {
return null;
}
const mapped = toToolCallFromNormalizedEnvelope(pass1.data);
if (!mapped) {
return null;
}
return mapped;
}
@@ -14,8 +14,6 @@ interface OpencodeToolCallParams {
metadata?: Record<string, unknown>;
}
const OpencodeToolCallStatusSchema = z.enum(["running", "completed", "failed", "canceled"]);
const OpencodeRawToolCallSchema = z
.object({
toolName: z.string().min(1),
@@ -28,123 +26,43 @@ const OpencodeRawToolCallSchema = z
})
.passthrough();
const OpencodeNormalizedToolCallPass1Schema = OpencodeRawToolCallSchema.transform((raw) => {
export function mapOpencodeToolCall(params: OpencodeToolCallParams): ToolCallTimelineItem | null {
const parsed = OpencodeRawToolCallSchema.safeParse(params);
if (!parsed.success) {
return null;
}
const raw = parsed.data;
const callId =
typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId.trim() : null;
if (callId === null) {
return null;
}
const name = raw.toolName.trim();
const input = raw.input ?? null;
const output = raw.output ?? null;
const error = raw.error ?? null;
const callId =
typeof raw.callId === "string" && raw.callId.trim().length > 0 ? raw.callId.trim() : null;
const rawStatus = typeof raw.status === "string" ? raw.status : undefined;
const status = normalizeToolCallStatus(rawStatus, error, output);
const detail = deriveOpencodeToolDetail(name, input, output);
return {
callId,
name: raw.toolName.trim(),
input,
output,
error,
metadata: raw.metadata,
status,
};
});
const OpencodeKnownToolNameSchema = z.union([
z.literal("shell"),
z.literal("bash"),
z.literal("exec_command"),
z.literal("read"),
z.literal("read_file"),
z.literal("write"),
z.literal("write_file"),
z.literal("create_file"),
z.literal("edit"),
z.literal("apply_patch"),
z.literal("apply_diff"),
z.literal("search"),
z.literal("web_search"),
]);
const OpencodeToolCallPass2BaseSchema = z.object({
callId: z.string().min(1),
name: z.string().min(1),
input: z.unknown().nullable(),
output: z.unknown().nullable(),
error: z.unknown().nullable(),
metadata: z.record(z.string(), z.unknown()).optional(),
status: OpencodeToolCallStatusSchema,
toolKind: z.enum(["known", "other"]),
});
const OpencodeToolCallPass2InputSchema = OpencodeToolCallPass2BaseSchema.omit({
toolKind: true,
});
const OpencodeToolCallPass2EnvelopeSchema = z.union([
OpencodeToolCallPass2InputSchema.extend({
name: OpencodeKnownToolNameSchema,
}).transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "known" as const,
})),
OpencodeToolCallPass2InputSchema.transform((normalized) => ({
...normalized,
name: normalized.name.trim(),
toolKind: "other" as const,
})),
]);
const OpencodeToolCallPass2Schema = z.discriminatedUnion("toolKind", [
OpencodeToolCallPass2BaseSchema.extend({
toolKind: z.literal("known"),
name: OpencodeKnownToolNameSchema,
}),
OpencodeToolCallPass2BaseSchema.extend({
toolKind: z.literal("other"),
}),
]);
type OpencodeToolCallPass2 = z.infer<typeof OpencodeToolCallPass2Schema>;
function toToolCallTimelineItem(normalized: OpencodeToolCallPass2): ToolCallTimelineItem {
const detail = deriveOpencodeToolDetail(normalized.name, normalized.input, normalized.output);
if (normalized.status === "failed") {
if (status === "failed") {
return {
type: "tool_call",
callId: normalized.callId,
name: normalized.name,
callId,
name,
status: "failed",
detail,
error: normalized.error ?? { message: "Tool call failed" },
...(normalized.metadata ? { metadata: normalized.metadata } : {}),
error: error ?? { message: "Tool call failed" },
...(raw.metadata ? { metadata: raw.metadata } : {}),
};
}
return {
type: "tool_call",
callId: normalized.callId,
name: normalized.name,
status: normalized.status,
callId,
name,
status,
detail,
error: null,
...(normalized.metadata ? { metadata: normalized.metadata } : {}),
...(raw.metadata ? { metadata: raw.metadata } : {}),
};
}
export function mapOpencodeToolCall(params: OpencodeToolCallParams): ToolCallTimelineItem | null {
const pass1 = OpencodeNormalizedToolCallPass1Schema.safeParse(params);
if (!pass1.success) {
return null;
}
const pass2Envelope = OpencodeToolCallPass2EnvelopeSchema.safeParse(pass1.data);
if (!pass2Envelope.success) {
return null;
}
const pass2 = OpencodeToolCallPass2Schema.safeParse(pass2Envelope.data);
if (!pass2.success) {
return null;
}
return toToolCallTimelineItem(pass2.data);
}