UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

118 lines • 4.35 kB
function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); } /** Parse once at the completed block boundary; schema validation remains upstream. */ function completedArguments(parts, initial) { let input = initial; if (parts.length > 0) { try { input = JSON.parse(parts.join('')); } catch { return { input: {}, failure: { reason: 'invalid-json' } }; } } if (!object(input)) return { input: {}, failure: { reason: 'invalid-argument-shape' } }; return { input }; } /** Per-response raw argument evidence, shared by SDK and fetch adapters. */ export class AnthropicToolArguments { blocks = new Map(); starts = new Set(); observedTool = false; ended = false; invalid = false; /** Record only the protocol facts needed to judge a tool's completed JSON. */ observe(event) { if (!object(event)) return; if (event.type === 'message_stop') { if (this.ended) this.invalid = true; this.ended = true; return; } if (!['content_block_start', 'content_block_delta', 'content_block_stop'].includes(String(event.type))) return; if (event.type === 'content_block_start' && object(event.content_block) && event.content_block.type === 'tool_use') this.observedTool = true; const index = event.index; if (typeof index !== 'number' || !Number.isSafeInteger(index) || index < 0) { this.invalid = true; return; } if (this.ended) this.invalid = true; if (event.type === 'content_block_start') { if (this.starts.has(index)) { this.invalid = true; return; } this.starts.add(index); const block = event.content_block; if (object(block) && block.type === 'tool_use' && typeof block.id === 'string' && typeof block.name === 'string') { this.blocks.set(index, { id: block.id, name: block.name, initial: block.input, parts: [], closed: false, invalid: false, }); } return; } const block = this.blocks.get(index); if (event.type === 'content_block_stop') { if (!this.starts.has(index)) this.invalid = true; if (block) { if (block.closed) block.invalid = true; block.closed = true; } return; } const delta = event.delta; if (object(delta) && delta.type === 'input_json_delta') { if (!block) { this.invalid = true; return; } if (block.closed || typeof delta.partial_json !== 'string') block.invalid = true; else block.parts.push(delta.partial_json); } else if (block && object(delta) && ['text_delta', 'thinking_delta', 'signature_delta', 'citations_delta'].includes(String(delta.type))) block.invalid = true; } /** EOF/abort is not a completed response, even if an SDK exposes a partial object. */ assertTerminal(hasFinalTools = false) { if (!this.ended && (hasFinalTools || this.observedTool)) throw new Error('Anthropic stream ended without message_stop; no completed response is available.'); } /** A finite refusal, never an SDK's partial/repaired object, for unsafe input. */ finish(index, tool) { const block = this.blocks.get(index); const raw = block?.parts.join('') ?? ''; if (this.invalid || !block || !block.closed || block.invalid || block.id !== tool.id || block.name !== tool.name) { return { input: {}, failure: { reason: 'incomplete-stream' }, raw }; } return { ...completedArguments(block.parts, block.initial), raw }; } } //# sourceMappingURL=anthropicToolArguments.js.map