import type { Message } from '../types/message/index.js'; /** * Represents the result of scanning messages for dangling tool call/result pairs. * Used to identify which messages should be removed to ensure message validity. */ export interface DanglingResult { /** Indices of assistant messages with unmatched tool calls */ assistantsWithUnmatchedCalls: number[]; /** Indices of tool messages with no matching assistant tool call */ orphanedToolMessages: number[]; /** Whether the message sequence is valid (no dangling messages) */ isValid: boolean; } /** * Scans a message sequence and identifies dangling tool call/result pairs. * * A dangling pair occurs when: * 1. An assistant message has tool calls but no matching tool message follows * 2. A tool message exists but its toolCallId doesn't match any preceding assistant tool call * * @param messages - Array of messages to scan * @returns DanglingResult with indices of invalid messages * * @example * ```typescript * const messages = [ * { role: 'user', content: 'test' }, * { role: 'assistant', content: null, toolCalls: [{ id: '1', type: 'function', function: { name: 'test', arguments: '{}' } }] }, * // Missing tool message for call id '1' * { role: 'user', content: 'next' } * ] * const result = findDanglingMessages(messages) * // result.assistantsWithUnmatchedCalls = [1] (index 1 has unmatched tool call) * ``` */ export declare function findDanglingMessages(messages: readonly Message[]): DanglingResult; /** * Removes dangling messages from a message sequence, preserving order. * * This function removes the minimum set of messages needed to ensure * all remaining tool call/result pairs are valid and complete. * * Algorithm: * 1. Identify dangling assistant messages and orphaned tool messages * 2. Remove orphaned tool messages * 3. For assistant messages with unmatched calls, remove both the assistant * message AND any following tool messages that attempt to satisfy it * * @param messages - Array of messages to clean * @returns New array with dangling messages removed, original order preserved * * @example * ```typescript * const messages = [ * { role: 'user', content: 'test' }, * { role: 'assistant', content: null, toolCalls: [{ id: '1', ... }] }, * // Missing tool response * { role: 'user', content: 'next' } * ] * const clean = removeDanglingMessages(messages) * // Result: [{ role: 'user', content: 'test' }, { role: 'user', content: 'next' }] * ``` */ export declare function removeDanglingMessages(messages: readonly Message[]): Message[]; /** Counts describing a provider-valid tool-history repair. */ export interface ToolHistoryRepairReport { /** Earlier duplicates in an immediate result batch; the last result wins. */ readonly duplicateToolResultsRemoved: number; /** Results with no immediately preceding owner call. */ readonly orphanedToolResultsRemoved: number; /** Error results inserted for calls whose durable outcome is unavailable. */ readonly syntheticToolResultsInserted: number; } /** A repaired copy and the exact changes made to it. */ export interface ToolHistoryRepairResult { readonly messages: Message[]; readonly report: ToolHistoryRepairReport; } /** Whether a repair changed the provider-bound history. */ export declare function toolHistoryRepairChanged(report: ToolHistoryRepairReport): boolean; /** * Repair provider tool-pairing violations without mutating the durable input. * * Results are valid only in the contiguous run immediately after their * assistant call. Orphaned/displaced results are removed, earlier immediate * duplicates are removed in favour of the last result, and every unanswered * call receives a conservative error result in call order. Duplicate call ids * fail closed: changing ids or tool calls would invalidate opaque native replay * state carried by the assistant message. */ export declare function repairToolMessageHistory(messages: readonly Message[]): ToolHistoryRepairResult; /** * Finds a safe index for trimming messages while preserving tool call/result atomicity. * * Given a desired trim point (maxIndex), adjusts it forward to ensure: * 1. The trim doesn't split a tool call/result pair * 2. The first message after the trim point is not a ToolMessage (orphaned result) * 3. All tool call/result pairs are kept intact (either fully included or fully excluded) * * Algorithm: * 1. Start from desired index * 2. Check if there's an incomplete tool call/result pair that started before the trim point * 3. If so, advance trim point past the complete pair * 4. If the new trim point starts with a tool message, advance past it * * @param messages - Array of messages to analyze * @param targetIndex - Desired trim point (exclusive upper bound) * @returns Safe trim index where message sequence is valid (at least 0, at most messages.length) * * @example * ```typescript * const messages = [ * { role: 'user', content: 'test' }, * { role: 'assistant', content: null, toolCalls: [{ id: '1', ... }] }, * { role: 'tool', content: 'result', toolCallId: '1' }, * { role: 'user', content: 'next' } * ] * const safeIdx = findSafeTrimIndex(messages, 2) * // Result: 3 (skips the incomplete pair at index 1-2) * ``` */ export declare function findSafeTrimIndex(messages: Message[], targetIndex: number): number; //# sourceMappingURL=dangling.d.ts.map