UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

56 lines (55 loc) • 2.75 kB
/** * Tool Use/Result Pair Repair * * After compaction (or a pointer-based history slice) the message array can * contain a `tool_call` whose `tool_result` was dropped, or a `tool_result` * whose `tool_call` was dropped. Providers reject both, so orphans are filled * with synthetic placeholders. * * Pairing is BATCH- and ID-aware, not adjacency-based. A single agent step * with parallel tool calls is persisted as every `tool_call` followed by every * `tool_result` (see flushPendingToolData), so `tool_call` is routinely * followed by another `tool_call` in perfectly healthy history. Matching on * adjacency treats that as an orphan and injects a bogus "result unavailable" * over a result that is present a few entries later. * * Two modes, chosen per batch: * - ID mode — `toolCallId` present: pair by id, order-independent. * - legacy mode — sessions written before `toolCallId` existed: pair * positionally WITHIN the batch (call[i] ↔ result[i]). */ import type { ChatMessage, RepairResult } from "../types/index.js"; /** * True for a row this module invented to stand in for a dropped call or * result. Replay must not present such a row as a real outcome: the model * would be told a call succeeded ("→ ok") or given a fabricated input when * the truth is that the record was lost. Recognised by id prefix, with the * placeholder text as a fallback for rows that travelled without their id. */ export declare function isRepairPlaceholder(row: { id?: string; role: string; content?: unknown; }): boolean; /** * Move a summarize/keep split so it never falls INSIDE a tool batch. * * `splitIndex` means "messages[0..splitIndex) get summarized" — so the summary * pointer lands on messages[splitIndex - 1]. Landing mid-batch leaves the * recent window starting on an orphaned `tool_result`, which providers reject. * * Preferred direction is BACKWARD (summarize less, keep the whole batch in the * recent window) since that never loses detail. When the batch starts at index * 0 there is nothing left to summarize, so the split moves forward past the * batch instead — the caller's "at least one message summarized" invariant * wins over keeping the batch recent. */ export declare function snapSplitToBatchBoundary(messages: ChatMessage[], splitIndex: number): number; /** * Repair orphaned tool_call/tool_result pairs in a message array. * * Guarantees on return: every `tool_call` is followed (within its batch) by * exactly one `tool_result`, and no `tool_result` precedes its `tool_call`. * A healthy batch — including a parallel one — is returned untouched. */ export declare function repairToolPairs(messages: ChatMessage[]): RepairResult;