Claude: Streamed tool calls parser

db04fff3df342e7c035c7dccccb88df5dd2fc75c

Cohee <18619528+Cohee1207@users.noreply.github.com>

1 files changed, +44 -17Showing whitespace changes
public/scripts/tool-calling.js+44 -17
@@ -136,6 +136,8 @@ export class ToolManager {
136 */136 */
137 static #tools = new Map();137 static #tools = new Map();
138138
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;
307310
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 data427 * @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 data433 // 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 }
404437
405 // Parsed tool calls from non-streaming data438 // Parsed tool calls from non-streaming data
@@ -412,21 +445,15 @@ export class ToolManager {
412 }445 }
413 }446 }
414447
415 if (Array.isArray(data?.content)) {
416 // Claude tool calls to OpenAI tool calls448 // 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 });
426451
452 if (content) {
427 return content;453 return content;
428 }454 }
429 }455 }
456 }
430457
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.