@juspay/neurolink 12.47.13 → 12.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
  3. package/dist/browser/neurolink.min.js +578 -565
  4. package/dist/cli/factories/commandFactory.d.ts +7 -0
  5. package/dist/cli/factories/commandFactory.js +26 -0
  6. package/dist/cli/loop/optionsSchema.js +9 -0
  7. package/dist/context/budgetChecker.js +54 -2
  8. package/dist/context/contextDecision.js +2 -0
  9. package/dist/context/toolPairRepair.d.ts +12 -0
  10. package/dist/context/toolPairRepair.js +29 -3
  11. package/dist/core/baseProvider.d.ts +92 -3
  12. package/dist/core/baseProvider.js +254 -10
  13. package/dist/core/conversationMemoryManager.js +17 -2
  14. package/dist/core/loopEngine.js +1 -1
  15. package/dist/core/modules/MessageBuilder.js +11 -3
  16. package/dist/core/modules/TelemetryHandler.d.ts +2 -2
  17. package/dist/core/modules/ToolsManager.d.ts +35 -0
  18. package/dist/core/modules/ToolsManager.js +159 -55
  19. package/dist/core/nativeGenerateLoop.d.ts +58 -1
  20. package/dist/core/nativeGenerateLoop.js +277 -3
  21. package/dist/core/redisConversationMemoryManager.js +10 -3
  22. package/dist/core/resolveRequestKind.d.ts +24 -4
  23. package/dist/core/resolveRequestKind.js +31 -4
  24. package/dist/core/toolExecutionRecorder.d.ts +6 -0
  25. package/dist/core/toolExecutionRecorder.js +24 -2
  26. package/dist/core/toolRoutingDecision.js +1 -0
  27. package/dist/factories/mediaHandlerCatalog.js +5 -0
  28. package/dist/factories/providerDescriptors.js +15 -0
  29. package/dist/files/fileTools.d.ts +1 -1
  30. package/dist/index.d.ts +5 -2
  31. package/dist/index.js +9 -2
  32. package/dist/memory/condensationPrompt.d.ts +8 -0
  33. package/dist/memory/condensationPrompt.js +29 -0
  34. package/dist/memory/hippocampusInitializer.d.ts +14 -2
  35. package/dist/memory/hippocampusInitializer.js +64 -3
  36. package/dist/neurolink.d.ts +252 -3
  37. package/dist/neurolink.js +898 -43
  38. package/dist/providers/amazonBedrock/client.js +6 -0
  39. package/dist/providers/amazonSagemaker.js +14 -1
  40. package/dist/providers/anthropic/client.js +148 -19
  41. package/dist/providers/anthropic/loopAdapter.js +2 -2
  42. package/dist/providers/googleAiStudio/client.js +29 -14
  43. package/dist/providers/openaiChatCompletionsBase.js +103 -23
  44. package/dist/providers/systemOneDecision.d.ts +6 -0
  45. package/dist/providers/systemOneDecision.js +14 -63
  46. package/dist/providers/typesafe.d.ts +6 -0
  47. package/dist/providers/typesafe.js +6 -0
  48. package/dist/rag/retrieval/searchDecision.js +1 -0
  49. package/dist/routing/classifierRouter.js +6 -5
  50. package/dist/routing/classifierStrategies.js +8 -1
  51. package/dist/types/classifierRouter.d.ts +16 -3
  52. package/dist/types/common.d.ts +2 -0
  53. package/dist/types/config.d.ts +9 -0
  54. package/dist/types/context.d.ts +8 -1
  55. package/dist/types/conversation.d.ts +87 -0
  56. package/dist/types/decision.d.ts +210 -1
  57. package/dist/types/decision.js +9 -1
  58. package/dist/types/dispatch.d.ts +11 -4
  59. package/dist/types/generate.d.ts +118 -54
  60. package/dist/types/loopEngine.d.ts +7 -2
  61. package/dist/types/memory.d.ts +177 -9
  62. package/dist/types/openaiCompatible.d.ts +11 -3
  63. package/dist/types/stream.d.ts +13 -16
  64. package/dist/types/stt.d.ts +58 -1
  65. package/dist/types/tools.d.ts +14 -0
  66. package/dist/types/tools.js +8 -0
  67. package/dist/types/tts.d.ts +108 -0
  68. package/dist/types/voice.d.ts +41 -4
  69. package/dist/utils/decisionAnswers.d.ts +31 -1
  70. package/dist/utils/decisionAnswers.js +114 -0
  71. package/dist/utils/decisionLimits.d.ts +39 -0
  72. package/dist/utils/decisionLimits.js +116 -0
  73. package/dist/utils/logger.d.ts +7 -0
  74. package/dist/utils/logger.js +9 -0
  75. package/dist/utils/messageBuilder.d.ts +45 -1
  76. package/dist/utils/messageBuilder.js +393 -16
  77. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
  78. package/dist/utils/multimodalOptionsBuilder.js +1 -0
  79. package/dist/utils/speechText.d.ts +41 -0
  80. package/dist/utils/speechText.js +364 -0
  81. package/dist/utils/toolResultStatus.d.ts +26 -0
  82. package/dist/utils/toolResultStatus.js +73 -0
  83. package/dist/utils/ttsProcessor.d.ts +29 -0
  84. package/dist/utils/ttsProcessor.js +84 -2
  85. package/dist/voice/index.d.ts +1 -0
  86. package/dist/voice/index.js +3 -0
  87. package/dist/voice/providers/ElevenLabsSTT.d.ts +42 -0
  88. package/dist/voice/providers/ElevenLabsSTT.js +351 -0
  89. package/dist/voice/providers/ElevenLabsTTS.d.ts +81 -1
  90. package/dist/voice/providers/ElevenLabsTTS.js +415 -74
  91. package/docs-site/static/search-index.json +48 -33
  92. package/package.json +5 -2
  93. package/dist/utils/toolEndEmitter.d.ts +0 -25
  94. package/dist/utils/toolEndEmitter.js +0 -64
