@datalayer/agent-runtimes 1.5.26 → 1.5.28

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 (49) hide show
  1. package/dist-embed/chunks/{ChatView-CUWE6I5M.js → ChatView-C0PWJ_wj.js} +4 -4
  2. package/dist-embed/datalayer-app-main.js +111 -101
  3. package/lib/apps/apps/AppKept.js +5 -1
  4. package/lib/apps/embed/element.js +10 -4
  5. package/lib/apps/embed/embedConfig.d.ts +10 -1
  6. package/lib/apps/embed/embedConfig.js +25 -1
  7. package/lib/apps/plugins/chat/ChatView.js +2 -2
  8. package/lib/chat/assistant/AssistantStage.js +17 -0
  9. package/lib/chat/assistant/clearance.d.ts +10 -0
  10. package/lib/chat/assistant/clearance.js +14 -0
  11. package/lib/chat/base/ChatBase.js +375 -437
  12. package/lib/chat/base/conversationItems.d.ts +55 -0
  13. package/lib/chat/base/conversationItems.js +126 -0
  14. package/lib/portable/adapterEvents.d.ts +107 -0
  15. package/lib/portable/adapterEvents.js +239 -0
  16. package/lib/portable/approvalCard.d.ts +56 -0
  17. package/lib/portable/approvalCard.js +56 -0
  18. package/lib/portable/conversation.d.ts +71 -18
  19. package/lib/portable/conversation.js +330 -74
  20. package/lib/portable/fixtures.js +12 -0
  21. package/lib/portable/index.d.ts +4 -1
  22. package/lib/portable/index.js +7 -1
  23. package/lib/portable/presence.js +4 -1
  24. package/lib/portable/runMeasures.d.ts +46 -0
  25. package/lib/portable/runMeasures.js +156 -0
  26. package/lib/protocols/ACPAdapter.js +13 -8
  27. package/lib/specs/voices.js +47 -0
  28. package/lib/utils/utils.d.ts +2 -8
  29. package/lib/utils/utils.js +3 -26
  30. package/lib/voice/ComposerVoice.js +25 -3
  31. package/lib/voice/VoiceInput.d.ts +2 -2
  32. package/lib/voice/VoiceInput.js +6 -4
  33. package/lib/voice/VoiceMenu.js +2 -1
  34. package/lib/voice/chatVoice.d.ts +4 -3
  35. package/lib/voice/chatVoice.js +6 -9
  36. package/lib/voice/consent.d.ts +8 -0
  37. package/lib/voice/consent.js +10 -0
  38. package/lib/voice/hearing.d.ts +23 -1
  39. package/lib/voice/hearing.js +18 -3
  40. package/lib/voice/index.d.ts +11 -6
  41. package/lib/voice/index.js +1 -0
  42. package/lib/voice/serverHearing.d.ts +58 -0
  43. package/lib/voice/serverHearing.js +136 -0
  44. package/lib/voice/settings.d.ts +5 -0
  45. package/lib/voice/settings.js +8 -0
  46. package/lib/voice/speaker.d.ts +4 -3
  47. package/lib/voice/speaker.js +1 -2
  48. package/lib/voice/useVoiceSpeaker.js +0 -3
  49. package/package.json +2 -2
@@ -13,6 +13,7 @@ export * from './sentences';
13
13
  export * from './pinned';
14
14
  export * from './capture';
15
15
  export * from './hearing';
16
+ export * from './serverHearing';
16
17
  export * from './speaker';
17
18
  export * from './consent';
18
19
  export * from './settings';
