@juspay/neurolink 12.49.20 → 12.51.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 (65) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/browser/neurolink.min.js +671 -671
  3. package/dist/cli/commands/setup.d.ts +7 -5
  4. package/dist/cli/commands/setup.js +10 -8
  5. package/dist/constants/enums.d.ts +7 -15
  6. package/dist/constants/enums.js +8 -16
  7. package/dist/core/conversationMemoryManager.js +10 -4
  8. package/dist/core/redisConversationMemoryManager.js +6 -5
  9. package/dist/factories/providerDescriptors.d.ts +17 -3
  10. package/dist/factories/providerDescriptors.js +81 -56
  11. package/dist/factories/providerRegistry.d.ts +9 -1
  12. package/dist/factories/providerRegistry.js +19 -9
  13. package/dist/models/manifestRegistry.js +40 -3
  14. package/dist/neurolink.d.ts +52 -1
  15. package/dist/neurolink.js +195 -0
  16. package/dist/providers/catalog/index.generated.d.ts +3 -1
  17. package/dist/providers/catalog/index.generated.js +11 -0
  18. package/dist/providers/catalog/laya.json +107 -0
  19. package/dist/providers/catalog/loader.d.ts +7 -4
  20. package/dist/providers/catalog/loader.js +10 -3
  21. package/dist/providers/catalog/provider-catalog.schema.json +218 -11
  22. package/dist/providers/catalog/schema.d.ts +11 -9
  23. package/dist/providers/catalog/schema.js +333 -59
  24. package/dist/providers/configuredDecision.d.ts +23 -0
  25. package/dist/providers/configuredDecision.js +111 -0
  26. package/dist/providers/decide/baseUrl.d.ts +2 -0
  27. package/dist/providers/decide/baseUrl.js +14 -0
  28. package/dist/providers/decide/errors.d.ts +8 -0
  29. package/dist/providers/decide/errors.js +57 -0
  30. package/dist/providers/decide/formatters.d.ts +2 -0
  31. package/dist/providers/decide/formatters.js +4 -0
  32. package/dist/providers/decide/paths.d.ts +1 -0
  33. package/dist/providers/decide/paths.js +21 -0
  34. package/dist/providers/decide/template.d.ts +3 -0
  35. package/dist/providers/decide/template.js +13 -0
  36. package/dist/types/conversation.d.ts +109 -0
  37. package/dist/types/livekit.d.ts +246 -3
  38. package/dist/types/providerCatalog.d.ts +74 -1
  39. package/dist/types/providerCatalog.generated.d.ts +2 -2
  40. package/dist/types/providers.d.ts +12 -9
  41. package/dist/types/sdk.d.ts +1 -1
  42. package/dist/types/stream.d.ts +8 -1
  43. package/dist/utils/conversationTurnIdentity.d.ts +24 -0
  44. package/dist/utils/conversationTurnIdentity.js +81 -0
  45. package/dist/utils/modelChoices.js +11 -25
  46. package/dist/utils/providerConfig.d.ts +0 -5
  47. package/dist/utils/providerConfig.js +0 -17
  48. package/dist/voice/livekit/brain.js +10 -7
  49. package/dist/voice/livekit/index.d.ts +2 -0
  50. package/dist/voice/livekit/index.js +2 -0
  51. package/dist/voice/livekit/memoryBinding.d.ts +47 -0
  52. package/dist/voice/livekit/memoryBinding.js +152 -0
  53. package/dist/voice/livekit/realtimeMcpTools.js +20 -1
  54. package/dist/voice/livekit/realtimeMemory.d.ts +22 -0
  55. package/dist/voice/livekit/realtimeMemory.js +111 -0
  56. package/dist/voice/livekit/realtimeVoiceAgent.js +40 -1
  57. package/dist/voice/livekit/turnRecorder.d.ts +38 -0
  58. package/dist/voice/livekit/turnRecorder.js +362 -0
  59. package/dist/voice/livekit/voiceAgent.js +43 -5
  60. package/docs-site/static/search-index.json +8 -7
  61. package/package.json +7 -2
  62. package/dist/models/manifests/laya.d.ts +0 -12
  63. package/dist/models/manifests/laya.js +0 -54
  64. package/dist/providers/laya.d.ts +0 -40
  65. package/dist/providers/laya.js +0 -152
