@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
@@ -15,6 +15,7 @@ import { withTimeout } from "../utils/errorHandling.js";
15
15
  import { buildContextFromPointer, getEffectiveTokenThreshold, } from "../utils/conversationMemory.js";
16
16
  import { runWithCurrentLangfuseContext } from "../services/server/ai/observability/instrumentation.js";
17
17
  import { logger } from "../utils/logger.js";
18
+ import { extractIsErrorText } from "../utils/toolResultStatus.js";
18
19
  import { deserializeConversation, encodeStoredMessages, getSessionMessagesKey, MESSAGES_KEY_SUFFIX, isSessionMessagesKey, parseStoredMessages, serializeConversationMetadata, usesSplitMessageStorage, getNormalizedConfig, getPooledRedisClient, getSessionKey, getUserSessionsKey, releasePooledRedisClient, scanKeys, serializeConversation, } from "../utils/redis.js";
19
20
  const redisTracer = tracers.redis;
20
21
  const REDIS_TIMEOUT_MS = 5000;
@@ -1602,11 +1603,17 @@ User message: "${userMessage}"`;
1602
1603
  ? { stepIndex: Number(toolResult.stepIndex) }
1603
1604
  : {}),
1604
1605
  };
1605
- // Build result — success/error metadata only, NOT the output data
1606
+ // Build result — success/error metadata only, NOT the output data.
1607
+ // An `isError: true` payload the tool RETURNED (converted throw,
1608
+ // breaker refusal, MCP failure) is a failure as much as a thrown one,
1609
+ // and is flagged here so replay never reports it as "→ ok".
1610
+ const resultError = toolResult.error !== undefined && toolResult.error !== null
1611
+ ? String(toolResult.error)
1612
+ : extractIsErrorText(toolResultValue);
1606
1613
  const result = {
1607
- success: !toolResult.error,
1614
+ success: resultError === undefined,
1608
1615
  // result.result intentionally NOT stored — inferred from content at read time
1609
- error: toolResult.error ? String(toolResult.error) : undefined,
1616
+ error: resultError,
1610
1617
  };
1611
1618
  const toolResultMessage = {
1612
1619
  id: randomUUID(),
@@ -1,4 +1,23 @@
1
- import type { RequestKind, RequestKindInput } from "../types/index.js";
1
+ import type { RequestKind, RequestKindInput, RequestKindTTSInput, TTSSynthesisMode } from "../types/index.js";
2
+ /**
3
+ * Which text a `generate({ tts })` request synthesizes. An explicit
4
+ * `tts.mode` wins; otherwise the legacy `useAiResponse` flag decides, and the
5
+ * absence of both means `"direct"` — the documented default that every
6
+ * "speak this text" caller relies on.
7
+ *
8
+ * Every precedence site (this module, `BaseProvider.synthesizeAIResponseIfNeeded`,
9
+ * `NeuroLink.attemptMCPGeneration`, the AI Studio generate() override) reads
10
+ * the answer from here so the rule cannot drift between them.
11
+ */
12
+ export declare function resolveTTSMode(tts: RequestKindTTSInput | undefined): TTSSynthesisMode;
13
+ /**
14
+ * True when TTS is enabled and the request wants the input text synthesized,
15
+ * bypassing the LLM turn. `enabled` is tested for truthiness, as the dispatch
16
+ * it replaced did and as `synthesizeAIResponseIfNeeded` still does: a caller
17
+ * off the declared type (`enabled: 1`, `"true"`) reaches the same branch at
18
+ * every site rather than being routed to a model call by one of them.
19
+ */
20
+ export declare function isDirectTTSRequest(tts: RequestKindTTSInput | undefined): boolean;
2
21
  /**
3
22
  * The dispatch decision for "what kind of request is this" — text, image,
4
23
  * video, music, avatar, direct TTS synthesis, or PPT generation — at the
@@ -18,9 +37,10 @@ import type { RequestKind, RequestKindInput } from "../types/index.js";
18
37
  * non-image output.format (json/structured/text) — this lets dual-mode
19
38
  * models like gemini-3.1-flash-image-preview still perform text or
20
39
  * structured generation when requested.
21
- * 3. tts.enabled without tts.useAiResponse — direct synthesis, bypassing
22
- * the LLM turn entirely (useAiResponse means the LLM's own text
23
- * response gets synthesized afterward, which is NOT this branch).
40
+ * 3. tts.enabled with `resolveTTSMode()` answering "direct" — i.e. no
41
+ * `mode: "response"` and no legacy `useAiResponse: true` — direct
42
+ * synthesis, bypassing the LLM turn entirely (response mode means the
43
+ * LLM's own text gets synthesized afterward, which is NOT this branch).
24
44
  * 4. otherwise, "text".
25
45
  */
26
46
  export declare function resolveRequestKind(options: RequestKindInput, modelName?: string): RequestKind;
@@ -1,4 +1,30 @@
1
1
  import { isImageGenerationModel } from "./constants.js";
2
+ /**
3
+ * Which text a `generate({ tts })` request synthesizes. An explicit
4
+ * `tts.mode` wins; otherwise the legacy `useAiResponse` flag decides, and the
5
+ * absence of both means `"direct"` — the documented default that every
6
+ * "speak this text" caller relies on.
7
+ *
8
+ * Every precedence site (this module, `BaseProvider.synthesizeAIResponseIfNeeded`,
9
+ * `NeuroLink.attemptMCPGeneration`, the AI Studio generate() override) reads
10
+ * the answer from here so the rule cannot drift between them.
11
+ */
12
+ export function resolveTTSMode(tts) {
13
+ if (tts?.mode === "direct" || tts?.mode === "response") {
14
+ return tts.mode;
15
+ }
16
+ return tts?.useAiResponse ? "response" : "direct";
17
+ }
18
+ /**
19
+ * True when TTS is enabled and the request wants the input text synthesized,
20
+ * bypassing the LLM turn. `enabled` is tested for truthiness, as the dispatch
21
+ * it replaced did and as `synthesizeAIResponseIfNeeded` still does: a caller
22
+ * off the declared type (`enabled: 1`, `"true"`) reaches the same branch at
23
+ * every site rather than being routed to a model call by one of them.
24
+ */
25
+ export function isDirectTTSRequest(tts) {
26
+ return Boolean(tts?.enabled) && resolveTTSMode(tts) === "direct";
27
+ }
2
28
  /**
3
29
  * The dispatch decision for "what kind of request is this" — text, image,
4
30
  * video, music, avatar, direct TTS synthesis, or PPT generation — at the
@@ -18,9 +44,10 @@ import { isImageGenerationModel } from "./constants.js";
18
44
  * non-image output.format (json/structured/text) — this lets dual-mode
19
45
  * models like gemini-3.1-flash-image-preview still perform text or
20
46
  * structured generation when requested.
21
- * 3. tts.enabled without tts.useAiResponse — direct synthesis, bypassing
22
- * the LLM turn entirely (useAiResponse means the LLM's own text
23
- * response gets synthesized afterward, which is NOT this branch).
47
+ * 3. tts.enabled with `resolveTTSMode()` answering "direct" — i.e. no
48
+ * `mode: "response"` and no legacy `useAiResponse: true` — direct
49
+ * synthesis, bypassing the LLM turn entirely (response mode means the
50
+ * LLM's own text gets synthesized afterward, which is NOT this branch).
24
51
  * 4. otherwise, "text".
25
52
  */
26
53
  export function resolveRequestKind(options, modelName) {
@@ -42,7 +69,7 @@ export function resolveRequestKind(options, modelName) {
42
69
  if (isImageGenerationModel(modelName) && !requestsNonImageOutput) {
43
70
  return "image";
44
71
  }
45
- if (options.tts?.enabled && !options.tts?.useAiResponse) {
72
+ if (isDirectTTSRequest(options.tts)) {
46
73
  return "tts-direct";
47
74
  }
48
75
  return "text";
@@ -23,6 +23,12 @@ export declare function serializeToolResult(result: unknown, maxChars: number):
23
23
  * Error-shaped tool results (MCP `isError`, `{error}` payloads) count as
24
24
  * failures even though the loop received them as normal returns.
25
25
  */
26
+ /**
27
+ * True for an `error` value that actually carries something — `true`, a
28
+ * non-empty string, or a non-empty object/array — never a falsy placeholder
29
+ * a tool sets on every call (`error: false`, `error: 0`, `error: {}`).
30
+ */
31
+ export declare function isMeaningfulError(error: unknown): boolean;
26
32
  export declare function isErrorShapedToolResult(result: unknown): boolean;
27
33
  /**
28
34
  * Per-call recorder for tool executions. See module doc.
@@ -45,6 +45,26 @@ export function serializeToolResult(result, maxChars) {
45
45
  * Error-shaped tool results (MCP `isError`, `{error}` payloads) count as
46
46
  * failures even though the loop received them as normal returns.
47
47
  */
48
+ /**
49
+ * True for an `error` value that actually carries something — `true`, a
50
+ * non-empty string, or a non-empty object/array — never a falsy placeholder
51
+ * a tool sets on every call (`error: false`, `error: 0`, `error: {}`).
52
+ */
53
+ export function isMeaningfulError(error) {
54
+ if (error === true) {
55
+ return true;
56
+ }
57
+ if (typeof error === "string") {
58
+ return error.length > 0;
59
+ }
60
+ if (Array.isArray(error)) {
61
+ return error.length > 0;
62
+ }
63
+ if (typeof error === "object" && error !== null) {
64
+ return Object.keys(error).length > 0;
65
+ }
66
+ return false;
67
+ }
48
68
  export function isErrorShapedToolResult(result) {
49
69
  if (!result || typeof result !== "object") {
50
70
  return false;
@@ -53,10 +73,12 @@ export function isErrorShapedToolResult(result) {
53
73
  if (record.isError === true) {
54
74
  return true;
55
75
  }
56
- if (record.error !== undefined && record.error !== null) {
76
+ if (isMeaningfulError(record.error)) {
57
77
  return true;
58
78
  }
59
- if (typeof record.status === "string") {
79
+ if (typeof record.status === "string" &&
80
+ record.success !== true &&
81
+ record.ok !== true) {
60
82
  return ["error", "failed", "failure", "fail"].includes(record.status.toLowerCase().trim());
61
83
  }
62
84
  return false;
@@ -101,6 +101,7 @@ export async function selectServersByDecision(userQuery, routableServers, decide
101
101
  questions[decisionKey(SERVER_NAMESPACE, index)] = serverQuestion(server);
102
102
  });
103
103
  const result = await decide({
104
+ site: "toolRouting",
104
105
  // Structured state rather than a prompt string: the vendor accepts JSON,
105
106
  // and naming the fields is what stops the model reading the catalogue as
106
107
  // part of the user's request.
@@ -24,6 +24,11 @@ export const MEDIA_HANDLER_CATALOG = [
24
24
  { kind: "stt", name: "deepgram" },
25
25
  { kind: "stt", name: "google-stt" },
26
26
  { kind: "stt", name: "azure-stt" },
27
+ // "elevenlabs" is reused across kinds on purpose — TTS primary, STT alias —
28
+ // the same way "vertex" is a TTS alias and the Video primary. Each kind has
29
+ // its own registry, so `stt: { provider: "elevenlabs" }` and
30
+ // `tts: { provider: "elevenlabs" }` resolve to different handlers.
31
+ { kind: "stt", name: "elevenlabs-stt", aliases: ["scribe", "elevenlabs"] },
27
32
  // --- Realtime ---
28
33
  { kind: "realtime", name: "openai-realtime" },
29
34
  { kind: "realtime", name: "gemini-live" },
@@ -449,6 +449,21 @@ const HAND_DESCRIPTORS = [
449
449
  // defaultHealthSweepPriority: all three feed generation fallback chains,
450
450
  // and a text-less provider must never be reachable from them.
451
451
  timeouts: { decideMs: 5000 },
452
+ // ADVISORY, not enforced: Jev's server refuses an over-limit request
453
+ // itself (measured by bisection — a 33,002-token state accepted, 33,003
454
+ // rejected), so this row exists for `decisionLimits()` to report, and the
455
+ // pre-flight check skips it. Refusing locally would turn a measured server
456
+ // ceiling into a client one that drifts from it silently. The number
457
+ // mirrors TYPESAFE_MAX_STATE_TOKENS in providers/typesafe.ts (which
458
+ // cannot be imported here without a provider → descriptor cycle); the
459
+ // decide suite pins the two equal. No question COUNT cap has been
460
+ // measured — 400 questions beside a near-ceiling state were accepted —
461
+ // because TypeSafe bounds a request by tokens (TYPESAFE_MAX_REQUEST_TOKENS,
462
+ // 64K on state plus all questions), not by count.
463
+ decisionLimits: {
464
+ maxStateTokens: 33_000,
465
+ advisory: true,
466
+ },
452
467
  setupUrl: "https://console.typesafe.ai/keys",
453
468
  },
454
469
  // Laya MUST stay after TypeSafe, XOR after Laya, Perplexity after XOR, and
@@ -157,7 +157,7 @@ export declare function createFileTools(registry: FileReferenceRegistry): {
157
157
  } | undefined;
158
158
  columns?: string[] | undefined;
159
159
  entry_path?: string | undefined;
160
- format?: "text" | "summary" | "detailed" | undefined;
160
+ format?: "text" | "detailed" | "summary" | undefined;
161
161
  }, {
162
162
  success: false;
163
163
  error: string | undefined;
package/dist/index.d.ts CHANGED
@@ -55,12 +55,14 @@ export { createSkillStore, FileSystemSkillStore, InMemorySkillStore, } from "./s
55
55
  export { S3SkillStore } from "./skills/skillStoreS3.js";
56
56
  export { RedisSkillStore } from "./skills/skillStoreRedis.js";
57
57
  export { createSkillTools } from "./skills/skillTools.js";
58
+ export { validateCondensationPrompt } from "./memory/condensationPrompt.js";
58
59
  export * from "./types/index.js";
59
60
  export { isAbortError } from "./utils/errorHandling.js";
60
61
  export { calculateCost, hasPricing } from "./utils/pricing.js";
61
62
  export { getAvailableProviders, getBestProvider, isValidProvider, } from "./utils/providerUtils.js";
62
63
  export { TTSProcessor } from "./utils/ttsProcessor.js";
63
64
  export { TTS_ERROR_CODES, TTSError } from "./utils/ttsProcessor.js";
65
+ export { prepareTextForSpeech, resolveSpeechSanitizeOptions, } from "./utils/speechText.js";
64
66
  export { VideoProcessor } from "./utils/videoProcessor.js";
65
67
  export { VIDEO_ERROR_CODES, VideoError } from "./utils/videoProcessor.js";
66
68
  export { AvatarProcessor } from "./utils/avatarProcessor.js";
@@ -68,7 +70,7 @@ export { AVATAR_ERROR_CODES, AvatarError } from "./utils/avatarProcessor.js";
68
70
  export { MusicProcessor } from "./utils/musicProcessor.js";
69
71
  export { MUSIC_ERROR_CODES, MusicError } from "./utils/musicProcessor.js";
70
72
  export { STTProcessor } from "./utils/sttProcessor.js";
71
- export { AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, SixtyDBTTS, SixtyDBTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler, AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler, BaseRealtimeHandler, GeminiLive, GeminiLiveHandler, OpenAIRealtime, OpenAIRealtimeHandler, RealtimeProcessor, RealtimeError, STTError, VoiceError, registerDefaultRealtimeHandlers, registerDefaultSTTHandlers, registerDefaultTTSHandlers, } from "./voice/index.js";
73
+ export { AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, SixtyDBTTS, SixtyDBTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler, AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, ElevenLabsSTT, ElevenLabsSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler, BaseRealtimeHandler, GeminiLive, GeminiLiveHandler, OpenAIRealtime, OpenAIRealtimeHandler, RealtimeProcessor, RealtimeError, STTError, VoiceError, registerDefaultRealtimeHandlers, registerDefaultSTTHandlers, registerDefaultTTSHandlers, } from "./voice/index.js";
72
74
  export { BeatovenMusic, BeatovenMusicHandler, ElevenLabsMusic, ElevenLabsMusicHandler, LyriaMusic, LyriaMusicHandler, registerDefaultMusicHandlers, ReplicateMusic, ReplicateMusicHandler, } from "./music/index.js";
73
75
  export { DIDAvatar, DIDAvatarHandler, HeyGenAvatar, HeyGenAvatarHandler, registerDefaultAvatarHandlers, ReplicateAvatar, ReplicateAvatarHandler, } from "./avatar/index.js";
74
76
  export { isVertexVideoConfigured, KlingVideoHandler, registerDefaultVideoHandlers, ReplicateVideoHandler, RunwayVideoHandler, VertexVideoHandler, } from "./adapters/video/index.js";
@@ -417,7 +419,8 @@ export { classifyProviderError, ModelPool, createDefaultRequestRouter, Classifie
417
419
  * if (team && team.confidence > 0.7) { route(team.choice); }
418
420
  * ```
