@datalayer/agent-runtimes 1.5.43 → 1.5.45

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 (91) hide show
  1. package/dist-embed/chunks/1f30a-DxwAt2Ba.js +1 -0
  2. package/dist-embed/chunks/1f310-AqUM9a8q.js +1 -0
  3. package/dist-embed/chunks/1f324-8MHLOhTj.js +1 -0
  4. package/dist-embed/chunks/1f326-YSBr3aXI.js +1 -0
  5. package/dist-embed/chunks/1f327-Ie-nQBNO.js +1 -0
  6. package/dist-embed/chunks/1f33e-Cfl2WM63.js +1 -0
  7. package/dist-embed/chunks/1f372-3XLDlXkE.js +1 -0
  8. package/dist-embed/chunks/1f373-BPUJt2bp.js +1 -0
  9. package/dist-embed/chunks/1f39b-CSNtbAXr.js +1 -0
  10. package/dist-embed/chunks/1f3ab-BUCNQHz1.js +1 -0
  11. package/dist-embed/chunks/1f41b-Dre1z0gP.js +1 -0
  12. package/dist-embed/chunks/1f441-D8yah4C4.js +1 -0
  13. package/dist-embed/chunks/1f465-CPle6Hjq.js +1 -0
  14. package/dist-embed/chunks/1f4b9-aacFyckt.js +1 -0
  15. package/dist-embed/chunks/1f4be-DPAcAU4R.js +1 -0
  16. package/dist-embed/chunks/1f4c1-Ch6aJ0cj.js +1 -0
  17. package/dist-embed/chunks/1f4c2-Dx7BKmLO.js +1 -0
  18. package/dist-embed/chunks/1f4c4-CNvjvC5k.js +1 -0
  19. package/dist-embed/chunks/1f4d3-GTxRgjv7.js +1 -0
  20. package/dist-embed/chunks/1f4d6-COOZY-Q_.js +1 -0
  21. package/dist-embed/chunks/1f4dc-BhoW4cjD.js +1 -0
  22. package/dist-embed/chunks/1f4e1-DezU8ow-.js +1 -0
  23. package/dist-embed/chunks/1f4e2-DsStZsI-.js +1 -0
  24. package/dist-embed/chunks/1f4e7-BBi7c_zx.js +1 -0
  25. package/dist-embed/chunks/1f500-D9xRo-IT.js +1 -0
  26. package/dist-embed/chunks/1f501-CzKeDPcw.js +1 -0
  27. package/dist-embed/chunks/1f504-BYIHMoDP.js +1 -0
  28. package/dist-embed/chunks/1f52d-2QxCLKhU.js +1 -0
  29. package/dist-embed/chunks/1f552-4d0Y0kn7.js +1 -0
  30. package/dist-embed/chunks/1f58b-Cp8F_HBg.js +1 -0
  31. package/dist-embed/chunks/1f5b2-D6BUULuh.js +1 -0
  32. package/dist-embed/chunks/1f5c3-DjkgJLaz.js +1 -0
  33. package/dist-embed/chunks/1f5d1-C4Kpe4HL.js +1 -0
  34. package/dist-embed/chunks/1f5dc-DyLO7aB9.js +1 -0
  35. package/dist-embed/chunks/1f604-CEekcaYE.js +1 -0
  36. package/dist-embed/chunks/1f917-CyyS9fgf.js +1 -0
  37. package/dist-embed/chunks/1f9d1-200d-2696-D9eSB-0q.js +1 -0
  38. package/dist-embed/chunks/1fa79-DHhIZbWB.js +1 -0
  39. package/dist-embed/chunks/1fa9d-DUI9e77m.js +1 -0
  40. package/dist-embed/chunks/1fa9f-10ALsMaD.js +1 -0
  41. package/dist-embed/chunks/23f3-ASjqFsbT.js +1 -0
  42. package/dist-embed/chunks/2611-BnnmLm-K.js +1 -0
  43. package/dist-embed/chunks/2702-CfLyp35r.js +1 -0
  44. package/dist-embed/chunks/270f-CLwo30mk.js +1 -0
  45. package/dist-embed/chunks/2728-Qg6be-6p.js +1 -0
  46. package/dist-embed/chunks/2b1c-Uc1aRwAf.js +1 -0
  47. package/dist-embed/chunks/{ChatView-kCmBbnDa.js → ChatView-Dy-IgGFL.js} +1 -1
  48. package/dist-embed/datalayer-app-main.js +111 -106
  49. package/lib/apps/plugins/chat/ChatView.js +2 -2
  50. package/lib/chat/ChatFloating.js +1 -1
  51. package/lib/chat/ChatInline.js +43 -22
  52. package/lib/chat/base/ChatBase.js +66 -10
  53. package/lib/chat/messages/ChatMessageList.d.ts +7 -1
  54. package/lib/chat/messages/ChatMessageList.js +8 -2
  55. package/lib/components/agents/AgentSummary.d.ts +2 -24
  56. package/lib/components/agents/AgentSummary.js +3 -19
  57. package/lib/hooks/useChat.js +110 -144
  58. package/lib/portable/activity.d.ts +78 -0
  59. package/lib/portable/activity.js +129 -0
  60. package/lib/portable/fixtures.d.ts +7 -0
  61. package/lib/portable/fixtures.js +50 -0
  62. package/lib/portable/index.d.ts +4 -1
  63. package/lib/portable/index.js +7 -1
  64. package/lib/portable/lifecycle.d.ts +77 -0
  65. package/lib/portable/lifecycle.js +135 -0
  66. package/lib/portable/runtimes.d.ts +68 -0
  67. package/lib/portable/runtimes.js +69 -0
  68. package/lib/protocols/VercelAIAdapter.js +11 -0
  69. package/lib/specs/voices.js +62 -0
  70. package/lib/types/chat.d.ts +10 -0
  71. package/lib/voice/ComposerVoice.js +12 -3
  72. package/lib/voice/VoiceMenu.d.ts +2 -1
  73. package/lib/voice/VoiceMenu.js +2 -2
  74. package/lib/voice/captions.d.ts +31 -0
  75. package/lib/voice/captions.js +156 -0
  76. package/lib/voice/chatVoice.d.ts +5 -2
  77. package/lib/voice/chatVoice.js +10 -4
  78. package/lib/voice/choices.d.ts +2 -0
  79. package/lib/voice/choices.js +6 -0
  80. package/lib/voice/handsFree.d.ts +12 -0
  81. package/lib/voice/handsFree.js +18 -0
  82. package/lib/voice/index.d.ts +1 -0
  83. package/lib/voice/index.js +1 -0
  84. package/lib/voice/sayAloud.d.ts +25 -0
  85. package/lib/voice/sayAloud.js +34 -0
  86. package/lib/voice/serverHearing.d.ts +63 -2
  87. package/lib/voice/serverHearing.js +311 -7
  88. package/lib/voice/settings.d.ts +12 -0
  89. package/lib/voice/settings.js +9 -0
  90. package/package.json +1 -1
  91. package/scripts/voice/measure.py +77 -17