@@ -0,0 +1,21 @@
1
+ export function readPath(value, path) {
2
+ const segments = path.split(".");
3
+ if (segments.some((segment) => segment === "")) {
4
+ return undefined;
5
+ }
6
+ let current = value;
7
+ for (const segment of segments) {
8
+ if (typeof current !== "object" ||
9
+ current === null ||
10
+ Array.isArray(current)) {
11
+ return undefined;
12
+ }
13
+ // Own data properties only: no prototype names, and no getter is run.
14
+ const descriptor = Object.getOwnPropertyDescriptor(current, segment);
15
+ if (descriptor === undefined || !("value" in descriptor)) {
16
+ return undefined;
17
+ }
18
+ current = descriptor.value;
19
+ }
20
+ return current;
21
+ }
@@ -0,0 +1,3 @@
1
+ export declare function fillEndpointTemplate(template: string, parts: {
2
+ baseURL: string;
3
+ }): string;
@@ -0,0 +1,13 @@
1
+ const PLACEHOLDER = /\{([^{}]*)\}/g;
2
+ export function fillEndpointTemplate(template, parts) {
3
+ if (/[{}]/.test(template.replace(PLACEHOLDER, ""))) {
4
+ throw new Error(`Decide endpoint template "${template}" has an unmatched brace; write placeholders as {baseURL}.`);
5
+ }
6
+ // A function replacer, so "$&" or "$1" inside a base URL is inserted as text.
7
+ return template.replace(PLACEHOLDER, (_match, name) => {
8
+ if (name !== "baseURL") {
9
+ throw new Error(`Decide endpoint template "${template}" uses an unknown placeholder {${name}}; the only one supported is {baseURL}.`);
10
+ }
11
+ return parts.baseURL;
12
+ });
13
+ }
@@ -295,6 +295,8 @@ export type ToolResultData = {
295
295
  /** Error message if execution failed */
296
296
  error?: string;
297
297
  };
298
+ /** How a conversation turn was entered. */
299
+ export type ConversationModality = "text" | "voice";
298
300
  /** Metadata associated with a ChatMessage. */
299
301
  export type ChatMessageMetadata = {
300
302
  /** Is this a summary message? */
@@ -328,6 +330,22 @@ export type ChatMessageMetadata = {
328
330
  thinkingExpanded?: boolean;
329
331
  /** Step index for reconstructing parallel vs sequential tool calls */
330
332
  stepIndex?: number;
333
+ /**
334
+ * How the message was entered: typed (`"text"`) or spoken (`"voice"`).
335
+ * Absent on every message stored before this field existed, and on typed
336
+ * turns that did not set it; readers treat absence as `"text"`.
337
+ */
338
+ modality?: ConversationModality;
339
+ /**
340
+ * Stable id of the voice turn this message belongs to. The stored message
341
+ * ids are `${turnId}:user` and `${turnId}:assistant`, which is what makes
342
+ * recording the same turn twice a no-op.
343
+ */
344
+ turnId?: string;
345
+ /** True when the message was cut off (barge-in, or the call ended) before it finished. */
346
+ interrupted?: boolean;
347
+ /** True when the call ended while this turn was still open. */
348
+ callEnded?: boolean;
331
349
  /**
332
350
  * Head/tail preview of a large tool output.
333
351
  * Only present on tool_result messages where the output exceeded truncation limits.
@@ -515,6 +533,97 @@ export type StoreConversationTurnOptions = {
515
533
  * for the canonical partition-and-reanchor pattern).
516
534
  */
517
535
  skillMessages?: ChatMessage[];
536
+ /**
537
+ * How the turn was entered. Stamped on both stored messages' metadata when
538
+ * set; a typed turn that leaves it unset is stored exactly as before.
539
+ */
540
+ modality?: ConversationModality;
541
+ /**
542
+ * Idempotency key for the turn. When set, the user and assistant messages
543
+ * are stored with the ids `${turnId}:user` and `${turnId}:assistant`, and a
544
+ * turn whose assistant id is already in the session's recent messages is
545
+ * skipped, so a retry after an ambiguous failure cannot duplicate it.
546
+ */
547
+ turnId?: string;
548
+ /** The assistant message was cut off before it finished. */
549
+ interrupted?: boolean;
550
+ /** The call ended while this turn was open. */
551
+ callEnded?: boolean;
552
+ };
553
+ /** Id and metadata a memory manager gives one stored message of a turn. */
554
+ export type TurnMessageIdentity = {
555
+ /** Stable id for the message, or undefined to let the manager mint a random one. */
556
+ id: string | undefined;
557
+ /** Metadata to set on the message, or undefined when there is none to add. */
558
+ metadata: ChatMessageMetadata | undefined;
559
+ };
560
+ /**
561
+ * Input to `NeuroLink.recordConversationTurn`: one finished turn from a
562
+ * transport that does not go through generate()/stream() (a voice call),
563
+ * written to the same conversation memory typed chat uses.
564
+ */
565
+ export type RecordConversationTurnOptions = {
566
+ /** Conversation to append to. Must not contain ":" (it is part of the stored key). */
567
+ sessionId: string;
568
+ /**
569
+ * Owner of the conversation. Required: typed chat falls back to a shared
570
+ * placeholder user when it is missing, and a spoken turn must never land
571
+ * there.
572
+ */
573
+ userId: string;
574
+ /** What the person said. Must be non-blank. */
575
+ userMessage: string;
576
+ /** What the assistant said (or the closing note for a cut-off turn). Must be non-blank unless `events` carries tool activity. */
577
+ aiResponse: string;
578
+ /** How the turn was entered. Stamped on both stored messages. */
579
+ modality?: ConversationModality;
580
+ /** Idempotency key. Recording the same `turnId` again is a no-op. */
581
+ turnId?: string;
582
+ /** The assistant message was cut off before it finished. */
583
+ interrupted?: boolean;
584
+ /** The call ended while this turn was open. */
585
+ callEnded?: boolean;
586
+ /** Tool and UI events of the turn, in the shape typed chat stores. */
587
+ events?: StreamEventSequence[];
588
+ /** When the turn started. */
589
+ startTime?: Date;
590
+ /** Provider and model that answered, for token-threshold selection. */
591
+ provider?: string;
592
+ model?: string;
593
+ /** Observability request identifier. */
594
+ requestId?: string;
595
+ /** Per-call override of summarization, as on generate()/stream(). */
596
+ enableSummarization?: boolean;
597
+ /** Per-call long-term memory switches, as on generate()/stream(). */
598
+ memory?: {
599
+ enabled?: boolean;
600
+ write?: boolean;
601
+ };
602
+ };
603
+ /** Outcome of `NeuroLink.recordConversationTurn`. */
604
+ export type RecordConversationTurnResult = {
605
+ /** False when the turn was deliberately not stored; see `reason`. */
606
+ recorded: boolean;
607
+ /** Why nothing was stored. Only set when `recorded` is false. */
608
+ reason?: "empty-user-message" | "empty-response";
609
+ sessionId: string;
610
+ userId: string;
611
+ turnId?: string;
612
+ };
613
+ /** Options for `NeuroLink.getConversationSeed`. */
614
+ export type ConversationSeedOptions = {
615
+ sessionId: string;
616
+ userId: string;
617
+ /** Most recent user/assistant exchanges to return (default 10). */
618
+ maxTurns?: number;
619
+ /** Cap on the total characters returned; the oldest messages are dropped first (default 6000). */
620
+ maxChars?: number;
621
+ };
622
+ /** One message of a conversation seed. */
623
+ export type ConversationSeedMessage = {
624
+ role: "user" | "assistant";
625
+ content: string;
626
+ modality?: ConversationModality;
518
627
  };
519
628
  /**
520
629
  * Lightweight session metadata for efficient session listing
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import type { NeuroLinkEvents, TypedEventEmitter } from "./common.js";
13
13
  import type { StreamOptions, StreamResult } from "./stream.js";
14
+ import type { ConversationSeedMessage, ConversationSeedOptions, RecordConversationTurnOptions, RecordConversationTurnResult, StreamEventSequence } from "./conversation.js";
14
15
  /**
15
16
  * Minimal structural shape of the NeuroLink instance the brain depends on.
16
17
  *
@@ -57,6 +58,18 @@ export type LiveKitBrainTurn = {
57
58
  conversationId: string;
58
59
  /** Cancellation signal; aborting stops the in-flight LLM and tool calls. */
59
60
  signal?: AbortSignal;
61
+ /**
62
+ * User the conversation belongs to for THIS call, overriding the brain's
63
+ * static `userId`. A call whose memory binding verified passes the bound
64
+ * user here so the turn is stored under the person on the call.
65
+ */
66
+ userId?: string;
67
+ /**
68
+ * Run this turn without reading or writing conversation memory. Used for the
69
+ * agent's opening greeting on a bound call, which is an instruction to the
70
+ * agent and must not land in the person's conversation as something they said.
71
+ */
72
+ ephemeral?: boolean;
60
73
  };
