@datalayer/agent-runtimes 1.5.42 → 1.5.44

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 (83) 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/datalayer-app-main.js +67 -67
  48. package/lib/apps/apps/sessionRules.d.ts +14 -1
  49. package/lib/apps/apps/sessionRules.js +68 -2
  50. package/lib/apps/core/canvasParts.d.ts +7 -1
  51. package/lib/apps/plugins/app-rules/AppRulesCard.js +2 -2
  52. package/lib/apps/plugins/canvas-parts/index.d.ts +9 -1
  53. package/lib/apps/plugins/canvas-parts/index.js +28 -0
  54. package/lib/chat/ChatInline.js +43 -22
  55. package/lib/chat/base/ChatBase.js +43 -2
  56. package/lib/components/agents/AgentSummary.d.ts +2 -24
  57. package/lib/components/agents/AgentSummary.js +3 -19
  58. package/lib/hooks/useChat.js +110 -144
  59. package/lib/portable/activity.d.ts +78 -0
  60. package/lib/portable/activity.js +129 -0
  61. package/lib/portable/fixtures.d.ts +7 -0
  62. package/lib/portable/fixtures.js +50 -0
  63. package/lib/portable/index.d.ts +4 -1
  64. package/lib/portable/index.js +7 -1
  65. package/lib/portable/lifecycle.d.ts +77 -0
  66. package/lib/portable/lifecycle.js +135 -0
  67. package/lib/portable/runtimes.d.ts +68 -0
  68. package/lib/portable/runtimes.js +69 -0
  69. package/lib/voice/VoiceMenu.d.ts +2 -1
  70. package/lib/voice/VoiceMenu.js +2 -2
  71. package/lib/voice/chatVoice.d.ts +5 -2
  72. package/lib/voice/chatVoice.js +10 -4
  73. package/lib/voice/choices.d.ts +2 -0
  74. package/lib/voice/choices.js +6 -0
  75. package/lib/voice/index.d.ts +1 -0
  76. package/lib/voice/index.js +1 -0
  77. package/lib/voice/sayAloud.d.ts +25 -0
  78. package/lib/voice/sayAloud.js +34 -0
  79. package/lib/voice/serverHearing.d.ts +34 -2
  80. package/lib/voice/serverHearing.js +234 -6
  81. package/lib/voice/settings.d.ts +12 -0
  82. package/lib/voice/settings.js +9 -0
  83. package/package.json +1 -1
@@ -27,7 +27,7 @@ export { conversationEventsOf, sanitizeAssistantContent, snapshotOfMessages, } f
27
27
  export { PRESENCE_LINES, presenceOfConversation, presenceState, presenceToolOf, } from './presence';
28
28
  // One contract for every renderer of a conversation's components.
29
29
  export { ANSWER_BELOW, ANSWER_COMPLETE, composerOf, droppedWords, ENDED_WORDS, messageLabel, OPENING_WORDS, presenceLine, showsQuestion, stepLine, TOOL_STATUS_WORDS, toolLabel, } from './views';
30
- export { conversationFixture } from './fixtures';
30
+ export { activityFixture, conversationFixture } from './fixtures';
31
31
  // The protocol displays a run's tool results carry (MOBILE M-037).