@@ -64,15 +65,19 @@ export interface ChatVoice {
64
65
  * (VO-10, VO-69): 1,500 when unsaid; longer for a person who pauses.
65
66
  */
66
67
  silenceMs?: number;
67
- /** ai-agents, whose speech service says the answers. */
68
+ /** ai-agents, whose speech service says the answers, and hears where the browser does not. */
68
69
  speechUrl?: string;
69
- /** The token the speech service is asked with. */
70
- token?: string;
71
70
  /**
72
- * The deployment an embedded application's page speaks for (VO-19): what
73
- * the speech service checks an embed token against, and counts its spend on.
71
+ * Where what is said is heard (§5): `device`, the browser; `server`,
72
+ * Datalayer's speech service; `auto`, the default, the browser where a
73
+ * model there hears the language, else the server. The Appspec's
74
+ * `interface.voice.where`.
74
75
  */
75
- deployment?: string;
76
+ where?: 'auto' | 'device' | 'server';
77
+ /** The person asked for their language to be found by Whisper on the server (VO-14). */
78
+ detect?: boolean;
79
+ /** The token the speech service is asked with. */
80
+ token?: string;
76
81
  /** Send what is said at once, rather than put it in the composer. */
77
82
  sendWhatISay?: boolean;
78
83
  /**
@@ -7,6 +7,7 @@ export * from './sentences';
7
7
  export * from './pinned';
8
8
  export * from './capture';
9
9
  export * from './hearing';
10
+ export * from './serverHearing';
10
11
  export * from './speaker';
11
12
  export * from './consent';
12
13
  export * from './settings';
@@ -0,0 +1,58 @@
1
+ import type { Hearing, HearingProgress, HeardSoFar } from './hearing';
2
+ import type { Transcript } from './types';
3
+ /** Where the speech service hears a recording, on ai-agents' host. */
4
+ export declare const TRANSCRIPTIONS_PATH = "/api/ai-agents/v1/speech/transcriptions";
5
+ /** The model the speech service hears with (agentspecs' catalogue). */
6
+ export declare const SERVER_HEARING_MODEL = "whisper-large-v3-turbo";
7
+ /** Where a chat's voice hears: in the browser, or on Datalayer's servers. */
8
+ export type HearingPlace = 'device' | 'server';
9
+ /** What decides where a chat's voice hears. */
10
+ export interface HearingAsked {
11
+ language: string;
12
+ /** The application's `interface.voice.where`; `auto` when unsaid. */
13
+ where?: 'auto' | 'device' | 'server';
14
+ /** The person asked for their language to be detected (VO-14). */
15
+ detect?: boolean;
16
+ /** The speech service; none, and everything is heard in the browser. */
17
+ speechUrl?: string;
18
+ }
19
+ /**
20
+ * Where a chat's voice hears (§5): in the browser unless the speech service
21
+ * is named and the person asked for detection, the application says
22
+ * `server`, or it says `auto` and no model in the browser hears the language.
23
+ */
24
+ export declare function hearingPlace(voice: HearingAsked): HearingPlace;
25
+ /** Mono samples at 16 kHz as a 16-bit PCM WAV file: what the service reads. */
26
+ export declare function wavOf(samples: Float32Array, rate?: number): ArrayBuffer;
27
+ /** What finds the speech in what was heard: the browser's VAD (`DeviceHearing`). */
28
+ export interface SpeechFinder {
29
+ readyToFindSpeech(progress?: HearingProgress): Promise<void>;
30
+ findSpeech(audio: Float32Array): Promise<{
31
+ speech: Float32Array;
32
+ silentMs: number;
33
+ }>;
34
+ }
35
+ export interface ServerHearingOptions {
36
+ /** ai-agents' origin, where the speech service is. */
37
+ speechUrl: string;
38
+ /** The token the conversation speaks with: the person's, a visitor's, an embed's. */
39
+ token?: string;
40
+ /** Let Whisper find the language (VO-14), rather than tell it. */
41
+ detect?: boolean;
42
+ fetcher?: typeof fetch;
43
+ }
44
+ /**
45
+ * Hears on Datalayer's servers: the browser's VAD finds the speech and when
46
+ * it stopped; Whisper, on the speech service, writes it.
47
+ */
48
+ export declare class ServerHearing implements Hearing {
49
+ private readonly finder;
50
+ private readonly options;
51
+ readonly where: "server";
52
+ constructor(finder: SpeechFinder, options: ServerHearingOptions);
53
+ /** Only the VAD loads in the page: the words are written on the server. */
54
+ 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>;
57
+ hear(audio: Float32Array, language: string): Promise<Transcript | undefined>;
58
+ }
@@ -0,0 +1,136 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Hearing on Datalayer's servers (VOICE.md VO-34, VO-14, §5): what was said
7
+ * in one press, kept to its speech by Silero VAD in the page, then sent as a
8
+ * WAV recording to the speech service's `POST /speech/transcriptions`, where
9
+ * Whisper large-v3-turbo hears it — in any language it knows, and finding
10
+ * the language itself when the person asks for that (detection, VO-14). The
11
+ * sound is held in the request only: the service keeps neither it nor the
12
+ * words (VO-60).
13
+ *
14
+ * Used where the application's voice says `where: server`, where it says
15
+ * `auto` and no model in the browser hears the language, and where the
16
+ * person asked for their language to be detected — never when they chose to
17
+ * keep speech on the device. A clip model: its words come at the end, not
18
+ * while the person speaks (VO-11).
19
+ *
20
+ * @module voice/serverHearing
21
+ */
22
+ import { transcriberFor } from '../specs/voices';
23
+ import { HEARING_RATE } from './capture';
24
+ /** Where the speech service hears a recording, on ai-agents' host. */
25
+ export const TRANSCRIPTIONS_PATH = '/api/ai-agents/v1/speech/transcriptions';
26
+ /** The model the speech service hears with (agentspecs' catalogue). */
27
+ export const SERVER_HEARING_MODEL = 'whisper-large-v3-turbo';
28
+ /** The shortest speech worth hearing, in samples: a word said alone. */
29
+ const SHORTEST = HEARING_RATE * 0.2;
30
+ /**
31
+ * Where a chat's voice hears (§5): in the browser unless the speech service
32
+ * is named and the person asked for detection, the application says
33
+ * `server`, or it says `auto` and no model in the browser hears the language.
34
+ */
35
+ export function hearingPlace(voice) {
36
+ if (!voice.speechUrl) {
37
+ return 'device';
38
+ }
39
+ if (voice.detect || voice.where === 'server') {
40
+ return 'server';
41
+ }
42
+ if (voice.where === 'device') {
43
+ return 'device';
44
+ }
45
+ return transcriberFor(voice.language) ? 'device' : 'server';
46
+ }
47
+ /** Mono samples at 16 kHz as a 16-bit PCM WAV file: what the service reads. */
48
+ export function wavOf(samples, rate = HEARING_RATE) {
49
+ const bytes = new ArrayBuffer(44 + samples.length * 2);
50
+ const view = new DataView(bytes);
51
+ const text = (at, value) => {
52
+ for (let i = 0; i < value.length; i++) {
53
+ view.setUint8(at + i, value.charCodeAt(i));
54
+ }
55
+ };
56
+ text(0, 'RIFF');
57
+ view.setUint32(4, 36 + samples.length * 2, true);
58
+ text(8, 'WAVE');
59
+ text(12, 'fmt ');
60
+ view.setUint32(16, 16, true);
61
+ view.setUint16(20, 1, true); // PCM
62
+ view.setUint16(22, 1, true); // mono
63
+ view.setUint32(24, rate, true);
64
+ view.setUint32(28, rate * 2, true);
65
+ view.setUint16(32, 2, true);
66
+ view.setUint16(34, 16, true);
67
+ text(36, 'data');
68
+ view.setUint32(40, samples.length * 2, true);
69
+ for (let i = 0; i < samples.length; i++) {
70
+ const sample = Math.max(-1, Math.min(1, samples[i]));
71
+ view.setInt16(44 + i * 2, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);
72
+ }
73
+ return bytes;
74
+ }
75
+ /**
76
+ * Hears on Datalayer's servers: the browser's VAD finds the speech and when
77
+ * it stopped; Whisper, on the speech service, writes it.
78
+ */
79
+ export class ServerHearing {
80
+ finder;
81
+ options;
82
+ where = 'server';
83
+ constructor(finder, options) {
84
+ this.finder = finder;
85
+ this.options = options;
86
+ }
87
+ /** Only the VAD loads in the page: the words are written on the server. */
88
+ ready(_language, progress) {
89
+ return this.finder.readyToFindSpeech(progress);
90
+ }
91
+ /** Whether speech was found and how long it has been silent; its words come at the end. */
92
+ async hearSoFar(audio) {
93
+ const { speech, silentMs } = await this.finder.findSpeech(audio);
94
+ return { text: '', spoke: speech.length >= SHORTEST, silentMs };
95
+ }
96
+ async hear(audio, language) {
97
+ const started = performance.now();
98
+ const { speech } = await this.finder.findSpeech(audio);
99
+ if (speech.length < SHORTEST) {
100
+ return undefined;
101
+ }
102
+ const { speechUrl, token, detect, fetcher = fetch } = this.options;
103
+ const query = detect ? '' : `?language=${encodeURIComponent(language)}`;
104
+ const answered = await fetcher(`${speechUrl.replace(/\/+$/, '')}${TRANSCRIPTIONS_PATH}${query}`, {
105
+ method: 'POST',
106
+ headers: {
107
+ 'Content-Type': 'audio/wav',
108
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
109
+ },
110
+ body: wavOf(speech),
111
+ });
112
+ const body = (await answered.json().catch(() => ({})));
113
+ if (!answered.ok) {
114
+ throw new Error(typeof body.detail === 'string' && body.detail
115
+ ? body.detail
116
+ : `What you said could not be heard on Datalayer's servers (${answered.status}).`);
117
+ }
118
+ const text = typeof body.text === 'string' ? body.text.trim() : '';
119
+ if (!text) {
120
+ return undefined;
121
+ }
122
+ return {
123
+ text,
124
+ metadata: {
125
+ input: 'voice',
126
+ language: typeof body.language === 'string' && body.language
127
+ ? body.language
128
+ : language,
129
+ engine: SERVER_HEARING_MODEL,
130
+ where: 'server',
131
+ },
132
+ seconds: speech.length / HEARING_RATE,
133
+ ms: Math.round(performance.now() - started),
134
+ };
135
+ }
136
+ }
@@ -57,6 +57,11 @@ export interface VoiceSettings {
57
57
  * speech service, so answers are spoken only if the browser can do it.
58
58
  */
59
59
  onDeviceOnly?: boolean;
60
+ /**
61
+ * Let Whisper find the language they speak (VO-14): what they say is
62
+ * heard on Datalayer's servers, which detect it, rather than in the browser.
63
+ */
64
+ detectLanguage?: boolean;
60
65
  }
61
66
  /** The settings as they can be used: numbers in range, nothing else trusted. */
62
67
  export declare function saneVoiceSettings(settings: VoiceSettings | null | undefined): VoiceSettings;
@@ -40,6 +40,9 @@ export function saneVoiceSettings(settings) {
40
40
  if (typeof kept.onDeviceOnly === 'boolean') {
41
41
  sane.onDeviceOnly = kept.onDeviceOnly;
42
42
  }
43
+ if (typeof kept.detectLanguage === 'boolean') {
44
+ sane.detectLanguage = kept.detectLanguage;
45
+ }
43
46
  return sane;
44
47
  }
45
48
  /** What was kept, or nothing. Storage may be refused, or hold nonsense. */
@@ -174,6 +177,9 @@ export function voiceForPerson(voice, person = {}, browser = browserLanguage())
174
177
  if (chosen.microphoneId) {
175
178
  next.microphoneId = chosen.microphoneId;
176
179
  }
180
+ if (chosen.detectLanguage) {
181
+ next.detect = true;
182
+ }
177
183
  if (chosen.onDeviceOnly) {
178
184
  // Nothing is asked of the speech service: no token, no origin. Answers
179
185
  // are said by that service only (decision 1: no voice made in the
@@ -181,6 +187,8 @@ export function voiceForPerson(voice, person = {}, browser = browserLanguage())
181
187
  next.speechUrl = undefined;
182
188
  next.token = undefined;
183
189
  next.output = 'off';
190
+ next.where = 'device';
191
+ next.detect = false;
184
192
  }
185
193
  return next;
186
194
  }