@@ -0,0 +1,156 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Captions always (VOICE.md VO-65): the sentence being said is marked in the
7
+ * open conversation, not by colour alone. The answer that holds it is marked
8
+ * `aria-current` and `data-voice-saying` with a bar beside it; the sentence
9
+ * itself is underlined in place through the CSS Custom Highlight API, which
10
+ * draws over the text without touching the DOM React owns. A browser without
11
+ * that API keeps the answer's mark.
12
+ *
13
+ * @module voice/captions
14
+ */
15
+ import { useEffect } from 'react';
16
+ import { plainWords } from './sentences';
17
+ import { answerText } from './useSpokenAnswers';
18
+ /** The name the sentence's highlight is registered under. */
19
+ export const SAID_HIGHLIGHT = 'voice-said';
20
+ /** What the sentence being said looks like: underlined, thick, offset. */
21
+ const SAID_STYLE = `::highlight(${SAID_HIGHLIGHT}) {
22
+ text-decoration: underline;
23
+ text-decoration-thickness: 2px;
24
+ text-underline-offset: 3px;
25
+ background-color: var(--bgColor-accent-muted, rgba(84, 174, 255, 0.2));
26
+ }`;
27
+ /** Words compared as words: spaces collapsed, case kept. */
28
+ function squeezed(text) {
29
+ return text.replace(/\s+/g, ' ').trim();
30
+ }
31
+ /**
32
+ * The id of the answer a sentence is being said from: the newest answer of
33
+ * the agent whose words hold it, or none.
34
+ */
35
+ export function saidMessageId(items, sentence) {
36
+ const said = squeezed(sentence ?? '');
37
+ if (!said) {
38
+ return undefined;
39
+ }
40
+ for (let index = items.length - 1; index >= 0; index -= 1) {
41
+ const text = answerText(items[index]);
42
+ if (text !== undefined && squeezed(plainWords(text)).includes(said)) {
43
+ const id = items[index].id;
44
+ return typeof id === 'string' ? id : undefined;
45
+ }
46
+ }
47
+ return undefined;
48
+ }
49
+ /**
50
+ * Where a sentence is in what an element shows, as a `Range` over its text
51
+ * — across the bold, the links and the line breaks it is drawn with — or
52
+ * none when the element does not show it as said (a code block said as *a
53
+ * code block*).
54
+ */
55
+ export function sentenceRange(root, sentence) {
56
+ const said = squeezed(sentence);
57
+ if (!said || !root.ownerDocument) {
58
+ return undefined;
59
+ }
60
+ const document = root.ownerDocument;
61
+ const walker = document.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */);
62
+ // The text, spaces collapsed, and where each of its characters came from.
63
+ let text = '';
64
+ const from = [];
65
+ let space = true;
66
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
67
+ const value = node.nodeValue ?? '';
68
+ for (let offset = 0; offset < value.length; offset += 1) {
69
+ const char = value[offset];
70
+ if (/\s/.test(char)) {
71
+ if (space) {
72
+ continue;
73
+ }
74
+ space = true;
75
+ text += ' ';
76
+ }
77
+ else {
78
+ space = false;
79
+ text += char;
80
+ }
81
+ from.push({ node, offset });
82
+ }
83
+ }
84
+ const start = text.indexOf(said);
85
+ if (start < 0) {
86
+ return undefined;
87
+ }
88
+ const first = from[start];
89
+ const last = from[start + said.length - 1];
90
+ const range = document.createRange();
91
+ range.setStart(first.node, first.offset);
92
+ range.setEnd(last.node, last.offset + 1);
93
+ return range;
94
+ }
95
+ function registry() {
96
+ const css = globalThis.CSS;
97
+ return css?.highlights;
98
+ }
99
+ function highlightOf(range) {
100
+ const Highlight = globalThis.Highlight;
101
+ return Highlight ? new Highlight(range) : undefined;
102
+ }
103
+ let styled = false;
104
+ function ensureStyle(document) {
105
+ if (styled || document.getElementById('datalayer-voice-said')) {
106
+ styled = true;
107
+ return;
108
+ }
109
+ const style = document.createElement('style');
110
+ style.id = 'datalayer-voice-said';
111
+ style.textContent = SAID_STYLE;
112
+ document.head.appendChild(style);
113
+ styled = true;
114
+ }
115
+ /**
116
+ * Underlines `sentence` in the answer marked `data-voice-saying` under
117
+ * `root`, for as long as it is said; the mark goes when it ends. Drawn
118
+ * again whenever `version` changes (the answer still being written).
119
+ */
120
+ export function useSaidSentence(root, sentence, version) {
121
+ useEffect(() => {
122
+ const highlights = registry();
123
+ const element = root.current?.querySelector('[data-voice-saying]');
124
+ if (!highlights || !element || !sentence) {
125
+ return;
126
+ }
127
+ const underline = () => {
128
+ const range = sentenceRange(element, sentence);
129
+ const highlight = range ? highlightOf(range) : undefined;
130
+ if (!highlight) {
131
+ return false;
132
+ }
133
+ ensureStyle(element.ownerDocument);
134
+ highlights.set(SAID_HIGHLIGHT, highlight);
135
+ return true;
136
+ };
137
+ // The answer's Markdown may be drawn after this effect: wait for it.
138
+ let watcher;
139
+ if (!underline() && typeof MutationObserver !== 'undefined') {
140
+ watcher = new MutationObserver(() => {
141
+ if (underline()) {
142
+ watcher?.disconnect();
143
+ }
144
+ });
145
+ watcher.observe(element, {
146
+ childList: true,
147
+ subtree: true,
148
+ characterData: true,
149
+ });
150
+ }
151
+ return () => {
152
+ watcher?.disconnect();
153
+ highlights.delete(SAID_HIGHLIGHT);
154
+ };
155
+ }, [root, sentence, version]);
156
+ }
@@ -59,7 +59,10 @@ export declare const SILENCE_MS = 1500;
59
59
  * in: the person's own settings first where they made some (`voiceForPerson`:
