Claude: Streamed tool calls parser

db04fff3df342e7c035c7dccccb88df5dd2fc75c

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

1 files changed, +46 -19Ignore whitespace
public/scripts/tool-calling.js+46 -19
@@ -136,6 +136,8 @@ export class ToolManager {
136136 */
137137 static #tools = new Map();
138138
139+ static #INPUT_DELTA_KEY = '__input_json_delta';
140+
139141 /**
140142 * Returns an Array of all tools that have been registered.
141143 * @type {ToolDefinition[]}
@@ -304,20 +306,48 @@ export class ToolManager {
304306 }
305307 if (typeof parsed?.content_block === 'object') {
306308 const choiceIndex = 0;
309+ const toolCallIndex = parsed?.index ?? 0;
307310
308311 if (parsed?.content_block?.type === 'tool_use') {
309312 if (!Array.isArray(toolCalls[choiceIndex])) {
310313 toolCalls[choiceIndex] = [];
311314 }
312-
313- const toolCallIndex = toolCalls[choiceIndex].length;
314-
315315 if (toolCalls[choiceIndex][toolCallIndex] === undefined) {
316316 toolCalls[choiceIndex][toolCallIndex] = {};
317317 }
318-
319318 const targetToolCall = toolCalls[choiceIndex][toolCallIndex];
320319 ObjectToolManager.assign#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+ }
321351 }
322352 }
323353 }
@@ -397,9 +427,12 @@ export class ToolManager {
397427 * @returns {any[]} Tool calls from the response data
398428 */
399429 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+
400433 // Parsed tool calls from streaming data
401434 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];
403436 }
404437
405438 // Parsed tool calls from non-streaming data
@@ -412,19 +445,13 @@ export class ToolManager {
412445 }
413446 }
414447
448+ // Claude tool calls to OpenAI tool calls
415449 if (Array.isArray(data?.content)) {
416- // Claude tool calls to OpenAI tool calls
450+ const content = data.content.filter(c => c.type === 'tool_use').map(convertClaudeToolCall);
417- const content = data.content.filter(c => c.type === 'tool_use').map(c => {
451+
418- return {
452+ if (content) {
419- id: c.id,
453+ return content;
420- function: {
454+ }
421- name: c.name,
422- arguments: c.input,
423- },
424- };
425- });
426-
427- return content;
428455 }
429456 }
430457