refactor tool-call mappers with shared detail primitives
This commit is contained in:
@@ -8,6 +8,7 @@ import type { PendingPermission } from "@/types/shared";
|
|||||||
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
|
import type { AgentLifecycleStatus } from "@server/shared/agent-lifecycle";
|
||||||
import type {
|
import type {
|
||||||
AgentPermissionResponse,
|
AgentPermissionResponse,
|
||||||
|
AgentPermissionRequest,
|
||||||
AgentSessionConfig,
|
AgentSessionConfig,
|
||||||
AgentProvider,
|
AgentProvider,
|
||||||
AgentMode,
|
AgentMode,
|
||||||
@@ -80,7 +81,7 @@ export interface Agent {
|
|||||||
capabilities: AgentCapabilityFlags;
|
capabilities: AgentCapabilityFlags;
|
||||||
currentModeId: string | null;
|
currentModeId: string | null;
|
||||||
availableModes: AgentMode[];
|
availableModes: AgentMode[];
|
||||||
pendingPermissions: any[];
|
pendingPermissions: AgentPermissionRequest[];
|
||||||
persistence: AgentPersistenceHandle | null;
|
persistence: AgentPersistenceHandle | null;
|
||||||
runtimeInfo?: AgentRuntimeInfo;
|
runtimeInfo?: AgentRuntimeInfo;
|
||||||
lastUsage?: AgentUsage;
|
lastUsage?: AgentUsage;
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { ToolCallDetail } from "../../agent-sdk-types.js";
|
||||||
|
import {
|
||||||
|
ToolEditInputSchema,
|
||||||
|
ToolEditOutputSchema,
|
||||||
|
ToolReadInputSchema,
|
||||||
|
ToolReadOutputSchema,
|
||||||
|
ToolSearchInputSchema,
|
||||||
|
ToolShellInputSchema,
|
||||||
|
ToolShellOutputSchema,
|
||||||
|
ToolWriteInputSchema,
|
||||||
|
ToolWriteOutputSchema,
|
||||||
|
toEditToolDetail,
|
||||||
|
toReadToolDetail,
|
||||||
|
toSearchToolDetail,
|
||||||
|
toShellToolDetail,
|
||||||
|
toWriteToolDetail,
|
||||||
|
toolDetailBranchByName,
|
||||||
|
} from "../tool-call-detail-primitives.js";
|
||||||
|
|
||||||
|
const ClaudeKnownToolDetailSchema = z.union([
|
||||||
|
toolDetailBranchByName("Bash", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByName("bash", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByName("shell", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByName("exec_command", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByName("Read", ToolReadInputSchema, ToolReadOutputSchema, toReadToolDetail),
|
||||||
|
toolDetailBranchByName("read", ToolReadInputSchema, ToolReadOutputSchema, toReadToolDetail),
|
||||||
|
toolDetailBranchByName("read_file", ToolReadInputSchema, ToolReadOutputSchema, toReadToolDetail),
|
||||||
|
toolDetailBranchByName("view_file", ToolReadInputSchema, ToolReadOutputSchema, toReadToolDetail),
|
||||||
|
toolDetailBranchByName("Write", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByName("write", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByName("write_file", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByName("create_file", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByName("Edit", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("MultiEdit", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("multi_edit", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("edit", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("apply_patch", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("apply_diff", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("str_replace_editor", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByName("WebSearch", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
toolDetailBranchByName("web_search", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
toolDetailBranchByName("search", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function deriveClaudeToolDetail(
|
||||||
|
name: string,
|
||||||
|
input: unknown,
|
||||||
|
output: unknown
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
const parsed = ClaudeKnownToolDetailSchema.safeParse({
|
||||||
|
name,
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
});
|
||||||
|
if (!parsed.success) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||||
import {
|
import { coerceToolCallId } from "../tool-call-mapper-utils.js";
|
||||||
coerceToolCallId,
|
import { deriveClaudeToolDetail } from "./tool-call-detail-parser.js";
|
||||||
commandFromValue,
|
|
||||||
flattenReadContent as flattenToolReadContent,
|
|
||||||
nonEmptyString,
|
|
||||||
truncateDiffText,
|
|
||||||
} from "../tool-call-mapper-utils.js";
|
|
||||||
|
|
||||||
type MapperParams = {
|
type MapperParams = {
|
||||||
callId?: string | null;
|
callId?: string | null;
|
||||||
@@ -17,8 +12,6 @@ type MapperParams = {
|
|||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_DIFF_TEXT_CHARS = 12_000;
|
|
||||||
|
|
||||||
const ClaudeMapperParamsSchema = z
|
const ClaudeMapperParamsSchema = z
|
||||||
.object({
|
.object({
|
||||||
callId: z.string().optional().nullable(),
|
callId: z.string().optional().nullable(),
|
||||||
@@ -33,437 +26,6 @@ const ClaudeFailedMapperParamsSchema = ClaudeMapperParamsSchema.extend({
|
|||||||
error: z.unknown(),
|
error: z.unknown(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
|
||||||
|
|
||||||
const ClaudeShellInputSchema = z
|
|
||||||
.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
command: CommandValueSchema,
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
directory: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
cmd: CommandValueSchema,
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
directory: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
])
|
|
||||||
.transform((value) => {
|
|
||||||
const commandValue = "command" in value ? value.command : value.cmd;
|
|
||||||
return {
|
|
||||||
command: commandFromValue(commandValue),
|
|
||||||
cwd: nonEmptyString(value.cwd) ?? nonEmptyString(value.directory),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const ClaudeShellOutputObjectSchema = z
|
|
||||||
.object({
|
|
||||||
command: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
aggregated_output: z.string().optional(),
|
|
||||||
exitCode: z.number().finite().nullable().optional(),
|
|
||||||
exit_code: z.number().finite().nullable().optional(),
|
|
||||||
metadata: z
|
|
||||||
.object({
|
|
||||||
exitCode: z.number().finite().nullable().optional(),
|
|
||||||
exit_code: z.number().finite().nullable().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
structuredContent: z
|
|
||||||
.object({
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
structured_content: z
|
|
||||||
.object({
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
result: z
|
|
||||||
.object({
|
|
||||||
command: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const ClaudeShellOutputSchema = z.union([
|
|
||||||
z.string().transform((value) => ({
|
|
||||||
command: undefined,
|
|
||||||
output: nonEmptyString(value),
|
|
||||||
exitCode: undefined,
|
|
||||||
})),
|
|
||||||
ClaudeShellOutputObjectSchema.transform((value) => ({
|
|
||||||
command: nonEmptyString(value.command) ?? nonEmptyString(value.result?.command),
|
|
||||||
output:
|
|
||||||
nonEmptyString(value.output) ??
|
|
||||||
nonEmptyString(value.text) ??
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.aggregated_output) ??
|
|
||||||
nonEmptyString(value.structuredContent?.output) ??
|
|
||||||
nonEmptyString(value.structuredContent?.text) ??
|
|
||||||
nonEmptyString(value.structuredContent?.content) ??
|
|
||||||
nonEmptyString(value.structured_content?.output) ??
|
|
||||||
nonEmptyString(value.structured_content?.text) ??
|
|
||||||
nonEmptyString(value.structured_content?.content) ??
|
|
||||||
nonEmptyString(value.result?.output) ??
|
|
||||||
nonEmptyString(value.result?.text) ??
|
|
||||||
nonEmptyString(value.result?.content),
|
|
||||||
exitCode:
|
|
||||||
value.exitCode ??
|
|
||||||
value.exit_code ??
|
|
||||||
value.metadata?.exitCode ??
|
|
||||||
value.metadata?.exit_code ??
|
|
||||||
undefined,
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeReadPathInputSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.file_path,
|
|
||||||
offset: value.offset,
|
|
||||||
limit: value.limit,
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.path,
|
|
||||||
offset: value.offset,
|
|
||||||
limit: value.limit,
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
offset: value.offset,
|
|
||||||
limit: value.limit,
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeReadChunkSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
output: z.string(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeReadContentSchema = z.union([
|
|
||||||
z.string(),
|
|
||||||
ClaudeReadChunkSchema,
|
|
||||||
z.array(ClaudeReadChunkSchema),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeReadPayloadSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: ClaudeReadContentSchema,
|
|
||||||
text: ClaudeReadContentSchema.optional(),
|
|
||||||
output: ClaudeReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: ClaudeReadContentSchema.optional(),
|
|
||||||
text: ClaudeReadContentSchema,
|
|
||||||
output: ClaudeReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: ClaudeReadContentSchema.optional(),
|
|
||||||
text: ClaudeReadContentSchema.optional(),
|
|
||||||
output: ClaudeReadContentSchema,
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeReadOutputSchema = z.union([
|
|
||||||
z.string().transform((value) => ({ content: nonEmptyString(value) })),
|
|
||||||
ClaudeReadChunkSchema.transform((value) => ({ content: flattenReadContent(value) })),
|
|
||||||
z.array(ClaudeReadChunkSchema).transform((value) => ({ content: flattenReadContent(value) })),
|
|
||||||
ClaudeReadPayloadSchema.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.content) ??
|
|
||||||
flattenReadContent(value.text) ??
|
|
||||||
flattenReadContent(value.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ data: ClaudeReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.data.content) ??
|
|
||||||
flattenReadContent(value.data.text) ??
|
|
||||||
flattenReadContent(value.data.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ structuredContent: ClaudeReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.structuredContent.content) ??
|
|
||||||
flattenReadContent(value.structuredContent.text) ??
|
|
||||||
flattenReadContent(value.structuredContent.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ structured_content: ClaudeReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.structured_content.content) ??
|
|
||||||
flattenReadContent(value.structured_content.text) ??
|
|
||||||
flattenReadContent(value.structured_content.output),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeWritePathInputSchema = z.union([
|
|
||||||
z.object({ file_path: z.string() }).passthrough().transform((value) => ({ filePath: value.file_path })),
|
|
||||||
z.object({ path: z.string() }).passthrough().transform((value) => ({ filePath: value.path })),
|
|
||||||
z.object({ filePath: z.string() }).passthrough().transform((value) => ({ filePath: value.filePath })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeWriteContentSchema = z
|
|
||||||
.object({
|
|
||||||
content: z.string().optional(),
|
|
||||||
new_content: z.string().optional(),
|
|
||||||
newContent: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const ClaudeWriteInputSchema = z
|
|
||||||
.intersection(ClaudeWritePathInputSchema, ClaudeWriteContentSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ClaudeWriteOutputSchema = z.union([
|
|
||||||
z
|
|
||||||
.intersection(ClaudeWritePathInputSchema, ClaudeWriteContentSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
})),
|
|
||||||
ClaudeWriteContentSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeEditTextSchema = z
|
|
||||||
.object({
|
|
||||||
old_string: z.string().optional(),
|
|
||||||
old_str: z.string().optional(),
|
|
||||||
oldContent: z.string().optional(),
|
|
||||||
old_content: z.string().optional(),
|
|
||||||
new_string: z.string().optional(),
|
|
||||||
new_str: z.string().optional(),
|
|
||||||
newContent: z.string().optional(),
|
|
||||||
new_content: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const ClaudeEditInputSchema = z
|
|
||||||
.intersection(ClaudeWritePathInputSchema, ClaudeEditTextSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
oldString:
|
|
||||||
nonEmptyString(value.old_string) ??
|
|
||||||
nonEmptyString(value.old_str) ??
|
|
||||||
nonEmptyString(value.oldContent) ??
|
|
||||||
nonEmptyString(value.old_content),
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.new_string) ??
|
|
||||||
nonEmptyString(value.new_str) ??
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ClaudeEditOutputFileSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.path,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.file_path,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeEditOutputSchema = z.union([
|
|
||||||
z
|
|
||||||
.intersection(ClaudeWritePathInputSchema, ClaudeEditTextSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ files: z.array(ClaudeEditOutputFileSchema).min(1) })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.files[0]?.filePath,
|
|
||||||
unifiedDiff: value.files[0]?.unifiedDiff,
|
|
||||||
newString: undefined,
|
|
||||||
})),
|
|
||||||
ClaudeEditTextSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ClaudeSearchInputSchema = z.union([
|
|
||||||
z.object({ query: z.string() }).passthrough().transform((value) => ({ query: value.query })),
|
|
||||||
z.object({ q: z.string() }).passthrough().transform((value) => ({ query: value.q })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
function flattenReadContent(
|
|
||||||
value: z.infer<typeof ClaudeReadContentSchema> | undefined
|
|
||||||
): string | undefined {
|
|
||||||
return flattenToolReadContent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function coerceCallId(callId: string | null | undefined, name: string, input: unknown): string {
|
function coerceCallId(callId: string | null | undefined, name: string, input: unknown): string {
|
||||||
return coerceToolCallId({
|
return coerceToolCallId({
|
||||||
providerPrefix: "claude",
|
providerPrefix: "claude",
|
||||||
@@ -473,144 +35,6 @@ function coerceCallId(callId: string | null | undefined, name: string, input: un
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toShellDetail(
|
|
||||||
input: z.infer<typeof ClaudeShellInputSchema> | null,
|
|
||||||
output: z.infer<typeof ClaudeShellOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const command = input?.command ?? output?.command;
|
|
||||||
if (!command) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "shell",
|
|
||||||
command,
|
|
||||||
...(input?.cwd ? { cwd: input.cwd } : {}),
|
|
||||||
...(output?.output ? { output: output.output } : {}),
|
|
||||||
...(output?.exitCode !== undefined ? { exitCode: output.exitCode } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toReadDetail(
|
|
||||||
input: z.infer<typeof ClaudeReadPathInputSchema> | null,
|
|
||||||
output: z.infer<typeof ClaudeReadOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
if (!input?.filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "read",
|
|
||||||
filePath: input.filePath,
|
|
||||||
...(output?.content ? { content: output.content } : {}),
|
|
||||||
...(input.offset !== undefined ? { offset: input.offset } : {}),
|
|
||||||
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toWriteDetail(
|
|
||||||
input: z.infer<typeof ClaudeWriteInputSchema> | null,
|
|
||||||
output: z.infer<typeof ClaudeWriteOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = input?.filePath ?? output?.filePath;
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "write",
|
|
||||||
filePath,
|
|
||||||
...(input?.content ? { content: input.content } : output?.content ? { content: output.content } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toEditDetail(
|
|
||||||
input: z.infer<typeof ClaudeEditInputSchema> | null,
|
|
||||||
output: z.infer<typeof ClaudeEditOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = input?.filePath ?? output?.filePath;
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "edit",
|
|
||||||
filePath,
|
|
||||||
...(input?.oldString ? { oldString: input.oldString } : {}),
|
|
||||||
...(input?.newString ? { newString: input.newString } : output?.newString ? { newString: output.newString } : {}),
|
|
||||||
...(input?.unifiedDiff
|
|
||||||
? { unifiedDiff: input.unifiedDiff }
|
|
||||||
: output?.unifiedDiff
|
|
||||||
? { unifiedDiff: output.unifiedDiff }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSearchDetail(input: z.infer<typeof ClaudeSearchInputSchema> | null): ToolCallDetail | undefined {
|
|
||||||
if (!input?.query) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "search",
|
|
||||||
query: input.query,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function claudeToolBranch<Name extends string, InputSchema extends z.ZodTypeAny, OutputSchema extends z.ZodTypeAny>(
|
|
||||||
name: Name,
|
|
||||||
inputSchema: InputSchema,
|
|
||||||
outputSchema: OutputSchema,
|
|
||||||
mapper: (
|
|
||||||
input: z.infer<InputSchema> | null,
|
|
||||||
output: z.infer<OutputSchema> | null
|
|
||||||
) => ToolCallDetail | undefined
|
|
||||||
) {
|
|
||||||
return z
|
|
||||||
.object({
|
|
||||||
name: z.literal(name),
|
|
||||||
input: inputSchema.nullable(),
|
|
||||||
output: outputSchema.nullable(),
|
|
||||||
})
|
|
||||||
.transform(({ input, output }) => mapper(input, output));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ClaudeKnownToolDetailSchema = z.union([
|
|
||||||
claudeToolBranch("Bash", ClaudeShellInputSchema, ClaudeShellOutputSchema, toShellDetail),
|
|
||||||
claudeToolBranch("bash", ClaudeShellInputSchema, ClaudeShellOutputSchema, toShellDetail),
|
|
||||||
claudeToolBranch("shell", ClaudeShellInputSchema, ClaudeShellOutputSchema, toShellDetail),
|
|
||||||
claudeToolBranch("exec_command", ClaudeShellInputSchema, ClaudeShellOutputSchema, toShellDetail),
|
|
||||||
claudeToolBranch("Read", ClaudeReadPathInputSchema, ClaudeReadOutputSchema, toReadDetail),
|
|
||||||
claudeToolBranch("read", ClaudeReadPathInputSchema, ClaudeReadOutputSchema, toReadDetail),
|
|
||||||
claudeToolBranch("read_file", ClaudeReadPathInputSchema, ClaudeReadOutputSchema, toReadDetail),
|
|
||||||
claudeToolBranch("view_file", ClaudeReadPathInputSchema, ClaudeReadOutputSchema, toReadDetail),
|
|
||||||
claudeToolBranch("Write", ClaudeWriteInputSchema, ClaudeWriteOutputSchema, toWriteDetail),
|
|
||||||
claudeToolBranch("write", ClaudeWriteInputSchema, ClaudeWriteOutputSchema, toWriteDetail),
|
|
||||||
claudeToolBranch("write_file", ClaudeWriteInputSchema, ClaudeWriteOutputSchema, toWriteDetail),
|
|
||||||
claudeToolBranch("create_file", ClaudeWriteInputSchema, ClaudeWriteOutputSchema, toWriteDetail),
|
|
||||||
claudeToolBranch("Edit", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("MultiEdit", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("multi_edit", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("edit", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("apply_patch", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("apply_diff", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("str_replace_editor", ClaudeEditInputSchema, ClaudeEditOutputSchema, toEditDetail),
|
|
||||||
claudeToolBranch("WebSearch", ClaudeSearchInputSchema, z.unknown(), (input) => toSearchDetail(input)),
|
|
||||||
claudeToolBranch("web_search", ClaudeSearchInputSchema, z.unknown(), (input) => toSearchDetail(input)),
|
|
||||||
claudeToolBranch("search", ClaudeSearchInputSchema, z.unknown(), (input) => toSearchDetail(input)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
function deriveDetail(name: string, input: unknown, output: unknown): ToolCallDetail | undefined {
|
|
||||||
const parsed = ClaudeKnownToolDetailSchema.safeParse({
|
|
||||||
name,
|
|
||||||
input,
|
|
||||||
output,
|
|
||||||
});
|
|
||||||
if (!parsed.success) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return parsed.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildBase(params: MapperParams): {
|
function buildBase(params: MapperParams): {
|
||||||
callId: string;
|
callId: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -622,7 +46,7 @@ function buildBase(params: MapperParams): {
|
|||||||
const parsedParams = ClaudeMapperParamsSchema.parse(params);
|
const parsedParams = ClaudeMapperParamsSchema.parse(params);
|
||||||
const input = parsedParams.input ?? null;
|
const input = parsedParams.input ?? null;
|
||||||
const output = parsedParams.output ?? null;
|
const output = parsedParams.output ?? null;
|
||||||
const detail = deriveDetail(parsedParams.name, input, output);
|
const detail = deriveClaudeToolDetail(parsedParams.name, input, output);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callId: coerceCallId(parsedParams.callId, parsedParams.name, input),
|
callId: coerceCallId(parsedParams.callId, parsedParams.name, input),
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { ToolCallDetail } from "../../agent-sdk-types.js";
|
||||||
|
import { stripCwdPrefix } from "../../../../shared/path-utils.js";
|
||||||
|
import {
|
||||||
|
ToolEditInputSchema,
|
||||||
|
ToolEditOutputSchema,
|
||||||
|
ToolReadInputSchema,
|
||||||
|
ToolReadOutputWithPathSchema,
|
||||||
|
ToolSearchInputSchema,
|
||||||
|
ToolShellInputSchema,
|
||||||
|
ToolShellOutputSchema,
|
||||||
|
ToolWriteInputSchema,
|
||||||
|
ToolWriteOutputSchema,
|
||||||
|
toEditToolDetail,
|
||||||
|
toReadToolDetail,
|
||||||
|
toSearchToolDetail,
|
||||||
|
toShellToolDetail,
|
||||||
|
toWriteToolDetail,
|
||||||
|
toolDetailBranchByNameWithCwd,
|
||||||
|
} from "../tool-call-detail-primitives.js";
|
||||||
|
|
||||||
|
export type CodexToolDetailContext = {
|
||||||
|
cwd?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CODEX_BUILTIN_TOOL_NAMES = new Set([
|
||||||
|
"shell",
|
||||||
|
"bash",
|
||||||
|
"exec",
|
||||||
|
"exec_command",
|
||||||
|
"command",
|
||||||
|
"read",
|
||||||
|
"read_file",
|
||||||
|
"write",
|
||||||
|
"write_file",
|
||||||
|
"create_file",
|
||||||
|
"edit",
|
||||||
|
"apply_patch",
|
||||||
|
"apply_diff",
|
||||||
|
"web_search",
|
||||||
|
"search",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function normalizeCodexFilePath(
|
||||||
|
filePath: string,
|
||||||
|
cwd: string | null | undefined
|
||||||
|
): string | undefined {
|
||||||
|
if (!filePath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof cwd === "string" && cwd.length > 0) {
|
||||||
|
return stripCwdPrefix(filePath, cwd);
|
||||||
|
}
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePathForCwd(cwd: string | null): (filePath: string) => string | undefined {
|
||||||
|
return (filePath) => normalizeCodexFilePath(filePath, cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodexKnownToolDetailSchema = z.union([
|
||||||
|
toolDetailBranchByNameWithCwd("Bash", ToolShellInputSchema, ToolShellOutputSchema, (input, output) =>
|
||||||
|
toShellToolDetail(input, output)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("shell", ToolShellInputSchema, ToolShellOutputSchema, (input, output) =>
|
||||||
|
toShellToolDetail(input, output)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("bash", ToolShellInputSchema, ToolShellOutputSchema, (input, output) =>
|
||||||
|
toShellToolDetail(input, output)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("exec", ToolShellInputSchema, ToolShellOutputSchema, (input, output) =>
|
||||||
|
toShellToolDetail(input, output)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd(
|
||||||
|
"exec_command",
|
||||||
|
ToolShellInputSchema,
|
||||||
|
ToolShellOutputSchema,
|
||||||
|
(input, output) => toShellToolDetail(input, output)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("command", ToolShellInputSchema, ToolShellOutputSchema, (input, output) =>
|
||||||
|
toShellToolDetail(input, output)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("read", ToolReadInputSchema, ToolReadOutputWithPathSchema, (input, output, cwd) =>
|
||||||
|
toReadToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd(
|
||||||
|
"read_file",
|
||||||
|
ToolReadInputSchema,
|
||||||
|
ToolReadOutputWithPathSchema,
|
||||||
|
(input, output, cwd) => toReadToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("write", ToolWriteInputSchema, ToolWriteOutputSchema, (input, output, cwd) =>
|
||||||
|
toWriteToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd(
|
||||||
|
"write_file",
|
||||||
|
ToolWriteInputSchema,
|
||||||
|
ToolWriteOutputSchema,
|
||||||
|
(input, output, cwd) => toWriteToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd(
|
||||||
|
"create_file",
|
||||||
|
ToolWriteInputSchema,
|
||||||
|
ToolWriteOutputSchema,
|
||||||
|
(input, output, cwd) => toWriteToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("edit", ToolEditInputSchema, ToolEditOutputSchema, (input, output, cwd) =>
|
||||||
|
toEditToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd(
|
||||||
|
"apply_patch",
|
||||||
|
ToolEditInputSchema,
|
||||||
|
ToolEditOutputSchema,
|
||||||
|
(input, output, cwd) => toEditToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd(
|
||||||
|
"apply_diff",
|
||||||
|
ToolEditInputSchema,
|
||||||
|
ToolEditOutputSchema,
|
||||||
|
(input, output, cwd) => toEditToolDetail(input, output, { normalizePath: normalizePathForCwd(cwd) })
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("search", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
toolDetailBranchByNameWithCwd("web_search", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function deriveCodexToolDetail(params: {
|
||||||
|
name: string;
|
||||||
|
input: unknown;
|
||||||
|
output: unknown;
|
||||||
|
cwd?: string | null;
|
||||||
|
}): ToolCallDetail | undefined {
|
||||||
|
const parsed = CodexKnownToolDetailSchema.safeParse({
|
||||||
|
name: params.name,
|
||||||
|
input: params.input,
|
||||||
|
output: params.output,
|
||||||
|
cwd: params.cwd ?? null,
|
||||||
|
});
|
||||||
|
if (!parsed.success) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||||
import { stripCwdPrefix } from "../../../../shared/path-utils.js";
|
import { CommandValueSchema } from "../tool-call-detail-primitives.js";
|
||||||
import {
|
import {
|
||||||
coerceToolCallId,
|
coerceToolCallId,
|
||||||
commandFromValue,
|
commandFromValue,
|
||||||
flattenReadContent as flattenToolReadContent,
|
|
||||||
nonEmptyString,
|
|
||||||
truncateDiffText,
|
truncateDiffText,
|
||||||
} from "../tool-call-mapper-utils.js";
|
} from "../tool-call-mapper-utils.js";
|
||||||
|
import {
|
||||||
|
CODEX_BUILTIN_TOOL_NAMES,
|
||||||
|
deriveCodexToolDetail,
|
||||||
|
normalizeCodexFilePath,
|
||||||
|
} from "./tool-call-detail-parser.js";
|
||||||
|
|
||||||
type CodexMapperOptions = { cwd?: string | null };
|
type CodexMapperOptions = { cwd?: string | null };
|
||||||
|
|
||||||
@@ -26,461 +29,9 @@ const CodexRolloutToolCallParamsSchema = z
|
|||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
|
|
||||||
const CommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
// ---------------------------------------------------------------------------
|
||||||
|
// Thread-item parsing
|
||||||
const CodexShellInputSchema = z
|
// ---------------------------------------------------------------------------
|
||||||
.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
command: CommandValueSchema,
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
directory: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
cmd: CommandValueSchema,
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
directory: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
])
|
|
||||||
.transform((value) => {
|
|
||||||
const commandValue = "command" in value ? value.command : value.cmd;
|
|
||||||
return {
|
|
||||||
command: commandFromValue(commandValue),
|
|
||||||
cwd: nonEmptyString(value.cwd) ?? nonEmptyString(value.directory),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const CodexShellOutputObjectSchema = z
|
|
||||||
.object({
|
|
||||||
command: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
aggregatedOutput: z.string().optional(),
|
|
||||||
exitCode: z.number().finite().nullable().optional(),
|
|
||||||
exit_code: z.number().finite().nullable().optional(),
|
|
||||||
metadata: z
|
|
||||||
.object({
|
|
||||||
exitCode: z.number().finite().nullable().optional(),
|
|
||||||
exit_code: z.number().finite().nullable().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
structuredContent: z
|
|
||||||
.object({
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
structured_content: z
|
|
||||||
.object({
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
result: z
|
|
||||||
.object({
|
|
||||||
command: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const CodexShellOutputSchema = z.union([
|
|
||||||
z.string().transform((value) => ({
|
|
||||||
command: undefined,
|
|
||||||
output: nonEmptyString(value),
|
|
||||||
exitCode: undefined,
|
|
||||||
})),
|
|
||||||
CodexShellOutputObjectSchema.transform((value) => ({
|
|
||||||
command: nonEmptyString(value.command) ?? nonEmptyString(value.result?.command),
|
|
||||||
output:
|
|
||||||
nonEmptyString(value.output) ??
|
|
||||||
nonEmptyString(value.text) ??
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.aggregatedOutput) ??
|
|
||||||
nonEmptyString(value.structuredContent?.output) ??
|
|
||||||
nonEmptyString(value.structuredContent?.text) ??
|
|
||||||
nonEmptyString(value.structuredContent?.content) ??
|
|
||||||
nonEmptyString(value.structured_content?.output) ??
|
|
||||||
nonEmptyString(value.structured_content?.text) ??
|
|
||||||
nonEmptyString(value.structured_content?.content) ??
|
|
||||||
nonEmptyString(value.result?.output) ??
|
|
||||||
nonEmptyString(value.result?.text) ??
|
|
||||||
nonEmptyString(value.result?.content),
|
|
||||||
exitCode:
|
|
||||||
value.exitCode ??
|
|
||||||
value.exit_code ??
|
|
||||||
value.metadata?.exitCode ??
|
|
||||||
value.metadata?.exit_code ??
|
|
||||||
undefined,
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexPathSchema = z.union([
|
|
||||||
z.object({ path: z.string() }).passthrough().transform((value) => value.path),
|
|
||||||
z.object({ file_path: z.string() }).passthrough().transform((value) => value.file_path),
|
|
||||||
z.object({ filePath: z.string() }).passthrough().transform((value) => value.filePath),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexReadArgumentsSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({ filePath: value.path, offset: value.offset, limit: value.limit })),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({ filePath: value.file_path, offset: value.offset, limit: value.limit })),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({ filePath: value.filePath, offset: value.offset, limit: value.limit })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexReadChunkSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
output: z.string(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexReadContentSchema = z.union([z.string(), CodexReadChunkSchema, z.array(CodexReadChunkSchema)]);
|
|
||||||
|
|
||||||
const CodexReadPayloadSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: CodexReadContentSchema,
|
|
||||||
text: CodexReadContentSchema.optional(),
|
|
||||||
output: CodexReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: CodexReadContentSchema.optional(),
|
|
||||||
text: CodexReadContentSchema,
|
|
||||||
output: CodexReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: CodexReadContentSchema.optional(),
|
|
||||||
text: CodexReadContentSchema.optional(),
|
|
||||||
output: CodexReadContentSchema,
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexReadResultWithPathSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
content: CodexReadContentSchema.optional(),
|
|
||||||
text: CodexReadContentSchema.optional(),
|
|
||||||
output: CodexReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.path,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.content) ??
|
|
||||||
flattenReadContent(value.text) ??
|
|
||||||
flattenReadContent(value.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
content: CodexReadContentSchema.optional(),
|
|
||||||
text: CodexReadContentSchema.optional(),
|
|
||||||
output: CodexReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.file_path,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.content) ??
|
|
||||||
flattenReadContent(value.text) ??
|
|
||||||
flattenReadContent(value.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
content: CodexReadContentSchema.optional(),
|
|
||||||
text: CodexReadContentSchema.optional(),
|
|
||||||
output: CodexReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.content) ??
|
|
||||||
flattenReadContent(value.text) ??
|
|
||||||
flattenReadContent(value.output),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexReadResultSchema = z.union([
|
|
||||||
z.string().transform((value) => ({ filePath: undefined, content: nonEmptyString(value) })),
|
|
||||||
CodexReadChunkSchema.transform((value) => ({ filePath: undefined, content: flattenReadContent(value) })),
|
|
||||||
z.array(CodexReadChunkSchema).transform((value) => ({ filePath: undefined, content: flattenReadContent(value) })),
|
|
||||||
CodexReadPayloadSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.content) ??
|
|
||||||
flattenReadContent(value.text) ??
|
|
||||||
flattenReadContent(value.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ data: CodexReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.data.content) ??
|
|
||||||
flattenReadContent(value.data.text) ??
|
|
||||||
flattenReadContent(value.data.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ structuredContent: CodexReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.structuredContent.content) ??
|
|
||||||
flattenReadContent(value.structuredContent.text) ??
|
|
||||||
flattenReadContent(value.structuredContent.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ structured_content: CodexReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.structured_content.content) ??
|
|
||||||
flattenReadContent(value.structured_content.text) ??
|
|
||||||
flattenReadContent(value.structured_content.output),
|
|
||||||
})),
|
|
||||||
CodexReadResultWithPathSchema,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexWriteContentSchema = z
|
|
||||||
.object({
|
|
||||||
content: z.string().optional(),
|
|
||||||
new_content: z.string().optional(),
|
|
||||||
newContent: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const CodexWriteArgumentsSchema = z
|
|
||||||
.intersection(CodexPathSchema.transform((filePath) => ({ filePath })), CodexWriteContentSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const CodexWriteResultSchema = z.union([
|
|
||||||
z
|
|
||||||
.intersection(CodexPathSchema.transform((filePath) => ({ filePath })), CodexWriteContentSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
})),
|
|
||||||
CodexWriteContentSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexEditTextSchema = z
|
|
||||||
.object({
|
|
||||||
old_string: z.string().optional(),
|
|
||||||
old_str: z.string().optional(),
|
|
||||||
oldContent: z.string().optional(),
|
|
||||||
old_content: z.string().optional(),
|
|
||||||
new_string: z.string().optional(),
|
|
||||||
new_str: z.string().optional(),
|
|
||||||
newContent: z.string().optional(),
|
|
||||||
new_content: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const CodexEditArgumentsSchema = z
|
|
||||||
.intersection(CodexPathSchema.transform((filePath) => ({ filePath })), CodexEditTextSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
oldString:
|
|
||||||
nonEmptyString(value.old_string) ??
|
|
||||||
nonEmptyString(value.old_str) ??
|
|
||||||
nonEmptyString(value.oldContent) ??
|
|
||||||
nonEmptyString(value.old_content),
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.new_string) ??
|
|
||||||
nonEmptyString(value.new_str) ??
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff)
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const CodexEditResultFileSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.path,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff)
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.file_path,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff)
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff)
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexEditResultSchema = z.union([
|
|
||||||
z
|
|
||||||
.intersection(CodexPathSchema.transform((filePath) => ({ filePath })), CodexEditTextSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff)
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ files: z.array(CodexEditResultFileSchema).min(1) })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.files[0]?.filePath,
|
|
||||||
unifiedDiff: value.files[0]?.unifiedDiff,
|
|
||||||
newString: undefined,
|
|
||||||
})),
|
|
||||||
CodexEditTextSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff)
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexSearchArgumentsSchema = z.union([
|
|
||||||
z.object({ query: z.string() }).passthrough().transform((value) => ({ query: value.query })),
|
|
||||||
z.object({ q: z.string() }).passthrough().transform((value) => ({ query: value.q })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CodexCommandExecutionItemSchema = z
|
const CodexCommandExecutionItemSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -548,12 +99,6 @@ const CodexThreadItemSchema = z.discriminatedUnion("type", [
|
|||||||
CodexWebSearchItemSchema,
|
CodexWebSearchItemSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function flattenReadContent(
|
|
||||||
value: z.infer<typeof CodexReadContentSchema> | undefined
|
|
||||||
): string | undefined {
|
|
||||||
return flattenToolReadContent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function coerceCallId(raw: string | null | undefined, name: string, input: unknown): string {
|
function coerceCallId(raw: string | null | undefined, name: string, input: unknown): string {
|
||||||
return coerceToolCallId({
|
return coerceToolCallId({
|
||||||
providerPrefix: "codex",
|
providerPrefix: "codex",
|
||||||
@@ -563,21 +108,11 @@ function coerceCallId(raw: string | null | undefined, name: string, input: unkno
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeCodexFilePath(filePath: string | undefined, cwd: string | null | undefined): string | undefined {
|
function resolveStatus(
|
||||||
if (typeof filePath !== "string") {
|
rawStatus: string | undefined,
|
||||||
return undefined;
|
error: unknown,
|
||||||
}
|
output: unknown
|
||||||
const trimmed = filePath.trim();
|
): ToolCallTimelineItem["status"] {
|
||||||
if (!trimmed) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (typeof cwd === "string" && cwd.length > 0) {
|
|
||||||
return stripCwdPrefix(trimmed, cwd);
|
|
||||||
}
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveStatus(rawStatus: string | undefined, error: unknown, output: unknown): ToolCallTimelineItem["status"] {
|
|
||||||
if (error !== undefined && error !== null) {
|
if (error !== undefined && error !== null) {
|
||||||
return "failed";
|
return "failed";
|
||||||
}
|
}
|
||||||
@@ -601,180 +136,6 @@ function resolveStatus(rawStatus: string | undefined, error: unknown, output: un
|
|||||||
return output !== null && output !== undefined ? "completed" : "running";
|
return output !== null && output !== undefined ? "completed" : "running";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toShellDetail(
|
|
||||||
input: z.infer<typeof CodexShellInputSchema> | null,
|
|
||||||
output: z.infer<typeof CodexShellOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const command = input?.command ?? output?.command;
|
|
||||||
if (!command) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "shell",
|
|
||||||
command,
|
|
||||||
...(input?.cwd ? { cwd: input.cwd } : {}),
|
|
||||||
...(output?.output ? { output: output.output } : {}),
|
|
||||||
...(output?.exitCode !== undefined ? { exitCode: output.exitCode } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toReadDetail(
|
|
||||||
input: z.infer<typeof CodexReadArgumentsSchema> | null,
|
|
||||||
output: z.infer<typeof CodexReadResultSchema> | null,
|
|
||||||
cwd: string | null | undefined
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = normalizeCodexFilePath(input?.filePath ?? output?.filePath, cwd);
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "read",
|
|
||||||
filePath,
|
|
||||||
...(output?.content ? { content: output.content } : {}),
|
|
||||||
...(input?.offset !== undefined ? { offset: input.offset } : {}),
|
|
||||||
...(input?.limit !== undefined ? { limit: input.limit } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toWriteDetail(
|
|
||||||
input: z.infer<typeof CodexWriteArgumentsSchema> | null,
|
|
||||||
output: z.infer<typeof CodexWriteResultSchema> | null,
|
|
||||||
cwd: string | null | undefined
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = normalizeCodexFilePath(input?.filePath ?? output?.filePath, cwd);
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "write",
|
|
||||||
filePath,
|
|
||||||
...(input?.content ? { content: input.content } : output?.content ? { content: output.content } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toEditDetail(
|
|
||||||
input: z.infer<typeof CodexEditArgumentsSchema> | null,
|
|
||||||
output: z.infer<typeof CodexEditResultSchema> | null,
|
|
||||||
cwd: string | null | undefined
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = normalizeCodexFilePath(input?.filePath ?? output?.filePath, cwd);
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "edit",
|
|
||||||
filePath,
|
|
||||||
...(input?.oldString ? { oldString: input.oldString } : {}),
|
|
||||||
...(input?.newString ? { newString: input.newString } : output?.newString ? { newString: output.newString } : {}),
|
|
||||||
...(input?.unifiedDiff
|
|
||||||
? { unifiedDiff: input.unifiedDiff }
|
|
||||||
: output?.unifiedDiff
|
|
||||||
? { unifiedDiff: output.unifiedDiff }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSearchDetail(input: z.infer<typeof CodexSearchArgumentsSchema> | null): ToolCallDetail | undefined {
|
|
||||||
if (!input?.query) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "search",
|
|
||||||
query: input.query,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function codexToolDetailBranch<Name extends string, InputSchema extends z.ZodTypeAny, OutputSchema extends z.ZodTypeAny>(
|
|
||||||
name: Name,
|
|
||||||
inputSchema: InputSchema,
|
|
||||||
outputSchema: OutputSchema,
|
|
||||||
mapper: (params: {
|
|
||||||
input: z.infer<InputSchema> | null;
|
|
||||||
output: z.infer<OutputSchema> | null;
|
|
||||||
cwd: string | null | undefined;
|
|
||||||
}) => ToolCallDetail | undefined
|
|
||||||
) {
|
|
||||||
return z
|
|
||||||
.object({
|
|
||||||
name: z.literal(name),
|
|
||||||
input: inputSchema.nullable(),
|
|
||||||
output: outputSchema.nullable(),
|
|
||||||
cwd: z.string().optional().nullable(),
|
|
||||||
})
|
|
||||||
.transform(({ input, output, cwd }) => mapper({ input, output, cwd }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const CodexKnownToolDetailSchema = z.union([
|
|
||||||
codexToolDetailBranch("Bash", CodexShellInputSchema, CodexShellOutputSchema, ({ input, output }) =>
|
|
||||||
toShellDetail(input, output)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("shell", CodexShellInputSchema, CodexShellOutputSchema, ({ input, output }) =>
|
|
||||||
toShellDetail(input, output)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("bash", CodexShellInputSchema, CodexShellOutputSchema, ({ input, output }) =>
|
|
||||||
toShellDetail(input, output)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("exec", CodexShellInputSchema, CodexShellOutputSchema, ({ input, output }) =>
|
|
||||||
toShellDetail(input, output)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("exec_command", CodexShellInputSchema, CodexShellOutputSchema, ({ input, output }) =>
|
|
||||||
toShellDetail(input, output)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("command", CodexShellInputSchema, CodexShellOutputSchema, ({ input, output }) =>
|
|
||||||
toShellDetail(input, output)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("read", CodexReadArgumentsSchema, CodexReadResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toReadDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("read_file", CodexReadArgumentsSchema, CodexReadResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toReadDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("write", CodexWriteArgumentsSchema, CodexWriteResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toWriteDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("write_file", CodexWriteArgumentsSchema, CodexWriteResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toWriteDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("create_file", CodexWriteArgumentsSchema, CodexWriteResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toWriteDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("edit", CodexEditArgumentsSchema, CodexEditResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toEditDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("apply_patch", CodexEditArgumentsSchema, CodexEditResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toEditDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("apply_diff", CodexEditArgumentsSchema, CodexEditResultSchema, ({ input, output, cwd }) =>
|
|
||||||
toEditDetail(input, output, cwd)
|
|
||||||
),
|
|
||||||
codexToolDetailBranch("search", CodexSearchArgumentsSchema, z.unknown(), ({ input }) => toSearchDetail(input)),
|
|
||||||
codexToolDetailBranch("web_search", CodexSearchArgumentsSchema, z.unknown(), ({ input }) =>
|
|
||||||
toSearchDetail(input)
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
function deriveToolDetail(params: {
|
|
||||||
name: string;
|
|
||||||
input: unknown;
|
|
||||||
output: unknown;
|
|
||||||
cwd?: string | null;
|
|
||||||
}): ToolCallDetail | undefined {
|
|
||||||
const parsed = CodexKnownToolDetailSchema.safeParse({
|
|
||||||
name: params.name,
|
|
||||||
input: params.input,
|
|
||||||
output: params.output,
|
|
||||||
cwd: params.cwd ?? null,
|
|
||||||
});
|
|
||||||
if (!parsed.success) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return parsed.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildToolCall(params: {
|
function buildToolCall(params: {
|
||||||
callId: string;
|
callId: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -812,24 +173,6 @@ function buildToolCall(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CODEX_BUILTIN_TOOL_NAMES = new Set([
|
|
||||||
"shell",
|
|
||||||
"bash",
|
|
||||||
"exec",
|
|
||||||
"exec_command",
|
|
||||||
"command",
|
|
||||||
"read",
|
|
||||||
"read_file",
|
|
||||||
"write",
|
|
||||||
"write_file",
|
|
||||||
"create_file",
|
|
||||||
"edit",
|
|
||||||
"apply_patch",
|
|
||||||
"apply_diff",
|
|
||||||
"web_search",
|
|
||||||
"search",
|
|
||||||
]);
|
|
||||||
|
|
||||||
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) {
|
||||||
@@ -903,11 +246,18 @@ function mapFileChangeItem(
|
|||||||
const changes = item.changes ?? [];
|
const changes = item.changes ?? [];
|
||||||
|
|
||||||
const files = changes
|
const files = changes
|
||||||
.map((change) => ({
|
.map((change) => {
|
||||||
path: normalizeCodexFilePath(change.path, options?.cwd),
|
const pathValue =
|
||||||
kind: change.kind,
|
typeof change.path === "string"
|
||||||
diff: change.diff,
|
? normalizeCodexFilePath(change.path.trim(), options?.cwd)
|
||||||
}))
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: pathValue,
|
||||||
|
kind: change.kind,
|
||||||
|
diff: change.diff,
|
||||||
|
};
|
||||||
|
})
|
||||||
.filter((change) => change.path !== undefined);
|
.filter((change) => change.path !== undefined);
|
||||||
|
|
||||||
const input = toNullableObject({
|
const input = toNullableObject({
|
||||||
@@ -969,7 +319,7 @@ function mapMcpToolCallItem(
|
|||||||
const error = item.error ?? null;
|
const error = item.error ?? null;
|
||||||
const callId = coerceCallId(item.id ?? item.callID ?? item.call_id, name, input);
|
const callId = coerceCallId(item.id ?? item.callID ?? item.call_id, name, input);
|
||||||
const status = resolveStatus(item.status, error, output);
|
const status = resolveStatus(item.status, error, output);
|
||||||
const detail = deriveToolDetail({
|
const detail = deriveCodexToolDetail({
|
||||||
name: tool,
|
name: tool,
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
@@ -1031,6 +381,10 @@ function createCodexThreadItemToTimelineSchema(options?: CodexMapperOptions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Public API
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function mapCodexToolCallFromThreadItem(
|
export function mapCodexToolCallFromThreadItem(
|
||||||
item: unknown,
|
item: unknown,
|
||||||
options?: CodexMapperOptions
|
options?: CodexMapperOptions
|
||||||
@@ -1055,7 +409,7 @@ export function mapCodexRolloutToolCall(params: {
|
|||||||
const error = parsed.error ?? null;
|
const error = parsed.error ?? null;
|
||||||
const status = resolveStatus("completed", error, output);
|
const status = resolveStatus("completed", error, output);
|
||||||
const callId = coerceCallId(parsed.callId, parsed.name, input);
|
const callId = coerceCallId(parsed.callId, parsed.name, input);
|
||||||
const detail = deriveToolDetail({
|
const detail = deriveCodexToolDetail({
|
||||||
name: parsed.name,
|
name: parsed.name,
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { ToolCallDetail } from "../../agent-sdk-types.js";
|
||||||
|
import {
|
||||||
|
ToolEditInputSchema,
|
||||||
|
ToolEditOutputSchema,
|
||||||
|
ToolReadInputSchema,
|
||||||
|
ToolReadOutputSchema,
|
||||||
|
ToolSearchInputSchema,
|
||||||
|
ToolShellInputSchema,
|
||||||
|
ToolShellOutputSchema,
|
||||||
|
ToolWriteInputSchema,
|
||||||
|
ToolWriteOutputSchema,
|
||||||
|
toEditToolDetail,
|
||||||
|
toReadToolDetail,
|
||||||
|
toSearchToolDetail,
|
||||||
|
toShellToolDetail,
|
||||||
|
toWriteToolDetail,
|
||||||
|
toolDetailBranchByToolName,
|
||||||
|
} from "../tool-call-detail-primitives.js";
|
||||||
|
|
||||||
|
const OpencodeKnownToolDetailSchema = z.union([
|
||||||
|
toolDetailBranchByToolName("shell", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByToolName("bash", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByToolName("exec_command", ToolShellInputSchema, ToolShellOutputSchema, toShellToolDetail),
|
||||||
|
toolDetailBranchByToolName("read", ToolReadInputSchema, ToolReadOutputSchema, toReadToolDetail),
|
||||||
|
toolDetailBranchByToolName("read_file", ToolReadInputSchema, ToolReadOutputSchema, toReadToolDetail),
|
||||||
|
toolDetailBranchByToolName("write", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByToolName("write_file", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByToolName("create_file", ToolWriteInputSchema, ToolWriteOutputSchema, toWriteToolDetail),
|
||||||
|
toolDetailBranchByToolName("edit", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByToolName("apply_patch", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByToolName("apply_diff", ToolEditInputSchema, ToolEditOutputSchema, toEditToolDetail),
|
||||||
|
toolDetailBranchByToolName("search", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
toolDetailBranchByToolName("web_search", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
|
toSearchToolDetail(input)
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function deriveOpencodeToolDetail(
|
||||||
|
toolName: string,
|
||||||
|
input: unknown,
|
||||||
|
output: unknown
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
const parsed = OpencodeKnownToolDetailSchema.safeParse({
|
||||||
|
toolName,
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
});
|
||||||
|
if (!parsed.success) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
import type { ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||||
import {
|
import { coerceToolCallId } from "../tool-call-mapper-utils.js";
|
||||||
coerceToolCallId,
|
import { deriveOpencodeToolDetail } from "./tool-call-detail-parser.js";
|
||||||
commandFromValue,
|
|
||||||
flattenReadContent as flattenToolReadContent,
|
|
||||||
nonEmptyString,
|
|
||||||
truncateDiffText,
|
|
||||||
} from "../tool-call-mapper-utils.js";
|
|
||||||
|
|
||||||
type OpencodeToolCallParams = {
|
type OpencodeToolCallParams = {
|
||||||
toolName: string;
|
toolName: string;
|
||||||
@@ -19,8 +14,6 @@ type OpencodeToolCallParams = {
|
|||||||
metadata?: Record<string, unknown>;
|
metadata?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_DIFF_TEXT_CHARS = 12_000;
|
|
||||||
|
|
||||||
const FAILED_STATUSES = new Set(["error", "failed", "failure"]);
|
const FAILED_STATUSES = new Set(["error", "failed", "failure"]);
|
||||||
const CANCELED_STATUSES = new Set(["canceled", "cancelled", "aborted", "interrupted"]);
|
const CANCELED_STATUSES = new Set(["canceled", "cancelled", "aborted", "interrupted"]);
|
||||||
const COMPLETED_STATUSES = new Set(["complete", "completed", "success", "succeeded", "done"]);
|
const COMPLETED_STATUSES = new Set(["complete", "completed", "success", "succeeded", "done"]);
|
||||||
@@ -37,435 +30,6 @@ const OpencodeToolCallParamsSchema = z
|
|||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
|
|
||||||
const CommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
|
||||||
|
|
||||||
const OpencodeShellInputSchema = z
|
|
||||||
.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
command: CommandValueSchema,
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
directory: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
cmd: CommandValueSchema,
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
directory: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
])
|
|
||||||
.transform((value) => {
|
|
||||||
const commandValue = "command" in value ? value.command : value.cmd;
|
|
||||||
return {
|
|
||||||
command: commandFromValue(commandValue),
|
|
||||||
cwd: nonEmptyString(value.cwd) ?? nonEmptyString(value.directory),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const OpencodeShellOutputObjectSchema = z
|
|
||||||
.object({
|
|
||||||
command: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
exitCode: z.number().finite().nullable().optional(),
|
|
||||||
exit_code: z.number().finite().nullable().optional(),
|
|
||||||
metadata: z
|
|
||||||
.object({
|
|
||||||
exitCode: z.number().finite().nullable().optional(),
|
|
||||||
exit_code: z.number().finite().nullable().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
structuredContent: z
|
|
||||||
.object({
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
structured_content: z
|
|
||||||
.object({
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
result: z
|
|
||||||
.object({
|
|
||||||
command: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const OpencodeShellOutputSchema = z.union([
|
|
||||||
z.string().transform((value) => ({
|
|
||||||
command: undefined,
|
|
||||||
output: nonEmptyString(value),
|
|
||||||
exitCode: undefined,
|
|
||||||
})),
|
|
||||||
OpencodeShellOutputObjectSchema.transform((value) => ({
|
|
||||||
command: nonEmptyString(value.command) ?? nonEmptyString(value.result?.command),
|
|
||||||
output:
|
|
||||||
nonEmptyString(value.output) ??
|
|
||||||
nonEmptyString(value.text) ??
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.structuredContent?.output) ??
|
|
||||||
nonEmptyString(value.structuredContent?.text) ??
|
|
||||||
nonEmptyString(value.structuredContent?.content) ??
|
|
||||||
nonEmptyString(value.structured_content?.output) ??
|
|
||||||
nonEmptyString(value.structured_content?.text) ??
|
|
||||||
nonEmptyString(value.structured_content?.content) ??
|
|
||||||
nonEmptyString(value.result?.output) ??
|
|
||||||
nonEmptyString(value.result?.text) ??
|
|
||||||
nonEmptyString(value.result?.content),
|
|
||||||
exitCode:
|
|
||||||
value.exitCode ??
|
|
||||||
value.exit_code ??
|
|
||||||
value.metadata?.exitCode ??
|
|
||||||
value.metadata?.exit_code ??
|
|
||||||
undefined,
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeReadPathInputSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.file_path,
|
|
||||||
offset: value.offset,
|
|
||||||
limit: value.limit,
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.path,
|
|
||||||
offset: value.offset,
|
|
||||||
limit: value.limit,
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
offset: z.number().finite().optional(),
|
|
||||||
limit: z.number().finite().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
offset: value.offset,
|
|
||||||
limit: value.limit,
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeReadChunkSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string(),
|
|
||||||
output: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
text: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
output: z.string(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeReadContentSchema = z.union([
|
|
||||||
z.string(),
|
|
||||||
OpencodeReadChunkSchema,
|
|
||||||
z.array(OpencodeReadChunkSchema),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeReadPayloadSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: OpencodeReadContentSchema,
|
|
||||||
text: OpencodeReadContentSchema.optional(),
|
|
||||||
output: OpencodeReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: OpencodeReadContentSchema.optional(),
|
|
||||||
text: OpencodeReadContentSchema,
|
|
||||||
output: OpencodeReadContentSchema.optional(),
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
content: OpencodeReadContentSchema.optional(),
|
|
||||||
text: OpencodeReadContentSchema.optional(),
|
|
||||||
output: OpencodeReadContentSchema,
|
|
||||||
})
|
|
||||||
.passthrough(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeReadOutputSchema = z.union([
|
|
||||||
z.string().transform((value) => ({ content: nonEmptyString(value) })),
|
|
||||||
OpencodeReadChunkSchema.transform((value) => ({ content: flattenReadContent(value) })),
|
|
||||||
z.array(OpencodeReadChunkSchema).transform((value) => ({ content: flattenReadContent(value) })),
|
|
||||||
OpencodeReadPayloadSchema.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.content) ??
|
|
||||||
flattenReadContent(value.text) ??
|
|
||||||
flattenReadContent(value.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ data: OpencodeReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.data.content) ??
|
|
||||||
flattenReadContent(value.data.text) ??
|
|
||||||
flattenReadContent(value.data.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ structuredContent: OpencodeReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.structuredContent.content) ??
|
|
||||||
flattenReadContent(value.structuredContent.text) ??
|
|
||||||
flattenReadContent(value.structuredContent.output),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ structured_content: OpencodeReadPayloadSchema })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
content:
|
|
||||||
flattenReadContent(value.structured_content.content) ??
|
|
||||||
flattenReadContent(value.structured_content.text) ??
|
|
||||||
flattenReadContent(value.structured_content.output),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeWritePathInputSchema = z.union([
|
|
||||||
z.object({ file_path: z.string() }).passthrough().transform((value) => ({ filePath: value.file_path })),
|
|
||||||
z.object({ path: z.string() }).passthrough().transform((value) => ({ filePath: value.path })),
|
|
||||||
z.object({ filePath: z.string() }).passthrough().transform((value) => ({ filePath: value.filePath })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeWriteContentSchema = z
|
|
||||||
.object({
|
|
||||||
content: z.string().optional(),
|
|
||||||
new_content: z.string().optional(),
|
|
||||||
newContent: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const OpencodeWriteInputSchema = z
|
|
||||||
.intersection(OpencodeWritePathInputSchema, OpencodeWriteContentSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const OpencodeWriteOutputSchema = z.union([
|
|
||||||
z
|
|
||||||
.intersection(OpencodeWritePathInputSchema, OpencodeWriteContentSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
})),
|
|
||||||
OpencodeWriteContentSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
content:
|
|
||||||
nonEmptyString(value.content) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.newContent),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeEditTextSchema = z
|
|
||||||
.object({
|
|
||||||
old_string: z.string().optional(),
|
|
||||||
old_str: z.string().optional(),
|
|
||||||
oldContent: z.string().optional(),
|
|
||||||
old_content: z.string().optional(),
|
|
||||||
new_string: z.string().optional(),
|
|
||||||
new_str: z.string().optional(),
|
|
||||||
newContent: z.string().optional(),
|
|
||||||
new_content: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough();
|
|
||||||
|
|
||||||
const OpencodeEditInputSchema = z
|
|
||||||
.intersection(OpencodeWritePathInputSchema, OpencodeEditTextSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
oldString:
|
|
||||||
nonEmptyString(value.old_string) ??
|
|
||||||
nonEmptyString(value.old_str) ??
|
|
||||||
nonEmptyString(value.oldContent) ??
|
|
||||||
nonEmptyString(value.old_content),
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.new_string) ??
|
|
||||||
nonEmptyString(value.new_str) ??
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const OpencodeEditOutputFileSchema = z.union([
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
path: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.path,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
file_path: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.file_path,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
filePath: z.string(),
|
|
||||||
patch: z.string().optional(),
|
|
||||||
diff: z.string().optional(),
|
|
||||||
unified_diff: z.string().optional(),
|
|
||||||
unifiedDiff: z.string().optional(),
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeEditOutputSchema = z.union([
|
|
||||||
z
|
|
||||||
.intersection(OpencodeWritePathInputSchema, OpencodeEditTextSchema)
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.filePath,
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
z
|
|
||||||
.object({ files: z.array(OpencodeEditOutputFileSchema).min(1) })
|
|
||||||
.passthrough()
|
|
||||||
.transform((value) => ({
|
|
||||||
filePath: value.files[0]?.filePath,
|
|
||||||
unifiedDiff: value.files[0]?.unifiedDiff,
|
|
||||||
newString: undefined,
|
|
||||||
})),
|
|
||||||
OpencodeEditTextSchema.transform((value) => ({
|
|
||||||
filePath: undefined,
|
|
||||||
newString:
|
|
||||||
nonEmptyString(value.newContent) ??
|
|
||||||
nonEmptyString(value.new_content) ??
|
|
||||||
nonEmptyString(value.content),
|
|
||||||
unifiedDiff: truncateDiffText(
|
|
||||||
nonEmptyString(value.patch) ??
|
|
||||||
nonEmptyString(value.diff) ??
|
|
||||||
nonEmptyString(value.unified_diff) ??
|
|
||||||
nonEmptyString(value.unifiedDiff),
|
|
||||||
MAX_DIFF_TEXT_CHARS
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const OpencodeSearchInputSchema = z.union([
|
|
||||||
z.object({ query: z.string() }).passthrough().transform((value) => ({ query: value.query })),
|
|
||||||
z.object({ q: z.string() }).passthrough().transform((value) => ({ query: value.q })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
function flattenReadContent(
|
|
||||||
value: z.infer<typeof OpencodeReadContentSchema> | undefined
|
|
||||||
): string | undefined {
|
|
||||||
return flattenToolReadContent(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function coerceCallId(callId: string | null | undefined, toolName: string, input: unknown): string {
|
function coerceCallId(callId: string | null | undefined, toolName: string, input: unknown): string {
|
||||||
return coerceToolCallId({
|
return coerceToolCallId({
|
||||||
providerPrefix: "opencode",
|
providerPrefix: "opencode",
|
||||||
@@ -475,7 +39,11 @@ function coerceCallId(callId: string | null | undefined, toolName: string, input
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveStatus(rawStatus: unknown, error: unknown, output: unknown): ToolCallTimelineItem["status"] {
|
function resolveStatus(
|
||||||
|
rawStatus: unknown,
|
||||||
|
error: unknown,
|
||||||
|
output: unknown
|
||||||
|
): ToolCallTimelineItem["status"] {
|
||||||
if (error !== null && error !== undefined) {
|
if (error !== null && error !== undefined) {
|
||||||
return "failed";
|
return "failed";
|
||||||
}
|
}
|
||||||
@@ -499,146 +67,13 @@ function resolveStatus(rawStatus: unknown, error: unknown, output: unknown): Too
|
|||||||
return output !== null && output !== undefined ? "completed" : "running";
|
return output !== null && output !== undefined ? "completed" : "running";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toShellDetail(
|
|
||||||
input: z.infer<typeof OpencodeShellInputSchema> | null,
|
|
||||||
output: z.infer<typeof OpencodeShellOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const command = input?.command ?? output?.command;
|
|
||||||
if (!command) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "shell",
|
|
||||||
command,
|
|
||||||
...(input?.cwd ? { cwd: input.cwd } : {}),
|
|
||||||
...(output?.output ? { output: output.output } : {}),
|
|
||||||
...(output?.exitCode !== undefined ? { exitCode: output.exitCode } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toReadDetail(
|
|
||||||
input: z.infer<typeof OpencodeReadPathInputSchema> | null,
|
|
||||||
output: z.infer<typeof OpencodeReadOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
if (!input?.filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "read",
|
|
||||||
filePath: input.filePath,
|
|
||||||
...(output?.content ? { content: output.content } : {}),
|
|
||||||
...(input.offset !== undefined ? { offset: input.offset } : {}),
|
|
||||||
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toWriteDetail(
|
|
||||||
input: z.infer<typeof OpencodeWriteInputSchema> | null,
|
|
||||||
output: z.infer<typeof OpencodeWriteOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = input?.filePath ?? output?.filePath;
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "write",
|
|
||||||
filePath,
|
|
||||||
...(input?.content ? { content: input.content } : output?.content ? { content: output.content } : {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toEditDetail(
|
|
||||||
input: z.infer<typeof OpencodeEditInputSchema> | null,
|
|
||||||
output: z.infer<typeof OpencodeEditOutputSchema> | null
|
|
||||||
): ToolCallDetail | undefined {
|
|
||||||
const filePath = input?.filePath ?? output?.filePath;
|
|
||||||
if (!filePath) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "edit",
|
|
||||||
filePath,
|
|
||||||
...(input?.oldString ? { oldString: input.oldString } : {}),
|
|
||||||
...(input?.newString ? { newString: input.newString } : output?.newString ? { newString: output.newString } : {}),
|
|
||||||
...(input?.unifiedDiff
|
|
||||||
? { unifiedDiff: input.unifiedDiff }
|
|
||||||
: output?.unifiedDiff
|
|
||||||
? { unifiedDiff: output.unifiedDiff }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSearchDetail(input: z.infer<typeof OpencodeSearchInputSchema> | null): ToolCallDetail | undefined {
|
|
||||||
if (!input?.query) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: "search",
|
|
||||||
query: input.query,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function opencodeToolBranch<
|
|
||||||
ToolName extends string,
|
|
||||||
InputSchema extends z.ZodTypeAny,
|
|
||||||
OutputSchema extends z.ZodTypeAny,
|
|
||||||
>(
|
|
||||||
toolName: ToolName,
|
|
||||||
inputSchema: InputSchema,
|
|
||||||
outputSchema: OutputSchema,
|
|
||||||
mapper: (
|
|
||||||
input: z.infer<InputSchema> | null,
|
|
||||||
output: z.infer<OutputSchema> | null
|
|
||||||
) => ToolCallDetail | undefined
|
|
||||||
) {
|
|
||||||
return z
|
|
||||||
.object({
|
|
||||||
toolName: z.literal(toolName),
|
|
||||||
input: inputSchema.nullable(),
|
|
||||||
output: outputSchema.nullable(),
|
|
||||||
})
|
|
||||||
.transform(({ input, output }) => mapper(input, output));
|
|
||||||
}
|
|
||||||
|
|
||||||
const OpencodeKnownToolDetailSchema = z.union([
|
|
||||||
opencodeToolBranch("shell", OpencodeShellInputSchema, OpencodeShellOutputSchema, toShellDetail),
|
|
||||||
opencodeToolBranch("bash", OpencodeShellInputSchema, OpencodeShellOutputSchema, toShellDetail),
|
|
||||||
opencodeToolBranch("exec_command", OpencodeShellInputSchema, OpencodeShellOutputSchema, toShellDetail),
|
|
||||||
opencodeToolBranch("read", OpencodeReadPathInputSchema, OpencodeReadOutputSchema, toReadDetail),
|
|
||||||
opencodeToolBranch("read_file", OpencodeReadPathInputSchema, OpencodeReadOutputSchema, toReadDetail),
|
|
||||||
opencodeToolBranch("write", OpencodeWriteInputSchema, OpencodeWriteOutputSchema, toWriteDetail),
|
|
||||||
opencodeToolBranch("write_file", OpencodeWriteInputSchema, OpencodeWriteOutputSchema, toWriteDetail),
|
|
||||||
opencodeToolBranch("create_file", OpencodeWriteInputSchema, OpencodeWriteOutputSchema, toWriteDetail),
|
|
||||||
opencodeToolBranch("edit", OpencodeEditInputSchema, OpencodeEditOutputSchema, toEditDetail),
|
|
||||||
opencodeToolBranch("apply_patch", OpencodeEditInputSchema, OpencodeEditOutputSchema, toEditDetail),
|
|
||||||
opencodeToolBranch("apply_diff", OpencodeEditInputSchema, OpencodeEditOutputSchema, toEditDetail),
|
|
||||||
opencodeToolBranch("search", OpencodeSearchInputSchema, z.unknown(), (input) => toSearchDetail(input)),
|
|
||||||
opencodeToolBranch("web_search", OpencodeSearchInputSchema, z.unknown(), (input) => toSearchDetail(input)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
function deriveDetail(toolName: string, input: unknown, output: unknown): ToolCallDetail | undefined {
|
|
||||||
const parsed = OpencodeKnownToolDetailSchema.safeParse({
|
|
||||||
toolName,
|
|
||||||
input,
|
|
||||||
output,
|
|
||||||
});
|
|
||||||
if (!parsed.success) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return parsed.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapOpencodeToolCall(params: OpencodeToolCallParams): ToolCallTimelineItem {
|
export function mapOpencodeToolCall(params: OpencodeToolCallParams): ToolCallTimelineItem {
|
||||||
const parsedParams = OpencodeToolCallParamsSchema.parse(params);
|
const parsedParams = OpencodeToolCallParamsSchema.parse(params);
|
||||||
const input = parsedParams.input ?? null;
|
const input = parsedParams.input ?? null;
|
||||||
const output = parsedParams.output ?? null;
|
const output = parsedParams.output ?? null;
|
||||||
const status = resolveStatus(parsedParams.status, parsedParams.error, output);
|
const status = resolveStatus(parsedParams.status, parsedParams.error, output);
|
||||||
const callId = coerceCallId(parsedParams.callId, parsedParams.toolName, input);
|
const callId = coerceCallId(parsedParams.callId, parsedParams.toolName, input);
|
||||||
const detail = deriveDetail(parsedParams.toolName, input, output);
|
const detail = deriveOpencodeToolDetail(parsedParams.toolName, input, output);
|
||||||
|
|
||||||
if (status === "failed") {
|
if (status === "failed") {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,694 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { ToolCallDetail } from "../agent-sdk-types.js";
|
||||||
|
import {
|
||||||
|
commandFromValue,
|
||||||
|
flattenReadContent as flattenToolReadContent,
|
||||||
|
nonEmptyString,
|
||||||
|
truncateDiffText,
|
||||||
|
} from "./tool-call-mapper-utils.js";
|
||||||
|
|
||||||
|
export const CommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
||||||
|
|
||||||
|
export const ToolShellInputSchema = z
|
||||||
|
.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
command: CommandValueSchema,
|
||||||
|
cwd: z.string().optional(),
|
||||||
|
directory: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
cmd: CommandValueSchema,
|
||||||
|
cwd: z.string().optional(),
|
||||||
|
directory: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
])
|
||||||
|
.transform((value) => {
|
||||||
|
const commandValue = "command" in value ? value.command : value.cmd;
|
||||||
|
return {
|
||||||
|
command: commandFromValue(commandValue),
|
||||||
|
cwd: nonEmptyString(value.cwd) ?? nonEmptyString(value.directory),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const ToolShellOutputObjectSchema = z
|
||||||
|
.object({
|
||||||
|
command: z.string().optional(),
|
||||||
|
output: z.string().optional(),
|
||||||
|
text: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
aggregated_output: z.string().optional(),
|
||||||
|
aggregatedOutput: z.string().optional(),
|
||||||
|
exitCode: z.number().finite().nullable().optional(),
|
||||||
|
exit_code: z.number().finite().nullable().optional(),
|
||||||
|
metadata: z
|
||||||
|
.object({
|
||||||
|
exitCode: z.number().finite().nullable().optional(),
|
||||||
|
exit_code: z.number().finite().nullable().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.optional(),
|
||||||
|
structuredContent: z
|
||||||
|
.object({
|
||||||
|
output: z.string().optional(),
|
||||||
|
text: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.optional(),
|
||||||
|
structured_content: z
|
||||||
|
.object({
|
||||||
|
output: z.string().optional(),
|
||||||
|
text: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.optional(),
|
||||||
|
result: z
|
||||||
|
.object({
|
||||||
|
command: z.string().optional(),
|
||||||
|
output: z.string().optional(),
|
||||||
|
text: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
|
export const ToolShellOutputSchema = z.union([
|
||||||
|
z.string().transform((value) => ({
|
||||||
|
command: undefined,
|
||||||
|
output: nonEmptyString(value),
|
||||||
|
exitCode: undefined,
|
||||||
|
})),
|
||||||
|
ToolShellOutputObjectSchema.transform((value) => ({
|
||||||
|
command: nonEmptyString(value.command) ?? nonEmptyString(value.result?.command),
|
||||||
|
output:
|
||||||
|
nonEmptyString(value.output) ??
|
||||||
|
nonEmptyString(value.text) ??
|
||||||
|
nonEmptyString(value.content) ??
|
||||||
|
nonEmptyString(value.aggregated_output) ??
|
||||||
|
nonEmptyString(value.aggregatedOutput) ??
|
||||||
|
nonEmptyString(value.structuredContent?.output) ??
|
||||||
|
nonEmptyString(value.structuredContent?.text) ??
|
||||||
|
nonEmptyString(value.structuredContent?.content) ??
|
||||||
|
nonEmptyString(value.structured_content?.output) ??
|
||||||
|
nonEmptyString(value.structured_content?.text) ??
|
||||||
|
nonEmptyString(value.structured_content?.content) ??
|
||||||
|
nonEmptyString(value.result?.output) ??
|
||||||
|
nonEmptyString(value.result?.text) ??
|
||||||
|
nonEmptyString(value.result?.content),
|
||||||
|
exitCode:
|
||||||
|
value.exitCode ??
|
||||||
|
value.exit_code ??
|
||||||
|
value.metadata?.exitCode ??
|
||||||
|
value.metadata?.exit_code ??
|
||||||
|
undefined,
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ToolPathInputSchema = z.union([
|
||||||
|
z.object({ path: z.string() }).passthrough().transform((value) => ({ filePath: value.path })),
|
||||||
|
z
|
||||||
|
.object({ file_path: z.string() })
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({ filePath: value.file_path })),
|
||||||
|
z
|
||||||
|
.object({ filePath: z.string() })
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({ filePath: value.filePath })),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ToolReadInputSchema = z.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
path: z.string(),
|
||||||
|
offset: z.number().finite().optional(),
|
||||||
|
limit: z.number().finite().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({ filePath: value.path, offset: value.offset, limit: value.limit })),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
file_path: z.string(),
|
||||||
|
offset: z.number().finite().optional(),
|
||||||
|
limit: z.number().finite().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({ filePath: value.file_path, offset: value.offset, limit: value.limit })),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
filePath: z.string(),
|
||||||
|
offset: z.number().finite().optional(),
|
||||||
|
limit: z.number().finite().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({ filePath: value.filePath, offset: value.offset, limit: value.limit })),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ToolReadChunkSchema = z.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
text: z.string(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
output: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
text: z.string().optional(),
|
||||||
|
content: z.string(),
|
||||||
|
output: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
text: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
output: z.string(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ToolReadContentSchema = z.union([z.string(), ToolReadChunkSchema, z.array(ToolReadChunkSchema)]);
|
||||||
|
|
||||||
|
const ToolReadPayloadSchema = z.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
content: ToolReadContentSchema,
|
||||||
|
text: ToolReadContentSchema.optional(),
|
||||||
|
output: ToolReadContentSchema.optional(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
content: ToolReadContentSchema.optional(),
|
||||||
|
text: ToolReadContentSchema,
|
||||||
|
output: ToolReadContentSchema.optional(),
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
content: ToolReadContentSchema.optional(),
|
||||||
|
text: ToolReadContentSchema.optional(),
|
||||||
|
output: ToolReadContentSchema,
|
||||||
|
})
|
||||||
|
.passthrough(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
function flattenReadContent(
|
||||||
|
value: z.infer<typeof ToolReadContentSchema> | undefined
|
||||||
|
): string | undefined {
|
||||||
|
return flattenToolReadContent(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToolReadOutputContentSchema = z.union([
|
||||||
|
z.string().transform((value) => ({ filePath: undefined, content: nonEmptyString(value) })),
|
||||||
|
ToolReadChunkSchema.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content: flattenReadContent(value),
|
||||||
|
})),
|
||||||
|
z.array(ToolReadChunkSchema).transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content: flattenReadContent(value),
|
||||||
|
})),
|
||||||
|
ToolReadPayloadSchema.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.content) ??
|
||||||
|
flattenReadContent(value.text) ??
|
||||||
|
flattenReadContent(value.output),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({ data: ToolReadPayloadSchema })
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.data.content) ??
|
||||||
|
flattenReadContent(value.data.text) ??
|
||||||
|
flattenReadContent(value.data.output),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({ structuredContent: ToolReadPayloadSchema })
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.structuredContent.content) ??
|
||||||
|
flattenReadContent(value.structuredContent.text) ??
|
||||||
|
flattenReadContent(value.structuredContent.output),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({ structured_content: ToolReadPayloadSchema })
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.structured_content.content) ??
|
||||||
|
flattenReadContent(value.structured_content.text) ??
|
||||||
|
flattenReadContent(value.structured_content.output),
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ToolReadOutputPathSchema = z.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
path: z.string(),
|
||||||
|
content: ToolReadContentSchema.optional(),
|
||||||
|
text: ToolReadContentSchema.optional(),
|
||||||
|
output: ToolReadContentSchema.optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.path,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.content) ??
|
||||||
|
flattenReadContent(value.text) ??
|
||||||
|
flattenReadContent(value.output),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
file_path: z.string(),
|
||||||
|
content: ToolReadContentSchema.optional(),
|
||||||
|
text: ToolReadContentSchema.optional(),
|
||||||
|
output: ToolReadContentSchema.optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.file_path,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.content) ??
|
||||||
|
flattenReadContent(value.text) ??
|
||||||
|
flattenReadContent(value.output),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
filePath: z.string(),
|
||||||
|
content: ToolReadContentSchema.optional(),
|
||||||
|
text: ToolReadContentSchema.optional(),
|
||||||
|
output: ToolReadContentSchema.optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.filePath,
|
||||||
|
content:
|
||||||
|
flattenReadContent(value.content) ??
|
||||||
|
flattenReadContent(value.text) ??
|
||||||
|
flattenReadContent(value.output),
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
type ToolReadOutputValue = {
|
||||||
|
filePath?: string;
|
||||||
|
content?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToolReadOutputSchema: z.ZodType<
|
||||||
|
ToolReadOutputValue,
|
||||||
|
z.ZodTypeDef,
|
||||||
|
unknown
|
||||||
|
> = ToolReadOutputContentSchema;
|
||||||
|
|
||||||
|
export const ToolReadOutputWithPathSchema: z.ZodType<
|
||||||
|
ToolReadOutputValue,
|
||||||
|
z.ZodTypeDef,
|
||||||
|
unknown
|
||||||
|
> = z.union([ToolReadOutputContentSchema, ToolReadOutputPathSchema]);
|
||||||
|
|
||||||
|
export const ToolWriteContentSchema = z
|
||||||
|
.object({
|
||||||
|
content: z.string().optional(),
|
||||||
|
new_content: z.string().optional(),
|
||||||
|
newContent: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
|
export const ToolWriteInputSchema = z
|
||||||
|
.intersection(ToolPathInputSchema, ToolWriteContentSchema)
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.filePath,
|
||||||
|
content:
|
||||||
|
nonEmptyString(value.content) ??
|
||||||
|
nonEmptyString(value.new_content) ??
|
||||||
|
nonEmptyString(value.newContent),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const ToolWriteOutputSchema = z.union([
|
||||||
|
z
|
||||||
|
.intersection(ToolPathInputSchema, ToolWriteContentSchema)
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.filePath,
|
||||||
|
content:
|
||||||
|
nonEmptyString(value.content) ??
|
||||||
|
nonEmptyString(value.new_content) ??
|
||||||
|
nonEmptyString(value.newContent),
|
||||||
|
})),
|
||||||
|
ToolWriteContentSchema.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
content:
|
||||||
|
nonEmptyString(value.content) ??
|
||||||
|
nonEmptyString(value.new_content) ??
|
||||||
|
nonEmptyString(value.newContent),
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ToolEditTextSchema = z
|
||||||
|
.object({
|
||||||
|
old_string: z.string().optional(),
|
||||||
|
old_str: z.string().optional(),
|
||||||
|
oldContent: z.string().optional(),
|
||||||
|
old_content: z.string().optional(),
|
||||||
|
new_string: z.string().optional(),
|
||||||
|
new_str: z.string().optional(),
|
||||||
|
newContent: z.string().optional(),
|
||||||
|
new_content: z.string().optional(),
|
||||||
|
content: z.string().optional(),
|
||||||
|
patch: z.string().optional(),
|
||||||
|
diff: z.string().optional(),
|
||||||
|
unified_diff: z.string().optional(),
|
||||||
|
unifiedDiff: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough();
|
||||||
|
|
||||||
|
export const ToolEditInputSchema = z
|
||||||
|
.intersection(ToolPathInputSchema, ToolEditTextSchema)
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.filePath,
|
||||||
|
oldString:
|
||||||
|
nonEmptyString(value.old_string) ??
|
||||||
|
nonEmptyString(value.old_str) ??
|
||||||
|
nonEmptyString(value.oldContent) ??
|
||||||
|
nonEmptyString(value.old_content),
|
||||||
|
newString:
|
||||||
|
nonEmptyString(value.new_string) ??
|
||||||
|
nonEmptyString(value.new_str) ??
|
||||||
|
nonEmptyString(value.newContent) ??
|
||||||
|
nonEmptyString(value.new_content) ??
|
||||||
|
nonEmptyString(value.content),
|
||||||
|
unifiedDiff: truncateDiffText(
|
||||||
|
nonEmptyString(value.patch) ??
|
||||||
|
nonEmptyString(value.diff) ??
|
||||||
|
nonEmptyString(value.unified_diff) ??
|
||||||
|
nonEmptyString(value.unifiedDiff)
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ToolEditOutputFileSchema = z.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
path: z.string(),
|
||||||
|
patch: z.string().optional(),
|
||||||
|
diff: z.string().optional(),
|
||||||
|
unified_diff: z.string().optional(),
|
||||||
|
unifiedDiff: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.path,
|
||||||
|
unifiedDiff: truncateDiffText(
|
||||||
|
nonEmptyString(value.patch) ??
|
||||||
|
nonEmptyString(value.diff) ??
|
||||||
|
nonEmptyString(value.unified_diff) ??
|
||||||
|
nonEmptyString(value.unifiedDiff)
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
file_path: z.string(),
|
||||||
|
patch: z.string().optional(),
|
||||||
|
diff: z.string().optional(),
|
||||||
|
unified_diff: z.string().optional(),
|
||||||
|
unifiedDiff: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.file_path,
|
||||||
|
unifiedDiff: truncateDiffText(
|
||||||
|
nonEmptyString(value.patch) ??
|
||||||
|
nonEmptyString(value.diff) ??
|
||||||
|
nonEmptyString(value.unified_diff) ??
|
||||||
|
nonEmptyString(value.unifiedDiff)
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
filePath: z.string(),
|
||||||
|
patch: z.string().optional(),
|
||||||
|
diff: z.string().optional(),
|
||||||
|
unified_diff: z.string().optional(),
|
||||||
|
unifiedDiff: z.string().optional(),
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.filePath,
|
||||||
|
unifiedDiff: truncateDiffText(
|
||||||
|
nonEmptyString(value.patch) ??
|
||||||
|
nonEmptyString(value.diff) ??
|
||||||
|
nonEmptyString(value.unified_diff) ??
|
||||||
|
nonEmptyString(value.unifiedDiff)
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ToolEditOutputSchema = z.union([
|
||||||
|
z
|
||||||
|
.intersection(ToolPathInputSchema, ToolEditTextSchema)
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.filePath,
|
||||||
|
newString:
|
||||||
|
nonEmptyString(value.newContent) ??
|
||||||
|
nonEmptyString(value.new_content) ??
|
||||||
|
nonEmptyString(value.content),
|
||||||
|
unifiedDiff: truncateDiffText(
|
||||||
|
nonEmptyString(value.patch) ??
|
||||||
|
nonEmptyString(value.diff) ??
|
||||||
|
nonEmptyString(value.unified_diff) ??
|
||||||
|
nonEmptyString(value.unifiedDiff)
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
z
|
||||||
|
.object({ files: z.array(ToolEditOutputFileSchema).min(1) })
|
||||||
|
.passthrough()
|
||||||
|
.transform((value) => ({
|
||||||
|
filePath: value.files[0]?.filePath,
|
||||||
|
unifiedDiff: value.files[0]?.unifiedDiff,
|
||||||
|
newString: undefined,
|
||||||
|
})),
|
||||||
|
ToolEditTextSchema.transform((value) => ({
|
||||||
|
filePath: undefined,
|
||||||
|
newString:
|
||||||
|
nonEmptyString(value.newContent) ??
|
||||||
|
nonEmptyString(value.new_content) ??
|
||||||
|
nonEmptyString(value.content),
|
||||||
|
unifiedDiff: truncateDiffText(
|
||||||
|
nonEmptyString(value.patch) ??
|
||||||
|
nonEmptyString(value.diff) ??
|
||||||
|
nonEmptyString(value.unified_diff) ??
|
||||||
|
nonEmptyString(value.unifiedDiff)
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ToolSearchInputSchema = z.union([
|
||||||
|
z.object({ query: z.string() }).passthrough().transform((value) => ({ query: value.query })),
|
||||||
|
z.object({ q: z.string() }).passthrough().transform((value) => ({ query: value.q })),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type ParsedToolShellInput = z.infer<typeof ToolShellInputSchema>;
|
||||||
|
export type ParsedToolShellOutput = z.infer<typeof ToolShellOutputSchema>;
|
||||||
|
export type ParsedToolReadInput = z.infer<typeof ToolReadInputSchema>;
|
||||||
|
export type ParsedToolReadOutput = z.infer<typeof ToolReadOutputSchema>;
|
||||||
|
export type ParsedToolReadOutputWithPath = z.infer<typeof ToolReadOutputWithPathSchema>;
|
||||||
|
export type ParsedToolWriteInput = z.infer<typeof ToolWriteInputSchema>;
|
||||||
|
export type ParsedToolWriteOutput = z.infer<typeof ToolWriteOutputSchema>;
|
||||||
|
export type ParsedToolEditInput = z.infer<typeof ToolEditInputSchema>;
|
||||||
|
export type ParsedToolEditOutput = z.infer<typeof ToolEditOutputSchema>;
|
||||||
|
export type ParsedToolSearchInput = z.infer<typeof ToolSearchInputSchema>;
|
||||||
|
|
||||||
|
type NormalizePathFn = (filePath: string) => string | undefined;
|
||||||
|
|
||||||
|
function normalizeDetailPath(
|
||||||
|
filePath: string | undefined,
|
||||||
|
normalizePath?: NormalizePathFn
|
||||||
|
): string | undefined {
|
||||||
|
if (typeof filePath !== "string") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const trimmed = filePath.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return normalizePath ? normalizePath(trimmed) : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toShellToolDetail(
|
||||||
|
input: ParsedToolShellInput | null,
|
||||||
|
output: ParsedToolShellOutput | null
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
const command = input?.command ?? output?.command;
|
||||||
|
if (!command) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "shell",
|
||||||
|
command,
|
||||||
|
...(input?.cwd ? { cwd: input.cwd } : {}),
|
||||||
|
...(output?.output ? { output: output.output } : {}),
|
||||||
|
...(output?.exitCode !== undefined ? { exitCode: output.exitCode } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toReadToolDetail(
|
||||||
|
input: ParsedToolReadInput | null,
|
||||||
|
output: ParsedToolReadOutput | ParsedToolReadOutputWithPath | null,
|
||||||
|
options?: { normalizePath?: NormalizePathFn }
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
const filePath = normalizeDetailPath(
|
||||||
|
input?.filePath ?? output?.filePath,
|
||||||
|
options?.normalizePath
|
||||||
|
);
|
||||||
|
if (!filePath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "read",
|
||||||
|
filePath,
|
||||||
|
...(output?.content ? { content: output.content } : {}),
|
||||||
|
...(input?.offset !== undefined ? { offset: input.offset } : {}),
|
||||||
|
...(input?.limit !== undefined ? { limit: input.limit } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toWriteToolDetail(
|
||||||
|
input: ParsedToolWriteInput | null,
|
||||||
|
output: ParsedToolWriteOutput | null,
|
||||||
|
options?: { normalizePath?: NormalizePathFn }
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
const filePath = normalizeDetailPath(
|
||||||
|
input?.filePath ?? output?.filePath,
|
||||||
|
options?.normalizePath
|
||||||
|
);
|
||||||
|
if (!filePath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "write",
|
||||||
|
filePath,
|
||||||
|
...(input?.content ? { content: input.content } : output?.content ? { content: output.content } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toEditToolDetail(
|
||||||
|
input: ParsedToolEditInput | null,
|
||||||
|
output: ParsedToolEditOutput | null,
|
||||||
|
options?: { normalizePath?: NormalizePathFn }
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
const filePath = normalizeDetailPath(
|
||||||
|
input?.filePath ?? output?.filePath,
|
||||||
|
options?.normalizePath
|
||||||
|
);
|
||||||
|
if (!filePath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "edit",
|
||||||
|
filePath,
|
||||||
|
...(input?.oldString ? { oldString: input.oldString } : {}),
|
||||||
|
...(input?.newString ? { newString: input.newString } : output?.newString ? { newString: output.newString } : {}),
|
||||||
|
...(input?.unifiedDiff
|
||||||
|
? { unifiedDiff: input.unifiedDiff }
|
||||||
|
: output?.unifiedDiff
|
||||||
|
? { unifiedDiff: output.unifiedDiff }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSearchToolDetail(
|
||||||
|
input: ParsedToolSearchInput | null
|
||||||
|
): ToolCallDetail | undefined {
|
||||||
|
if (!input?.query) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "search",
|
||||||
|
query: input.query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toolDetailBranchByName<
|
||||||
|
Name extends string,
|
||||||
|
InputSchema extends z.ZodTypeAny,
|
||||||
|
OutputSchema extends z.ZodTypeAny,
|
||||||
|
>(
|
||||||
|
name: Name,
|
||||||
|
inputSchema: InputSchema,
|
||||||
|
outputSchema: OutputSchema,
|
||||||
|
mapper: (
|
||||||
|
input: z.infer<InputSchema> | null,
|
||||||
|
output: z.infer<OutputSchema> | null
|
||||||
|
) => ToolCallDetail | undefined
|
||||||
|
) {
|
||||||
|
return z
|
||||||
|
.object({
|
||||||
|
name: z.literal(name),
|
||||||
|
input: inputSchema.nullable(),
|
||||||
|
output: outputSchema.nullable(),
|
||||||
|
})
|
||||||
|
.transform(({ input, output }) => mapper(input, output));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toolDetailBranchByToolName<
|
||||||
|
Name extends string,
|
||||||
|
InputSchema extends z.ZodTypeAny,
|
||||||
|
OutputSchema extends z.ZodTypeAny,
|
||||||
|
>(
|
||||||
|
toolName: Name,
|
||||||
|
inputSchema: InputSchema,
|
||||||
|
outputSchema: OutputSchema,
|
||||||
|
mapper: (
|
||||||
|
input: z.infer<InputSchema> | null,
|
||||||
|
output: z.infer<OutputSchema> | null
|
||||||
|
) => ToolCallDetail | undefined
|
||||||
|
) {
|
||||||
|
return z
|
||||||
|
.object({
|
||||||
|
toolName: z.literal(toolName),
|
||||||
|
input: inputSchema.nullable(),
|
||||||
|
output: outputSchema.nullable(),
|
||||||
|
})
|
||||||
|
.transform(({ input, output }) => mapper(input, output));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toolDetailBranchByNameWithCwd<
|
||||||
|
Name extends string,
|
||||||
|
InputSchema extends z.ZodTypeAny,
|
||||||
|
OutputSchema extends z.ZodTypeAny,
|
||||||
|
>(
|
||||||
|
name: Name,
|
||||||
|
inputSchema: InputSchema,
|
||||||
|
outputSchema: OutputSchema,
|
||||||
|
mapper: (
|
||||||
|
input: z.infer<InputSchema> | null,
|
||||||
|
output: z.infer<OutputSchema> | null,
|
||||||
|
cwd: string | null
|
||||||
|
) => ToolCallDetail | undefined
|
||||||
|
) {
|
||||||
|
return z
|
||||||
|
.object({
|
||||||
|
name: z.literal(name),
|
||||||
|
input: inputSchema.nullable(),
|
||||||
|
output: outputSchema.nullable(),
|
||||||
|
cwd: z.string().optional().nullable(),
|
||||||
|
})
|
||||||
|
.transform(({ input, output, cwd }) => mapper(input, output, cwd ?? null));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user