419
421
  */
420
- export { decisionBooleanConfidence, decisionKey, gateDecisionBoolean, readDecisionBoolean, readDecisionChoice, readDecisionScore, servesInferenceKind, } from "./utils/decisionAnswers.js";
422
+ export { decisionBooleanConfidence, decisionKey, gateDecisionBoolean, HOST_DECISION_NAMESPACE, isDecisionQuestion, readDecisionBoolean, readDecisionChoice, readDecisionScore, servesInferenceKind, } from "./utils/decisionAnswers.js";
423
+ export { estimateDecisionStateTokens, resolveDecisionLimitsReading, } from "./utils/decisionLimits.js";
421
424
  export { DECISION_PROVIDERS, resolveDefaultDecisionProvider, } from "./factories/providerDescriptors.js";
422
425
  export { TYPESAFE_MAX_REQUEST_TOKENS, TYPESAFE_MAX_STATE_TOKENS, } from "./providers/typesafe.js";
423
426
  /**
package/dist/index.js CHANGED
@@ -95,6 +95,8 @@ export { createSkillStore, FileSystemSkillStore, InMemorySkillStore, } from "./s
95
95
  export { S3SkillStore } from "./skills/skillStoreS3.js";
96
96
  export { RedisSkillStore } from "./skills/skillStoreRedis.js";
97
97
  export { createSkillTools } from "./skills/skillTools.js";
98
+ // Memory (Hippocampus) — condensation prompt template validation
99
+ export { validateCondensationPrompt } from "./memory/condensationPrompt.js";
98
100
  // Export ALL types from the centralized type barrel
99
101
  export * from "./types/index.js";
100
102
  // Error utilities
@@ -106,6 +108,7 @@ export { getAvailableProviders, getBestProvider, isValidProvider, } from "./util
106
108
  // TTS utilities
107
109
  export { TTSProcessor } from "./utils/ttsProcessor.js";
108
110
  export { TTS_ERROR_CODES, TTSError } from "./utils/ttsProcessor.js";
111
+ export { prepareTextForSpeech, resolveSpeechSanitizeOptions, } from "./utils/speechText.js";
109
112
  // Video utilities
110
113
  export { VideoProcessor } from "./utils/videoProcessor.js";
111
114
  export { VIDEO_ERROR_CODES, VideoError } from "./utils/videoProcessor.js";
@@ -131,7 +134,7 @@ export {
131
134
  // TTS
132
135
  AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsTTS, ElevenLabsTTSHandler, FishAudioTTS, FishAudioTTSHandler, SixtyDBTTS, SixtyDBTTSHandler, GoogleTTSHandler, OpenAITTS, OpenAITTSHandler,
133
136
  // STT
134
- AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler,
137
+ AzureSTT, AzureSTTHandler, DeepgramSTT, DeepgramSTTHandler, ElevenLabsSTT, ElevenLabsSTTHandler, GoogleSTT, GoogleSTTHandler, OpenAISTT, OpenAISTTHandler, WhisperSTT, WhisperSTTHandler,
135
138
  // Realtime
136
139
  BaseRealtimeHandler, GeminiLive, GeminiLiveHandler, OpenAIRealtime, OpenAIRealtimeHandler, RealtimeProcessor,
137
140
  // Voice error classes (RealtimeError + STTError + VoiceError)
@@ -655,7 +658,11 @@ export { classifyProviderError, ModelPool, createDefaultRequestRouter, Classifie
655
658
  * if (team && team.confidence > 0.7) { route(team.choice); }
656
659
  * ```
657
660
  */