61
74
  /**
62
75
  * The brain's public surface: stream the assistant reply as text deltas.
@@ -155,6 +168,15 @@ export type LiveKitVoiceAgentConfig = {
155
168
  conversationIdPrefix?: string;
156
169
  /** Optional user id recorded alongside memory. */
157
170
  userId?: string;
171
+ /**
172
+ * Per-call memory identity. When set, the call's room metadata is checked
173
+ * for a signed memory binding (see `signVoiceMemoryBinding`); a binding that
174
+ * verifies replaces `userId` and the room-derived conversation id for that
175
+ * call, and the turns the brain streams are stamped as voice. A call whose
176
+ * binding is missing or does not verify runs with the static `userId` and
177
+ * the room-derived conversation id exactly as before.
178
+ */
179
+ memory?: LiveKitVoiceAgentMemoryConfig;
158
180
  greeting?: string;
159
181
  /** Silero VAD tuning (stricter = ignores background noise). */
160
182
  vad?: LiveKitVadConfig;
@@ -170,6 +192,11 @@ export type LiveKitVoiceAgentConfig = {
170
192
  */
171
193
  events?: LiveKitEventBridgeConfig;
172
194
  };
195
+ /** Per-call memory identity for the cascaded voice agent. */
196
+ export type LiveKitVoiceAgentMemoryConfig = {
197
+ /** Shared secret the binding is signed with. Defaults to `NEUROLINK_VOICE_MEMORY_SECRET`. */
198
+ secret?: string;
199
+ };
173
200
  /** Options for `startVoiceAgentWorker` — launches the LiveKit Agents worker. */
