mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
refactor: finish strict tool-call typing and mapper dedup
This commit is contained in:
@@ -54,9 +54,9 @@ export type MessageEntry =
|
||||
id: string;
|
||||
timestamp: number;
|
||||
toolName: string;
|
||||
args: any;
|
||||
result?: any;
|
||||
error?: any;
|
||||
args: unknown | null;
|
||||
result?: unknown | null;
|
||||
error?: unknown | null;
|
||||
status: "executing" | "completed" | "failed";
|
||||
};
|
||||
|
||||
|
||||
@@ -2,11 +2,25 @@ import { z } from "zod";
|
||||
|
||||
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||
import {
|
||||
StandardEditInputSchema,
|
||||
StandardEditOutputSchema,
|
||||
StandardReadOutputSchema,
|
||||
StandardReadPathInputSchema,
|
||||
StandardSearchInputSchema,
|
||||
StandardShellInputSchema,
|
||||
StandardShellOutputSchema,
|
||||
StandardWriteInputSchema,
|
||||
StandardWriteOutputSchema,
|
||||
toStandardEditDetail,
|
||||
toStandardReadDetail,
|
||||
toStandardSearchDetail,
|
||||
toStandardShellDetail,
|
||||
toStandardWriteDetail,
|
||||
} from "../standard-tool-call-schemas.js";
|
||||
import {
|
||||
CLAUDE_KNOWN_TOOL_ALIASES,
|
||||
coerceToolCallId,
|
||||
commandFromValue,
|
||||
flattenReadContent as flattenToolReadContent,
|
||||
nonEmptyString,
|
||||
truncateDiffText,
|
||||
unionToolDetailSchemas,
|
||||
} from "../tool-call-mapper-utils.js";
|
||||
|
||||
type MapperParams = {
|
||||
@@ -17,8 +31,6 @@ type MapperParams = {
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const MAX_DIFF_TEXT_CHARS = 12_000;
|
||||
|
||||
const ClaudeMapperParamsSchema = z
|
||||
.object({
|
||||
callId: z.string().optional().nullable(),
|
||||
@@ -33,437 +45,6 @@ const ClaudeFailedMapperParamsSchema = ClaudeMapperParamsSchema.extend({
|
||||
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 {
|
||||
return coerceToolCallId({
|
||||
providerPrefix: "claude",
|
||||
@@ -473,96 +54,13 @@ 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,
|
||||
function claudeToolBranch<InputSchema extends z.ZodTypeAny, OutputSchema extends z.ZodTypeAny>(
|
||||
name: string,
|
||||
inputSchema: InputSchema,
|
||||
outputSchema: OutputSchema,
|
||||
mapper: (
|
||||
input: z.infer<InputSchema> | null,
|
||||
output: z.infer<OutputSchema> | null
|
||||
input: z.output<InputSchema> | null | undefined,
|
||||
output: z.output<OutputSchema> | null | undefined
|
||||
) => ToolCallDetail | undefined
|
||||
) {
|
||||
return z
|
||||
@@ -574,29 +72,30 @@ function claudeToolBranch<Name extends string, InputSchema extends z.ZodTypeAny,
|
||||
.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)),
|
||||
const ClaudeKnownToolDetailSchema: z.ZodType<ToolCallDetail | undefined> = unionToolDetailSchemas([
|
||||
...CLAUDE_KNOWN_TOOL_ALIASES.shell.map((name) =>
|
||||
claudeToolBranch(name, StandardShellInputSchema, StandardShellOutputSchema, (input, output) =>
|
||||
toStandardShellDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...CLAUDE_KNOWN_TOOL_ALIASES.read.map((name) =>
|
||||
claudeToolBranch(name, StandardReadPathInputSchema, StandardReadOutputSchema, (input, output) =>
|
||||
toStandardReadDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...CLAUDE_KNOWN_TOOL_ALIASES.write.map((name) =>
|
||||
claudeToolBranch(name, StandardWriteInputSchema, StandardWriteOutputSchema, (input, output) =>
|
||||
toStandardWriteDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...CLAUDE_KNOWN_TOOL_ALIASES.edit.map((name) =>
|
||||
claudeToolBranch(name, StandardEditInputSchema, StandardEditOutputSchema, (input, output) =>
|
||||
toStandardEditDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...CLAUDE_KNOWN_TOOL_ALIASES.search.map((name) =>
|
||||
claudeToolBranch(name, StandardSearchInputSchema, z.unknown(), (input) => toStandardSearchDetail(input ?? null))
|
||||
),
|
||||
]);
|
||||
|
||||
function deriveDetail(name: string, input: unknown, output: unknown): ToolCallDetail | undefined {
|
||||
|
||||
@@ -2,11 +2,29 @@ import { z } from "zod";
|
||||
|
||||
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||
import {
|
||||
StandardEditInputSchema,
|
||||
StandardEditOutputSchema,
|
||||
StandardReadChunkSchema,
|
||||
StandardReadPathInputSchema,
|
||||
StandardSearchInputSchema,
|
||||
StandardShellInputSchema,
|
||||
StandardShellOutputSchema,
|
||||
StandardWriteInputSchema,
|
||||
StandardWriteOutputSchema,
|
||||
toStandardEditDetail,
|
||||
toStandardSearchDetail,
|
||||
toStandardShellDetail,
|
||||
toStandardWriteDetail,
|
||||
} from "../standard-tool-call-schemas.js";
|
||||
import {
|
||||
CODEX_MCP_KNOWN_TOOL_ALIASES,
|
||||
CODEX_ROLLOUT_KNOWN_TOOL_ALIASES,
|
||||
coerceToolCallId,
|
||||
commandFromValue,
|
||||
flattenReadContent as flattenToolReadContent,
|
||||
nonEmptyString,
|
||||
truncateDiffText,
|
||||
unionToolDetailSchemas,
|
||||
} from "../tool-call-mapper-utils.js";
|
||||
|
||||
type CodexMapperOptions = { cwd?: string | null };
|
||||
@@ -27,165 +45,21 @@ const CodexRolloutToolCallParamsSchema = z
|
||||
|
||||
const CommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
||||
|
||||
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 CodexShellInputSchema = StandardShellInputSchema;
|
||||
const CodexShellOutputSchema = StandardShellOutputSchema;
|
||||
const CodexReadArgumentsSchema = StandardReadPathInputSchema;
|
||||
const CodexWriteArgumentsSchema = StandardWriteInputSchema;
|
||||
const CodexWriteResultSchema = StandardWriteOutputSchema;
|
||||
const CodexEditArgumentsSchema = StandardEditInputSchema;
|
||||
const CodexEditResultSchema = StandardEditOutputSchema;
|
||||
const CodexSearchArgumentsSchema = StandardSearchInputSchema;
|
||||
|
||||
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 CodexReadContentSchema = z.union([
|
||||
z.string(),
|
||||
StandardReadChunkSchema,
|
||||
z.array(StandardReadChunkSchema),
|
||||
]);
|
||||
|
||||
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({
|
||||
@@ -260,8 +134,8 @@ const CodexReadResultWithPathSchema = z.union([
|
||||
|
||||
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) })),
|
||||
StandardReadChunkSchema.transform((value) => ({ filePath: undefined, content: flattenReadContent(value) })),
|
||||
z.array(StandardReadChunkSchema).transform((value) => ({ filePath: undefined, content: flattenReadContent(value) })),
|
||||
CodexReadPayloadSchema.transform((value) => ({
|
||||
filePath: undefined,
|
||||
content:
|
||||
@@ -302,184 +176,6 @@ const CodexReadResultSchema = z.union([
|
||||
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
|
||||
.object({
|
||||
@@ -607,18 +303,7 @@ 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 } : {}),
|
||||
};
|
||||
return toStandardShellDetail(input, output);
|
||||
}
|
||||
|
||||
function toReadDetail(
|
||||
@@ -645,16 +330,7 @@ function toWriteDetail(
|
||||
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 } : {}),
|
||||
};
|
||||
return toStandardWriteDetail(input, output, (filePath) => normalizeCodexFilePath(filePath, cwd));
|
||||
}
|
||||
|
||||
function toEditDetail(
|
||||
@@ -662,32 +338,11 @@ function toEditDetail(
|
||||
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 }
|
||||
: {}),
|
||||
};
|
||||
return toStandardEditDetail(input, output, (filePath) => normalizeCodexFilePath(filePath, cwd));
|
||||
}
|
||||
|
||||
function toSearchDetail(input: z.infer<typeof CodexSearchArgumentsSchema> | null): ToolCallDetail | undefined {
|
||||
if (!input?.query) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
type: "search",
|
||||
query: input.query,
|
||||
};
|
||||
return toStandardSearchDetail(input);
|
||||
}
|
||||
|
||||
function codexMcpToolBranch<ToolName extends string, InputSchema extends z.ZodTypeAny, OutputSchema extends z.ZodTypeAny>(
|
||||
@@ -710,32 +365,24 @@ function codexMcpToolBranch<ToolName extends string, InputSchema extends z.ZodTy
|
||||
.transform(({ arguments: input, result: output, cwd }) => mapper(input, output, cwd));
|
||||
}
|
||||
|
||||
const CodexKnownMcpToolDetailSchema = z.union([
|
||||
codexMcpToolBranch("shell", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
const CodexKnownMcpToolDetailSchema: z.ZodType<ToolCallDetail | undefined> = unionToolDetailSchemas([
|
||||
...CODEX_MCP_KNOWN_TOOL_ALIASES.shell.map((tool) =>
|
||||
codexMcpToolBranch(tool, CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
)
|
||||
),
|
||||
codexMcpToolBranch("bash", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_MCP_KNOWN_TOOL_ALIASES.read.map((tool) =>
|
||||
codexMcpToolBranch(tool, CodexReadArgumentsSchema, CodexReadResultSchema, toReadDetail)
|
||||
),
|
||||
codexMcpToolBranch("exec", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_MCP_KNOWN_TOOL_ALIASES.write.map((tool) =>
|
||||
codexMcpToolBranch(tool, CodexWriteArgumentsSchema, CodexWriteResultSchema, toWriteDetail)
|
||||
),
|
||||
codexMcpToolBranch("exec_command", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_MCP_KNOWN_TOOL_ALIASES.edit.map((tool) =>
|
||||
codexMcpToolBranch(tool, CodexEditArgumentsSchema, CodexEditResultSchema, toEditDetail)
|
||||
),
|
||||
codexMcpToolBranch("command", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_MCP_KNOWN_TOOL_ALIASES.search.map((tool) =>
|
||||
codexMcpToolBranch(tool, CodexSearchArgumentsSchema, z.unknown(), (input) => toSearchDetail(input))
|
||||
),
|
||||
codexMcpToolBranch("read", CodexReadArgumentsSchema, CodexReadResultSchema, toReadDetail),
|
||||
codexMcpToolBranch("read_file", CodexReadArgumentsSchema, CodexReadResultSchema, toReadDetail),
|
||||
codexMcpToolBranch("write", CodexWriteArgumentsSchema, CodexWriteResultSchema, toWriteDetail),
|
||||
codexMcpToolBranch("write_file", CodexWriteArgumentsSchema, CodexWriteResultSchema, toWriteDetail),
|
||||
codexMcpToolBranch("create_file", CodexWriteArgumentsSchema, CodexWriteResultSchema, toWriteDetail),
|
||||
codexMcpToolBranch("edit", CodexEditArgumentsSchema, CodexEditResultSchema, toEditDetail),
|
||||
codexMcpToolBranch("apply_patch", CodexEditArgumentsSchema, CodexEditResultSchema, toEditDetail),
|
||||
codexMcpToolBranch("apply_diff", CodexEditArgumentsSchema, CodexEditResultSchema, toEditDetail),
|
||||
codexMcpToolBranch("search", CodexSearchArgumentsSchema, z.unknown(), (input) => toSearchDetail(input)),
|
||||
codexMcpToolBranch("web_search", CodexSearchArgumentsSchema, z.unknown(), (input) => toSearchDetail(input)),
|
||||
]);
|
||||
|
||||
function codexRolloutToolBranch<Name extends string, InputSchema extends z.ZodTypeAny, OutputSchema extends z.ZodTypeAny>(
|
||||
@@ -756,45 +403,30 @@ function codexRolloutToolBranch<Name extends string, InputSchema extends z.ZodTy
|
||||
.transform(({ input, output }) => mapper(input, output));
|
||||
}
|
||||
|
||||
const CodexKnownRolloutDetailSchema = z.union([
|
||||
codexRolloutToolBranch("Bash", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
const CodexKnownRolloutDetailSchema: z.ZodType<ToolCallDetail | undefined> = unionToolDetailSchemas([
|
||||
...CODEX_ROLLOUT_KNOWN_TOOL_ALIASES.shell.map((name) =>
|
||||
codexRolloutToolBranch(name, CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
)
|
||||
),
|
||||
codexRolloutToolBranch("shell", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_ROLLOUT_KNOWN_TOOL_ALIASES.read.map((name) =>
|
||||
codexRolloutToolBranch(name, CodexReadArgumentsSchema, CodexReadResultSchema, (input, output) =>
|
||||
toReadDetail(input, output, null)
|
||||
)
|
||||
),
|
||||
codexRolloutToolBranch("bash", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_ROLLOUT_KNOWN_TOOL_ALIASES.write.map((name) =>
|
||||
codexRolloutToolBranch(name, CodexWriteArgumentsSchema, CodexWriteResultSchema, (input, output) =>
|
||||
toWriteDetail(input, output, null)
|
||||
)
|
||||
),
|
||||
codexRolloutToolBranch("exec_command", CodexShellInputSchema, CodexShellOutputSchema, (input, output) =>
|
||||
toShellDetail(input, output)
|
||||
...CODEX_ROLLOUT_KNOWN_TOOL_ALIASES.edit.map((name) =>
|
||||
codexRolloutToolBranch(name, CodexEditArgumentsSchema, CodexEditResultSchema, (input, output) =>
|
||||
toEditDetail(input, output, null)
|
||||
)
|
||||
),
|
||||
codexRolloutToolBranch("read", CodexReadArgumentsSchema, CodexReadResultSchema, (input, output) =>
|
||||
toReadDetail(input, output, null)
|
||||
...CODEX_ROLLOUT_KNOWN_TOOL_ALIASES.search.map((name) =>
|
||||
codexRolloutToolBranch(name, CodexSearchArgumentsSchema, z.unknown(), (input) => toSearchDetail(input))
|
||||
),
|
||||
codexRolloutToolBranch("read_file", CodexReadArgumentsSchema, CodexReadResultSchema, (input, output) =>
|
||||
toReadDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("write", CodexWriteArgumentsSchema, CodexWriteResultSchema, (input, output) =>
|
||||
toWriteDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("write_file", CodexWriteArgumentsSchema, CodexWriteResultSchema, (input, output) =>
|
||||
toWriteDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("create_file", CodexWriteArgumentsSchema, CodexWriteResultSchema, (input, output) =>
|
||||
toWriteDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("edit", CodexEditArgumentsSchema, CodexEditResultSchema, (input, output) =>
|
||||
toEditDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("apply_patch", CodexEditArgumentsSchema, CodexEditResultSchema, (input, output) =>
|
||||
toEditDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("apply_diff", CodexEditArgumentsSchema, CodexEditResultSchema, (input, output) =>
|
||||
toEditDetail(input, output, null)
|
||||
),
|
||||
codexRolloutToolBranch("search", CodexSearchArgumentsSchema, z.unknown(), (input) => toSearchDetail(input)),
|
||||
codexRolloutToolBranch("web_search", CodexSearchArgumentsSchema, z.unknown(), (input) => toSearchDetail(input)),
|
||||
]);
|
||||
|
||||
function deriveMcpToolDetail(
|
||||
@@ -864,23 +496,9 @@ 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",
|
||||
]);
|
||||
const CODEX_BUILTIN_TOOL_NAMES = new Set(
|
||||
Object.values(CODEX_MCP_KNOWN_TOOL_ALIASES).flat()
|
||||
);
|
||||
|
||||
function buildMcpToolName(server: string | undefined, tool: string): string {
|
||||
const trimmedTool = tool.trim();
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { ToolCallDetail, ToolCallTimelineItem } from "../../agent-sdk-types.js";
|
||||
import {
|
||||
StandardEditInputSchema,
|
||||
StandardEditOutputSchema,
|
||||
StandardReadOutputSchema,
|
||||
StandardReadPathInputSchema,
|
||||
StandardSearchInputSchema,
|
||||
StandardShellInputSchema,
|
||||
StandardShellOutputSchema,
|
||||
StandardWriteInputSchema,
|
||||
StandardWriteOutputSchema,
|
||||
toStandardEditDetail,
|
||||
toStandardReadDetail,
|
||||
toStandardSearchDetail,
|
||||
toStandardShellDetail,
|
||||
toStandardWriteDetail,
|
||||
} from "../standard-tool-call-schemas.js";
|
||||
import {
|
||||
coerceToolCallId,
|
||||
commandFromValue,
|
||||
flattenReadContent as flattenToolReadContent,
|
||||
nonEmptyString,
|
||||
truncateDiffText,
|
||||
OPENCODE_KNOWN_TOOL_ALIASES,
|
||||
unionToolDetailSchemas,
|
||||
} from "../tool-call-mapper-utils.js";
|
||||
|
||||
type OpencodeToolCallParams = {
|
||||
@@ -19,8 +33,6 @@ type OpencodeToolCallParams = {
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const MAX_DIFF_TEXT_CHARS = 12_000;
|
||||
|
||||
const FAILED_STATUSES = new Set(["error", "failed", "failure"]);
|
||||
const CANCELED_STATUSES = new Set(["canceled", "cancelled", "aborted", "interrupted"]);
|
||||
const COMPLETED_STATUSES = new Set(["complete", "completed", "success", "succeeded", "done"]);
|
||||
@@ -37,435 +49,6 @@ const OpencodeToolCallParamsSchema = z
|
||||
})
|
||||
.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 {
|
||||
return coerceToolCallId({
|
||||
providerPrefix: "opencode",
|
||||
@@ -499,100 +82,13 @@ function resolveStatus(rawStatus: unknown, error: unknown, output: unknown): Too
|
||||
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,
|
||||
function opencodeToolBranch<InputSchema extends z.ZodTypeAny, OutputSchema extends z.ZodTypeAny>(
|
||||
toolName: string,
|
||||
inputSchema: InputSchema,
|
||||
outputSchema: OutputSchema,
|
||||
mapper: (
|
||||
input: z.infer<InputSchema> | null,
|
||||
output: z.infer<OutputSchema> | null
|
||||
input: z.output<InputSchema> | null | undefined,
|
||||
output: z.output<OutputSchema> | null | undefined
|
||||
) => ToolCallDetail | undefined
|
||||
) {
|
||||
return z
|
||||
@@ -604,20 +100,32 @@ function opencodeToolBranch<
|
||||
.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)),
|
||||
const OpencodeKnownToolDetailSchema: z.ZodType<ToolCallDetail | undefined> = unionToolDetailSchemas([
|
||||
...OPENCODE_KNOWN_TOOL_ALIASES.shell.map((toolName) =>
|
||||
opencodeToolBranch(toolName, StandardShellInputSchema, StandardShellOutputSchema, (input, output) =>
|
||||
toStandardShellDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...OPENCODE_KNOWN_TOOL_ALIASES.read.map((toolName) =>
|
||||
opencodeToolBranch(toolName, StandardReadPathInputSchema, StandardReadOutputSchema, (input, output) =>
|
||||
toStandardReadDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...OPENCODE_KNOWN_TOOL_ALIASES.write.map((toolName) =>
|
||||
opencodeToolBranch(toolName, StandardWriteInputSchema, StandardWriteOutputSchema, (input, output) =>
|
||||
toStandardWriteDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...OPENCODE_KNOWN_TOOL_ALIASES.edit.map((toolName) =>
|
||||
opencodeToolBranch(toolName, StandardEditInputSchema, StandardEditOutputSchema, (input, output) =>
|
||||
toStandardEditDetail(input ?? null, output ?? null)
|
||||
)
|
||||
),
|
||||
...OPENCODE_KNOWN_TOOL_ALIASES.search.map((toolName) =>
|
||||
opencodeToolBranch(toolName, StandardSearchInputSchema, z.unknown(), (input) =>
|
||||
toStandardSearchDetail(input ?? null)
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
function deriveDetail(toolName: string, input: unknown, output: unknown): ToolCallDetail | undefined {
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { ToolCallDetail } from "../agent-sdk-types.js";
|
||||
import {
|
||||
commandFromValue,
|
||||
flattenReadContent,
|
||||
nonEmptyString,
|
||||
truncateDiffText,
|
||||
} from "./tool-call-mapper-utils.js";
|
||||
|
||||
export type StandardShellInput = {
|
||||
command?: string;
|
||||
cwd?: string;
|
||||
};
|
||||
|
||||
export type StandardShellOutput = {
|
||||
command?: string;
|
||||
output?: string;
|
||||
exitCode?: number | null;
|
||||
};
|
||||
|
||||
export type StandardReadPathInput = {
|
||||
filePath: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
export type StandardReadOutput = {
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type StandardWriteInput = {
|
||||
filePath: string;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type StandardWriteOutput = {
|
||||
filePath?: string;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type StandardEditInput = {
|
||||
filePath: string;
|
||||
oldString?: string;
|
||||
newString?: string;
|
||||
unifiedDiff?: string;
|
||||
};
|
||||
|
||||
export type StandardEditOutput = {
|
||||
filePath?: string;
|
||||
newString?: string;
|
||||
unifiedDiff?: string;
|
||||
};
|
||||
|
||||
export type StandardSearchInput = {
|
||||
query: string;
|
||||
};
|
||||
|
||||
const CommandValueSchema = z.union([z.string(), z.array(z.string())]);
|
||||
|
||||
export const StandardShellInputSchema = 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 StandardShellOutputObjectSchema = 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 StandardShellOutputSchema = z.union([
|
||||
z.string().transform((value) => ({
|
||||
command: undefined,
|
||||
output: nonEmptyString(value),
|
||||
exitCode: undefined,
|
||||
})),
|
||||
StandardShellOutputObjectSchema.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,
|
||||
})),
|
||||
]);
|
||||
|
||||
const StandardPathSchema = 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 })),
|
||||
]);
|
||||
|
||||
export const StandardReadPathInputSchema = 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,
|
||||
})),
|
||||
]);
|
||||
|
||||
export const StandardReadChunkSchema = 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 StandardReadContentSchema = z.union([
|
||||
z.string(),
|
||||
StandardReadChunkSchema,
|
||||
z.array(StandardReadChunkSchema),
|
||||
]);
|
||||
|
||||
const StandardReadPayloadSchema = z.union([
|
||||
z
|
||||
.object({
|
||||
content: StandardReadContentSchema,
|
||||
text: StandardReadContentSchema.optional(),
|
||||
output: StandardReadContentSchema.optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
z
|
||||
.object({
|
||||
content: StandardReadContentSchema.optional(),
|
||||
text: StandardReadContentSchema,
|
||||
output: StandardReadContentSchema.optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
z
|
||||
.object({
|
||||
content: StandardReadContentSchema.optional(),
|
||||
text: StandardReadContentSchema.optional(),
|
||||
output: StandardReadContentSchema,
|
||||
})
|
||||
.passthrough(),
|
||||
]);
|
||||
|
||||
export const StandardReadOutputSchema: z.ZodType<StandardReadOutput, z.ZodTypeDef, unknown> = z.union([
|
||||
z.string().transform((value) => ({ content: nonEmptyString(value) })),
|
||||
StandardReadChunkSchema.transform((value) => ({ content: flattenReadContent(value) })),
|
||||
z.array(StandardReadChunkSchema).transform((value) => ({ content: flattenReadContent(value) })),
|
||||
StandardReadPayloadSchema.transform((value) => ({
|
||||
content:
|
||||
flattenReadContent(value.content) ??
|
||||
flattenReadContent(value.text) ??
|
||||
flattenReadContent(value.output),
|
||||
})),
|
||||
z
|
||||
.object({ data: StandardReadPayloadSchema })
|
||||
.passthrough()
|
||||
.transform((value) => ({
|
||||
content:
|
||||
flattenReadContent(value.data.content) ??
|
||||
flattenReadContent(value.data.text) ??
|
||||
flattenReadContent(value.data.output),
|
||||
})),
|
||||
z
|
||||
.object({ structuredContent: StandardReadPayloadSchema })
|
||||
.passthrough()
|
||||
.transform((value) => ({
|
||||
content:
|
||||
flattenReadContent(value.structuredContent.content) ??
|
||||
flattenReadContent(value.structuredContent.text) ??
|
||||
flattenReadContent(value.structuredContent.output),
|
||||
})),
|
||||
z
|
||||
.object({ structured_content: StandardReadPayloadSchema })
|
||||
.passthrough()
|
||||
.transform((value) => ({
|
||||
content:
|
||||
flattenReadContent(value.structured_content.content) ??
|
||||
flattenReadContent(value.structured_content.text) ??
|
||||
flattenReadContent(value.structured_content.output),
|
||||
})),
|
||||
]);
|
||||
|
||||
const StandardWriteContentSchema = z
|
||||
.object({
|
||||
content: z.string().optional(),
|
||||
new_content: z.string().optional(),
|
||||
newContent: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const StandardWriteInputSchema = z
|
||||
.intersection(StandardPathSchema, StandardWriteContentSchema)
|
||||
.transform((value) => ({
|
||||
filePath: value.filePath,
|
||||
content:
|
||||
nonEmptyString(value.content) ??
|
||||
nonEmptyString(value.new_content) ??
|
||||
nonEmptyString(value.newContent),
|
||||
}));
|
||||
|
||||
export const StandardWriteOutputSchema = z.union([
|
||||
z
|
||||
.intersection(StandardPathSchema, StandardWriteContentSchema)
|
||||
.transform((value) => ({
|
||||
filePath: value.filePath,
|
||||
content:
|
||||
nonEmptyString(value.content) ??
|
||||
nonEmptyString(value.new_content) ??
|
||||
nonEmptyString(value.newContent),
|
||||
})),
|
||||
StandardWriteContentSchema.transform((value) => ({
|
||||
filePath: undefined,
|
||||
content:
|
||||
nonEmptyString(value.content) ??
|
||||
nonEmptyString(value.new_content) ??
|
||||
nonEmptyString(value.newContent),
|
||||
})),
|
||||
]);
|
||||
|
||||
const StandardEditTextSchema = 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 StandardEditInputSchema = z
|
||||
.intersection(StandardPathSchema, StandardEditTextSchema)
|
||||
.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 StandardEditOutputFileSchema = 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 StandardEditOutputSchema = z.union([
|
||||
z
|
||||
.intersection(StandardPathSchema, StandardEditTextSchema)
|
||||
.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(StandardEditOutputFileSchema).min(1) })
|
||||
.passthrough()
|
||||
.transform((value) => ({
|
||||
filePath: value.files[0]?.filePath,
|
||||
unifiedDiff: value.files[0]?.unifiedDiff,
|
||||
newString: undefined,
|
||||
})),
|
||||
StandardEditTextSchema.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 StandardSearchInputSchema = 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 function toStandardShellDetail(
|
||||
input: StandardShellInput | null,
|
||||
output: StandardShellOutput | 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 toStandardReadDetail(
|
||||
input: StandardReadPathInput | null,
|
||||
output: StandardReadOutput | null,
|
||||
normalizePath?: (filePath: string) => string | undefined
|
||||
): ToolCallDetail | undefined {
|
||||
const path = input?.filePath;
|
||||
if (!path) {
|
||||
return undefined;
|
||||
}
|
||||
const filePath = normalizePath ? normalizePath(path) : path;
|
||||
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 toStandardWriteDetail(
|
||||
input: StandardWriteInput | null,
|
||||
output: StandardWriteOutput | null,
|
||||
normalizePath?: (filePath: string) => string | undefined
|
||||
): ToolCallDetail | undefined {
|
||||
const rawPath = input?.filePath ?? output?.filePath;
|
||||
if (!rawPath) {
|
||||
return undefined;
|
||||
}
|
||||
const filePath = normalizePath ? normalizePath(rawPath) : rawPath;
|
||||
if (!filePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "write",
|
||||
filePath,
|
||||
...(input?.content ? { content: input.content } : output?.content ? { content: output.content } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function toStandardEditDetail(
|
||||
input: StandardEditInput | null,
|
||||
output: StandardEditOutput | null,
|
||||
normalizePath?: (filePath: string) => string | undefined
|
||||
): ToolCallDetail | undefined {
|
||||
const rawPath = input?.filePath ?? output?.filePath;
|
||||
if (!rawPath) {
|
||||
return undefined;
|
||||
}
|
||||
const filePath = normalizePath ? normalizePath(rawPath) : rawPath;
|
||||
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 toStandardSearchDetail(input: StandardSearchInput | null): ToolCallDetail | undefined {
|
||||
if (!input?.query) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
type: "search",
|
||||
query: input.query,
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,54 @@
|
||||
import { z } from "zod";
|
||||
|
||||
type ReadChunkLike = {
|
||||
text?: string;
|
||||
content?: string;
|
||||
output?: string;
|
||||
};
|
||||
|
||||
type ToolAliasKind = "shell" | "read" | "write" | "edit" | "search";
|
||||
export type KnownToolAliases = Record<ToolAliasKind, readonly string[]>;
|
||||
|
||||
export const CLAUDE_KNOWN_TOOL_ALIASES: KnownToolAliases = {
|
||||
shell: ["Bash", "bash", "shell", "exec_command"],
|
||||
read: ["Read", "read", "read_file", "view_file"],
|
||||
write: ["Write", "write", "write_file", "create_file"],
|
||||
edit: [
|
||||
"Edit",
|
||||
"MultiEdit",
|
||||
"multi_edit",
|
||||
"edit",
|
||||
"apply_patch",
|
||||
"apply_diff",
|
||||
"str_replace_editor",
|
||||
],
|
||||
search: ["WebSearch", "web_search", "search"],
|
||||
};
|
||||
|
||||
export const OPENCODE_KNOWN_TOOL_ALIASES: KnownToolAliases = {
|
||||
shell: ["shell", "bash", "exec_command"],
|
||||
read: ["read", "read_file"],
|
||||
write: ["write", "write_file", "create_file"],
|
||||
edit: ["edit", "apply_patch", "apply_diff"],
|
||||
search: ["search", "web_search"],
|
||||
};
|
||||
|
||||
export const CODEX_MCP_KNOWN_TOOL_ALIASES: KnownToolAliases = {
|
||||
shell: ["shell", "bash", "exec", "exec_command", "command"],
|
||||
read: ["read", "read_file"],
|
||||
write: ["write", "write_file", "create_file"],
|
||||
edit: ["edit", "apply_patch", "apply_diff"],
|
||||
search: ["search", "web_search"],
|
||||
};
|
||||
|
||||
export const CODEX_ROLLOUT_KNOWN_TOOL_ALIASES: KnownToolAliases = {
|
||||
shell: ["Bash", "shell", "bash", "exec_command"],
|
||||
read: ["read", "read_file"],
|
||||
write: ["write", "write_file", "create_file"],
|
||||
edit: ["edit", "apply_patch", "apply_diff"],
|
||||
search: ["search", "web_search"],
|
||||
};
|
||||
|
||||
export function nonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined;
|
||||
}
|
||||
@@ -91,3 +136,14 @@ export function coerceToolCallId(params: {
|
||||
|
||||
return `${params.providerPrefix}-${hashText(`${params.toolName}:${serialized}`)}`;
|
||||
}
|
||||
|
||||
export function unionToolDetailSchemas(schemas: z.ZodTypeAny[]): z.ZodTypeAny {
|
||||
if (schemas.length === 0) {
|
||||
throw new Error("Expected at least one schema when building tool detail union");
|
||||
}
|
||||
let union = schemas[0];
|
||||
for (let i = 1; i < schemas.length; i += 1) {
|
||||
union = union.or(schemas[i]);
|
||||
}
|
||||
return union;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { z } from "zod";
|
||||
import type { ManagedAgent } from "./agent/agent-manager.js";
|
||||
import { toAgentPayload } from "./agent/agent-projections.js";
|
||||
import type { AgentStreamEvent } from "./agent/agent-sdk-types.js";
|
||||
@@ -9,10 +8,9 @@ import type {
|
||||
import { AgentStreamEventPayloadSchema as AgentStreamEventPayloadRuntimeSchema } from "../shared/messages.js";
|
||||
|
||||
export * from "../shared/messages.js";
|
||||
type AgentStreamEventPayloadInput = z.input<typeof AgentStreamEventPayloadRuntimeSchema>;
|
||||
|
||||
function validateStreamEventPayload(
|
||||
payload: AgentStreamEventPayloadInput
|
||||
payload: unknown
|
||||
): AgentStreamEventPayload | null {
|
||||
const parsed = AgentStreamEventPayloadRuntimeSchema.safeParse(payload);
|
||||
if (!parsed.success) {
|
||||
@@ -44,5 +42,5 @@ export function serializeAgentStreamEvent(
|
||||
});
|
||||
}
|
||||
|
||||
return validateStreamEventPayload(event as AgentStreamEventPayloadInput);
|
||||
return validateStreamEventPayload(event);
|
||||
}
|
||||
|
||||
17
packages/server/src/shared/path-utils.ts
Normal file
17
packages/server/src/shared/path-utils.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export function stripCwdPrefix(filePath: string, cwd?: string): string {
|
||||
if (!cwd || !filePath) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
||||
const normalizedPath = filePath.replace(/\\/g, "/");
|
||||
const prefix = `${normalizedCwd}/`;
|
||||
|
||||
if (normalizedPath.startsWith(prefix)) {
|
||||
return normalizedPath.slice(prefix.length);
|
||||
}
|
||||
if (normalizedPath === normalizedCwd) {
|
||||
return ".";
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ToolCallTimelineItem } from "../server/agent/agent-sdk-types.js";
|
||||
import { stripCwdPrefix } from "./path-utils.js";
|
||||
|
||||
export type ToolCallDisplayInput = Pick<
|
||||
ToolCallTimelineItem,
|
||||
@@ -38,24 +39,6 @@ function humanizeToolName(name: string): string {
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function stripCwdPrefix(filePath: string, cwd?: string): string {
|
||||
if (!cwd || !filePath) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
||||
const normalizedPath = filePath.replace(/\\/g, "/");
|
||||
const prefix = `${normalizedCwd}/`;
|
||||
|
||||
if (normalizedPath.startsWith(prefix)) {
|
||||
return normalizedPath.slice(prefix.length);
|
||||
}
|
||||
if (normalizedPath === normalizedCwd) {
|
||||
return ".";
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function formatErrorText(error: unknown): string | undefined {
|
||||
if (error === null || error === undefined) {
|
||||
return undefined;
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
import { z } from "zod";
|
||||
import { stripCwdPrefix } from "../shared/path-utils.js";
|
||||
|
||||
const SHELL_WRAPPER_PREFIX_PATTERN =
|
||||
/^\/bin\/(?:zsh|bash|sh)\s+(?:-[a-zA-Z]+\s+)?/;
|
||||
const CD_AND_PATTERN = /^cd\s+(?:"[^"]+"|'[^']+'|\S+)\s+&&\s+/;
|
||||
|
||||
export function stripCwdPrefix(filePath: string, cwd?: string): string {
|
||||
if (!cwd || !filePath) return filePath;
|
||||
|
||||
const normalizedCwd = cwd.replace(/\\/g, "/").replace(/\/+$/, "");
|
||||
const normalizedPath = filePath.replace(/\\/g, "/");
|
||||
|
||||
const prefix = `${normalizedCwd}/`;
|
||||
if (normalizedPath.startsWith(prefix)) {
|
||||
return normalizedPath.slice(prefix.length);
|
||||
}
|
||||
if (normalizedPath === normalizedCwd) {
|
||||
return ".";
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
export { stripCwdPrefix };
|
||||
|
||||
export function stripShellWrapperPrefix(command: string): string {
|
||||
const prefixMatch = command.match(SHELL_WRAPPER_PREFIX_PATTERN);
|
||||
|
||||
Reference in New Issue
Block a user