658
- export { decisionBooleanConfidence, decisionKey, gateDecisionBoolean, readDecisionBoolean, readDecisionChoice, readDecisionScore, servesInferenceKind, } from "./utils/decisionAnswers.js";
661
+ export { decisionBooleanConfidence, decisionKey, gateDecisionBoolean, HOST_DECISION_NAMESPACE, isDecisionQuestion, readDecisionBoolean, readDecisionChoice, readDecisionScore, servesInferenceKind, } from "./utils/decisionAnswers.js";
662
+ // The estimator the pre-flight refusal uses, and the flattening behind
663
+ // `neurolink.decisionLimits()` — exported so a host measures a state with the
664
+ // very function that will judge it.
665
+ export { estimateDecisionStateTokens, resolveDecisionLimitsReading, } from "./utils/decisionLimits.js";
659
666
  export { DECISION_PROVIDERS, resolveDefaultDecisionProvider, } from "./factories/providerDescriptors.js";
660
667
  export { TYPESAFE_MAX_REQUEST_TOKENS, TYPESAFE_MAX_STATE_TOKENS, } from "./providers/typesafe.js";
661
668
  /**
@@ -0,0 +1,8 @@
1
+ import type { CondensationPromptValidation } from "../types/index.js";
2
+ /**
3
+ * Check a condensation prompt template for the placeholders Hippocampus
4
+ * expects. Pure — never logs or throws. `valid` is false only when a
5
+ * structural placeholder (`OLD_MEMORY` / `NEW_CONTENT`) is missing;
6
+ * a missing `MAX_WORDS` is reported in `missing` but leaves `valid` true.
7
+ */
8
+ export declare function validateCondensationPrompt(prompt: string): CondensationPromptValidation;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Placeholders `@juspay/hippocampus` substitutes into a condensation prompt
3
+ * (`replaceAll` in `Hippocampus.add()`). None is required by Hippocampus
4
+ * itself — a missing one simply stays literal — which is exactly why the
5
+ * two structural ones must be checked here: a prompt without
6
+ * `{{NEW_CONTENT}}` produces a memory that never grows, and one without
7
+ * `{{OLD_MEMORY}}` overwrites the whole summary on every turn. Both fail
8
+ * silently at the LLM boundary.
9
+ */
10
+ const CONDENSATION_PLACEHOLDERS = [
11
+ "OLD_MEMORY",
12
+ "NEW_CONTENT",
13
+ "MAX_WORDS",
14
+ ];
15
+ const FATAL_PLACEHOLDERS = new Set([
16
+ "OLD_MEMORY",
17
+ "NEW_CONTENT",
18
+ ]);
19
+ /**
20
+ * Check a condensation prompt template for the placeholders Hippocampus
21
+ * expects. Pure — never logs or throws. `valid` is false only when a
22
+ * structural placeholder (`OLD_MEMORY` / `NEW_CONTENT`) is missing;
23
+ * a missing `MAX_WORDS` is reported in `missing` but leaves `valid` true.
24
+ */
25
+ export function validateCondensationPrompt(prompt) {
26
+ const missing = CONDENSATION_PLACEHOLDERS.filter((name) => !prompt.includes(`{{${name}}}`));
27
+ const fatal = missing.filter((name) => FATAL_PLACEHOLDERS.has(name));
28
+ return { valid: fatal.length === 0, missing, fatal };
29
+ }
@@ -1,2 +1,14 @@
1
- import type { HippocampusConfig, HippocampusLike } from "../types/index.js";
2
- export declare function initializeHippocampus(config: HippocampusConfig): HippocampusLike | null;
1
+ import type { HippocampusClientFactory, HippocampusConfig, HippocampusLike } from "../types/index.js";
2
+ /**
3
+ * Construct (or adopt) the Hippocampus client. `config.neurolink.instance`
4
+ * and `config.neurolink.credentials` are read by `@juspay/hippocampus`
5
+ * ≥0.2.0 itself (the peer floor is 0.2.1, which also fixes literal
6
+ * placeholder substitution), so condensation runs through the
7
+ * instance NeuroLink supplies (the one carrying this host's credentials)
8
+ * with no help from this side.
9
+ *
10
+ * Every path that disables memory logs at `error`: warnings are hidden at
11
+ * the default log level, and a host whose memory is silently off has no
12
+ * other way to learn it.
13
+ */
14
+ export declare function initializeHippocampus(config: HippocampusConfig, injectedClient?: HippocampusLike | HippocampusClientFactory): HippocampusLike | null;
@@ -25,10 +25,70 @@ function loadHippocampusModule() {
25
25
  return null;
26
26
  }