32
32
  export { DISPLAY_META_KEY, displayLine, displayOfToolResult, PAP_CONFIRM_TYPE, PAP_HANDOFF_TOOL, PAP_OUTCOME_TYPE, PAP_REPLIES_TYPE, PAP_SIGN_IN_TYPE, protocolDisplayOf, replyAuthor,
33
33
  // A coding agent's diff and terminal (SHIELD §4.3, SH-16).
@@ -42,5 +42,11 @@ export { SIGN_IN_POLL_MS, SIGN_IN_STATUS_PATH, signInStandsOf, signInStatusClien
42
42
  export { emptyFields, fieldProblems, MAX_VALUE_LENGTH, PROTECTED_INPUT_WORDS, protectedInputClient, protectedRequestOf, ProtectedRequestGone, sentWords, submitBody, submitWords, ticketComplete, ticketOfPage, } from './protectedInput';
43
43
  // How a streamed turn went, measured without its words (MOBILE M-036).
44
44
  export { failureOf, otlpMetricsOf, RunMeter, } from './runMeasures';
45
+ // What an application is doing, and its record's entries (LOOP R-15, MOBILE M-015b).
46
+ export { ACTIVITY_WORDS, activityOf, entryOf, entrySaid, mergeActivityPages, runEndOf, scheduledSays, sessionSentence, wakesOn, } from './activity';
47
+ // A deployment's pause, resume and reset, and its recorded events (LOOP R-17, MOBILE M-041b).
48
+ export { deploymentEventLine, deploymentEventOf, notDeletedOf, resetBody, resetCountsOf, resetLines, stateChangeBody, stoppedWorkLine, stoppedWorkOf, } from './lifecycle';
49
+ // A person's agent runtimes, and an agent's summary (MOBILE M-014b).
50
+ export { agentRuntimesOf, agentSummaryOfRuntime, CREATING_STATUSES, isAgentRuntime, isCreatingAgent, runtimeOf, } from './runtimes';
45
51
  // The runtime's routes a native client drives.
46
52
  export { agentRunClient, agentRuntimesUrlOf, appSessionsClient, RuntimeRefusal, } from './sessions';
@@ -0,0 +1,77 @@
1
+ /**
2
+ * A deployment's lifecycle as its owner drives it (LOOP R-17, D-04): pause
3
+ * and resume one exact version, reset what it holds, and the events recorded
4
+ * with who did each. The requests' bodies, the answers read and the words
5
+ * said — the same in the Studio and Datalayer Mobile (MOBILE M-041b). Pure:
6
+ * each client sends them with its own authentication.
7
+ *
8
+ * @module portable/lifecycle
9
+ */
10
+ /** Whether a deployment answers now. */
11
+ export type DeploymentState = 'live' | 'paused';
12
+ /** Pause or resume, for one exact version: `PATCH /apps/deployments/{uid}`'s body. */
13
+ export declare const stateChangeBody: (version: number, state: DeploymentState) => {
14
+ version: number;
15
+ state: DeploymentState;
16
+ };
17
+ /** What pausing stopped, and what it could not, with why. */
18
+ export type StoppedWork = {
19
+ stopped: {
20
+ what: string;
21
+ runtime: string;
22
+ }[];
23
+ notStopped: {
24
+ what: string;
25
+ runtime: string;
26
+ why: string;
27
+ }[];
28
+ };
29
+ export declare function stoppedWorkOf(raw: Record<string, unknown>): StoppedWork;
30
+ /** What pausing did, in a sentence. */
31
+ export declare function stoppedWorkLine(work: StoppedWork): string;
32
+ /** What a Reset deletes, counted for its confirmation. */
33
+ export type ResetCounts = {
34
+ conversations: number;
35
+ entries: number;
36
+ schedules: number;
37
+ /**
38
+ * Why its memories stay: they are its application's, not the
39
+ * deployment's (LOOP R-18) — forgotten under *What it remembers*.
40
+ */
41
+ memoriesNote: string;
42
+ };
43
+ export declare function resetCountsOf(raw: Record<string, unknown>): ResetCounts;
44
+ /** The Reset confirmed against the counts the person was shown: `POST …/reset`'s body. */
45
+ export declare const resetBody: (counts: Pick<ResetCounts, "conversations" | "schedules">) => {
46
+ conversations: number;
47
+ schedules: number;
48
+ };
49
+ /** What a Reset could not delete, with why: `POST …/reset`'s answer. */
50
+ export declare function notDeletedOf(body: Record<string, unknown>): {
51
+ what: string;
52
+ why: string;
53
+ }[];
54
+ /**
55
+ * What goes, as the confirmation lists it — "12 conversations, 2
56
+ * schedules" — and what does not, said apart.
57
+ */
58
+ export declare function resetLines(counts: ResetCounts): {
59
+ goes: string;
60
+ stays: string[];
61
+ };
62
+ /** What happened to a deployment, recorded with who did it (D-04, R-17). */
63
+ export type DeploymentEventKind = 'promoted' | 'rolled_back' | 'paused' | 'resumed' | 'reset';
64
+ /** Who promoted, rolled back, paused, resumed or reset a deployment, and when. */
65
+ export type DeploymentEvent = {
66
+ uid: string;
67
+ kind: DeploymentEventKind;
68
+ actor: string;
69
+ at: string;
70
+ details: Record<string, unknown>;
71
+ };
72
+ export declare function deploymentEventOf(raw: Record<string, unknown>): DeploymentEvent;
73
+ /**
74
+ * One event, in a line: "Paused by eric, 4 Oct 2026, 10:12", "Rolled back
75
+ * from version 3 to version 2 by eric, …".
76
+ */
77
+ export declare function deploymentEventLine(event: DeploymentEvent, locale?: string): string;
@@ -0,0 +1,135 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** Pause or resume, for one exact version: `PATCH /apps/deployments/{uid}`'s body. */
6
+ export const stateChangeBody = (version, state) => {
7
+ if (!Number.isInteger(version) || version < 1) {
8
+ throw new Error(`A deployment's state is changed for one exact version: ${String(version)} is none.`);
9
+ }
10
+ return { version, state };
11
+ };
12
+ const listOf = (raw) => Array.isArray(raw)
13
+ ? raw.filter(item => item && typeof item === 'object')
14
+ : [];
15
+ const said = (value) => typeof value === 'string' ? value : value == null ? '' : String(value);
16
+ const count = (value) => {
17
+ const n = Number(value);
18
+ return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0;
19
+ };
20
+ const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
21
+ export function stoppedWorkOf(raw) {
22
+ return {
23
+ stopped: listOf(raw.stopped).map(item => ({
24
+ what: said(item.what),
25
+ runtime: said(item.runtime),
26
+ })),
27
+ notStopped: listOf(raw.not_stopped).map(item => ({
28
+ what: said(item.what),
29
+ runtime: said(item.runtime),
30
+ why: said(item.why),
31
+ })),
32
+ };
33
+ }
34
+ /** What pausing did, in a sentence. */
35
+ export function stoppedWorkLine(work) {
36
+ const stopped = work.stopped.length;
37
+ const first = stopped === 0
38
+ ? 'Paused: nothing was running.'
39
+ : `Paused: ${plural(stopped, 'session')} in progress stopped.`;
40
+ if (!work.notStopped.length) {
41
+ return first;
42
+ }
43
+ const missed = work.notStopped
44
+ .map(item => `${item.what}${item.runtime ? ` (${item.runtime})` : ''}: ${item.why}`)
45
+ .join(' ');
46
+ return `${first} Not stopped — ${missed}`;
47
+ }
48
+ export function resetCountsOf(raw) {
49
+ return {
50
+ conversations: count(raw.conversations),
51
+ entries: count(raw.entries),
52
+ schedules: count(raw.schedules),
53
+ memoriesNote: typeof raw.memories_note === 'string' ? raw.memories_note : '',
54
+ };
55
+ }
56
+ /** The Reset confirmed against the counts the person was shown: `POST …/reset`'s body. */
57
+ export const resetBody = (counts) => ({
58
+ conversations: counts.conversations,
59
+ schedules: counts.schedules,
60
+ });
61
+ /** What a Reset could not delete, with why: `POST …/reset`'s answer. */
62
+ export function notDeletedOf(body) {
63
+ return listOf(body.not_deleted).map(item => ({
64
+ what: said(item.what),
65
+ why: said(item.why),
66
+ }));
67
+ }
68
+ /**
69
+ * What goes, as the confirmation lists it — "12 conversations, 2
70
+ * schedules" — and what does not, said apart.
71
+ */
72
+ export function resetLines(counts) {
73
+ return {
74
+ goes: [
75
+ plural(counts.conversations, 'conversation'),
76
+ plural(counts.schedules, 'schedule'),
77
+ ].join(', '),
78
+ stays: [
79
+ 'The deployment, its address and the version it runs stay.',
80
+ ...(counts.memoriesNote ? [counts.memoriesNote] : []),
81
+ ],
82
+ };
83
+ }
84
+ const EVENT_KINDS = [
85
+ 'promoted',
86
+ 'rolled_back',
87
+ 'paused',
88
+ 'resumed',
89
+ 'reset',
90
+ ];
91
+ export function deploymentEventOf(raw) {
92
+ if (!EVENT_KINDS.includes(raw.kind)) {
93
+ throw new Error(`A deployment's event of no known kind: ${String(raw.kind)}.`);
94
+ }
95
+ return {
96
+ uid: said(raw.uid),
97
+ kind: raw.kind,
98
+ actor: said(raw.actor_handle || raw.actor_uid || ''),
99
+ at: typeof raw.at === 'string' ? raw.at : '',
100
+ details: raw.details && typeof raw.details === 'object'
101
+ ? raw.details
102
+ : {},
103
+ };
104
+ }
105
+ /**
106
+ * One event, in a line: "Paused by eric, 4 Oct 2026, 10:12", "Rolled back
107
+ * from version 3 to version 2 by eric, …".
108
+ */
109
+ export function deploymentEventLine(event, locale) {
110
+ const from = count(event.details.from);
111
+ const to = count(event.details.to);
112
+ const moved = from && to ? ` from version ${from} to version ${to}` : '';
113
+ const verb = {
114
+ promoted: `Promoted${moved}`,
115
+ rolled_back: `Rolled back${moved}`,
116
+ paused: 'Paused',
117
+ resumed: 'Resumed',
118
+ reset: 'Reset',
119
+ }[event.kind];
120
+ const when = event.at
121
+ ? new Date(event.at).toLocaleString(locale, {
122
+ dateStyle: 'medium',
123
+ timeStyle: 'short',
124
+ })
125
+ : '';
126
+ const deleted = event.details.deleted;
127
+ const what = event.kind === 'reset' && deleted
128
+ ? ` — ${plural(count(deleted.conversations), 'conversation')} and ${plural(count(deleted.schedules), 'schedule')} deleted`
129
+ : '';
130
+ // Paused by an owner of its owner's organization (I-09): said as such.
131
+ const forOrganization = event.kind === 'paused' && event.details.by === 'organization_owner'
132
+ ? ', an owner of your organization'
133
+ : '';
134
+ return `${verb}${event.actor ? ` by ${event.actor}${forOrganization}` : ''}${when ? `, ${when}` : ''}${what}`;
135
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * A person's agent runtimes as the runtimes service lists them
3
+ * (`GET /api/runtimes/v1/runtimes`), and an agent's summary and status as
4
+ * every client says them — the web's `AgentSummary` badge and Datalayer
5
+ * Mobile's Agents (MOBILE M-014b). A raw runtime carries no application
6
+ * capabilities: no Rules, Protocols or Activity are read from it.
7
+ *
8
+ * @module portable/runtimes
9
+ */
10
+ /** One agent runtime, as a client lists it. */
11
+ export type AgentRuntime = {
12
+ uid: string;
13
+ runtimeName: string;
14
+ givenName: string;
15
+ environment: string;
16
+ /** The agentspec it runs, when the runtime says. */
17
+ agentSpecId: string;
18
+ type: string;
19
+ state: string;
20
+ startedAt: string;
21
+ expiredAt: string;
22
+ /** Its Jupyter address, from which its agents' is read (`agentRuntimesUrlOf`). */
23
+ ingress: string;
24
+ };
25
+ /** A runtime of the runtimes service's answer. */
26
+ export declare function runtimeOf(raw: Record<string, unknown>): AgentRuntime;
27
+ /** The runtimes service's list, agent runtimes only: notebooks and terminals belong to the web workspace. */
28
+ export declare function agentRuntimesOf(body: Record<string, unknown>): AgentRuntime[];
29
+ /** An agent runtime, not a notebook's or a terminal's. */
30
+ export declare const isAgentRuntime: (runtime: AgentRuntime) => boolean;
31
+ /** What a summary of an agent says of it. */
32
+ export interface AgentSummaryData {
33
+ agentName: string;
34
+ location: string;
35
+ specId?: string;
36
+ /**
37
+ * The framework turning this agent's loop, as its spec declares it —
38
+ * `pydantic-ai` server-side, `vercel-ai` in the page.
39
+ */
40
+ harness?: string;
41
+ /**
42
+ * The runtime variant the host chose: `<location>-<harness>`.
43
+ *
44
+ * Shown next to the harness rather than instead of it, because they answer
45
+ * different questions and can disagree: the harness is what the spec asked
46
+ * for, the variant is what it actually got.
47
+ */
48
+ variant?: string;
49
+ status?: string;
50
+ baseUrl?: string;
51
+ sandboxBaseUrl?: string;
52
+ agentId?: string;
53
+ isReady?: boolean;
54
+ error?: string;
55
+ }
56
+ /**
57
+ * Statuses that represent an agent actively being created/started. The spinner
58
+ * only shows for these; app-navigation states ("selected", "switching") and
59
+ * settled states ("ready", "running", ...) never spin.
60
+ */
61
+ export declare const CREATING_STATUSES: ReadonlySet<string>;
62
+ /**
63
+ * An agent genuinely being created: it is not ready, has no id yet, and its
64
+ * status is one of creation or startup.
65
+ */
66
+ export declare function isCreatingAgent(summary: AgentSummaryData): boolean;
67
+ /** The summary of an agent the runtimes service lists: named as given, at Datalayer. */
68
+ export declare function agentSummaryOfRuntime(runtime: AgentRuntime): AgentSummaryData;
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
6
+ const text = (value) => typeof value === 'string' ? value : '';
7
+ /** A runtime of the runtimes service's answer. */
8
+ export function runtimeOf(raw) {
9
+ const environment = isRecord(raw.environment) ? raw.environment : {};
10
+ return {
11
+ uid: text(raw.uid),
12
+ runtimeName: text(raw.runtime_name) || text(raw.pod_name),
13
+ givenName: text(raw.given_name),
14
+ environment: text(environment.title) ||
15
+ text(environment.name) ||
16
+ text(raw.environment_title) ||
17
+ text(raw.environment_name),
18
+ agentSpecId: text(raw.agent_spec_id) || text(raw.attached_agent_spec_id_s),
19
+ type: text(raw.type),
20
+ state: text(raw.state) || text(raw.status) || 'running',
21
+ startedAt: text(raw.started_at),
22
+ expiredAt: text(raw.expired_at),
23
+ ingress: text(raw.ingress),
24
+ };
25
+ }
26
+ /** The runtimes service's list, agent runtimes only: notebooks and terminals belong to the web workspace. */
27
+ export function agentRuntimesOf(body) {
28
+ const listed = Array.isArray(body.runtimes) ? body.runtimes : [];
29
+ return listed
30
+ .filter(isRecord)
31
+ .map(runtimeOf)
32
+ .filter(runtime => runtime.runtimeName && isAgentRuntime(runtime));
33
+ }
34
+ /** An agent runtime, not a notebook's or a terminal's. */
35
+ export const isAgentRuntime = (runtime) => runtime.type === 'agent' ||
36
+ Boolean(runtime.agentSpecId) ||
37
+ /agent/i.test(runtime.environment);
38
+ /**
39
+ * Statuses that represent an agent actively being created/started. The spinner
40
+ * only shows for these; app-navigation states ("selected", "switching") and
41
+ * settled states ("ready", "running", ...) never spin.
42
+ */
43
+ export const CREATING_STATUSES = new Set([
44
+ 'creating',
45
+ 'starting',
46
+ 'launching',
47
+ 'connecting',
48
+ 'pending',
49
+ 'resuming',
50
+ ]);
51
+ /**
52
+ * An agent genuinely being created: it is not ready, has no id yet, and its
53
+ * status is one of creation or startup.
54
+ */
55
+ export function isCreatingAgent(summary) {
56
+ return (!summary.isReady &&
57
+ !summary.agentId &&
58
+ CREATING_STATUSES.has((summary.status || '').toLowerCase()));
59
+ }
60
+ /** The summary of an agent the runtimes service lists: named as given, at Datalayer. */
61
+ export function agentSummaryOfRuntime(runtime) {
62
+ return {
63
+ agentName: runtime.givenName || runtime.runtimeName,
64
+ location: 'datalayer',
65
+ specId: runtime.agentSpecId || undefined,
66
+ status: runtime.state,
67
+ isReady: !CREATING_STATUSES.has(runtime.state.toLowerCase()),
68
+ };
69
+ }
@@ -2,7 +2,8 @@
2
2
  * The composer's voice menu (VOICE.md VO-14, VO-21, VO-25, VO-30): beside
3
3
  * the microphone, what a person chooses for themselves — the language they
4
4
  * speak and hear, when answers are heard, the voice, its speed and loudness,
5
- * *Send what I say*, the microphone, and *Keep speech on this device*. Each
5
+ * how long they pause before they are done (VO-69), *Send what I say*, the
6
+ * microphone, and *Keep speech on this device*. Each
6
7
  * choice is kept in the person's settings and heard at once by every chat
7
8
  * on the page; *Use the application's* forgets them all.
8
9
  *
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useState } from 'react';
3
3
  import { ActionList, ActionMenu, IconButton } from '@primer/react';
4
4
  import { GearIcon } from '@primer/octicons-react';
5
- import { OUTPUT_CHOICES, RATE_CHOICES, VOLUME_CHOICES, catalogueVoiceSpeaks, voiceLanguages, voicesSpeaking, } from './choices';
5
+ import { OUTPUT_CHOICES, PAUSE_CHOICES, RATE_CHOICES, VOLUME_CHOICES, catalogueVoiceSpeaks, voiceLanguages, voicesSpeaking, } from './choices';
6
6
  import { withVoiceSetting } from './settings';
7
7
  import { useVoiceSettings } from './useVoiceSettings';
8
8
  /** The microphones the browser lists; named only once one was allowed. */
@@ -28,6 +28,6 @@ export function VoiceMenu({ voice, answers, disabled = false, }) {
28
28
  void microphones().then(setDevices);
29
29
  }
30
30
  }, children: [_jsx(ActionMenu.Anchor, { children: _jsx(IconButton, { icon: GearIcon, size: "small", variant: "invisible", "aria-label": "Voice settings", "data-voice-menu": "", disabled: disabled }) }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Language" }), voiceLanguages().map(language => (_jsx(ActionList.Item, { selected: language.value === voice.language, onSelect: () => choose('language', language.value), "data-voice-language": language.value, children: language.label }, language.value)))] }), answers && !onDevice ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Answers heard" }), OUTPUT_CHOICES.map(output => (_jsx(ActionList.Item, { selected: output.value === voice.output, onSelect: () => choose('output', output.value), "data-voice-output": output.value, children: output.label }, output.value)))] }), voices.length > 0 ? (_jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Voice" }), voices.map(spoken => (_jsx(ActionList.Item, { selected: spoken.id === voice.voice, onSelect: () => choose('voice', spoken.id), "data-voice-voice": spoken.id, children: spoken.name }, spoken.id)))] })) : null, _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Speed" }), RATE_CHOICES.map(rate => (_jsx(ActionList.Item, { selected: rate.value === (voice.rate ?? 1), onSelect: () => choose('rate', rate.value), "data-voice-rate": rate.value, children: rate.label }, rate.value)))] }), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Loudness" }), VOLUME_CHOICES.map(volume => (_jsx(ActionList.Item, { selected: volume.value === (voice.volume ?? 1), onSelect: () => choose('volume', volume.value), "data-voice-volume": volume.value, children: volume.label }, volume.value)))] })] })) : null, named.length > 1 ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Microphone" }), named.map(device => (_jsx(ActionList.Item, { selected: device.deviceId ===
31
- (voice.microphoneId ?? named[0].deviceId), onSelect: () => choose('microphoneId', device.deviceId), "data-voice-microphone": device.deviceId, children: device.label }, device.deviceId)))] })] })) : null, _jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "multiple", children: [_jsxs(ActionList.Item, { selected: Boolean(voice.sendWhatISay), onSelect: () => choose('sendWhatISay', !voice.sendWhatISay), "data-voice-send-what-i-say": "", children: ["Send what I say", _jsx(ActionList.Description, { variant: "block", children: "Sent as soon as it is heard, not left in the box." })] }), voice.speechUrl && !onDevice ? (_jsxs(ActionList.Item, { selected: detect, onSelect: () => choose('detectLanguage', !detect), "data-voice-detect": "", children: ["Detect the language I speak", _jsx(ActionList.Description, { variant: "block", children: "Heard on Datalayer\u2019s servers by Whisper, which finds the language; nothing is kept there." })] })) : null, _jsxs(ActionList.Item, { selected: onDevice, onSelect: () => choose('onDeviceOnly', !onDevice), "data-voice-on-device": "", children: ["Keep speech on this device", _jsx(ActionList.Description, { variant: "block", children: "Nothing is sent to Datalayer\u2019s speech service: you are heard in the browser, answers are not spoken." })] })] }), Object.keys(settings).length > 0 ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { onSelect: () => keep({}), "data-voice-settings-reset": "", children: "Use the application\u2019s" })] })) : null] }) })] }));
31
+ (voice.microphoneId ?? named[0].deviceId), onSelect: () => choose('microphoneId', device.deviceId), "data-voice-microphone": device.deviceId, children: device.label }, device.deviceId)))] })] })) : null, _jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "single", children: [_jsx(ActionList.GroupHeading, { children: "Pauses before I am done" }), PAUSE_CHOICES.map(pause => (_jsx(ActionList.Item, { selected: pause.value === (settings.pause ?? 1), onSelect: () => choose('pause', pause.value === 1 ? undefined : pause.value), "data-voice-pause": pause.value, children: pause.label }, pause.value)))] }), _jsx(ActionList.Divider, {}), _jsxs(ActionList.Group, { selectionVariant: "multiple", children: [_jsxs(ActionList.Item, { selected: Boolean(voice.sendWhatISay), onSelect: () => choose('sendWhatISay', !voice.sendWhatISay), "data-voice-send-what-i-say": "", children: ["Send what I say", _jsx(ActionList.Description, { variant: "block", children: "Sent as soon as it is heard, not left in the box." })] }), voice.speechUrl && !onDevice ? (_jsxs(ActionList.Item, { selected: detect, onSelect: () => choose('detectLanguage', !detect), "data-voice-detect": "", children: ["Detect the language I speak", _jsx(ActionList.Description, { variant: "block", children: "Heard on Datalayer\u2019s servers by Whisper, which finds the language; nothing is kept there." })] })) : null, _jsxs(ActionList.Item, { selected: onDevice, onSelect: () => choose('onDeviceOnly', !onDevice), "data-voice-on-device": "", children: ["Keep speech on this device", _jsx(ActionList.Description, { variant: "block", children: "Nothing is sent to Datalayer\u2019s speech service: you are heard in the browser, answers are not spoken." })] })] }), Object.keys(settings).length > 0 ? (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsx(ActionList.Item, { onSelect: () => keep({}), "data-voice-settings-reset": "", children: "Use the application\u2019s" })] })) : null] }) })] }));
32
32
  }
