Claude: Streamed tool calls parser
| @@ -136,6 +136,8 @@ export class ToolManager { | |||
| 136 | */ | 136 | */ |
| 137 | static #tools = new Map(); | 137 | static #tools = new Map(); |
| 138 | 138 | ||
| 139 | static #INPUT_DELTA_KEY = '__input_json_delta'; | ||
| 140 | |||
| 139 | /** | 141 | /** |
| 140 | * Returns an Array of all tools that have been registered. | 142 | * Returns an Array of all tools that have been registered. |
| 141 | * @type {ToolDefinition[]} | 143 | * @type {ToolDefinition[]} |
| @@ -304,20 +306,48 @@ export class ToolManager { | |||
| 304 | } | 306 | } |
| 305 | if (typeof parsed?.content_block === 'object') { | 307 | if (typeof parsed?.content_block === 'object') { |
| 306 | const choiceIndex = 0; | 308 | const choiceIndex = 0; |
| 309 | const toolCallIndex = parsed?.index ?? 0; | ||
| 307 | 310 | ||
| 308 | if (parsed?.content_block?.type === 'tool_use') { | 311 | if (parsed?.content_block?.type === 'tool_use') { |
| 309 | if (!Array.isArray(toolCalls[choiceIndex])) { | 312 | if (!Array.isArray(toolCalls[choiceIndex])) { |
| 310 | toolCalls[choiceIndex] = []; | 313 | toolCalls[choiceIndex] = []; |
| 311 | } | 314 | } |
| 312 | |||
| 313 | const toolCallIndex = toolCalls[choiceIndex].length; | ||
| 314 | |||
| 315 | if (toolCalls[choiceIndex][toolCallIndex] === undefined) { | 315 | if (toolCalls[choiceIndex][toolCallIndex] === undefined) { |
| 316 | toolCalls[choiceIndex][toolCallIndex] = {}; | 316 | toolCalls[choiceIndex][toolCallIndex] = {}; |
| 317 | } | 317 | } |
| 318 | |||
| 319 | const targetToolCall = toolCalls[choiceIndex][toolCallIndex]; | 318 | const targetToolCall = toolCalls[choiceIndex][toolCallIndex]; |
| 320 | Object.assign(targetToolCall, parsed.content_block); | 319 | ToolManager.#applyToolCallDelta(targetToolCall, parsed.content_block); |
| 320 | } | ||
| 321 | } | ||
| 322 | if (typeof parsed?.delta === 'object') { | ||
| 323 | const choiceIndex = 0; | ||
| 324 | const toolCallIndex = parsed?.index ?? 0; | ||
| 325 | const targetToolCall = toolCalls[choiceIndex]?.[toolCallIndex]; | ||
| 326 | if (targetToolCall){ | ||
| 327 | if (parsed?.delta?.type === 'input_json_delta') { | ||
| 328 | const jsonDelta = parsed?.delta?.partial_json; | ||
| 329 | if (!targetToolCall[this.#INPUT_DELTA_KEY]) { | ||
| 330 | targetToolCall[this.#INPUT_DELTA_KEY] = ''; | ||
| 331 | } | ||
| 332 | targetToolCall[this.#INPUT_DELTA_KEY] += jsonDelta; | ||
| 333 | } | ||
| 334 | } | ||
| 335 | } | ||
| 336 | if (parsed?.type === 'content_block_stop') { | ||
| 337 | const choiceIndex = 0; | ||
| 338 | const toolCallIndex = parsed?.index ?? 0; | ||
| 339 | const targetToolCall = toolCalls[choiceIndex]?.[toolCallIndex]; | ||
| 340 | if (targetToolCall) { | ||
| 341 | const jsonDeltaString = targetToolCall[this.#INPUT_DELTA_KEY]; | ||
| 342 | if (jsonDeltaString) { | ||
| 343 | try { | ||
| 344 | const jsonDelta = { input: JSON.parse(jsonDeltaString) }; | ||
| 345 | delete targetToolCall[this.#INPUT_DELTA_KEY]; | ||
| 346 | ToolManager.#applyToolCallDelta(targetToolCall, jsonDelta); | ||
| 347 | } catch (error) { | ||
| 348 | console.warn('Failed to apply input JSON delta:', error); | ||
| 349 | } | ||
| 350 | } | ||
| 321 | } | 351 | } |
| 322 | } | 352 | } |
| 323 | } | 353 | } |
| @@ -397,9 +427,12 @@ export class ToolManager { | |||
| 397 | * @returns {any[]} Tool calls from the response data | 427 | * @returns {any[]} Tool calls from the response data |
| 398 | */ | 428 | */ |
| 399 | static #getToolCallsFromData(data) { | 429 | static #getToolCallsFromData(data) { |
| 430 | const isClaudeToolCall = c => Array.isArray(c) ? c.filter(x => x).every(isClaudeToolCall) : c?.input && c?.name && c?.id; | ||
| 431 | const convertClaudeToolCall = c => ({ id: c.id, function: { name: c.name, arguments: c.input } }); | ||
| 432 | |||
| 400 | // Parsed tool calls from streaming data | 433 | // Parsed tool calls from streaming data |
| 401 | if (Array.isArray(data) && data.length > 0) { | 434 | if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) { |
| 402 | return data[0]; | 435 | return isClaudeToolCall(data[0]) ? data[0].filter(x => x).map(convertClaudeToolCall) : data[0]; |
| 403 | } | 436 | } |
| 404 | 437 | ||
| 405 | // Parsed tool calls from non-streaming data | 438 | // Parsed tool calls from non-streaming data |
| @@ -412,21 +445,15 @@ export class ToolManager { | |||
| 412 | } | 445 | } |
| 413 | } | 446 | } |
| 414 | 447 | ||
| 415 | if (Array.isArray(data?.content)) { | ||
| 416 | // Claude tool calls to OpenAI tool calls | 448 | // Claude tool calls to OpenAI tool calls |
| 417 | const content = data.content.filter(c => c.type === 'tool_use').map(c => { | 449 | if (Array.isArray(data?.content)) { |
| 418 | return { | 450 | const content = data.content.filter(c => c.type === 'tool_use').map(convertClaudeToolCall); |
| 419 | id: c.id, | ||
| 420 | function: { | ||
| 421 | name: c.name, | ||
| 422 | arguments: c.input, | ||
| 423 | }, | ||
| 424 | }; | ||
| 425 | }); | ||
| 426 | 451 | ||
| 452 | if (content) { | ||
| 427 | return content; | 453 | return content; |
| 428 | } | 454 | } |
| 429 | } | 455 | } |
| 456 | } | ||
| 430 | 457 | ||
| 431 | /** | 458 | /** |
| 432 | * Check for function tool calls in the response data and invoke them. | 459 | * Check for function tool calls in the response data and invoke them. |