@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
198 lines (197 loc) • 7.12 kB
JavaScript
/**
* Deterministic pre-dispatch history truncation.
*
* Runs inside the shared context preflight, so one implementation bounds input
* cost on every dispatch path: native Anthropic, the translated/SDK fallback
* that serves Vertex, and native or bridged Codex.
*
* Deliberately provider-free. Summarizing history would add an outbound model
* call to the very requests this exists to make cheaper, so reduction is pure
* removal of the oldest complete units.
*/
function record(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
/**
* A turn that cannot open a history.
*
* Verified against the live API: an assistant turn at the head is accepted
* (plain, carrying a complete tool pair, repeated, or opening with a signed
* `thinking` block). A `role: "system"` message is not — the Messages API
* requires the initial system prompt in the top-level `system` parameter.
* Items with no role (Codex `*_call` / `*_call_output`) are not turns and are
* left alone.
*/
function isDirectiveTurn(item) {
return record(item) && item.role === "system";
}
function blocksOf(item) {
return Array.isArray(item.content) ? item.content : [item];
}
/**
* Tool-call ids this item opens.
*
* Matched on the `_call` suffix rather than an enumerated list of item types.
* The Responses API has several call families — `function_call`,
* `custom_tool_call`, `local_shell_call`, `computer_call` — and an unrecognised
* one is worse than useless here: it opens no id, so unit grouping cannot see
* the pair and truncation is free to cut between a call and its answer. A live
* Codex session failed exactly that way, upstream rejecting the request with
* "No tool call found for custom tool call output with call_id ...". Suffix
* matching keeps the next family from reintroducing it silently.
*/
function definedCallIds(item) {
if (!record(item)) {
return [];
}
const ids = [];
if (typeof item.type === "string" &&
item.type.endsWith("_call") &&
typeof item.call_id === "string") {
ids.push(item.call_id);
}
for (const block of blocksOf(item)) {
if (record(block) &&
block.type === "tool_use" &&
typeof block.id === "string") {
ids.push(block.id);
}
}
return ids;
}
/**
* Tool-call ids this item answers.
*
* Suffix-matched for the same reason `definedCallIds` is. `_call_output` is
* checked rather than `_call` because no output type ends in `_call`, so the
* two predicates stay disjoint without ordering between them.
*/
function referencedCallIds(item) {
if (!record(item)) {
return [];
}
const ids = [];
if (typeof item.type === "string" &&
item.type.endsWith("_call_output") &&
typeof item.call_id === "string") {
ids.push(item.call_id);
}
for (const block of blocksOf(item)) {
if (record(block) &&
block.type === "tool_result" &&
typeof block.tool_use_id === "string") {
ids.push(block.tool_use_id);
}
}
return ids;
}
/**
* Group items so a tool call and every answer to it stay in one unit.
*
* A unit stays open while any call it opened is still unanswered, so removing
* whole units can never strand a tool answer — an Anthropic `tool_result` or
* any Responses API `*_call_output`.
*/
function groupIntoUnits(items) {
const units = [];
let current = [];
const awaiting = new Set();
for (let index = 0; index < items.length; index += 1) {
const item = items[index];
current.push(index);
for (const id of referencedCallIds(item)) {
awaiting.delete(id);
}
for (const id of definedCallIds(item)) {
awaiting.add(id);
}
if (awaiting.size === 0) {
units.push(current);
current = [];
}
}
if (current.length > 0) {
// An unanswered trailing call still belongs with the turn that opened it.
units.push(current);
}
return units;
}
/**
* Pick the history container.
*
* `input` is overloaded across paths: the translated/SDK shape carries the
* current turn there as an object, while Codex carries the item array. Only an
* array is history, so the object form is never touched.
*/
function historyFieldOf(wire) {
if (Array.isArray(wire.messages)) {
return "messages";
}
if (Array.isArray(wire.conversationMessages)) {
return "conversationMessages";
}
if (Array.isArray(wire.input)) {
return "input";
}
return undefined;
}
/**
* Remove the oldest complete units until the estimate reaches `targetTokens`.
*
* Returns the body unchanged when there is nothing safe to remove. The newest
* unit is always retained: it carries the turn being answered.
*/
export function truncateHistoryForBudget(args) {
const unchanged = {
body: args.body,
historyModified: false,
unitsRemoved: 0,
itemsRemoved: 0,
};
const wire = args.body;
const field = historyFieldOf(wire);
if (field === undefined) {
return unchanged;
}
const items = wire[field];
const units = groupIntoUnits(items);
if (units.length <= 1) {
return unchanged;
}
// Everything outside the history container — tools, instructions, schema —
// is fixed cost that truncation must never touch, so only unit costs are
// subtracted from the running projection.
const unitTokens = units.map((unit) => args.estimate(unit.map((index) => items[index])));
// The translated shape keeps the current turn in object-valued `input`, so
// every `conversationMessages` unit is prior history and all of them may go.
// The other shapes carry the current turn in the array itself, so the newest
// unit there is the turn being answered and must survive.
const removable = field === "conversationMessages" ? units.length : units.length - 1;
let removedUnits = 0;
let projected = args.inputTokensEstimate;
while (removedUnits < removable && projected > args.targetTokens) {
projected -= unitTokens[removedUnits];
removedUnits += 1;
}
if (removedUnits === 0) {
return unchanged;
}
// Only leading system directives are skipped. Advancing to the next *user*
// turn instead would discard every tool exchange in between, which in an
// agentic history is most of the conversation; an assistant head is
// accepted upstream, and unit grouping already keeps every `tool_result`
// with its `tool_use`.
while (removedUnits < removable &&
isDirectiveTurn(items[units[removedUnits][0]])) {
removedUnits += 1;
}
const keptIndices = units.slice(removedUnits).flat();
const kept = keptIndices.map((index) => items[index]);
const itemsRemoved = items.length - kept.length;
return {
body: { ...args.body, [field]: kept },
historyModified: true,
unitsRemoved: removedUnits,
itemsRemoved,
};
}