@datalayer/agent-runtimes 1.5.60 → 1.5.61

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.
@@ -50,6 +50,13 @@ export interface SpeakerOptions {
50
50
  onState?: (state: SpeakerState) => void;
51
51
  /** Called with how long a sentence took from asked to first sound, in ms. */
52
52
  onFirstSound?: (ms: number) => void;
53
+ /**
54
+ * Say a long first sentence's first clause on its own (VO-52): the service
55
+ * makes a sentence whole, in about the time its length takes, so the first
56
+ * sound comes sooner when the first thing asked is shorter. Off for a
57
+ * caller that follows its sentences one by one (a deck's narrator).
58
+ */
59
+ firstClauseFirst?: boolean;
53
60
  fetcher?: typeof fetch;
54
61
  }
55
62
  /**
@@ -60,6 +67,14 @@ export interface SpeakerOptions {
60
67
  * 14 October 2026).
61
68
  */
62
69
  export declare const SENTENCES_AHEAD = 2;
70
+ /** A first sentence longer than this is said clause first (`firstClauseFirst`). */
71
+ export declare const FIRST_CLAUSE_SPLIT_CHARS = 60;
72
+ /**
73
+ * A sentence cut after its first clause — a comma, semicolon, colon or dash
74
+ * followed by a space — when it is long and neither part is too short (VO-52);
75
+ * else the sentence alone. Never cut inside a number (`1,000`).
76
+ */
77
+ export declare function firstClause(sentence: string): string[];
63
78
  /** Plays an answer's sentences, said by the speech service. */
64
79
  export declare class ServerSpeaker {
65
80
  private options;
@@ -21,6 +21,34 @@ export const SYNTHESES_PATH = '/api/ai-agents/v1/speech/syntheses';
21
21
  * 14 October 2026).
22
22
  */
23
23
  export const SENTENCES_AHEAD = 2;
24
+ /** A first sentence longer than this is said clause first (`firstClauseFirst`). */
25
+ export const FIRST_CLAUSE_SPLIT_CHARS = 60;
26
+ /** Neither part of a sentence cut at its first clause is shorter than this. */
27
+ const SHORTEST_CLAUSE_CHARS = 15;
28
+ /**
29
+ * A sentence cut after its first clause — a comma, semicolon, colon or dash
30
+ * followed by a space — when it is long and neither part is too short (VO-52);
31
+ * else the sentence alone. Never cut inside a number (`1,000`).
32
+ */
33
+ export function firstClause(sentence) {
34
+ if (sentence.length <= FIRST_CLAUSE_SPLIT_CHARS) {
35
+ return [sentence];
36
+ }
37
+ const boundary = /[,;:\u2013\u2014]\s+/g;
38
+ let found;
39
+ while ((found = boundary.exec(sentence)) !== null) {
40
+ const end = found.index + 1;
41
+ if (end < SHORTEST_CLAUSE_CHARS) {
42
+ continue;
43
+ }
44
+ const rest = sentence.slice(found.index + found[0].length);
45
+ if (rest.length < SHORTEST_CLAUSE_CHARS) {
46
+ break;
47
+ }
48
+ return [sentence.slice(0, end), rest];
49
+ }
50
+ return [sentence];
51
+ }
24
52
  /** Plays an answer's sentences, said by the speech service. */
25
53
  export class ServerSpeaker {
26
54
  options;
@@ -99,7 +127,14 @@ export class ServerSpeaker {
99
127
  if (!sentence) {
100
128
  return;
101
129
  }
102
- this.queue.push({ text: sentence, asked: performance.now() });
130
+ // Nothing playing or waiting: this is the first thing heard (VO-52).
131
+ const first = this.options.firstClauseFirst &&
132
+ !this.queue.length &&
133
+ !this.playing &&
134
+ !this.running;
135
+ for (const part of first ? firstClause(sentence) : [sentence]) {
136
+ this.queue.push({ text: part, asked: performance.now() });
137
+ }
103
138
  if (!this.state.speaking) {
104
139
  // Something new to say: the last refusal is no longer the news.
105
140
  this.set({ speaking: true, refused: undefined });
@@ -80,6 +80,18 @@ export interface SpokenMetadata {
80
80
  language: string;
81
81
  engine: string;
82
82
  where: 'device' | 'server';
83
+ /**
84
+ * The turn's sound, sent to be kept with its words when the application's
85
+ * record keeps `audio` and the person agreed (VO-63); never on the
86
+ * conversation's own message.
87
+ */
88
+ sound?: SpokenSound;
89
+ }
90
+ /** A turn's recording, to keep (VO-63): a WAV, base64. */
91
+ export interface SpokenSound {
92
+ data: string;
93
+ type: 'audio/wav';
94
+ seconds: number;
83
95
  }
84
96
  /** What the person said, once heard. */
85
97
  export interface Transcript {
@@ -30,6 +30,8 @@ export function useVoiceSpeaker(voice, on = true) {
30
30
  rate: voice.rate,
31
31
  volume: voice.volume,
32
32
  onState: setState,
33
+ // The chat's answers: a long first sentence's first clause first.
34
+ firstClauseFirst: true,
33
35
  })
34
36
  : undefined,
35
37
  // eslint-disable-next-line react-hooks/exhaustive-deps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.5.60",
3
+ "version": "1.5.61",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",