60
60
  * their language, voice, rate, volume, microphone; VO-14, VO-25, VO-30),
61
61
  * else the host's; answers not heard, the language's first voice,
62
- * Datalayer's models and packages, a silence of 1.5 s. Answers heard need
63
- * the speech service's address: refused without.
62
+ * Datalayer's models and packages, a silence of 1.5 s. A person who pauses
63
+ * longer before they are done has both silences — the one that stops a
64
+ * microphone clicked on, and the one that ends a hands-free turn — made
65
+ * longer by their choice (`pause`, VO-69). Answers heard need the speech
66
+ * service's address: refused without.
64
67
  */
65
68
  export declare function settledVoice(given: ChatVoice, person?: VoiceSettings): SettledVoice;
@@ -11,7 +11,8 @@
11
11
  * @module voice/chatVoice
12
12
  */
13
13
  import { voiceFor } from '../specs/voices';
14
- import { heldVoiceSettings, voiceForPerson, } from './settings';
14
+ import { TURN_SILENCE_MS } from './handsFree';
15
+ import { heldVoiceSettings, saneVoiceSettings, voiceForPerson, } from './settings';
15
16
  /**
16
17
  * Datalayer's origin for the browser's speech models, as pinned
17
18
  * (`s3://datalayer-models-speech` behind its CDN; VO-48, VO-49): each model
@@ -84,11 +85,15 @@ export const SILENCE_MS = 1500;
84
85
  * in: the person's own settings first where they made some (`voiceForPerson`:
85
86
  * their language, voice, rate, volume, microphone; VO-14, VO-25, VO-30),
86
87
  * else the host's; answers not heard, the language's first voice,
87
- * Datalayer's models and packages, a silence of 1.5 s. Answers heard need
88
- * the speech service's address: refused without.
88
+ * Datalayer's models and packages, a silence of 1.5 s. A person who pauses
89
+ * longer before they are done has both silences — the one that stops a
90
+ * microphone clicked on, and the one that ends a hands-free turn — made
91
+ * longer by their choice (`pause`, VO-69). Answers heard need the speech
92
+ * service's address: refused without.
89
93
  */
