'use strict'; const crypto = require('crypto'); function formatOpenAIStreamToolCalls(calls, idStore, toolsRaw) { if (!Array.isArray(calls) || calls.length === 0) { return []; } const normalized = normalizeParsedToolCallsForSchemas(calls, toolsRaw); return normalized.map((c, idx) => ({ index: idx, id: ensureStreamToolCallID(idStore, idx), type: 'function', function: { name: c.name, arguments: JSON.stringify(c.input || {}), }, })); } function normalizeParsedToolCallsForSchemas(calls, toolsRaw) { if (!Array.isArray(calls) || calls.length === 0) { return calls; } const schemas = buildToolSchemaIndex(toolsRaw); if (!schemas) { return calls; } let changedAny = false; const out = calls.map((call) => { const name = String(call && call.name || '').trim().toLowerCase(); const schema = schemas[name]; if (!schema || !call || !call.input || typeof call.input !== 'object' || Array.isArray(call.input)) { return call; } const [normalized, changed] = normalizeToolValueWithSchema(call.input, schema); if (!changed || !normalized || typeof normalized !== 'object' || Array.isArray(normalized)) { return call; } changedAny = true; return { ...call, input: normalized }; }); return changedAny ? out : calls; } function buildToolSchemaIndex(toolsRaw) { if (!Array.isArray(toolsRaw) || toolsRaw.length === 0) { return null; } const out = {}; for (const item of toolsRaw) { if (!item || typeof item !== 'object' || Array.isArray(item)) { continue; } const [name, schema] = extractToolNameAndSchema(item); if (!name || !schema || typeof schema !== 'object' || Array.isArray(schema)) { continue; } out[name.toLowerCase()] = schema; } return Object.keys(out).length > 0 ? out : null; } function extractToolNameAndSchema(tool) { const fn = tool && typeof tool.function === 'object' && !Array.isArray(tool.function) ? tool.function : null; const name = firstNonEmptyString(tool.name, fn && fn.name); const schema = firstNonNil( tool.parameters, tool.input_schema, tool.inputSchema, tool.schema, fn && fn.parameters, fn && fn.input_schema, fn && fn.inputSchema, fn && fn.schema, ); return [name, schema]; } function normalizeToolValueWithSchema(value, schema) { if (value == null || !schema || typeof schema !== 'object' || Array.isArray(schema)) { return [value, false]; } if (shouldCoerceSchemaToString(schema)) { return stringifySchemaValue(value); } if (looksLikeObjectSchema(schema)) { if (!value || typeof value !== 'object' || Array.isArray(value)) { return [value, false]; } const properties = schema.properties && typeof schema.properties === 'object' && !Array.isArray(schema.properties) ? schema.properties : null; const additional = schema.additionalProperties; let changed = false; const out = {}; for (const [key, current] of Object.entries(value)) { let next = current; let fieldChanged = false; if (properties && Object.prototype.hasOwnProperty.call(properties, key)) { [next, fieldChanged] = normalizeToolValueWithSchema(current, properties[key]); } else if (additional != null) { [next, fieldChanged] = normalizeToolValueWithSchema(current, additional); } out[key] = next; changed = changed || fieldChanged; } return changed ? [out, true] : [value, false]; } if (looksLikeArraySchema(schema)) { if (!Array.isArray(value) || value.length === 0 || schema.items == null) { return [value, false]; } let changed = false; const out = value.map((item, idx) => { const itemSchema = Array.isArray(schema.items) ? schema.items[idx] : schema.items; if (itemSchema == null) { return item; } const [next, itemChanged] = normalizeToolValueWithSchema(item, itemSchema); changed = changed || itemChanged; return next; }); return changed ? [out, true] : [value, false]; } return [value, false]; } function shouldCoerceSchemaToString(schema) { if (!schema || typeof schema !== 'object' || Array.isArray(schema)) { return false; } if (typeof schema.const === 'string') { return true; } if (Array.isArray(schema.enum) && schema.enum.length > 0 && schema.enum.every((item) => typeof item === 'string')) { return true; } if (typeof schema.type === 'string') { return schema.type.trim().toLowerCase() === 'string'; } if (Array.isArray(schema.type) && schema.type.length > 0) { let hasString = false; for (const item of schema.type) { if (typeof item !== 'string') { return false; } const typ = item.trim().toLowerCase(); if (typ === 'string') { hasString = true; } else if (typ !== 'null') { return false; } } return hasString; } return false; } function looksLikeObjectSchema(schema) { return !!schema && typeof schema === 'object' && !Array.isArray(schema) && ( (typeof schema.type === 'string' && schema.type.trim().toLowerCase() === 'object') || (schema.properties && typeof schema.properties === 'object' && !Array.isArray(schema.properties)) || schema.additionalProperties != null ); } function looksLikeArraySchema(schema) { return !!schema && typeof schema === 'object' && !Array.isArray(schema) && ( (typeof schema.type === 'string' && schema.type.trim().toLowerCase() === 'array') || schema.items != null ); } function stringifySchemaValue(value) { if (value == null) { return [value, false]; } if (typeof value === 'string') { return [value, false]; } try { return [JSON.stringify(value), true]; } catch { return [value, false]; } } function firstNonNil(...values) { for (const value of values) { if (value != null) { return value; } } return null; } function firstNonEmptyString(...values) { for (const value of values) { if (typeof value !== 'string') { continue; } const trimmed = value.trim(); if (trimmed) { return trimmed; } } return ''; } function ensureStreamToolCallID(idStore, index) { if (!(idStore instanceof Map)) { return `call_${newCallID()}`; } const key = Number.isInteger(index) ? index : 0; const existing = idStore.get(key); if (existing) { return existing; } const next = `call_${newCallID()}`; idStore.set(key, next); return next; } function newCallID() { if (typeof crypto.randomUUID === 'function') { return crypto.randomUUID().replace(/-/g, ''); } return `${Date.now()}${Math.floor(Math.random() * 1e9)}`; } module.exports = { formatOpenAIStreamToolCalls, };