@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
98 lines (97 loc) • 4.98 kB
TypeScript
/**
* The multi-step tool loop that the ai package's `generateText` used to supply.
*
* It deliberately loops over a provider's own `doGenerate` rather than over any
* streaming machinery. That is the whole lesson of the reverted first attempt:
* `doGenerate` is where the JSON-versus-SSE wire choice lives, along with the
* 400 retry, the context-overflow refit and the provider's own structured-output
* handling. Looping around the streaming path instead silently changed
* generate() to send `stream: true` and broke ten providers against a
* non-streaming body.
*
* Every provider whose delegating model exposes a v3-shaped `doGenerate` can
* share this: the v3 result shape (`content` parts, `finishReason`, `usage`) is
* the same across Anthropic, the OpenAI-compatible family and SageMaker.
*/
import type { NativeGenerateLoopArgs, NativeGenerateLoopResult, StepResult, StepToolChoiceInput, Tool, ToolExecutionSummaryInternal } from "../types/index.js";
/**
* Decide the tool choice for ONE step of a multi-step loop.
*
* A forced choice held on every step compels a tool call on every step, so
* the loop could only end when `maxSteps` ran out — which is exactly what
* happened before this existed. The rule: a forced choice applies while
* `step < toolChoiceSteps` (default 1), after which the model chooses
* (`"auto"`). `"auto"` and `"none"` pass through unchanged on every step.
*
* A `prepareStep` hook, when supplied, runs first and its `toolChoice` wins
* for the step — including a forced choice past `toolChoiceSteps`, which is
* how a caller forces a tool on step 3 only. A hook that returns no
* `toolChoice` defers to the rule. Nothing else on the hook's result is read.
*
* A forced single-tool choice, from the hook or from the turn, that names a
* tool outside `declaredTools` is warned about and not sent. A hook's choice
* is then treated as "no override" and the rule decides; the turn's own
* choice becomes `"auto"`. Without `declaredTools` nothing is checked.
*
* A hook that THROWS is logged and treated as "no override": the steps
* already billed are kept and the rule decides. An abort of the turn while
* the hook is pending is not a hook failure and is re-thrown. The two are
* told apart by the LOOP's signal, never by the error's name: a hook that
* rejects with its own `AbortError` (its policy service timed out) has
* failed, and must not end a turn the caller never cancelled.
*/
export declare function resolveStepToolChoice(input: StepToolChoiceInput): Promise<unknown>;
/**
* Translate a NeuroLink-shape tool choice (`"auto"` / `"none"` /
* `"required"` / `{ type: "tool", toolName }`) into the v3 call-option
* OBJECT every `doGenerate` converter switches on. The bare strings were
* being handed straight to `doGenerate`; Anthropic's converter normalises
* them, but the OpenAI-compatible one reads `.type` off the value and
* silently dropped every string form. An already-object value passes
* through untouched.
*/
export declare const toV3ToolChoice: (choice: unknown) => Record<string, unknown> | undefined;
/**
* The record of one completed step that `prepareStep` receives in `steps`.
* Built the same way by every native loop so a hook sees one shape.
*/
export declare const toPrepareStepRecord: (step: {
stepNumber: number;
content: Array<Record<string, unknown>>;
text: string;
toolCalls: Array<{
toolName: string;
toolCallId: string;
input: unknown;
}>;
toolResults: Array<{
toolName: string;
toolCallId: string;
output: unknown;
}>;
finishReason: string;
inputTokens: number;
outputTokens: number;
/** Prompt-cache tokens for the step, when the provider reports them. */
cacheReadTokens?: number;
cacheWriteTokens?: number;
}) => StepResult<Record<string, Tool>>;
/**
* Narrow a model handle to the delegating shape this loop drives.
* `LanguageModel` is a union that includes a bare string id, and a double
* assertion through unknown is banned by Critical Rule 14.
*/
export declare const hasNativeDoGenerate: (value: unknown) => value is {
doGenerate: (options: Record<string, unknown>) => Promise<Record<string, unknown>>;
};
/**
* Spell a JSON Schema into the conversation's system turn.
*
* The structured-output fallback for vendors that reject or ignore
* `response_format`. Merged into an existing trailing system message rather
* than appended as a second one: several self-hosted OpenAI-compatible stacks
* honour only the first system message, so a second would be dropped and the
* fallback would silently do nothing.
*/
export declare const appendJsonSchemaInstruction: (conversation: Array<Record<string, unknown>>, schema: unknown) => Array<Record<string, unknown>>;
export declare function runNativeGenerateLoop(args: NativeGenerateLoopArgs, toolExecutionSummaries: ToolExecutionSummaryInternal[]): Promise<NativeGenerateLoopResult>;