@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
59 lines (58 loc) • 3.02 kB
JavaScript
/**
* Guards applied around a single tool execution inside a native provider loop.
*
* Lives here rather than beside any one provider because it is used by all of
* them: both Vertex+Claude loops call it directly, and the Gemini paths reach
* it through `buildDedupedEngineTools`. It previously sat in
* `providers/googleNativeGemini3/utils.ts`, which meant the Anthropic loops
* imported a tool-execution primitive out of a Gemini module — accurate about
* where it was written, misleading about what depends on it.
*
* Nothing in here is provider-specific: it takes an executor and a guards
* object and returns a wrapped executor.
*/
import { raceWithAbort, withTimeout } from "../utils/async/index.js";
/**
* Everything a native provider loop wraps around a tool call that the shared
* engine does not do itself.
*
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
* abort is observed the moment it fires rather than after the tool settles,
* and the timeout still bounds a tool that neither settles nor honours its
* signal. The progress pings bracket the await because the stall watchdog is
* a whole-turn interval comparing wall-clock against the last progress mark —
* without them a legitimately slow tool reads as a stalled turn and is killed.
*
* Exported because a tool hydrated MID-TURN has to be wrapped the same way as
* one declared up front; keeping this inline made the discovered tool the one
* executor in the system that ran raw.
*/
export function guardToolExecutor(name, execute, guards) {
return async (args, opts) => {
const invoke = () => Promise.resolve(execute(args, opts));
// The span wraps the CALL, not the guard: a timeout or an abort is a fact
// about this tool invocation and belongs inside its observation.
const wrapInSpan = guards.withToolSpan;
const call = wrapInSpan ? () => wrapInSpan(name, invoke) : invoke;
guards.onProgress?.();
try {
const raced = guards.abortSignal
? raceWithAbort(call(), guards.abortSignal)
: call();
// `== null` catches both an omitted bound and an explicit `null` — a
// caller that asked for unbounded tool execution. Testing only for
// `undefined` would send `null` into `withTimeout`, where it parses as
// no delay and fires the deadline immediately.
return await (guards.toolTimeoutMs === undefined ||
guards.toolTimeoutMs === null
? raced
: withTimeout(raced, guards.toolTimeoutMs, `Tool "${name}" execution timed out after ${guards.toolTimeoutMs}ms`));
}
finally {
// In `finally`, not after a successful await: a tool that times out or
// throws has still consumed real time, and skipping the mark there would
// leave the watchdog measuring from before the call.
guards.onProgress?.();
}
};
}