source dump of claude code
at main 163 lines 5.5 kB view raw
1import { Ajv } from 'ajv' 2import { z } from 'zod/v4' 3import type { Tool, ToolInputJSONSchema } from '../../Tool.js' 4import { buildTool, type ToolDef } from '../../Tool.js' 5import { TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from '../../utils/errors.js' 6import { lazySchema } from '../../utils/lazySchema.js' 7import type { PermissionResult } from '../../utils/permissions/PermissionResult.js' 8import { jsonStringify } from '../../utils/slowOperations.js' 9 10// Allow any input object since the schema is provided dynamically 11const inputSchema = lazySchema(() => z.object({}).passthrough()) 12type InputSchema = ReturnType<typeof inputSchema> 13 14const outputSchema = lazySchema(() => 15 z.string().describe('Structured output tool result'), 16) 17type OutputSchema = ReturnType<typeof outputSchema> 18export type Output = z.infer<OutputSchema> 19 20export const SYNTHETIC_OUTPUT_TOOL_NAME = 'StructuredOutput' 21 22export function isSyntheticOutputToolEnabled(opts: { 23 isNonInteractiveSession: boolean 24}): boolean { 25 return opts.isNonInteractiveSession 26} 27 28export const SyntheticOutputTool = buildTool({ 29 isMcp: false, 30 isEnabled() { 31 // This tool is only created when conditions are met (see main.tsx where 32 // isSyntheticOutputToolEnabled() gates tool creation). Once created, always enabled. 33 return true 34 }, 35 isConcurrencySafe() { 36 return true 37 }, 38 isReadOnly() { 39 return true 40 }, 41 isOpenWorld() { 42 return false 43 }, 44 name: SYNTHETIC_OUTPUT_TOOL_NAME, 45 searchHint: 'return the final response as structured JSON', 46 maxResultSizeChars: 100_000, 47 async description(): Promise<string> { 48 return 'Return structured output in the requested format' 49 }, 50 async prompt(): Promise<string> { 51 return `Use this tool to return your final response in the requested structured format. You MUST call this tool exactly once at the end of your response to provide the structured output.` 52 }, 53 get inputSchema(): InputSchema { 54 return inputSchema() 55 }, 56 get outputSchema(): OutputSchema { 57 return outputSchema() 58 }, 59 async call(input) { 60 // The tool just validates and returns the input as the structured output 61 return { 62 data: 'Structured output provided successfully', 63 structured_output: input, 64 } 65 }, 66 async checkPermissions(input): Promise<PermissionResult> { 67 // Always allow this tool - it's just returning data 68 return { 69 behavior: 'allow', 70 updatedInput: input, 71 } 72 }, 73 // Minimal UI implementations - this tool is for non-interactive SDK/CLI use 74 renderToolUseMessage(input: Record<string, unknown>) { 75 const keys = Object.keys(input) 76 if (keys.length === 0) return null 77 if (keys.length <= 3) { 78 return keys.map(k => `${k}: ${jsonStringify(input[k])}`).join(', ') 79 } 80 return `${keys.length} fields: ${keys.slice(0, 3).join(', ')}` 81 }, 82 renderToolUseRejectedMessage() { 83 return 'Structured output rejected' 84 }, 85 renderToolUseErrorMessage() { 86 return 'Structured output error' 87 }, 88 renderToolUseProgressMessage() { 89 return null 90 }, 91 renderToolResultMessage(output: string) { 92 return output 93 }, 94 mapToolResultToToolResultBlockParam(content: string, toolUseID: string) { 95 return { 96 tool_use_id: toolUseID, 97 type: 'tool_result' as const, 98 content, 99 } 100 }, 101} satisfies ToolDef<InputSchema, Output>) 102 103type CreateResult = { tool: Tool<InputSchema> } | { error: string } 104 105// Workflow scripts call agent({schema: BUGS_SCHEMA}) 30-80 times per run with 106// the same schema object reference. Without caching, each call does 107// new Ajv() + validateSchema() + compile() (~1.4ms of JIT codegen). Identity 108// cache brings 80-call workflows from ~110ms to ~4ms Ajv overhead. 109const toolCache = new WeakMap<object, CreateResult>() 110 111/** 112 * Create a SyntheticOutputTool configured with the given JSON schema. 113 * Returns {tool} on success or {error} with Ajv's diagnostic message 114 * (e.g. "data/properties/bugs should be object") on invalid schema. 115 */ 116export function createSyntheticOutputTool( 117 jsonSchema: Record<string, unknown>, 118): CreateResult { 119 const cached = toolCache.get(jsonSchema) 120 if (cached) return cached 121 122 const result = buildSyntheticOutputTool(jsonSchema) 123 toolCache.set(jsonSchema, result) 124 return result 125} 126 127function buildSyntheticOutputTool( 128 jsonSchema: Record<string, unknown>, 129): CreateResult { 130 try { 131 const ajv = new Ajv({ allErrors: true }) 132 const isValidSchema = ajv.validateSchema(jsonSchema) 133 if (!isValidSchema) { 134 return { error: ajv.errorsText(ajv.errors) } 135 } 136 const validateSchema = ajv.compile(jsonSchema) 137 138 return { 139 tool: { 140 ...SyntheticOutputTool, 141 inputJSONSchema: jsonSchema as ToolInputJSONSchema, 142 async call(input) { 143 const isValid = validateSchema(input) 144 if (!isValid) { 145 const errors = validateSchema.errors 146 ?.map(e => `${e.instancePath || 'root'}: ${e.message}`) 147 .join(', ') 148 throw new TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS( 149 `Output does not match required schema: ${errors}`, 150 `StructuredOutput schema mismatch: ${(errors ?? '').slice(0, 150)}`, 151 ) 152 } 153 return { 154 data: 'Structured output provided successfully', 155 structured_output: input, 156 } 157 }, 158 }, 159 } 160 } catch (e) { 161 return { error: e instanceof Error ? e.message : String(e) } 162 } 163}