diff --git a/packages/core/tools/src/ptc.ts b/packages/core/tools/src/ptc.ts index 1bdf15c514..b3d85428c1 100644 --- a/packages/core/tools/src/ptc.ts +++ b/packages/core/tools/src/ptc.ts @@ -47,23 +47,26 @@ interface RunCodeFlavor { } /** Keep tool-call rules here; the runtime owns evaluation syntax and state retention. */ -function runCodeFlavor(language: string): RunCodeFlavor { +function runCodeFlavor(language: string, execution = { + programDescription: 'the program to execute', + outputInstruction: 'Curate the program output.', + codeDescription: `The ${language} program to execute, following this tool's execution instructions.`, +}): RunCodeFlavor { return { description: `Execute a ${language} program against the available tools. Takes two required arguments: ` - + '`code`, the program to execute, and `description`, a short summary of what the program does. ' + + `\`code\`, ${execution.programDescription}, and \`description\`, a short summary of what the program does. ` + 'Call tools as `await tools.name(args)` per the declarations in the system prompt. ' - + 'Curate the program output. Image-bearing subtool results are attached after the run.', - codeDescription: `The ${language} program to execute, following this tool's execution instructions.`, + + `${execution.outputInstruction} Image-bearing subtool results are attached after the run.`, + codeDescription: execution.codeDescription, } } const TYPESCRIPT_FLAVOR = runCodeFlavor('TypeScript') -const TYPESCRIPT_FUNCTION_FLAVOR: RunCodeFlavor = { - description: TYPESCRIPT_FLAVOR.description - .replace('`code`, the program to execute', '`code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work)') - .replace('Curate the program output.', 'Only what you print or return is program output — curate it.'), +const TYPESCRIPT_FUNCTION_FLAVOR = runCodeFlavor('TypeScript', { + programDescription: 'the BODY of an async function (erasable syntax only; top-level `await` and `return` work)', + outputInstruction: 'Only what you print or return is program output — curate it.', codeDescription: 'The program: the body of an async TypeScript function.', -} +}) const PYTHON_FLAVOR = runCodeFlavor('Python') /** diff --git a/packages/core/tools/src/ts-types.ts b/packages/core/tools/src/ts-types.ts index 3113b169c7..1c1444e0b1 100644 --- a/packages/core/tools/src/ts-types.ts +++ b/packages/core/tools/src/ts-types.ts @@ -246,19 +246,30 @@ export function jsonSchemaToTs(schema: unknown, indent = 0): string { } } -/** The fixed model-facing usage contract rendered above the declarations (see the PTC mode Agent Note's "What the model sees"). */ -const SDK_INSTRUCTIONS = `## Writing code for run_code +/** Render the argument and call instructions for the selected execution syntax. */ +function renderSdkInstructions(programFormat: 'function-body' | 'repl'): string { + const programDescription = programFormat === 'function-body' + ? 'the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)' + : 'the program to execute' + return `## Writing code for run_code -\`run_code\` takes two required arguments: \`code\`, the program to execute, and \`description\`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.` +\`run_code\` takes two required arguments: \`code\`, ${programDescription}, and \`description\`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.` +} -const SDK_PROGRAM_INSTRUCTIONS = `Inside the program: +/** Render tool execution and output instructions for the selected execution syntax. */ +function renderSdkProgramInstructions(programFormat: 'function-body' | 'repl'): string { + const outputInstruction = programFormat === 'function-body' + ? 'Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output.' + : 'Only values emitted as program output enter the conversation.' + return `Inside the program: - Call tools as \`await tools.name(args)\` — quoted access for exotic names: \`tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose \`message\` is human-readable — \`try/catch\` it to handle and continue. - Independent read-only calls MAY overlap under \`Promise.all\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`. -- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need. +- ${outputInstruction} A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need. Program-only SDK bindings:` +} /** Whether one string schema accepts the literal used by the bash example. */ function acceptsExampleString(schema: JsonSchemaNode | undefined, value: string): boolean { @@ -317,11 +328,7 @@ export function renderToolsSdk(schemas: ToolSdkSchema[], programFormat: 'functio ['declare const tools: {', ' [K in ToolName]: (args: ToolArgsMap[K]) => Promise;', '}'].join('\n'), ].join('\n\n') const jsonValue = 'type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }' - const instructions = programFormat === 'function-body' - ? SDK_INSTRUCTIONS.replace('the program to execute', 'the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)') - : SDK_INSTRUCTIONS - const programInstructions = programFormat === 'function-body' - ? SDK_PROGRAM_INSTRUCTIONS.replace('Only values emitted as program output enter the conversation.', 'Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output.') - : SDK_PROGRAM_INSTRUCTIONS + const instructions = renderSdkInstructions(programFormat) + const programInstructions = renderSdkProgramInstructions(programFormat) return `${instructions}${renderBashExample(sorted, programFormat)}\n\n${programInstructions}\n\n\`\`\`ts\n${jsonValue}\n\n${declaration}\n\`\`\`` }