90
94
  export function settledVoice(given, person = heldVoiceSettings()) {
91
95
  const voice = voiceForPerson(given, person);
96
+ const pause = saneVoiceSettings(person).pause ?? 1;
92
97
  const output = voice.output ?? 'off';
93
98
  if (output !== 'off' && !voice.speechUrl) {
94
99
  throw new Error(`A chat whose answers are heard (${output}) names the speech service: its speechUrl is missing.`);
@@ -99,6 +104,7 @@ export function settledVoice(given, person = heldVoiceSettings()) {
99
104
  voice: voice.voice || voiceFor(voice.language)?.id || '',
100
105
  modelsUrl: voice.modelsUrl || DATALAYER_SPEECH_MODELS_URL,
101
106
  engines: voice.engines ?? DATALAYER_VOICE_ENGINES,
102
- silenceMs: voice.silenceMs ?? SILENCE_MS,
107
+ silenceMs: Math.round((voice.silenceMs ?? SILENCE_MS) * pause),
108
+ turnSilenceMs: Math.round((voice.turnSilenceMs ?? TURN_SILENCE_MS) * pause),
103
109
  };
104
110
  }
@@ -28,6 +28,8 @@ export declare function catalogueVoiceSpeaks(voice: string, language: string): b
28
28
  /** The speeds offered, as multipliers of the voice's own. */
29
29
  export declare const RATE_CHOICES: VoiceChoice<number>[];
30
30
  /** The loudnesses offered, 0 to 1. */
31
+ /** How long the person pauses before they are done (VO-69): the silences, times this. */
32
+ export declare const PAUSE_CHOICES: VoiceChoice<number>[];
31
33
  export declare const VOLUME_CHOICES: VoiceChoice<number>[];
32
34
  /** When answers are heard (VO-21): what a person may choose where the application speaks. */
33
35
  export declare const OUTPUT_CHOICES: VoiceChoice<NonNullable<ChatVoice['output']>>[];
@@ -56,6 +56,12 @@ export const RATE_CHOICES = [
56
56
  { value: 1.5, label: 'Fastest' },
57
57
  ];
58
58
  /** The loudnesses offered, 0 to 1. */
59
+ /** How long the person pauses before they are done (VO-69): the silences, times this. */
60
+ export const PAUSE_CHOICES = [
61
+ { value: 1, label: 'Usual' },
62
+ { value: 2, label: 'Longer' },
63
+ { value: 3, label: 'Much longer' },
64
+ ];
59
65
  export const VOLUME_CHOICES = [
60
66
  { value: 0.25, label: 'Quiet' },
61
67
  { value: 0.5, label: 'Half' },
@@ -56,3 +56,15 @@ export declare function turnEnded(heard: HeardSoFarLike, turnSilenceMs?: number)
56
56
  * answer's echo — the browser's echo cancellation keeps that out.
57
57
  */
58
58
  export declare function bargedIn(state: HandsFreeState, heard: HeardSoFarLike): boolean;
59
+ /** A message as the conversation holds it: enough to find the answer spoken over. */
60
+ export interface ConversationItemLike {
61
+ id: string;
62
+ role?: string;
63
+ }
64
+ /**
65
+ * The answer the person spoke over (VO-13): the agent's last message after
66
+ * the person's last one — still being written, or written and being said.
67
+ * `undefined` while the agent has not answered the turn yet: there is
68
+ * nothing to mark.
69
+ */
70
+ export declare function answerSpokenOver(items: readonly ConversationItemLike[]): string | undefined;
@@ -46,3 +46,21 @@ export function turnEnded(heard, turnSilenceMs = TURN_SILENCE_MS) {
46
46
  export function bargedIn(state, heard) {
47
47
  return (state === 'speaking' || state === 'thinking') && heard.spoke;
48
48
  }
49
+ /**
50
+ * The answer the person spoke over (VO-13): the agent's last message after
51
+ * the person's last one — still being written, or written and being said.
52
+ * `undefined` while the agent has not answered the turn yet: there is
53
+ * nothing to mark.
54
+ */
55
+ export function answerSpokenOver(items) {
56
+ for (let i = items.length - 1; i >= 0; i -= 1) {
57
+ const role = items[i].role;
58
+ if (role === 'user') {
59
+ return undefined;
60
+ }
61
+ if (role === 'assistant') {
62
+ return items[i].id;
63
+ }
64
+ }
65
+ return undefined;
66
+ }
@@ -14,6 +14,7 @@ export * from './pinned';
14
14
  export * from './capture';
15
15
  export * from './hearing';
16
16
  export * from './serverHearing';
17
+ export * from './sayAloud';
17
18
  export * from './handsFree';
18
19
  export { HandsFree, type HandsFreeProps } from './HandsFree';
19
20
  export * from './speaker';
@@ -8,6 +8,7 @@ export * from './pinned';
8
8
  export * from './capture';
9
9
  export * from './hearing';
10
10
  export * from './serverHearing';
11
+ export * from './sayAloud';
11
12
  export * from './handsFree';
12
13
  export { HandsFree } from './HandsFree';
13
14
  export * from './speaker';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What only the runtime knows of voice, as it says it over AG-UI (VOICE.md
3
+ * VO-35) and Vercel AI (VO-36): `voice.settings` — the running application's
4
+ * voice and language, at a run's start — and `voice.say` — a short line the
5
+ * agent wants heard and not written (*One moment*), shown as a caption while
6
+ * it is said. The chat reads `voice.say` from a run's custom events
7
+ * (`CUSTOM`, the chat's `activity`) or data parts (`data-voice.say`); it
8
+ * already knows its application's voice from `/apps/configure` (VO-45), and
9
+ * `voice.settings` is for clients that do not ask it.
10
+ *
11
+ * @module voice/sayAloud
12
+ */
13
+ /** A line the agent wants heard, not written. */
14
+ export declare const VOICE_SAY_EVENT = "voice.say";
15
+ /** The running application's voice, at a run's start. */
16
+ export declare const VOICE_SETTINGS_EVENT = "voice.settings";
17
+ /** The longest line said aloud: the runtime refuses longer ones. */
18
+ export declare const SAY_ALOUD_MAX_CHARACTERS = 200;
19
+ /** A custom event or data part, as the chat's adapters give it. */
20
+ export interface VoiceActivity {
21
+ type: string;
22
+ data: unknown;
23
+ }
24
+ /** The line a `voice.say` says, or `undefined` for anything else. */
25
+ export declare function lineToSay(activity: VoiceActivity): string | undefined;
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * What only the runtime knows of voice, as it says it over AG-UI (VOICE.md
7
+ * VO-35) and Vercel AI (VO-36): `voice.settings` — the running application's
8
+ * voice and language, at a run's start — and `voice.say` — a short line the
9
+ * agent wants heard and not written (*One moment*), shown as a caption while
10
+ * it is said. The chat reads `voice.say` from a run's custom events
11
+ * (`CUSTOM`, the chat's `activity`) or data parts (`data-voice.say`); it
12
+ * already knows its application's voice from `/apps/configure` (VO-45), and
13
+ * `voice.settings` is for clients that do not ask it.
14
+ *
15
+ * @module voice/sayAloud
16
+ */
17
+ /** A line the agent wants heard, not written. */
18
+ export const VOICE_SAY_EVENT = 'voice.say';
19
+ /** The running application's voice, at a run's start. */
20
+ export const VOICE_SETTINGS_EVENT = 'voice.settings';
21
+ /** The longest line said aloud: the runtime refuses longer ones. */
22
+ export const SAY_ALOUD_MAX_CHARACTERS = 200;
23
+ const nameOf = (type) => type.replace(/^data-/, '');
24
+ /** The line a `voice.say` says, or `undefined` for anything else. */
25
+ export function lineToSay(activity) {
26
+ if (nameOf(activity.type) !== VOICE_SAY_EVENT) {
27
+ return undefined;
28
+ }
29
+ const data = activity.data;
30
+ const text = data && typeof data.text === 'string'
31
+ ? data.text.split(/\s+/).join(' ').trim()
32
+ : '';
33
+ return text ? text.slice(0, SAY_ALOUD_MAX_CHARACTERS) : undefined;
34
+ }
@@ -2,10 +2,19 @@ import type { Hearing, HearingProgress, HeardSoFar } from './hearing';
2
2
  import type { Transcript } from './types';
3
3
  /** Where the speech service hears a recording, on ai-agents' host. */
4
4
  export declare const TRANSCRIPTIONS_PATH = "/api/ai-agents/v1/speech/transcriptions";
5
+ /** Where the speech service hears speech as it is said. */
6
+ export declare const TRANSCRIPTION_STREAM_PATH = "/api/ai-agents/v1/speech/transcriptions/stream";
7
+ /** A stream no look or hearing has fed for this long is closed: its press was cancelled. */
8
+ export declare const STREAM_IDLE_MS = 3000;
9
+ /** How long the final words of a stream are waited for. */
10
+ export declare const STREAM_FINAL_MS = 60000;
5
11
  /** The model the speech service hears with (agentspecs' catalogue). */
6
12
  export declare const SERVER_HEARING_MODEL = "whisper-large-v3-turbo";
7
13
  /** Where a chat's voice hears: in the browser, or on Datalayer's servers. */
8
14
  export type HearingPlace = 'device' | 'server';
15
+ /** The speech service refused a press, in its own sentence (a limit, a visitor's day). */
16
+ export declare class ServerRefused extends Error {
17
+ }
9
18
  /** What decides where a chat's voice hears. */
10
19
  export interface HearingAsked {
11
20
  language: string;
@@ -40,7 +49,26 @@ export interface ServerHearingOptions {
40
49
  /** Let Whisper find the language (VO-14), rather than tell it. */
41
50
  detect?: boolean;
42
51
  fetcher?: typeof fetch;
52
+ /** Opens a WebSocket; the browser's own unless a test gives one. */
53
+ socket?: (url: string) => SpeechSocket;
54
+ }
55
+ /** What `ServerHearing` needs of a WebSocket. */
56
+ export interface SpeechSocket {
57
+ readonly readyState: number;
58
+ binaryType: string;
59
+ onopen: ((event: unknown) => void) | null;
60
+ onmessage: ((event: {
61
+ data: unknown;
62
+ }) => void) | null;
63
+ onclose: ((event: unknown) => void) | null;
64
+ onerror: ((event: unknown) => void) | null;
65
+ send(data: string | ArrayBuffer): void;
66
+ close(code?: number): void;
43
67
  }
68
+ /** 16 kHz samples in [-1, 1] as little-endian 16-bit PCM: what a `pcm16` stream takes. */
69
+ export declare function pcm16Of(samples: Float32Array): ArrayBuffer;
70
+ /** The ws(s) address of the stream on ai-agents' origin. */
71
+ export declare function streamUrlOf(speechUrl: string): string;
44
72
  /**
45
73
  * Hears on Datalayer's servers: the browser's VAD finds the speech and when
46
74
  * it stopped; Whisper, on the speech service, writes it.
@@ -52,7 +80,40 @@ export declare class ServerHearing implements Hearing {
52
80
  constructor(finder: SpeechFinder, options: ServerHearingOptions);
53
81
  /** Only the VAD loads in the page: the words are written on the server. */
54
82
  ready(_language: string, progress?: HearingProgress): Promise<void>;
55
- /** Whether speech was found and how long it has been silent; its words come at the end. */
56
- hearSoFar(audio: Float32Array): Promise<HeardSoFar>;
83
+ /** The press being heard as it is said, while one is. */
84
+ private press?;
85
+ /** The press's stream, opened anew when a new press began (its sound is shorter). */
86
+ private streamFor;
87
+ /**
88
+ * Whether speech was found and how long it has been silent, and the words
89
+ * the speech service heard so far: the sound goes to its stream as it comes.
90
+ */
91
+ hearSoFar(audio: Float32Array, language?: string): Promise<HeardSoFar>;
92
+ hear(audio: Float32Array, language: string): Promise<Transcript | undefined>;
93
+ }
94
+ /** Said when the device's model did not load and the server will hear instead. */
95
+ export declare const MOVED_TO_SERVER = "This device could not load its model: what you say will be heard on Datalayer's servers instead. Press the microphone again to agree.";
96
+ /**
97
+ * `where: auto` (§5): each press heard on the device when a model there
98
+ * hears the language and loads, on the speech service otherwise.
99
+ *
100
+ * The device's model failing to load moves the hearing to the server, and
101
+ * the press is refused in a sentence so that the server's own consent is
102
+ * asked at the next one. The server refusing a press — a limit, a visitor's
103
+ * day spent — moves it back to the device when a model there hears the
104
+ * language, and the press is heard there: the sound then stays in the page.
105
+ */
106
+ export declare class AutoHearing implements Hearing {
107
+ private readonly device;
108
+ private readonly server;
109
+ private readonly hearsHere;
110
+ private place;
111
+ /** The device's model did not load: it is not tried again on this page. */
112
+ private deviceBroken;
113
+ constructor(device: Hearing, server: Hearing, language: string, hearsHere?: (language: string) => boolean);
114
+ get where(): HearingPlace;
115
+ private get current();
116
+ ready(language: string, progress?: HearingProgress): Promise<void>;
117
+ hearSoFar(audio: Float32Array, language: string): Promise<HeardSoFar>;
57
118
  hear(audio: Float32Array, language: string): Promise<Transcript | undefined>;
58
119
  }