| 'use strict'; |
|
|
| const { |
| toStringSafe, |
| } = require('./state'); |
| const { |
| parseMarkupToolCalls, |
| stripFencedCodeBlocks, |
| containsToolCallWrapperSyntaxOutsideIgnored, |
| normalizeDSMLToolCallMarkup, |
| hasRepairableXMLToolCallsWrapper, |
| indexToolCDATAOpen, |
| sanitizeLooseCDATA, |
| } = require('./parse_payload'); |
|
|
| function extractToolNames(tools) { |
| if (!Array.isArray(tools) || tools.length === 0) { |
| return []; |
| } |
| const out = []; |
| const seen = new Set(); |
| for (const t of tools) { |
| if (!t || typeof t !== 'object') { |
| continue; |
| } |
| const fn = t.function && typeof t.function === 'object' ? t.function : t; |
| const name = toStringSafe(fn.name); |
| if (!name || seen.has(name)) { |
| continue; |
| } |
| seen.add(name); |
| out.push(name); |
| } |
| return out; |
| } |
|
|
| function parseToolCalls(text, toolNames) { |
| return parseToolCallsDetailed(text, toolNames).calls; |
| } |
|
|
| function parseToolCallsDetailed(text, toolNames) { |
| const result = emptyParseResult(); |
| const raw = toStringSafe(text); |
| if (!raw) { |
| return result; |
| } |
| if (shouldSkipToolCallParsingForCodeFenceExample(raw)) { |
| return result; |
| } |
| const normalized = normalizeDSMLToolCallMarkup(stripFencedCodeBlocks(raw).trim()); |
| if (!normalized.ok || !normalized.text) { |
| return result; |
| } |
| result.sawToolCallSyntax = looksLikeToolCallSyntax(normalized.text) || hasRepairableXMLToolCallsWrapper(normalized.text); |
| |
| let parsed = parseMarkupToolCalls(normalized.text); |
| if (parsed.length === 0 && indexToolCDATAOpen(normalized.text, 0) >= 0) { |
| const recovered = sanitizeLooseCDATA(normalized.text); |
| if (recovered !== normalized.text) { |
| parsed = parseMarkupToolCalls(recovered); |
| } |
| } |
| if (parsed.length === 0) { |
| return result; |
| } |
| result.sawToolCallSyntax = true; |
| const filtered = filterToolCallsDetailed(parsed, toolNames); |
| result.calls = filtered.calls; |
| result.rejectedToolNames = filtered.rejectedToolNames; |
| result.rejectedByPolicy = filtered.rejectedToolNames.length > 0 && filtered.calls.length === 0; |
| return result; |
| } |
|
|
| function parseStandaloneToolCalls(text, toolNames) { |
| return parseStandaloneToolCallsDetailed(text, toolNames).calls; |
| } |
|
|
| function parseStandaloneToolCallsDetailed(text, toolNames) { |
| const result = emptyParseResult(); |
| const raw = toStringSafe(text); |
| if (!raw) { |
| return result; |
| } |
| if (shouldSkipToolCallParsingForCodeFenceExample(raw)) { |
| return result; |
| } |
| const normalized = normalizeDSMLToolCallMarkup(stripFencedCodeBlocks(raw).trim()); |
| if (!normalized.ok || !normalized.text) { |
| return result; |
| } |
| result.sawToolCallSyntax = looksLikeToolCallSyntax(normalized.text) || hasRepairableXMLToolCallsWrapper(normalized.text); |
| |
| let parsed = parseMarkupToolCalls(normalized.text); |
| if (parsed.length === 0 && indexToolCDATAOpen(normalized.text, 0) >= 0) { |
| const recovered = sanitizeLooseCDATA(normalized.text); |
| if (recovered !== normalized.text) { |
| parsed = parseMarkupToolCalls(recovered); |
| } |
| } |
| if (parsed.length === 0) { |
| return result; |
| } |
|
|
| result.sawToolCallSyntax = true; |
| const filtered = filterToolCallsDetailed(parsed, toolNames); |
| result.calls = filtered.calls; |
| result.rejectedToolNames = filtered.rejectedToolNames; |
| result.rejectedByPolicy = filtered.rejectedToolNames.length > 0 && filtered.calls.length === 0; |
| return result; |
| } |
|
|
| function emptyParseResult() { |
| return { |
| calls: [], |
| sawToolCallSyntax: false, |
| rejectedByPolicy: false, |
| rejectedToolNames: [], |
| }; |
| } |
|
|
| function filterToolCallsDetailed(parsed, toolNames) { |
| const calls = []; |
| for (const tc of parsed) { |
| if (!tc || !tc.name) { |
| continue; |
| } |
| const input = tc.input && typeof tc.input === 'object' && !Array.isArray(tc.input) ? tc.input : {}; |
| calls.push({ |
| name: tc.name, |
| input, |
| }); |
| } |
| return { calls, rejectedToolNames: [] }; |
| } |
|
|
| function looksLikeToolCallSyntax(text) { |
| const styles = containsToolCallWrapperSyntaxOutsideIgnored(text); |
| return styles.dsml || styles.canonical; |
| } |
|
|
| function shouldSkipToolCallParsingForCodeFenceExample(text) { |
| if (!looksLikeToolCallSyntax(text)) { |
| return false; |
| } |
| const stripped = stripFencedCodeBlocks(text); |
| return !looksLikeToolCallSyntax(stripped); |
| } |
|
|
| module.exports = { |
| extractToolNames, |
| parseToolCalls, |
| parseToolCallsDetailed, |
| parseStandaloneToolCalls, |
| parseStandaloneToolCallsDetailed, |
| }; |
|
|