@juspay/neurolink 12.47.14 → 12.48.1

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 +2 -2
  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 +2 -1
  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
@@ -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
  /**
@@ -19,6 +19,9 @@
19
19
  * re-export) keep their original public names — only their definitions
20
20
  * move from `import("@juspay/hippocampus").Foo` to local structural form.
21
21
  */
22
+ import type { TokenUsage } from "./analytics.js";
23
+ import type { AdditionalMemoryUser } from "./generate.js";
24
+ import type { NeurolinkCredentials } from "./providers.js";
22
25
  export type MemorySqliteStorageConfig = {
23
26
  type: "sqlite";
24
27
  /** Path to SQLite file. Default: ./data/hippocampus.sqlite */
@@ -56,17 +59,159 @@ export type HippocampusAddOptions = {
56
59
  prompt?: string;
57
60
  maxWords?: number;
58
61
  };
62
+ /**
63
+ * The exact request shape Hippocampus's condenser sends to `generate()`
64
+ * (`@juspay/hippocampus` `src/client.ts`, the `neurolink.generate({...})`
65
+ * call inside `add()`). A full `NeuroLink` instance satisfies this
66
+ * structurally; so does any host object that can answer a plain text prompt.
67
+ */
68
+ export type HippocampusCondenserGenerateOptions = {
69
+ input: {
70
+ text: string;
71
+ };
72
+ provider?: string;
73
+ model?: string;
74
+ temperature?: number;
75
+ disableTools?: boolean;
76
+ };
77
+ /** The only field Hippocampus reads back from the condensation call. */
78
+ export type HippocampusCondenserResult = {
79
+ content?: string;
80
+ };
81
+ /**
82
+ * Minimal `generate()`-bearing object Hippocampus can use for condensation
83
+ * instead of constructing its own bare `new NeuroLink()`.
84
+ */
85
+ export type HippocampusNeurolinkLike = {
86
+ generate: (options: HippocampusCondenserGenerateOptions) => Promise<HippocampusCondenserResult>;
87
+ };
88
+ /**
89
+ * Condenser settings forwarded to Hippocampus.
90
+ *
91
+ * `provider` / `model` / `temperature` select the condensation model.
92
+ * `credentials` and `instance` are read by `@juspay/hippocampus` ≥0.2.0;
93
+ * the peer floor is 0.2.1, which also substitutes the condensation
94
+ * placeholders literally (user content containing `$&` no longer corrupts
95
+ * the prompt). NeuroLink always fills `instance` itself — a dedicated
96
+ * child instance that carries this instance's `credentials` — unless the
97
+ * host supplies one, so condensation stops depending on provider keys being
98
+ * present in `process.env`.
99
+ */
100
+ export type HippocampusNeurolinkConfig = {
101
+ provider?: string;
102
+ model?: string;
103
+ temperature?: number;
104
+ /**
105
+ * Credentials for the condenser's own NeuroLink instance. Defaults to the
106
+ * parent `new NeuroLink({ credentials })` value.
107
+ */
108
+ credentials?: NeurolinkCredentials;
109
+ /**
110
+ * Host-managed condenser. When set, NeuroLink does not build a child
111
+ * instance and passes this object through untouched.
112
+ */
113
+ instance?: HippocampusNeurolinkLike;
114
+ };
59
115
  /** Constructor config accepted by the Hippocampus class. */
60
116
  export type HippocampusConfig = {
61
117
  storage?: HippocampusStorageConfig;
62
118
  prompt?: string;
63
- neurolink?: {
64
- provider?: string;
65
- model?: string;
66
- temperature?: number;
67
- };
119
+ neurolink?: HippocampusNeurolinkConfig;
68
120
  maxWords?: number;
69
121
  };
122
+ /**
123
+ * One completed turn as seen by the memory write hooks. `usage`,
124
+ * `finishReason` and `toolsUsed` are best-effort: the generate path has the
125
+ * full result, the stream path only knows the provider, the accumulated
126
+ * text and this request's own stream result's tool names.
127
+ */
128
+ export type MemoryTurn = {
129
+ /** The caller's original prompt, before memory context was prepended. */
130
+ prompt: string;
131
+ /** The assistant's final text, trimmed. */
132
+ response: string;
133
+ /** Primary memory owner — `context.userId`. */
134
+ userId: string;
135
+ /** `context.sessionId`, when the call carried one. */
136
+ sessionId?: string;
137
+ provider?: string;
138
+ model?: string;
139
+ /** Names of tools that ran during the turn. */
140
+ toolsUsed?: string[];
141
+ /** Token usage — generate path only. */
142
+ usage?: TokenUsage;
143
+ /** Provider finish reason — generate path only. */
144
+ finishReason?: string;
145
+ };
146
+ /**
147
+ * Gate that decides whether a turn is written to memory at all. Runs inside
148
+ * the deferred background write, never on the response path. Returning
149
+ * `false` skips every `add()` for the turn (primary owner and
150
+ * `additionalUsers`). A thrown error is logged and treated as `false`.
151
+ */
152
+ export type MemoryShouldWriteHook = (turn: MemoryTurn) => boolean | Promise<boolean>;
153
+ /**
154
+ * Transform (or veto) the exact text handed to `Hippocampus.add()`. Receives
155
+ * the default `"User: …\nAssistant: …"` rendering; return a replacement
156
+ * string, or `null` to skip the write. A thrown error is logged and the
157
+ * write is skipped — a missed memory is recoverable, a polluted condensed
158
+ * summary is not.
159
+ */
160
+ export type MemoryBeforeStoreHook = (content: string, turn: MemoryTurn) => string | null | Promise<string | null>;
161
+ /**
162
+ * Write-side hooks accepted both per call (`generate({ memory })` /
163
+ * `stream({ memory })`) and on the instance
164
+ * (`conversationMemory.memory`). When both are set for the same hook, the
165
+ * per-call one wins for that call.
166
+ */
167
+ export type MemoryWriteHooks = {
168
+ shouldWrite?: MemoryShouldWriteHook;
169
+ onBeforeStore?: MemoryBeforeStoreHook;
170
+ };
171
+ /**
172
+ * Per-call `memory` block shared by `GenerateOptions` and `StreamOptions`.
173
+ *
174
+ * Overrides the instance-level memory behaviour for one call. All flags
175
+ * default to `true` when the instance memory SDK is enabled; when it is
176
+ * disabled this block has no effect.
177
+ */
178
+ export type MemoryCallOptions = MemoryWriteHooks & {
179
+ /** Master toggle for this call. When false, both read and write are skipped. Defaults to true. */
180
+ enabled?: boolean;
181
+ /** Whether to read condensed memory and prepend to prompt. Defaults to true. */
182
+ read?: boolean;
183
+ /** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
184
+ write?: boolean;
185
+ /**
186
+ * Additional users whose memory should be retrieved/stored alongside the primary user.
187
+ * Each entry can override the condensation prompt and maxWords for that user.
188
+ * Primary user is still determined by context.userId.
189
+ */
190
+ additionalUsers?: AdditionalMemoryUser[];
191
+ /**
192
+ * Condensation prompt for the primary owner on this call. Precedence:
193
+ * per-call > instance `conversationMemory.memory.prompt` > Hippocampus
194
+ * default. Must contain `{{OLD_MEMORY}}` and `{{NEW_CONTENT}}`; a template
195
+ * missing either is logged and the instance prompt is used instead. An
196
+ * empty or whitespace-only string counts as unset.
197
+ */
198
+ prompt?: string;
199
+ /** Max words for the primary owner's condensed memory on this call. */
200
+ maxWords?: number;
201
+ };
202
+ /** Placeholder names a condensation prompt template can carry. */
203
+ export type CondensationPlaceholder = "OLD_MEMORY" | "NEW_CONTENT" | "MAX_WORDS";
204
+ /**
205
+ * Result of `validateCondensationPrompt()`. `missing` lists every absent
206
+ * placeholder; `fatal` is the subset whose absence breaks memory
207
+ * (`OLD_MEMORY`: each turn overwrites the summary; `NEW_CONTENT`: memory
208
+ * never grows). A missing `MAX_WORDS` only loses the word cap.
209
+ */
210
+ export type CondensationPromptValidation = {
211
+ valid: boolean;
212
+ missing: CondensationPlaceholder[];
213
+ fatal: CondensationPlaceholder[];
214
+ };
70
215
  /**
71
216
  * Subset of the @juspay/hippocampus client surface that NeuroLink core
72
217
  * actually calls. Defining this locally lets the initializer / SDK code
@@ -78,13 +223,36 @@ export type HippocampusLike = {
78
223
  delete: (ownerId: string) => Promise<void>;
79
224
  close: () => Promise<void>;
80
225
  };
226
+ /** Builds a Hippocampus-compatible client from the config NeuroLink assembled. */
227
+ export type HippocampusClientFactory = (config: HippocampusConfig) => HippocampusLike;
81
228
  /**
82
- * Consumer-facing memory config. Same shape as before — the `enabled`
83
- * flag toggles activation while the rest is passed straight through to
84
- * the Hippocampus constructor when the optional package is installed.
229
+ * Consumer-facing memory config. The `enabled` flag toggles activation; the
230
+ * `HippocampusConfig` part is passed to the Hippocampus constructor when the
231
+ * optional package is installed. The remaining fields are NeuroLink-side and
232
+ * never reach Hippocampus.
85
233
  */
86
- export type HippocampusMemory = HippocampusConfig & {
234
+ export type HippocampusMemory = HippocampusConfig & MemoryWriteHooks & {
87
235
  enabled?: boolean;
236
+ /**
237
+ * Host-managed Hippocampus client (or any object with the same
238
+ * `add/get/delete/close` surface). When set, NeuroLink skips loading and
239
+ * constructing `@juspay/hippocampus` and uses this instance directly.
240
+ * `add()` and `get()` are what core calls and must both be functions;
241
+ * a client missing either disables memory with an `error` log.
242
+ *
243
+ * As a factory it receives the `HippocampusConfig` NeuroLink assembled —
244
+ * including the credentialed condenser under `neurolink.instance` — so a
245
+ * host can construct its own client (a subclass, a different version, a
246
+ * test double) from exactly what `new Hippocampus(config)` would get.
247
+ */
248
+ client?: HippocampusLike | HippocampusClientFactory;
249
+ /**
250
+ * When true, a condensation prompt template (instance `prompt` or
251
+ * `HC_CONDENSATION_PROMPT`) missing `{{OLD_MEMORY}}` or `{{NEW_CONTENT}}`
252
+ * makes the constructor throw instead of the default disable-and-log
253
+ * (at `error`). An empty or whitespace-only template counts as unset.
254
+ */
255
+ strictPrompt?: boolean;
88
256
  };
89
257
  /**
90
258
  * Shape of the dynamically-required `@juspay/hippocampus` module surface
@@ -1,8 +1,9 @@
1
- import type { DeferredUsage, NeuroLinkEvents, TypedEventEmitter } from "./common.js";
1
+ import type { DeferredUsage } from "./common.js";
2
2
  import type { JSONSchema7 } from "./middleware.js";
3
3
  import type { LanguageModelV3, LanguageModelV3CallOptions } from "./middleware.js";
4
4
  import type { StreamOptions } from "./stream.js";
5
5
  import type { Tool } from "./tools.js";
6
+ import type { StepResult } from "./providers.js";
6
7
  export type OpenAICompatChatRole = "system" | "user" | "assistant" | "tool";
7
8
  export type OpenAICompatTextContent = {
8
9
  type: "text";
@@ -223,6 +224,8 @@ export type ToolExecutionSummaryInternal = {
223
224
  error?: string;
224
225
  startTime: Date;
225
226
  endTime: Date;
227
+ /** Zero-based loop step that ran this tool, when the loop tracks one. */
228
+ stepIndex?: number;
226
229
  };
227
230
  export type StreamLoopArgs = {
228
231
  maxSteps: number;
@@ -233,11 +236,16 @@ export type StreamLoopArgs = {
233
236
  options: StreamOptions;
234
237
  conversation: OpenAICompatChatMessage[];
235
238
  openAITools: OpenAICompatChatTool[] | undefined;
236
- openAIToolChoice: OpenAICompatToolChoiceWire | undefined;
239
+ /**
240
+ * The wire `tool_choice` for one step. A function rather than a value
241
+ * because a forced choice applies only to the first `toolChoiceSteps`
242
+ * steps, and a caller's `prepareStep` hook may override it per step;
243
+ * `steps` is the record of the steps completed so far, handed to that hook.
244
+ */
245
+ stepToolChoice: (step: number, steps: StepResult<Record<string, Tool>>[]) => Promise<OpenAICompatToolChoiceWire | undefined>;
237
246
  toolsRecord: Record<string, Tool>;
238
247
  /** Wire → registered tool-name map when sanitization was needed (see buildWireToolNameMaps). */
239
248
  toolNameFromWire?: Map<string, string>;
240
- emitter: TypedEventEmitter<NeuroLinkEvents> | undefined;
241
249
  toolsUsed: string[];
242
250
  toolExecutionSummaries: ToolExecutionSummaryInternal[];
243
251
  pushChunk: (chunk: OpenAICompatStreamChunk) => void;
@@ -9,9 +9,10 @@ import type { TokenUsage } from "./analytics.js";
9
9
  import type { JsonValue, UnknownRecord } from "./common.js";
10
10
  import type { TerminalAgentModeOption, TerminalAgentModeVersion } from "./agentMode.js";
11
11
  import type { Content, ImageWithAltText } from "./content.js";
12
- import type { ChatMessage } from "./conversation.js";
12
+ import type { ChatMessage, ToolReplayMode } from "./conversation.js";
13
13
  import type { StreamNoOutputSentinel } from "./noOutputSentinel.js";
14
- import type { AdditionalMemoryUser, GenerateStopReason, TTSMetadata, ToolExecutionCaptureOptions } from "./generate.js";
14
+ import type { GenerateStopReason, TTSMetadata, ToolExecutionCaptureOptions } from "./generate.js";
15
+ import type { MemoryCallOptions } from "./memory.js";
15
16
  import type { AIModelProviderConfig, NeurolinkCredentials } from "./providers.js";
16
17
  import type { TTSChunk, TTSOptions, TTSResult } from "./tts.js";
17
18
  import type { STTOptions, STTResult } from "./stt.js";
@@ -580,10 +581,18 @@ export type StreamOptions = {
580
581
  /**
581
582
  * Tool choice configuration for streaming generation.
582
583
  * Mirrors generate() so translated/fallback requests can preserve forced tool use.
584
+ * A forced choice (`"required"` or a named tool) is applied only to the
585
+ * first `toolChoiceSteps` steps (default 1); see GenerateOptions.toolChoice.
583
586
  */
584
587
  toolChoice?: ToolChoice<Record<string, Tool>>;
588
+ /** Leading steps a forced `toolChoice` stays in force (default 1). See GenerateOptions.toolChoiceSteps. */
589
+ toolChoiceSteps?: number;
590
+ /** How stored tool steps are replayed into the prompt. See GenerateOptions.replayToolSteps. */
591
+ replayToolSteps?: ToolReplayMode;
585
592
  /**
586
593
  * Optional callback that runs before each stream step in a multi-step generation.
594
+ * Only a returned `toolChoice` is honoured (for that step); `activeTools` is
595
+ * accepted but ignored. See GenerateOptions.prepareStep.
587
596
  */
588
597
  prepareStep?: (options: {
589
598
  steps: StepResult<Record<string, Tool>>[];
@@ -739,21 +748,9 @@ export type StreamOptions = {
739
748
  * Override the global memory SDK behavior for this specific call.
740
749
  * All flags default to `true` when the global memory SDK is enabled.
741
750
  * If the global memory SDK is disabled, these flags have no effect.
751
+ * Shared with `GenerateOptions` — see `MemoryCallOptions`.
742
752
  */
743
- memory?: {
744
- /** Master toggle for this call. When false, both read and write are skipped. Defaults to true. */
745
- enabled?: boolean;
746
- /** Whether to read condensed memory and prepend to prompt. Defaults to true. */
747
- read?: boolean;
748
- /** Whether to write (add/condense) the conversation into memory after completion. Defaults to true. */
749
- write?: boolean;
750
- /**
751
- * Additional users whose memory should be retrieved/stored alongside the primary user.
752
- * Each entry can override the condensation prompt and maxWords for that user.
753
- * Primary user is still determined by context.userId.
754
- */
755
- additionalUsers?: AdditionalMemoryUser[];
756
- };
753
+ memory?: MemoryCallOptions;
757
754
  /** PII detection — scans and optionally redacts PII from input before the LLM call. */
758
755
  piiDetection?: {
759
756
  enabled?: boolean;