Spaces:
Running
Running
| export interface ParsedToolCall { | |
| name: string; | |
| arguments: Record<string, unknown>; | |
| raw: string; | |
| } | |
| export interface ToolCallParseResult { | |
| calls: ParsedToolCall[]; | |
| content: string; | |
| incomplete: boolean; | |
| errors: string[]; | |
| } | |
| const OPEN = '<tool_call>'; | |
| const CLOSE = '</tool_call>'; | |
| const FUNCTION_CLOSE = '</function>'; | |
| const PARAMETER_CLOSE = '</parameter>'; | |
| const MAX_TOOL_PAYLOAD = 64 * 1024; | |
| const NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_.:-]*$/; | |
| function isRecord(value: unknown): value is Record<string, unknown> { | |
| return typeof value === 'object' && value !== null && !Array.isArray(value); | |
| } | |
| function parseJsonEnvelope(raw: string): ParsedToolCall { | |
| const envelope: unknown = JSON.parse(raw); | |
| if (!isRecord(envelope) || typeof envelope.name !== 'string' || !envelope.name.trim()) { | |
| throw new Error('tool call must contain a non-empty name'); | |
| } | |
| let argumentsValue = envelope.arguments; | |
| if (typeof argumentsValue === 'string') argumentsValue = JSON.parse(argumentsValue); | |
| if (!isRecord(argumentsValue)) throw new Error('tool call arguments must be an object'); | |
| return { name: envelope.name.trim(), arguments: argumentsValue, raw }; | |
| } | |
| function parseFunctionEnvelope(raw: string): ParsedToolCall { | |
| const functionOpeningEnd = raw.indexOf('>'); | |
| if (!raw.startsWith('<function=') || functionOpeningEnd < 0) { | |
| throw new Error('tool call must contain a JSON or function envelope'); | |
| } | |
| const name = raw.slice('<function='.length, functionOpeningEnd).trim(); | |
| if (!NAME_PATTERN.test(name)) throw new Error('tool call must contain a valid function name'); | |
| const functionClosing = raw.lastIndexOf(FUNCTION_CLOSE); | |
| if (functionClosing < functionOpeningEnd || raw.slice(functionClosing + FUNCTION_CLOSE.length).trim()) { | |
| throw new Error('function tool call is missing its closing tag'); | |
| } | |
| const body = raw.slice(functionOpeningEnd + 1, functionClosing); | |
| const argumentsValue: Record<string, unknown> = {}; | |
| let cursor = 0; | |
| while (cursor < body.length) { | |
| while (/\s/u.test(body[cursor] ?? '')) cursor += 1; | |
| if (cursor >= body.length) break; | |
| if (!body.startsWith('<parameter=', cursor)) { | |
| throw new Error('function tool call contains text outside a parameter'); | |
| } | |
| const parameterOpeningEnd = body.indexOf('>', cursor); | |
| if (parameterOpeningEnd < 0) throw new Error('function tool call has an incomplete parameter tag'); | |
| const parameterName = body.slice(cursor + '<parameter='.length, parameterOpeningEnd).trim(); | |
| if (!NAME_PATTERN.test(parameterName)) throw new Error('function tool call contains an invalid parameter name'); | |
| if (Object.hasOwn(argumentsValue, parameterName)) throw new Error(`function tool call repeats parameter ${parameterName}`); | |
| const valueStart = parameterOpeningEnd + 1; | |
| const valueEnd = body.indexOf(PARAMETER_CLOSE, valueStart); | |
| if (valueEnd < 0) throw new Error(`function tool call is missing the closing tag for parameter ${parameterName}`); | |
| argumentsValue[parameterName] = body.slice(valueStart, valueEnd).trim(); | |
| cursor = valueEnd + PARAMETER_CLOSE.length; | |
| } | |
| return { name, arguments: argumentsValue, raw }; | |
| } | |
| function parseEnvelope(raw: string): ParsedToolCall { | |
| if (raw.length > MAX_TOOL_PAYLOAD) throw new Error('tool call exceeds 64 KiB'); | |
| return raw.startsWith('<function=') ? parseFunctionEnvelope(raw) : parseJsonEnvelope(raw); | |
| } | |
| export function parseToolCalls(value: string): ToolCallParseResult { | |
| const calls: ParsedToolCall[] = []; | |
| const errors: string[] = []; | |
| const content: string[] = []; | |
| let cursor = 0; | |
| let incomplete = false; | |
| while (cursor < value.length) { | |
| const opening = value.indexOf(OPEN, cursor); | |
| if (opening < 0) { | |
| content.push(value.slice(cursor)); | |
| break; | |
| } | |
| content.push(value.slice(cursor, opening)); | |
| const payloadStart = opening + OPEN.length; | |
| const closing = value.indexOf(CLOSE, payloadStart); | |
| if (closing < 0) { | |
| incomplete = true; | |
| break; | |
| } | |
| const raw = value.slice(payloadStart, closing).trim(); | |
| try { | |
| calls.push(parseEnvelope(raw)); | |
| } catch (error) { | |
| errors.push(error instanceof Error ? error.message : String(error)); | |
| } | |
| cursor = closing + CLOSE.length; | |
| } | |
| return { | |
| calls, | |
| content: content.join('').trim(), | |
| incomplete, | |
| errors, | |
| }; | |
| } | |
| export function renderToolResponse(value: unknown): string { | |
| const serialized = typeof value === 'string' ? value : JSON.stringify(value); | |
| return `<tool_response>\n${serialized.slice(0, MAX_TOOL_PAYLOAD)}\n</tool_response>`; | |
| } | |