174
201
  export type LiveKitWorkerLaunchOptions = {
175
202
  /**
@@ -208,7 +235,7 @@ export type LiveKitTokenRequest = {
208
235
  ttlSeconds?: number;
209
236
  };
210
237
  /** Discriminant tags for outbound voice events. */
211
- export type LiveKitVoiceEventType = "user-text" | "text" | "tool-start" | "tool-result" | "status" | "hitl-prompt" | "done";
238
+ export type LiveKitVoiceEventType = "user-text" | "text" | "tool-start" | "tool-result" | "status" | "hitl-prompt" | "done" | "memory";
212
239
  /**
213
240
  * A user STT transcript for display. Interim partials stream with
214
241
  * `final: false`; the end-of-utterance result has `final: true`. The client
@@ -277,15 +304,32 @@ export type LiveKitVoiceHitlPromptEvent = {
277
304
  allowModification?: boolean;
278
305
  };
279
306
  };
280
- /** The current turn has finished. */
307
+ /**
308
+ * The current turn has finished. `turnId` is set when the call's turns are
309
+ * being recorded: it is the id the stored messages carry (`${turnId}:user`,
310
+ * `${turnId}:assistant`), so a client can merge its live copy with history.
311
+ */
281
312
  export type LiveKitVoiceDoneEvent = {
282
313
  type: "done";
283
314
  data: {
284
315
  reason?: string;
316
+ turnId?: string;
317
+ };
318
+ };
319
+ /**
320
+ * Something about how the call is being remembered. `not-saved` means a turn
321
+ * could not be stored after retries; the call carries on, and a client may
322
+ * show a quiet note on that turn.
323
+ */
324
+ export type LiveKitVoiceMemoryEvent = {
325
+ type: "memory";
326
+ data: {
327
+ state: "not-saved";
328
+ turnId: string;
285
329
  };
286
330
  };
287
331
  /** Discriminated union of all outbound voice events (before enveloping). */
288
- export type LiveKitVoiceEvent = LiveKitVoiceUserTextEvent | LiveKitVoiceTextEvent | LiveKitVoiceToolStartEvent | LiveKitVoiceToolResultEvent | LiveKitVoiceStatusEvent | LiveKitVoiceHitlPromptEvent | LiveKitVoiceDoneEvent;
332
+ export type LiveKitVoiceEvent = LiveKitVoiceUserTextEvent | LiveKitVoiceTextEvent | LiveKitVoiceToolStartEvent | LiveKitVoiceToolResultEvent | LiveKitVoiceStatusEvent | LiveKitVoiceHitlPromptEvent | LiveKitVoiceDoneEvent | LiveKitVoiceMemoryEvent;
289
333
  /**
290
334
  * Wire format published to the browser: a `LiveKitVoiceEvent` plus a monotonic
291
335
  * sequence number and a timestamp so the client can order and de-duplicate.
@@ -442,6 +486,14 @@ export type RealtimeVoiceAgentConfig = {
442
486
  enabled?: boolean;
443
487
  mcpUrl?: string;
444
488
  };
489
+ /**
490
+ * Persist the call's turns to conversation memory and seed the model with
491
+ * the conversation's recent history. Off unless set. Takes effect only for
492
+ * a call whose room metadata carries a verified memory binding; otherwise
493
+ * the call runs exactly as it would without this option, and nothing is
494
+ * stored.
495
+ */
496
+ memory?: RealtimeVoiceMemoryConfig;
445
497
  /** Data-channel topic for outbound events (default "ai-events"). */
446
498
  eventsTopic?: string;
447
499
  /** Data-channel topic for inbound control messages (default "ai-control"). */
@@ -455,6 +507,186 @@ export type RealtimeVoiceAgentConfig = {
455
507
  */
456
508
  onLog?: (entry: RealtimeVoiceLogEntry, ctx: RealtimeVoiceLogContext) => void;
457
509
  };
510
+ /**
511
+ * The part of a NeuroLink instance a voice agent needs to persist and seed a
512
+ * call's turns. The real `NeuroLink` instance satisfies it.
513
+ */
514
+ export type LiveKitMemoryNeuroLink = {
515
+ recordConversationTurn: (options: RecordConversationTurnOptions) => Promise<RecordConversationTurnResult>;
516
+ getConversationSeed?: (options: ConversationSeedOptions) => Promise<ConversationSeedMessage[]>;
517
+ };
518
+ /** Memory options for `defineRealtimeVoiceAgent`. */
519
+ export type RealtimeVoiceMemoryConfig = {
520
+ /**
521
+ * Builds the NeuroLink instance, with conversation memory configured the
522
+ * same way the web app's is (same store, key prefix and lifetime), inside
523
+ * the job process. Called once per call, only after a binding verified.
524
+ */
525
+ createNeuroLink: () => LiveKitMemoryNeuroLink | Promise<LiveKitMemoryNeuroLink>;
526
+ /** Shared secret the binding is signed with. Defaults to `NEUROLINK_VOICE_MEMORY_SECRET`. */
527
+ secret?: string;
528
+ /** How long a closed reply waits for its user transcript before being dropped (default 1500 ms). */
529
+ settleMs?: number;
530
+ /** How long shutdown waits for pending writes (default 800 ms; the worker is reaped about 1 s after shutdown starts). */
531
+ flushBudgetMs?: number;
532
+ /** History seeding at call start. `false` turns it off. Defaults: 10 exchanges, 6000 characters. */
533
+ seed?: false | {
534
+ maxTurns?: number;
535
+ maxChars?: number;
536
+ };
537
+ };
538
+ /** One finished exchange of a voice call, as handed to the writer. */
539
+ export type VoiceTurnRecord = {
540
+ /** The assistant message id, or a synthetic id for a turn cut off by shutdown or barge-in. */
541
+ turnId: string;
542
+ userMessage: string;
543
+ aiResponse: string;
544
+ /** The reply was cut off. */
545
+ interrupted: boolean;
546
+ /** The call ended while this exchange was open. */
547
+ callEnded: boolean;
548
+ startTime: Date;
549
+ /** Tool events of the exchange, in the shape typed chat stores. */
550
+ events?: StreamEventSequence[];
551
+ };
552
+ /** A recorder's current exchange: what has been heard and said since the last reply closed. */
553
+ export type VoiceTurnOpenExchange = {
554
+ userSegments: string[];
555
+ assistantPartial: string;
556
+ assistantStarted: boolean;
557
+ events: StreamEventSequence[];
558
+ startedAt: number;
559
+ };
560
+ /** A reply that finished before its user transcript arrived, waiting out the settle window. */
561
+ export type VoiceTurnClosedExchange = {
562
+ turnId: string;
563
+ assistantText: string;
564
+ interrupted: boolean;
565
+ events: StreamEventSequence[];
566
+ startedAt: number;
567
+ timer: ReturnType<typeof setTimeout>;
568
+ };
569
+ /** The recorder returned by `createVoiceTurnRecorder`. */
570
+ export type VoiceTurnRecorder = {
571
+ /** A final transcript segment of what the user said. Interim segments are ignored. */
572
+ userTranscript: (segment: {
573
+ text: string;
574
+ final: boolean;
575
+ }) => void;
576
+ /** A piece of the assistant's reply as it is produced. */
577
+ assistantDelta: (delta: string) => void;
578
+ /** The assistant's message was added: the reply is complete (or was cut off). */
579
+ assistantMessage: (message: {
580
+ id: string;
581
+ text: string;
582
+ interrupted?: boolean;
583
+ }) => void;
584
+ /**
585
+ * The next assistant-only reply is the agent's opening greeting, not an
586
+ * answer: it is skipped and never paired with the user's first question.
587
+ * Call it once, when the agent is about to speak its greeting.
588
+ */
589
+ expectGreeting: () => void;
590
+ /** A tool call started in the current exchange. */
591
+ toolStart: (call: {
592
+ name: string;
593
+ args?: unknown;
594
+ }) => void;
595
+ /** A tool call finished in the current exchange. */
596
+ toolEnd: (call: {
597
+ name: string;
598
+ result?: unknown;
599
+ error?: string;
600
+ }) => void;
601
+ /**
602
+ * Shutdown: close what is open, then wait for pending writes — but no longer
603
+ * than `budgetMs`. Safe to call more than once.
604
+ */
605
+ flush: (budgetMs: number) => Promise<VoiceTurnRecorderStats>;
606
+ /** Counters, for logs and tests. */
607
+ stats: () => VoiceTurnRecorderStats;
608
+ };
609
+ /** What a realtime call needs from its memory once its binding verified. */
610
+ export type RealtimeCallMemory = {
611
+ recorder: VoiceTurnRecorder;
612
+ /** Recent history to seed the model with; empty when there is none or seeding is off. */
613
+ seed: ConversationSeedMessage[];
614
+ /** Late-bound publisher, set once the event bridge exists, for the not-saved notice. */
615
+ setPublisher: (publish: (type: LiveKitVoiceEventType, data: Record<string, unknown>) => void) => void;
616
+ };
617
+ /** Counters of a recorder, for logs and tests. */
618
+ export type VoiceTurnRecorderStats = {
619
+ written: number;
620
+ failed: number;
621
+ dropped: number;
622
+ pending: number;
623
+ };
624
+ /** Options for `createVoiceTurnRecorder`. */
625
+ export type VoiceTurnRecorderOptions = {
626
+ /** Stores one exchange. Throwing means "not stored"; the recorder retries. */
627
+ write: (record: VoiceTurnRecord) => Promise<void>;
628
+ /** A write succeeded. */
629
+ onRecorded?: (record: VoiceTurnRecord) => void;
630
+ /** A write failed after every retry. The call continues. */
631
+ onPermanentFailure?: (record: VoiceTurnRecord, error: unknown) => void;
632
+ /** How long a closed reply waits for its user transcript (default 1500 ms). */
633
+ settleMs?: number;
634
+ /** Retries after the first failed attempt (default 2). */
635
+ retries?: number;
636
+ /** Base delay between attempts; grows linearly (default 250 ms). */
637
+ retryDelayMs?: number;
638
+ /** Appended to a reply that was cut off by the call ending. */
639
+ callEndedNote?: string;
640
+ /** Stored as the reply when the call ended with the user's turn unanswered. */
641
+ noReplyNote?: string;
642
+ /** Prefix of synthetic turn ids (default: a random id). */
643
+ callId?: string;
644
+ /** Clock, for tests. */
645
+ now?: () => number;
646
+ };
647
+ /** A signed memory binding as it appears in room metadata under `memory`. */
648
+ export type VoiceMemoryBinding = {
649
+ userId: string;
650
+ sessionId: string;
651
+ /** Expiry, Unix seconds. */
652
+ exp: number;
653
+ /** base64url(HMAC-SHA256) over the room name, userId, sessionId and exp. */
654
+ sig: string;
655
+ };
656
+ /** Input to `signVoiceMemoryBinding`. */
657
+ export type VoiceMemoryBindingInput = {
658
+ /** The LiveKit room name the binding is for. */
659
+ room: string;
660
+ userId: string;
661
+ sessionId: string;
662
+ secret: string;
663
+ /** Lifetime in seconds (default 7200, at most 86400). */
664
+ ttlSeconds?: number;
665
+ /** Current time, Unix seconds (default: now). */
666
+ now?: number;
667
+ };
668
+ /** Input to `verifyVoiceMemoryBinding`. */
669
+ export type VoiceMemoryBindingVerifyInput = {
670
+ /** The room's base64(JSON) metadata string. */
671
+ roomMetadata: string | undefined;
672
+ /** The room name as LiveKit reports it for the joined room. */
673
+ roomName: string | undefined;
674
+ secret: string | undefined;
675
+ /** Current time, Unix seconds (default: now). */
676
+ now?: number;
677
+ };
678
+ /** Why a binding was not accepted. */
679
+ export type VoiceMemoryBindingRejection = "no-secret" | "no-binding" | "malformed" | "bad-signature" | "expired" | "expiry-too-far";
680
+ /** Result of `verifyVoiceMemoryBinding`. */
681
+ export type VoiceMemoryBindingVerdict = {
682
+ ok: true;
683
+ userId: string;
684
+ sessionId: string;
685
+ exp: number;
686
+ } | {
687
+ ok: false;
688
+ reason: VoiceMemoryBindingRejection;
689
+ };
458
690
  /** Auth token + base64 MCP execution context decoded from a room's metadata. */
459
691
  export type LiveKitRoomCallContext = {
460
692
  /** Lighthouse access JWT used as `x-auth-token` to the MCP server. */
@@ -509,4 +741,15 @@ export type BuildRealtimeMcpToolsParams = {
509
741
  * model can talk about.
510
742
  */
511
743
  toolTimeoutMs?: number;
744
+ /** Observes a tool call as it starts. A throwing observer never affects the call. */
745
+ onToolStart?: (call: {
746
+ name: string;
747
+ args: Record<string, unknown>;
748
+ }) => void;
749
+ /** Observes a tool call as it ends, with its result as text or its error. */
750
+ onToolEnd?: (call: {
751
+ name: string;
752
+ result?: string;
753
+ error?: string;
754
+ }) => void;
512
755
  };
@@ -4,6 +4,7 @@
4
4
  * src/lib/providers/catalog/<id>.json file per provider is the single
5
5
  * source of truth; see docs/superpowers/plans/2026-08-28-provider-json-catalog-spec.md.
6
6
  */
7
+ import type { DecideDialectName, DecisionErrorKind, DecisionLimits } from "./decision.js";
7
8
  export type CatalogPricingPerMTok = {
8
9
  input: number;
9
10
  output: number;
@@ -196,7 +197,7 @@ export type ProviderCatalogJson = {
196
197
  */
197
198
  credentialsKey?: string;
198
199
  aliases: string[];
199
- tier: 2;
200
+ tier?: 2;
200
201
  wire: CatalogWire;
201
202
  models: {
202
203
  default: string;
@@ -254,3 +255,75 @@ export type ProviderCatalogJson = {
254
255
  setup: CatalogSetup;
255
256
  evidence: CatalogEvidence;
256
257
  };
258
+ export type DecideFormatterName = "fastapi-validation";
259
+ export type DecideBaseURLTransform = "strip-trailing-slash";
260
+ export type DecideErrorRule = {
261
+ /** One status or a list of them. A rule sets this or `statusMin`, never both. */
262
+ status?: number | readonly number[];
263
+ /** Matches every status at or above this value. */
264
+ statusMin?: number;
265
+ kind: DecisionErrorKind;
266
+ retryable?: boolean;
267
+ };
268
+ export type DecideBodyShape = {
269
+ /** Dot path into the parsed error body. */
270
+ path: string;
271
+ isArray?: boolean;
272
+ formatter?: DecideFormatterName;
273
+ kind?: DecisionErrorKind;
274
+ retryable?: boolean;
275
+ };
276
+ export type DecideErrors = {
277
+ rules: readonly DecideErrorRule[];
278
+ default: DecisionErrorKind;
279
+ bodyShapes: readonly DecideBodyShape[];
280
+ };
281
+ export type DecideModel = {
282
+ placement: "body" | "body-unless-alias";
283
+ /** With `body-unless-alias`: the model name that means "send no model". */
284
+ alias?: string;
285
+ };
286
+ export type DecideBlock = {
287
+ dialect: DecideDialectName;
288
+ /** Decision order, lower runs first; unique across decide providers. */
289
+ order: number;
290
+ /** True when the provider has no built-in endpoint and the caller must supply one. */
291
+ baseURLRequired?: boolean;
292
+ baseURLNormalize?: readonly DecideBaseURLTransform[];
293
+ /** Request URL; `{baseURL}` is the only placeholder. */
294
+ endpoint: string;
295
+ model: DecideModel;
296
+ requestIdHeaders?: readonly string[];
297
+ response?: {
298
+ modelPaths?: readonly string[];
299
+ };
300
+ errors: DecideErrors;
301
+ };
302
+ export type DecideCatalogJson = {
303
+ $schema?: string;
304
+ id: string;
305
+ displayName: string;
306
+ enumTypeName?: string;
307
+ credentialsKey?: string;
308
+ aliases: string[];
309
+ inferenceKinds: readonly ["decide"];
310
+ tier?: 2;
311
+ wire: CatalogWire;
312
+ models: {
313
+ default: string;
314
+ catalog: Record<string, CatalogModelSpec>;
315
+ topModels?: string[];
316
+ testModel?: string;
317
+ };
318
+ timeouts?: {
319
+ decideMs?: number;
320
+ };
321
+ decisionLimits?: DecisionLimits;
322
+ decide: DecideBlock;
323
+ setup: CatalogSetup;
324
+ evidence: CatalogEvidence;
325
+ };
326
+ export type CatalogEntry = ProviderCatalogJson | DecideCatalogJson;
327
+ export type CatalogKeySource = Pick<CatalogEntry, "id" | "credentialsKey">;
328
+ export type CatalogEnvSource = Pick<CatalogEntry, "id" | "wire">;
329
+ export type CatalogSetupSource = Pick<CatalogEntry, "id" | "displayName" | "wire" | "setup">;
@@ -1,2 +1,2 @@
1
- export type CatalogProviderName = "a2agent" | "abliteration" | "above-dev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "api-route" | "arcee" | "atlas-cloud" | "avian-io" | "baidu-qianfan" | "baseten" | "bee-heossi" | "byteplus-modelark" | "bytez" | "cerebras" | "charm-hyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherless-ai" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetzner-inference" | "huggingface" | "inception-labs" | "inceptron" | "inco" | "inference-net" | "io-intelligence" | "koscompute" | "lemonfox-ai" | "lilac" | "llmtech" | "mancer" | "meta-model-api" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshot-ai" | "morph" | "naver-clova-studio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "pareto-inference" | "perplexity" | "poolside" | "prime-intellect" | "reka" | "sakana-ai" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinking-machines" | "tinfoil" | "together-ai" | "umans-ai" | "upstage" | "venice-ai" | "vispark" | "vultr-inference" | "wafer" | "wandb-inference" | "xai" | "z-ai";
2
- export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "lemonfoxAi" | "lilac" | "llmtech" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "zAi";
1
+ export type CatalogProviderName = "a2agent" | "abliteration" | "above-dev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "api-route" | "arcee" | "atlas-cloud" | "avian-io" | "baidu-qianfan" | "baseten" | "bee-heossi" | "byteplus-modelark" | "bytez" | "cerebras" | "charm-hyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherless-ai" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetzner-inference" | "huggingface" | "inception-labs" | "inceptron" | "inco" | "inference-net" | "io-intelligence" | "koscompute" | "laya" | "lemonfox-ai" | "lilac" | "llmtech" | "mancer" | "meta-model-api" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshot-ai" | "morph" | "naver-clova-studio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "pareto-inference" | "perplexity" | "poolside" | "prime-intellect" | "reka" | "sakana-ai" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinking-machines" | "tinfoil" | "together-ai" | "umans-ai" | "upstage" | "venice-ai" | "vispark" | "vultr-inference" | "wafer" | "wandb-inference" | "xai" | "z-ai";
2
+ export type CatalogCredentialKey = "a2agent" | "abliteration" | "aboveDev" | "ai21" | "aiand" | "aihubmix" | "aionlabs" | "ambient" | "apiRoute" | "arcee" | "atlasCloud" | "avianIo" | "baiduQianfan" | "baseten" | "beeHeossi" | "byteplusModelark" | "bytez" | "cerebras" | "charmHyper" | "chutes" | "cloudflare" | "crusoe" | "dashscope" | "deepinfra" | "deepseek" | "empiriolabs" | "featherlessAi" | "fireworks" | "friendli" | "gmicloud" | "gradientai" | "groq" | "hetznerInference" | "huggingFace" | "inceptionLabs" | "inceptron" | "inco" | "inferenceNet" | "ioIntelligence" | "koscompute" | "laya" | "lemonfoxAi" | "lilac" | "llmtech" | "mancer" | "metaModelApi" | "minimax" | "mistral" | "moark" | "modelscope" | "moonshotAi" | "morph" | "naverClovaStudio" | "nebius" | "neuralwatt" | "novita" | "ovhcloud" | "parasail" | "paretoInference" | "perplexity" | "poolside" | "primeIntellect" | "reka" | "sakanaAi" | "sambanova" | "sarvam" | "scaleway" | "siliconflow" | "stackit" | "stepfun" | "subconscious" | "synthetic" | "telnyx" | "thinkingMachines" | "tinfoil" | "together" | "umansAi" | "upstage" | "veniceAi" | "vispark" | "vultrInference" | "wafer" | "wandbInference" | "xai" | "zAi";
@@ -353,6 +353,10 @@ export type NeurolinkCredentials = {
353
353
  apiKey?: string;
354
354
  baseURL?: string;
355
355
  };
356
+ laya?: {
357
+ apiKey?: string;
358
+ baseURL?: string;
359
+ };
356
360
  lemonfoxAi?: {
357
361
  apiKey?: string;
358
362
  baseURL?: string;
@@ -585,15 +589,6 @@ export type NeurolinkCredentials = {
585
589
  */
586
590
  gatewayURL?: string;
587
591
  };
588
- /**
589
- * Laya (Convai, open weights) — the `decide` inference type. There is no
590
- * built-in endpoint: `baseURL` (or LAYA_BASE_URL) is required, pointing at a
591
- * Laya server or a proxy route to one; requests go to `<baseURL>/predict`.
592
- */
593
- laya?: {
594
- apiKey?: string;
595
- baseURL?: string;
596
- };
597
592
  /**
598
593
  * XOR (Juspay, open weights) — the `decide` inference type. There is no
599
594
  * built-in endpoint: `baseURL` (or XOR_BASE_URL) is required, pointing at the
@@ -2447,6 +2442,14 @@ export type ProviderDescriptor = {
2447
2442
  * checks to its own server (TypeSafe). See {@link DecisionLimits}.
2448
2443
  */
2449
2444
  decisionLimits?: DecisionLimits;
2445
+ /**
2446
+ * Precedence among `decide` providers: lower runs first and no two decide
2447
+ * descriptors share a value. resolveDefaultDecisionProvider() returns the
2448
+ * first configured provider in this order, so changing a number changes which
2449
+ * model routes for every host that configures several. Required on every
2450
+ * descriptor that serves `decide`; checked when the descriptors load.
2451
+ */
2452
+ decideOrder?: number;
2450
2453
  /**
2451
2454
  * Ascending priority (1 = tried first) in the auto-select fallback chain
2452
2455
  * used by getBestProvider(). Undefined = not part of the auto-select chain.
@@ -12,7 +12,7 @@ export type { AnalyticsData, TokenUsage } from "./analytics.js";
12
12
  export type { BaseCommandArgs, CommandResult, GenerateCommandArgs, CliGenerateResult, MCPCommandArgs, ModelsCommandArgs, CliStreamChunk, } from "./cli.js";
13
13
  export type { ErrorInfo, FunctionParameters, JsonArray, JsonObject, JsonValue, ProcessResult, Result, TestFunction, TestResult, Unknown, UnknownArray, UnknownRecord, } from "./common.js";
14
14
  export type { Content, ImageContent, MultimodalMessage, ProcessedImage, ProviderImageFormat, ProviderMultimodalPayload, TextContent, VisionCapability, } from "./content.js";
15
- export type { ChatMessage, ChatMessageMetadata, ToolResultData, ConversationMemoryConfig, ConversationMemoryError, ConversationMemoryEvents, MessageContent, MultimodalChatMessage, RedisConversationObject, RedisStorageConfig, SessionIdentifier, SessionMemory, SessionMetadata, StoreConversationTurnOptions, } from "./conversation.js";
15
+ export type { ChatMessage, ChatMessageMetadata, ToolResultData, ConversationMemoryConfig, ConversationMemoryError, ConversationMemoryEvents, ConversationModality, ConversationSeedMessage, ConversationSeedOptions, MessageContent, MultimodalChatMessage, RecordConversationTurnOptions, RecordConversationTurnResult, RedisConversationObject, RedisStorageConfig, SessionIdentifier, SessionMemory, SessionMetadata, StoreConversationTurnOptions, } from "./conversation.js";
16
16
  export type { DomainConfig, DomainConfigOptions, DomainEvaluationCriteria, DomainTemplate, DomainType, DomainValidationRule, } from "./domain.js";
17
17
  export type { EnhancedEvaluationResult, EvaluationContext, EvaluationCriteria, EvaluationData, EvaluationRequest, } from "./evaluation.js";
18
18
  export type { ExternalMCPConfigValidation, ExternalMCPManagerConfig, ExternalMCPOperationResult, ExternalMCPServerEvents, ExternalMCPServerHealth, ExternalMCPServerInstance, ExternalMCPServerStatus, ExternalMCPToolContext, ExternalMCPToolInfo, ExternalMCPToolResult, } from "./externalMcp.js";
@@ -9,7 +9,7 @@ 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, ToolReplayMode } from "./conversation.js";
12
+ import type { ChatMessage, ConversationModality, ToolReplayMode } from "./conversation.js";
13
13
  import type { StreamNoOutputSentinel } from "./noOutputSentinel.js";
14
14
  import type { GenerateStopReason, TTSMetadata, ToolExecutionCaptureOptions } from "./generate.js";
15
15
  import type { MemoryCallOptions } from "./memory.js";
@@ -666,6 +666,13 @@ export type StreamOptions = {
666
666
  workflow?: string;
667
667
  workflowConfig?: WorkflowConfig;
668
668
  enableSummarization?: boolean;
669
+ /**
670
+ * How this turn was entered. Stamped on the stored messages' metadata so
671
+ * history readers can tell a spoken turn from a typed one. Leave it unset
672
+ * for typed input: the stored turn is then identical to what it was before
673
+ * the field existed.
674
+ */
675
+ modality?: ConversationModality;
669
676
  /**
670
677
  * Maximum cumulative cost (USD) for this session.
671
678
  * Once the session spend reaches this limit, subsequent stream() calls