27
27
  }
28
- export function initializeHippocampus(config) {
28
+ /**
29
+ * The client methods NeuroLink core calls: `add()` on the write path and
30
+ * `get()` on the read path. `delete()` and `close()` are on the type for a
31
+ * host's own use, but core never calls them, so a client without them is
32
+ * still usable.
33
+ */
34
+ const REQUIRED_CLIENT_METHODS = ["add", "get"];
35
+ /**
36
+ * Construct (or adopt) the Hippocampus client. `config.neurolink.instance`
37
+ * and `config.neurolink.credentials` are read by `@juspay/hippocampus`
38
+ * ≥0.2.0 itself (the peer floor is 0.2.1, which also fixes literal
39
+ * placeholder substitution), so condensation runs through the
40
+ * instance NeuroLink supplies (the one carrying this host's credentials)
41
+ * with no help from this side.
42
+ *
43
+ * Every path that disables memory logs at `error`: warnings are hidden at
44
+ * the default log level, and a host whose memory is silently off has no
45
+ * other way to learn it.
46
+ */
47
+ export function initializeHippocampus(config, injectedClient) {
48
+ if (injectedClient) {
49
+ try {
50
+ const client = typeof injectedClient === "function"
51
+ ? injectedClient(config)
52
+ : injectedClient;
53
+ // A loosely typed or async factory can hand back undefined or a
54
+ // Promise; cache neither. A partial client — a write-only double with
55
+ // no get() — would make every read fail silently, so it is refused
56
+ // here rather than on each call. Memory is disabled with a clear
57
+ // message instead of failing every call with a generic error.
58
+ //
59
+ // The Promise case falls through to the same "missing" check below
60
+ // (a thenable has none of the required methods), but a rejecting
61
+ // factory's Promise needs a handler attached NOW — nothing else ever
62
+ // awaits it, so an unhandled rejection would otherwise end the
63
+ // process under the default Node rejection policy.
64
+ const maybeThenable = client;
65
+ if (client &&
66
+ typeof client === "object" &&
67
+ typeof maybeThenable.then === "function") {
68
+ maybeThenable.then(undefined, () => {
69
+ logger.error("[memoryInitializer] Host-supplied async client factory rejected");
70
+ });
71
+ }
72
+ const missing = !client || typeof client !== "object"
73
+ ? [...REQUIRED_CLIENT_METHODS]
74
+ : REQUIRED_CLIENT_METHODS.filter((name) => typeof client[name] !== "function");
75
+ if (missing.length > 0) {
76
+ logger.error(`[memoryInitializer] Host-supplied client has no ${missing
77
+ .map((name) => `${name}()`)
78
+ .join(" / ")}; disabling memory`);
79
+ return null;
80
+ }
81
+ logger.info("[memoryInitializer] Memory initialized with host-supplied client", { fromFactory: typeof injectedClient === "function" });
82
+ return client;
83
+ }
84
+ catch (error) {
85
+ logger.error("[memoryInitializer] Host-supplied client factory threw; disabling memory", { error: error instanceof Error ? error.message : String(error) });
86
+ return null;
87
+ }
88
+ }
29
89
  const mod = loadHippocampusModule();
30
90
  if (!mod) {
31
- logger.warn("[memoryInitializer] Memory configuration provided but @juspay/hippocampus is not installed. Run `pnpm add @juspay/hippocampus` (or your package manager equivalent) to enable memory.");
91
+ logger.error("[memoryInitializer] Memory configuration provided but @juspay/hippocampus is not installed. Run `pnpm add @juspay/hippocampus` (or your package manager equivalent) to enable memory.");
32
92
  return null;
33
93
  }
34
94
  try {
@@ -37,11 +97,12 @@ export function initializeHippocampus(config) {
37
97
  storageType: config.storage?.type || "sqlite",
38
98
  maxWords: config.maxWords || 50,
39
99
  hasCustomPrompt: !!config.prompt,
100
+ condenserSupplied: config.neurolink?.instance !== undefined,
40
101
  });
41
102
  return instance;
42
103
  }
43
104
  catch (error) {
44
- logger.warn("[memoryInitializer] Failed to initialize memory; disabling", {
105
+ logger.error("[memoryInitializer] Failed to initialize memory; disabling", {
45
106
  error: error instanceof Error ? error.message : String(error),
46
107
  });
47
108
  return null;