@@ -24,10 +24,11 @@ export interface SpeakerOptions {
24
24
  /** The voice of the catalogue. */
25
25
  voice: string;
26
26
  language: string;
27
- /** The person's token, an application's principal's, a visitor's or an embed's. */
27
+ /**
28
+ * The person's token, an application's principal's, a visitor's or an
29
+ * embed's — whose deployment is the one it names (VO-19, decision 16).
30
+ */
28
31
  token?: string;
29
- /** The deployment an embed's page speaks for (VO-19): sent with each sentence. */
30
- deployment?: string;
31
32
  /**
32
33
  * How fast it speaks, as a multiplier of the voice's own pace (VO-25,
33
34
  * VO-30). Applied here rather than asked of the service: `POST
@@ -114,7 +114,7 @@ export class ServerSpeaker {
114
114
  }
115
115
  }
116
116
  async fetch(text) {
117
- const { speechUrl, voice, language, token, deployment, fetcher = fetch, } = this.options;
117
+ const { speechUrl, voice, language, token, fetcher = fetch } = this.options;
118
118
  try {
119
119
  const answered = await fetcher(`${speechUrl.replace(/\/+$/, '')}${SYNTHESES_PATH}`, {
120
120
  method: 'POST',
@@ -127,7 +127,6 @@ export class ServerSpeaker {
127
127
  voice,
128
128
  language,
129
129
  format: 'opus',
130
- ...(deployment ? { deployment } : {}),
131
130
  }),
132
131
  });
133
132
  if (!answered.ok) {
@@ -27,7 +27,6 @@ export function useVoiceSpeaker(voice, on = true) {
27
27
  voice: voice.voice,
28
28
  language: voice.language,
29
29
  token: voice.token,
30
- deployment: voice.deployment,
31
30
  rate: voice.rate,
32
31
  volume: voice.volume,
33
32
  onState: setState,
@@ -40,7 +39,6 @@ export function useVoiceSpeaker(voice, on = true) {
40
39
  voice: voice?.voice,
41
40
  language: voice?.language,
42
41
  token: voice?.token,
43
- deployment: voice?.deployment,
44
42
  rate: voice?.rate,
45
43
  volume: voice?.volume,
46
44
  });
@@ -49,7 +47,6 @@ export function useVoiceSpeaker(voice, on = true) {
49
47
  voice?.voice,
50
48
  voice?.language,
51
49
  voice?.token,
52
- voice?.deployment,
53
50
  voice?.rate,
54
51
  voice?.volume,
55
52
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.26",
3
+ "version": "1.5.28",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -158,7 +158,7 @@
158
158
  "@datalayer/jupyter-react": "^2.0.20",
159
159
  "@datalayer/jupyter-server-nbmodel": "^0.2.8",
160
160
  "@datalayer/lexical-loro": "^1.0.6",
161
- "@datalayer/primer-addons": "^1.0.39",
161
+ "@datalayer/primer-addons": "^1.0.40",
162
162
  "@datalayer/primer-rjsf": "^1.0.3",
163
163
  "@datalayer/personal-agent-protocol": "^0.24.0",
164
164
  "@datalayer/reactor": "^1.3.0",