33
33
  export default VoiceMenu;
@@ -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' },
@@ -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,6 +2,12 @@ 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. */
@@ -40,7 +46,26 @@ export interface ServerHearingOptions {
40
46
  /** Let Whisper find the language (VO-14), rather than tell it. */
41
47
  detect?: boolean;
42
48
  fetcher?: typeof fetch;
49
+ /** Opens a WebSocket; the browser's own unless a test gives one. */
50
+ socket?: (url: string) => SpeechSocket;
43
51
  }
52
+ /** What `ServerHearing` needs of a WebSocket. */
53
+ export interface SpeechSocket {
54
+ readonly readyState: number;
55
+ binaryType: string;
56
+ onopen: ((event: unknown) => void) | null;
57
+ onmessage: ((event: {
58
+ data: unknown;
59
+ }) => void) | null;
60
+ onclose: ((event: unknown) => void) | null;
61
+ onerror: ((event: unknown) => void) | null;
62
+ send(data: string | ArrayBuffer): void;
63
+ close(code?: number): void;
64
+ }
65
+ /** 16 kHz samples in [-1, 1] as little-endian 16-bit PCM: what a `pcm16` stream takes. */
66
+ export declare function pcm16Of(samples: Float32Array): ArrayBuffer;
67
+ /** The ws(s) address of the stream on ai-agents' origin. */
68
+ export declare function streamUrlOf(speechUrl: string): string;
44
69
  /**
45
70
  * Hears on Datalayer's servers: the browser's VAD finds the speech and when
46
71
  * it stopped; Whisper, on the speech service, writes it.
@@ -52,7 +77,14 @@ export declare class ServerHearing implements Hearing {
52
77
  constructor(finder: SpeechFinder, options: ServerHearingOptions);
53
78
  /** Only the VAD loads in the page: the words are written on the server. */
54
79
  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>;
80
+ /** The press being heard as it is said, while one is. */
81
+ private press?;
82
+ /** The press's stream, opened anew when a new press began (its sound is shorter). */
83
+ private streamFor;
84
+ /**
85
+ * Whether speech was found and how long it has been silent, and the words
86
+ * the speech service heard so far: the sound goes to its stream as it comes.
87
+ */
88
+ hearSoFar(audio: Float32Array, language?: string): Promise<HeardSoFar>;
57
89
  hear(audio: Float32Array, language: string): Promise<Transcript | undefined>;
58
90
  }