@@ -44,12 +44,99 @@ export type { ModelMessage, SystemModelMessage, UserModelMessage, AssistantModel
44
44
  * uniqueness checks see.
45
45
  */
46
46
  export type StorageConfig = HippocampusStorageConfig;
47
+ /**
48
+ * How stored `tool_call` / `tool_result` rows are replayed into the prompt
49
+ * on later turns of a session (see `ConversationMemoryConfig.replayToolSteps`).
50
+ *
51
+ * - `"full"`: replayed as real tool-call / tool-result turns, so the model
52
+ * sees the exact call and its output.
53
+ * - `"marker"`: a compact text line per tool call
54
+ * (`[called <tool> → ok]` / `[called <tool> → error]`) folded into the
55
+ * assistant turn, so the model knows what it did without the payload.
56
+ * - `"off"`: tool rows are dropped from the prompt, as before this option.
57
+ */
58
+ export type ToolReplayMode = "full" | "marker" | "off";
59
+ /**
60
+ * The fields the tool-step replay reads off a stored history row. Structural
61
+ * rather than `ChatMessage` because the multimodal builder's history is typed
62
+ * as `{ role: string; content: string }` while carrying the same rows.
63
+ */
64
+ export type ToolHistoryRow = {
65
+ /** Row id; a `repair-*` prefix marks a placeholder `repairToolPairs` invented. */
66
+ id?: string;
67
+ role: string;
68
+ content?: unknown;
69
+ tool?: string;
70
+ toolCallId?: string;
71
+ args?: Record<string, unknown>;
72
+ result?: {
73
+ success?: boolean;
74
+ error?: string;
75
+ };
76
+ metadata?: {
77
+ stepIndex?: number;
78
+ isSummary?: boolean;
79
+ isSkill?: boolean;
80
+ };
81
+ };
82
+ /**
83
+ * What a replayed step tells the model about the call's outcome: `"ok"`,
84
+ * `"error"` (the result was a failure — thrown, rejected, or an `isError`
85
+ * payload), or `"unknown"` when the real result was lost to compaction and
86
+ * `repairToolPairs` filled the gap with a placeholder.
87
+ */
88
+ export type ToolReplayOutcome = "ok" | "error" | "unknown";
89
+ /** One paired call/result from a session's history, ready to replay. */
90
+ export type ToolReplayStep = {
91
+ callId: string;
92
+ toolName: string;
93
+ args: Record<string, unknown>;
94
+ output: string;
95
+ outcome: ToolReplayOutcome;
96
+ };
97
+ /**
98
+ * One tool call as handed to conversation-memory storage after a loop step.
99
+ * The stores read `args` (Redis also `arguments` / `parameters`), so the
100
+ * field is named for what they read — an `input` key here is silently
101
+ * persisted as `{}`, which is exactly the bug this type exists to prevent.
102
+ */
103
+ export type MemoryToolCallRecord = {
104
+ toolCallId?: string;
105
+ toolName?: string;
106
+ args?: Record<string, unknown>;
107
+ /** Zero-based loop step, persisted as `metadata.stepIndex` by stores that keep it. */
108
+ stepIndex?: number;
109
+ /** Gemini 3 thought signature riding on the step's first call. */
110
+ thoughtSignature?: string;
111
+ timestamp?: Date;
112
+ };
113
+ /** One tool result as handed to conversation-memory storage after a loop step. */
114
+ export type MemoryToolResultRecord = {
115
+ toolCallId?: string;
116
+ toolName?: string;
117
+ output?: unknown;
118
+ /** Legacy alias of `output`; stores read whichever is present. */
119
+ result?: unknown;
120
+ /** Set when the execution failed; persisted as `result.error` and `success: false`. */
121
+ error?: string;
122
+ stepIndex?: number;
123
+ timestamp?: Date;
124
+ };
47
125
  /**
48
126
  * Configuration for conversation memory feature
49
127
  */
50
128
  export type ConversationMemoryConfig = {
51
129
  /** Enable conversation memory feature */
52
130
  enabled: boolean;
131
+ /**
132
+ * How a session's stored tool steps are replayed into later prompts.
133
+ * Default `"marker"`. `"full"` replays real tool-call / tool-result turns
134
+ * (costs tokens per replayed step and, on Anthropic, changes the prompt
135
+ * prefix ahead of the cached system breakpoint); `"off"` restores the
136
+ * previous behaviour of dropping them. Overridable per request via
137
+ * `replayToolSteps` on the generate / stream options.
138
+ */
139
+ replayToolSteps?: ToolReplayMode;
53
140
  /** Maximum number of sessions to keep in memory (default: 50) */
54
141
  maxSessions?: number;
55
142
  /** Enable automatic summarization */
@@ -182,6 +182,56 @@ export type DecisionLimits = {
182
182
  }>>;
183
183
  /** What the provider accepts besides text. Absent means text only. */
184
184
  media?: DecisionMediaLimits;
185
+ /**
186
+ * True when these figures describe the provider's own server-side ceiling,
187
+ * published so callers can plan against it, and NeuroLink does NOT refuse
188
+ * locally — the server does, with its own `max_tokens_exceeded`. Absent or
189
+ * false means NeuroLink refuses an over-limit request before any network
190
+ * call. `NeuroLink.decisionLimits()` reports the distinction as
191
+ * `enforcedLocally`.
192
+ */
193
+ advisory?: boolean;
194
+ };
195
+ /**
196
+ * A provider's decision limits resolved for one model — the figure the
197
+ * pre-flight check compares against, flattened from a descriptor's
198
+ * `decisionLimits` with the per-model override applied.
199
+ *
200
+ * Returned by `NeuroLink.decisionLimits()` so a host extending NeuroLink's
201
+ * questions (see {@link DecisionHooks}) can size its additions before the
202
+ * call, instead of discovering the cap when the whole request is refused.
203
+ */
204
+ export type DecisionLimitsReading = {
205
+ provider: string;
206
+ model: string;
207
+ maxStateTokens: number;
208
+ /**
209
+ * Absent when the provider caps a request by tokens (or bytes) rather
210
+ * than by question count — TypeSafe and XOR; `Infinity` is never used.
211
+ */
212
+ maxQuestions?: number;
213
+ nonAsciiTokensPerChar?: number;
214
+ /** Tokens charged per ASCII digit; see {@link DecisionLimits.digitTokensPerChar}. */
215
+ digitTokensPerChar?: number;
216
+ /** Tokens charged per ASCII punctuation/symbol; see {@link DecisionLimits.symbolTokensPerChar}. */
217
+ symbolTokensPerChar?: number;
218
+ /** Tokens charged per astral character; see {@link DecisionLimits.astralTokensPerChar}. */
219
+ astralTokensPerChar?: number;
220
+ /** What the provider accepts besides text; absent means text only. */
221
+ media?: DecisionMediaLimits;
222
+ /**
223
+ * Whether NeuroLink itself refuses an over-limit request before any network
224
+ * call (Laya), or merely reports the server's ceiling (TypeSafe).
225
+ */
226
+ enforcedLocally: boolean;
227
+ };
228
+ /** What `NeuroLink.decisionLimits()` accepts: the same selection `decide()` takes. */
229
+ export type DecisionLimitsQuery = {
230
+ /** Provider name or alias; defaults to the configured decision provider. */
231
+ provider?: string;
232
+ /** Defaults to the provider's configured model, then its default model. */
233
+ model?: string;
234
+ credentials?: NeurolinkCredentials;
185
235
  };
186
236
  export type DecisionMediaLimits = {
187
237
  /** Most images one request may carry. */
@@ -287,6 +337,162 @@ export type DecisionOptions = DecisionRequest & {
287
337
  /** Per-call credential overrides, as `generate()` takes. */
288
338
  credentials?: NeurolinkCredentials;
289
339
  };
340
+ /**
341
+ * The built-in consumers of the `decide` inference type — one name per place
342
+ * NeuroLink asks a decision model something on its own behalf. Each consumer
343
+ * stamps its request with its site, which is what lets a host's
344
+ * {@link DecisionHooks} tell the calls apart and lets telemetry attribute them.
345
+ *
346
+ * - `routing` — the classifier router (difficulty, capabilities, risk, model, context scope)
347
+ * - `toolRouting` — one yes/no per MCP server
348
+ * - `contextRelevance` — one yes/no per earlier message, compaction stage 0
349
+ * - `summaryGate` — accept or reject a generated summary, compaction stage 3
350
+ * - `ragPlan` — per-query retrieval plan (`RAGPipeline` only)
351
+ */
352
+ export type DecisionSite = "routing" | "toolRouting" | "contextRelevance" | "summaryGate" | "ragPlan";
353
+ /**
354
+ * Options an internal decision site hands its caller: the public
355
+ * {@link DecisionOptions} plus in-process context that never reaches the
356
+ * wire. A bound `NeuroLink.tryDecide` accepts it unchanged; a host wrapping
357
+ * one can read `site` to see which consumer is asking.
358
+ */
359
+ export type DecisionCallerOptions = DecisionOptions & {
360
+ /** Which built-in consumer is asking. Unset on a host's own `tryDecide` call. */
361
+ site?: DecisionSite;
362
+ /** The conversation the outer request belongs to. Never sent to the model. */
363
+ sessionId?: string;
364
+ /** The outer request's id. Never sent to the model. */
365
+ requestId?: string;
366
+ };
367
+ /**
368
+ * What an internal decision site inherits from the `generate()` / `stream()`
369
+ * call it runs inside: the per-call credentials (so the decision goes to the
370
+ * caller's provider account, not the instance's), the abort signal, and the
371
+ * ids a host's {@link DecisionHooks} may want to correlate on.
372
+ */
373
+ export type DecisionSiteContext = {
374
+ credentials?: NeurolinkCredentials;
375
+ signal?: AbortSignal;
376
+ sessionId?: string;
377
+ requestId?: string;
378
+ };
379
+ /** What a site call adds to its decision span beyond the request itself. */
380
+ export type DecisionSiteAttributes = {
381
+ site: DecisionSite;
382
+ /** Questions a host's `extendQuestions` hook added; 0 without hooks. */
383
+ hostQuestionCount: number;
384
+ };
385
+ /**
386
+ * What a {@link DecisionHooks} callback sees before the call: the site, the
387
+ * state NeuroLink is about to send, and NeuroLink's own questions — as
388
+ * copies, so a hook that edits them cannot reach the wire request.
389
+ */
390
+ export type DecisionHookContext = {
391
+ site: DecisionSite;
392
+ state: DecisionState;
393
+ /** NeuroLink's own questions for this site, keyed by its own ids. */
394
+ questions: DecisionQuestionMap;
395
+ sessionId?: string;
396
+ requestId?: string;
397
+ };
398
+ /** What `onAnswers` receives once the decision has returned. */
399
+ export type DecisionHookAnswersContext = DecisionHookContext & {
400
+ /**
401
+ * The host's answers, keyed by the ids the host used in `extendQuestions`
402
+ * — the namespacing applied on the wire is undone here. Empty when the
403
+ * host added no questions.
404
+ */
405
+ answers: DecisionAnswerMap;
406
+ /** The whole result, including NeuroLink's own answers under its own ids. */
407
+ result: DecisionResult;
408
+ };
409
+ /**
410
+ * Ride along on the decision calls NeuroLink already makes.
411
+ *
412
+ * Latency on a decision model is flat in question count, so a host with its
413
+ * own yes/no or choice questions about the same request pays nothing to ask
414
+ * them in the round trip NeuroLink is making anyway — one request per
415
+ * {@link DecisionSite}, never a second. The host's questions are namespaced
416
+ * on the wire so they can never collide with NeuroLink's, capped so they can
417
+ * never push the request past the provider's question limit (which would
418
+ * refuse the whole call and degrade NeuroLink's own routing), and answered
419
+ * back under the host's original ids.
420
+ *
421
+ * Both hooks are fail-open: a hook that throws is logged and the decision
422
+ * proceeds with NeuroLink's own questions, and neither can change what
423
+ * NeuroLink does with its own answers — every value a hook or listener is
424
+ * handed is a copy. Without hooks, the decision payload (state and
425
+ * questions) is exactly what the consumer built; the outer request's
426
+ * per-call `credentials` are still forwarded, so the account and base URL
427
+ * on the wire can differ from the instance's. Hooks are inert without a
428
+ * decision provider: neither runs, and no event fires.
429
+ */
430
+ export type DecisionHooks = {
431
+ /**
432
+ * Return extra questions to send with this site's call, or `undefined` to
433
+ * add none. An invalid question is dropped with a warning; questions past
434
+ * the provider's cap are dropped from the end, also with a warning.
435
+ */
436
+ extendQuestions?: (context: DecisionHookContext) => DecisionQuestionMap | undefined | Promise<DecisionQuestionMap | undefined>;
437
+ /**
438
+ * Called with the host's answers (under the host's ids) and the full result
439
+ * whenever a site call returns one. Observe-only: it receives a copy, so
440
+ * mutating it cannot reach the answers NeuroLink's own consumer reads.
441
+ */
442
+ onAnswers?: (context: DecisionHookAnswersContext) => void | Promise<void>;
443
+ /**
444
+ * Upper bound, in milliseconds, on each hook call. Both hooks sit on the
445
+ * request path (routing, tool routing, compaction, RAG planning), so a
446
+ * hook that hangs would stall the turn; past this bound the call proceeds
447
+ * as if the hook had returned nothing, with a warning. Default 2000. Must
448
+ * be a finite number from 1 to {@link MAX_DECISION_HOOK_TIMEOUT_MS};
449
+ * anything else (including `Infinity`) falls back to the default with one
450
+ * warning, since a timer given such a delay fires immediately.
451
+ */
452
+ hookTimeoutMs?: number;
453
+ };
454
+ /** Default bound on a `DecisionHooks` callback, in milliseconds. */
455
+ export declare const DEFAULT_DECISION_HOOK_TIMEOUT_MS = 2000;
456
+ /**
457
+ * Largest `hookTimeoutMs` a timer honours (2^31 − 1 ms, about 24.8 days).
458
+ * A delay past it — or `Infinity`, `NaN`, 0, a negative — fires at once,
459
+ * so such a value falls back to the default instead of timing every hook
460
+ * out immediately.
461
+ */
462
+ export declare const MAX_DECISION_HOOK_TIMEOUT_MS = 2147483647;
463
+ /**
464
+ * Payload of the `decision:before` event, emitted once per site call. `state`
465
+ * and `questions` are copies: a listener that edits them changes neither
466
+ * the request on the wire nor the consumer's own question objects.
467
+ */
468
+ export type DecisionBeforeEvent = {
469
+ site: DecisionSite;
470
+ state: DecisionState;
471
+ /** Everything about to be sent, NeuroLink's own questions plus the host's (namespaced). */
472
+ questions: DecisionQuestionMap;
473
+ hostQuestionCount: number;
474
+ sessionId?: string;
475
+ requestId?: string;
476
+ };
477
+ /**
478
+ * Payload of the `decision:after` event, emitted once per site call whether
479
+ * or not it produced a result — a decision path that stopped working must
480
+ * stay observable. `result` is null when an emitted call failed; with no
481
+ * decision provider configured, no event fires at all.
482
+ */
483
+ export type DecisionAfterEvent = {
484
+ site: DecisionSite;
485
+ /** NeuroLink's own answers, under its own ids. Empty when `result` is null. */
486
+ answers: DecisionAnswerMap;
487
+ /** The host's answers, under the host's original ids. Empty when `result` is null. */
488
+ hostAnswers: DecisionAnswerMap;
489
+ latencyMs: number;
490
+ provider?: string;
491
+ model?: string;
492
+ result: DecisionResult | null;
493
+ sessionId?: string;
494
+ requestId?: string;
495
+ };
290
496
  /**
291
497
  * Injected fail-open decision caller — typically a bound `NeuroLink.tryDecide`,
292
498
  * which returns `null` on any failure rather than throwing.
@@ -297,8 +503,11 @@ export type DecisionOptions = DecisionRequest & {
297
503
  * makes "no decision provider configured" indistinguishable from "the call
298
504
  * failed" at every call site: both are `null`, and both mean *carry on as
299
505
  * before*.
506
+ *
507
+ * The options carry a {@link DecisionSite} stamp; a bound `tryDecide` routes
508
+ * a stamped request through the host's {@link DecisionHooks}.
300
509
  */
301
- export type DecisionCallerFn = (options: DecisionOptions) => Promise<DecisionResult | null>;
510
+ export type DecisionCallerFn = (options: DecisionCallerOptions) => Promise<DecisionResult | null>;
302
511
  /**
303
512
  * Shared gate for acting on a yes/no answer.
304
513
  *
@@ -18,4 +18,12 @@
18
18
  * `src/lib/types/` (CLAUDE.md rule 9). Note `evaluate`/`Evaluation*` is a
19
19
  * different, unrelated feature — scoring a generated response with RAGAS.
20
20
  */
21
- export {};
21
+ /** Default bound on a `DecisionHooks` callback, in milliseconds. */
22
+ export const DEFAULT_DECISION_HOOK_TIMEOUT_MS = 2000;
23
+ /**
24
+ * Largest `hookTimeoutMs` a timer honours (2^31 − 1 ms, about 24.8 days).
25
+ * A delay past it — or `Infinity`, `NaN`, 0, a negative — fires at once,
26
+ * so such a value falls back to the default instead of timing every hook
27
+ * out immediately.
28
+ */
29
+ export const MAX_DECISION_HOOK_TIMEOUT_MS = 2_147_483_647;
@@ -3,6 +3,7 @@
3
3
  * the single function that decides which of NeuroLink's output modes a
4
4
  * generate/stream request is asking for.
5
5
  */
6
+ import type { TTSSynthesisMode } from "./tts.js";
6
7
  export type RequestKind = "text" | "image" | "video" | "music" | "avatar" | "tts-direct" | "ppt";
7
8
  /**
8
9
  * Narrow structural subset of TextGenerationOptions/GenerateOptions that
@@ -15,8 +16,14 @@ export type RequestKindInput = {
15
16
  mode?: string;
16
17
  format?: string;
17
18
  };
18
- tts?: {
19
- enabled?: boolean;
20
- useAiResponse?: boolean;
21
- };
19
+ tts?: RequestKindTTSInput;
20
+ };
21
+ /**
22
+ * The two `tts` fields that decide between direct synthesis and synthesizing
23
+ * the model's reply. `mode` wins; `useAiResponse` is the legacy spelling.
24
+ */
25
+ export type RequestKindTTSInput = {
26
+ enabled?: boolean;
27
+ mode?: TTSSynthesisMode;
28
+ useAiResponse?: boolean;
22
29
  };
@@ -7,12 +7,13 @@ import type { ClaudeLimitSnapshot } from "./subscription.js";
7
7
  import type { JsonValue } from "./common.js";
8
8
  import type { TerminalAgentModeOption, TerminalAgentModeVersion } from "./agentMode.js";
9
9
  import type { Content, ImageWithAltText } from "./content.js";
10
- import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
10
+ import type { ChatMessage, ConversationMemoryConfig, ToolReplayMode } from "./conversation.js";
11
+ import type { MemoryCallOptions } from "./memory.js";
11
12
  import type { EvaluationData } from "./evaluation.js";
12
13
  import type { MiddlewareFactoryOptions, OnFinishCallback, OnErrorCallback } from "./middleware.js";
13
14
  import type { DirectorModeOptions, DirectorSegment, VideoGenerationResult, VideoOutputOptions } from "./multimodal.js";
14
15
  import type { PPTGenerationResult, PPTOutputOptions } from "./ppt.js";
15
- import type { TTSOptions, TTSResult } from "./tts.js";
16
+ import type { TTSOptions, TTSResult, TTSSynthesisMode } from "./tts.js";
16
17
  import type { STTOptions, STTResult } from "./stt.js";
17
18
  import type { AvatarOptions, AvatarResult } from "./avatar.js";
18
19
  import type { MusicOptions, MusicResult } from "./music.js";
@@ -577,23 +578,48 @@ export type GenerateOptions = {
577
578
  * - `"required"`: the model must call at least one tool
578
579
  * - `{ type: "tool", toolName: string }`: the model must call the specified tool
579
580
  *
580
- * Note: When used without `prepareStep`, this applies to **every step** in the
581
- * `maxSteps` loop. Using `"required"` or `{ type: "tool" }` without `prepareStep`
582
- * will cause infinite tool calls until `maxSteps` is exhausted.
581
+ * A forced choice (`"required"` or a named tool) is applied only to the
582
+ * first `toolChoiceSteps` steps of the `maxSteps` loop (default 1) and the
583
+ * model is then free to answer. Holding a forced choice on every step would
584
+ * compel a tool call on every step, and the loop would only end when
585
+ * `maxSteps` ran out. `"auto"` and `"none"` are applied unchanged on every
586
+ * step. Vertex, Google AI Studio and Bedrock do not honour `toolChoice`.
583
587
  */
584
588
  toolChoice?: ToolChoice<Record<string, Tool>>;
589
+ /**
590
+ * How many leading steps a forced `toolChoice` (`"required"` or a named
591
+ * tool) stays in force; from that step on the model chooses (`"auto"`).
592
+ * A non-negative integer, default 1. `0` never forces. Ignored for
593
+ * `"auto"` and `"none"`. A `prepareStep` result that names a `toolChoice`
594
+ * overrides this for that step.
595
+ */
596
+ toolChoiceSteps?: number;
597
+ /**
598
+ * How this request replays the session's stored tool steps
599
+ * (`tool_call` / `tool_result` rows in `conversationMessages`) into the
600
+ * prompt: `"full"` (real tool-call / tool-result turns), `"marker"` (a
601
+ * compact `[called <tool> → ok]` line per call, the default) or `"off"`.
602
+ * Overrides `conversationMemory.replayToolSteps` for this request.
603
+ */
604
+ replayToolSteps?: ToolReplayMode;
585
605
  /**
586
606
  * Optional callback that runs before each step in a multi-step generation.
587
- * Allows dynamically changing `toolChoice` and available tools per step.
588
607
  *
589
- * This is the recommended way to enforce specific tool calls on certain steps
590
- * while allowing the model freedom on others.
608
+ * Honoured field of the result: `toolChoice`, applied to that step only
609
+ * and taking precedence over `toolChoice` / `toolChoiceSteps`.
610
+ *
611
+ * Not honoured — accepted for source compatibility with the former Vercel
612
+ * AI SDK `experimental_prepareStep` shape, but ignored by every native
613
+ * loop: `model` (the turn's model cannot change mid-loop) and
614
+ * `experimental_activeTools` (tool visibility is fixed for the turn; use
615
+ * `toolFilter` / `excludeTools` instead).
591
616
  *
592
- * Maps to Vercel AI SDK's `experimental_prepareStep`.
617
+ * `steps` carries one record per completed step (content, text, tool calls,
618
+ * tool results, finish reason, usage); `model` is the resolved model id.
593
619
  *
594
620
  * @example Force a specific tool on step 0, then switch to auto:
595
621
  * ```typescript
596
- * prepareStep: ({ stepNumber, steps }) => {
622
+ * prepareStep: async ({ stepNumber }) => {
597
623
  * if (stepNumber === 0) {
598
624
  * return {
599
625
  * toolChoice: { type: 'tool', toolName: 'myTool' }
@@ -602,8 +628,6 @@ export type GenerateOptions = {
602
628
  * return { toolChoice: 'auto' };
603
629
  * }
604
630
  * ```
605
- *
606
- * @see https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text#parameters
607
631
  */
608
632
  prepareStep?: (options: {
609
633
  steps: StepResult<Record<string, Tool>>[];
@@ -761,22 +785,9 @@ export type GenerateOptions = {
761
785
  * Override the global memory SDK behavior for this specific call.
762
786
  * All flags default to `true` when the global memory SDK is enabled.
763
787
  * If the global memory SDK is disabled, these flags have no effect.
764
- *
788
+ * Shared with `StreamOptions` — see `MemoryCallOptions`.
765
789
  */
766
- memory?: {
767
- /** Master toggle for this call. When false, both read and write are skipped. Defaults to true. */
768
- enabled?: boolean;
769
- /** Whether to read condensed memory and prepend to prompt. Defaults to true. */
770
- read?: boolean;
771
- /** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
772
- write?: boolean;
773
- /**
774
- * Additional users whose memory should be retrieved/stored alongside the primary user.
775
- * Each entry can override the condensation prompt and maxWords for that user.
776
- * Primary user is still determined by context.userId.
777
- */
778
- additionalUsers?: AdditionalMemoryUser[];
779
- };
790
+ memory?: MemoryCallOptions;
780
791
  /**
781
792
  * PII detection — scans and optionally redacts PII from input before the LLM call.
782
793
  * @example { enabled: true, action: "redact", detectTypes: ["ssn", "email"] }
@@ -847,7 +858,11 @@ export type AdditionalMemoryUser = {
847
858
  read?: boolean;
848
859
  /** Whether to write conversation into this user's memory. Defaults to true. */
849
860
  write?: boolean;
850
- /** Custom condensation prompt for this user. Overrides the default Hippocampus prompt. */
861
+ /**
862
+ * Custom condensation prompt for this user. Overrides the instance prompt.
863
+ * Must contain `{{OLD_MEMORY}}` and `{{NEW_CONTENT}}`; a template missing
864
+ * either is logged and this owner falls back to the instance prompt.
865
+ */
851
866
  prompt?: string;
852
867
  /** Max words for this user's condensed memory. Overrides the default maxWords. */
853
868
  maxWords?: number;
@@ -1359,33 +1374,19 @@ export type TextGenerationOptions = {
1359
1374
  * - `"required"`: the model must call at least one tool
1360
1375
  * - `{ type: "tool", toolName: string }`: the model must call the specified tool
1361
1376
  *
1362
- * Note: When used without `prepareStep`, this applies to **every step** in the
1363
- * `maxSteps` loop. Using `"required"` or `{ type: "tool" }` without `prepareStep`
1364
- * will cause infinite tool calls until `maxSteps` is exhausted.
1377
+ * A forced choice is applied only to the first `toolChoiceSteps` steps
1378
+ * (default 1); see GenerateOptions.toolChoice.
1365
1379
  */
1366
1380
  toolChoice?: ToolChoice<Record<string, Tool>>;
1381
+ /** Leading steps a forced `toolChoice` stays in force (default 1). See GenerateOptions.toolChoiceSteps. */
1382
+ toolChoiceSteps?: number;
1383
+ /** How stored tool steps are replayed into the prompt. See GenerateOptions.replayToolSteps. */
1384
+ replayToolSteps?: ToolReplayMode;
1367
1385
  /**
1368
1386
  * Optional callback that runs before each step in a multi-step generation.
1369
- * Allows dynamically changing `toolChoice` and available tools per step.
1370
- *
1371
- * This is the recommended way to enforce specific tool calls on certain steps
1372
- * while allowing the model freedom on others.
1373
- *
1374
- * Maps to Vercel AI SDK's `experimental_prepareStep`.
1375
- *
1376
- * @example Force a specific tool on step 0, then switch to auto:
1377
- * ```typescript
1378
- * prepareStep: ({ stepNumber, steps }) => {
1379
- * if (stepNumber === 0) {
1380
- * return {
1381
- * toolChoice: { type: 'tool', toolName: 'myTool' }
1382
- * };
1383
- * }
1384
- * return { toolChoice: 'auto' };
1385
- * }
1386
- * ```
1387
- *
1388
- * @see https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text#parameters
1387
+ * Only a returned `toolChoice` is honoured (for that step); `model` and
1388
+ * `experimental_activeTools` are accepted but ignored. See
1389
+ * GenerateOptions.prepareStep.
1389
1390
  */
1390
1391
  prepareStep?: (options: {
1391
1392
  steps: StepResult<Record<string, Tool>>[];
@@ -1400,9 +1401,19 @@ export type TextGenerationOptions = {
1400
1401
  /**
1401
1402
  * Text-to-Speech (TTS) configuration
1402
1403
  *
1403
- * Enable audio generation from text. Behavior depends on useAiResponse flag:
1404
- * - When useAiResponse is false/undefined (default): TTS synthesizes the input text directly
1405
- * - When useAiResponse is true: TTS synthesizes the AI-generated response
1404
+ * Enable audio generation from text. Behavior depends on `tts.mode`
1405
+ * (explicit) or the legacy `useAiResponse` flag:
1406
+ * - `mode: "direct"` / useAiResponse false or undefined (default): TTS
1407
+ * synthesizes the input text directly, with no LLM call — `usage` is
1408
+ * zero and `content` echoes the input. A direct request that also
1409
+ * carries LLM-shaped options (tools, systemPrompt, messages, schema,
1410
+ * conversationMemory) logs a warning, since those options have no effect
1411
+ * in this mode.
1412
+ * - `mode: "response"` / useAiResponse true: TTS synthesizes the
1413
+ * AI-generated response after generation completes.
1414
+ *
1415
+ * `result.ttsMetadata.mode` reports which one ran. Set `tts.sanitize` to
1416
+ * strip markdown, URLs and emoji before synthesis.
1406
1417
  *
1407
1418
  * @example Using input text (default)
1408
1419
  * ```typescript
@@ -1714,6 +1725,12 @@ export type TTSMetadata = {
1714
1725
  attempted: boolean;
1715
1726
  /** Whether TTS synthesis completed successfully. */
1716
1727
  success: boolean;
1728
+ /**
1729
+ * What was synthesized: the input text (`"direct"`) or the model's reply
1730
+ * (`"response"`). Set by `generate()`; `stream()` always synthesizes the
1731
+ * response and reports `"response"` where it records metadata.
1732
+ */
1733
+ mode?: TTSSynthesisMode;
1717
1734
  /** Structured synthesis error details, present only when synthesis failed. */
1718
1735
  error?: {
1719
1736
  code: string;
@@ -1747,6 +1764,40 @@ export type ModelAliasConfig = {
1747
1764
  export type GenerateOptionsNormalized = GenerateOptions & {
1748
1765
  input: NonNullable<GenerateOptions["input"]>;
1749
1766
  };
1767
+ /**
1768
+ * The per-step hook shape every native loop calls: the narrowest common form
1769
+ * of the `prepareStep` callbacks declared on GenerateOptions, StreamOptions
1770
+ * and TextGenerationOptions, each of which is assignable to it. The loops
1771
+ * read only `toolChoice` off the result.
1772
+ */
1773
+ export type NativeLoopPrepareStep = (options: {
1774
+ steps: StepResult<Record<string, Tool>>[];
1775
+ stepNumber: number;
1776
+ maxSteps: number;
1777
+ model: LanguageModel;
1778
+ }) => PromiseLike<{
1779
+ toolChoice?: ToolChoice<Record<string, Tool>>;
1780
+ } | undefined>;
1781
+ /**
1782
+ * What `resolveStepToolChoice` needs to decide one step's tool choice.
1783
+ * Shared by the native generate loop and both stream loops so the
1784
+ * `toolChoiceSteps` / `prepareStep` rule cannot drift between them.
1785
+ */
1786
+ export type StepToolChoiceInput = {
1787
+ /** The turn's resolved tool choice in NeuroLink shape, or undefined. */
1788
+ base: unknown;
1789
+ /** Zero-based step index. */
1790
+ step: number;
1791
+ toolChoiceSteps?: number;
1792
+ prepareStep?: NativeLoopPrepareStep;
1793
+ /** Records of the steps completed so far, handed to `prepareStep`. */
1794
+ steps: StepResult<Record<string, Tool>>[];
1795
+ maxSteps: number;
1796
+ /** The resolved model id (always a string at runtime), handed to `prepareStep`. */
1797
+ model: string;
1798
+ /** The turn's abort signal; a pending `prepareStep` is released when it fires. */
1799
+ abortSignal?: AbortSignal;
1800
+ };
1750
1801
  /**
1751
1802
  * Inputs to the shared native generate loop (`core/nativeGenerateLoop.ts`).
1752
1803
  * One loop serves every provider whose delegating model exposes a v3-shaped
@@ -1769,7 +1820,14 @@ export type NativeGenerateLoopArgs = {
1769
1820
  tools?: Array<Record<string, unknown>>;
1770
1821
  /** Registered tools, used to execute a call the model asks for. */
1771
1822
  toolsRecord: Record<string, unknown>;
1823
+ /** The turn's resolved tool choice, applied per step via `resolveStepToolChoice`. */
1772
1824
  toolChoice?: unknown;
1825
+ /** Leading steps a forced `toolChoice` stays in force (default 1). */
1826
+ toolChoiceSteps?: number;
1827
+ /** Caller's per-step hook; only its `toolChoice` is honoured. */
1828
+ prepareStep?: NativeLoopPrepareStep;
1829
+ /** Resolved model id, handed to `prepareStep` as its `model` argument. */
1830
+ modelId: string;
1773
1831
  responseFormat?: Record<string, unknown>;
1774
1832
  providerOptions?: Record<string, Record<string, unknown>>;
1775
1833
  maxSteps: number;
@@ -1780,6 +1838,12 @@ export type NativeGenerateLoopArgs = {
1780
1838
  toolTimeoutMs?: number | null;
1781
1839
  /** Wraps one step: retry ladder plus provider error classification. */
1782
1840
  runStep: (call: () => Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>;
1841
+ /**
1842
+ * Called for a tool call the loop rejects before any execute runs (unknown
1843
+ * tool, arguments the schema rejected), so the provider can emit the
1844
+ * `tool:start` / `tool:end` pair no executor will.
1845
+ */
1846
+ onRejectedToolCall?: (toolName: string, error: string, toolCallId: string) => void;
1783
1847
  };
1784
1848
  export type NativeGenerateLoopResult = {
1785
1849
  text: string;
@@ -187,7 +187,12 @@ export type AgenticLoopAdapter<TConversation = unknown, TRaw = unknown> = {
187
187
  readonly resolveToolOnMiss?: (name: string) => {
188
188
  execute: (args: Record<string, unknown>, opts: unknown) => Promise<unknown>;
189
189
  } | undefined;
190
- buildStepRequest(conversation: TConversation, step: number): AgenticLoopStepRequest;
190
+ /**
191
+ * May return a promise: an adapter that consults a caller's `prepareStep`
192
+ * hook for the step's tool choice has to await it before it can build the
193
+ * request. The engine awaits the result either way.
194
+ */
195
+ buildStepRequest(conversation: TConversation, step: number): AgenticLoopStepRequest | Promise<AgenticLoopStepRequest>;
191
196
  executeStep(request: AgenticLoopStepRequest, channel: {
192
197
  push(chunk: AgenticLoopChunk): void;
193
198
  }, signal: AbortSignal): Promise<AgenticLoopStepResult<TRaw>>;
@@ -255,7 +260,7 @@ export type AnthropicLoopAdapterConfig<TMessage = Anthropic.Messages.MessagePara
255
260
  * type carries `stream?: boolean` fails to match `stream: true` for a field
256
261
  * the adapter is about to overwrite.
257
262
  */
258
- buildParams: (conversation: TMessage[], step: number) => Anthropic.Messages.MessageCreateParamsNonStreaming;
263
+ buildParams: (conversation: TMessage[], step: number) => Anthropic.Messages.MessageCreateParamsNonStreaming | Promise<Anthropic.Messages.MessageCreateParamsNonStreaming>;
259
264
  /** The turn's live tool record, used for deferred-catalog resolution. */
260
265
  toolsRecord: Record<string, Tool>;
261
266
  /**