@rimelabs/sdk 0.1.0-alpha.19 → 0.1.0-alpha.20

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-alpha.20](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.19...typescript-v0.1.0-alpha.20) (2026-10-22)
4
+
5
+
6
+ ### Features
7
+
8
+ * add Prism tool filler controls for Python and TypeScript ([#93](https://github.com/rimelabs/rime-sdk/issues/93)) ([c6d1344](https://github.com/rimelabs/rime-sdk/commit/c6d13445b565cf62b4dd0e7e630c4b86182d6a82))
9
+ * **realtime:** report what ended the user's turn ([#92](https://github.com/rimelabs/rime-sdk/issues/92)) ([88eb9f2](https://github.com/rimelabs/rime-sdk/commit/88eb9f20d9c9943a67b809452493cc27744b794e))
10
+
3
11
  ## [0.1.0-alpha.19](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.18...typescript-v0.1.0-alpha.19) (2026-10-21)
4
12
 
5
13
 
@@ -48,6 +48,34 @@ function copyJson(value) {
48
48
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, copyJson(item)]));
49
49
  throw new RimeInputError("Tool schemas must contain finite JSON values");
50
50
  }
51
+ function fillerSettings(value) {
52
+ if (!value || typeof value !== "object" || Array.isArray(value))
53
+ throw new RimeInputError("prismFiller must be a filler object");
54
+ const kind = value.type;
55
+ if (!["none", "text", "auto"].includes(kind))
56
+ throw new RimeInputError("Prism filler type must be none, text, or auto");
57
+ const field = kind === "text" ? "text" : kind === "auto" ? "instructions" : null;
58
+ if (Object.entries(value).some(([key, item]) => item !== undefined && key !== "type" && key !== field))
59
+ throw new RimeInputError("Prism filler has fields not allowed for its type");
60
+ const text = field === null ? undefined : value[field];
61
+ if (kind === "text" || text !== undefined) {
62
+ // Rust str::trim uses Unicode White_Space, which differs from JS trim.
63
+ if (typeof text !== "string" ||
64
+ !/\P{White_Space}/u.test(text) ||
65
+ [...text].length > 500)
66
+ throw new RimeInputError("Prism filler text must be nonblank and at most 500 characters");
67
+ // Unicode mode matches lone surrogates but preserves valid UTF-16 pairs.
68
+ if (/[\uD800-\uDFFF]/u.test(text))
69
+ throw new RimeInputError("Prism filler text must be valid UTF-8");
70
+ }
71
+ if (kind === "text")
72
+ return { type: "text", text: text };
73
+ if (kind === "auto")
74
+ return text === undefined
75
+ ? { type: "auto" }
76
+ : { type: "auto", instructions: text };
77
+ return { type: "none" };
78
+ }
51
79
  export class Realtime {
52
80
  credentials;
53
81
  checkOpen;
@@ -96,6 +124,9 @@ export class Realtime {
96
124
  throw new RimeInputError("Tool parameters must be an object");
97
125
  return {
98
126
  type: "function",
127
+ ...(tool.prismFiller === undefined
128
+ ? {}
129
+ : { prism_filler: fillerSettings(tool.prismFiller) }),
99
130
  function: {
100
131
  name: tool.name,
101
132
  description: tool.description ?? "",
@@ -14,6 +14,7 @@ export interface SessionSettings {
14
14
  description: string;
15
15
  parameters: t.JsonObject;
16
16
  };
17
+ prism_filler?: t.PrismFiller;
17
18
  }[];
18
19
  voice?: string;
19
20
  instructions?: string;
@@ -230,13 +230,25 @@ function decodeValue(value, sessionId) {
230
230
  audioStartMs: integer(event.audio_start_ms, "audio_start_ms"),
231
231
  };
232
232
  break;
233
- case "input_audio_buffer.speech_stopped":
233
+ case "input_audio_buffer.speech_stopped": {
234
+ const reason = event.prism_end_reason === undefined
235
+ ? null
236
+ : string(event.prism_end_reason, "prism_end_reason");
234
237
  payload = {
235
238
  kind: "speech.stopped",
236
239
  itemId: id(event.item_id, "item_id"),
237
240
  audioEndMs: integer(event.audio_end_ms, "audio_end_ms"),
241
+ endReason: reason === null ||
242
+ reason === "semantic" ||
243
+ reason === "timeout" ||
244
+ reason === "silence" ||
245
+ reason === "max_duration" ||
246
+ reason === "detector_changed"
247
+ ? reason
248
+ : "unknown",
238
249
  };
239
250
  break;
251
+ }
240
252
  case "input_audio_buffer.committed":
241
253
  payload = {
242
254
  kind: "input.committed",
@@ -14,10 +14,27 @@ export interface RealtimeTimeouts {
14
14
  export interface RealtimeOperationOptions {
15
15
  readonly signal?: AbortSignal;
16
16
  }
17
+ /** Speech while a tool runs. Text and instructions must be nonblank and at
18
+ * most 500 Unicode scalar values. Invalid UTF-8 is rejected.
19
+ * Omit the setting to use the server default. */
20
+ export type PrismFiller = {
21
+ readonly type: "none";
22
+ readonly text?: never;
23
+ readonly instructions?: never;
24
+ } | {
25
+ readonly type: "text";
26
+ readonly text: string;
27
+ readonly instructions?: never;
28
+ } | {
29
+ readonly type: "auto";
30
+ readonly instructions?: string;
31
+ readonly text?: never;
32
+ };
17
33
  export interface ToolDefinition {
18
34
  readonly name: string;
19
35
  readonly parameters: JsonObject;
20
36
  readonly description?: string;
37
+ readonly prismFiller?: PrismFiller;
21
38
  }
22
39
  export type TurnEagerness = "low" | "medium" | "high" | "auto";
23
40
  /** Omitted fields use the server's turn timing defaults. */
@@ -109,10 +126,24 @@ export interface SpeechStarted {
109
126
  readonly itemId: string;
110
127
  readonly audioStartMs: number;
111
128
  }
129
+ /** What ended the user's turn:
130
+ * - `semantic`: Prism judged from the user's speech that they had finished;
131
+ * `eagerness` sets how readily it does.
132
+ * - `timeout`: the user was silent for `endOfTurnTimeoutMs`, and the timeout
133
+ * ended the turn before Prism judged that they had finished.
134
+ * - `silence`: Prism's backup turn detection ended the turn at a short pause;
135
+ * it does not apply `eagerness` or `endOfTurnTimeoutMs`.
136
+ * - `max_duration`: the user spoke for the longest turn Prism takes; what
137
+ * they say next starts a new turn.
138
+ * - `detector_changed`: Prism switched between its main and backup turn
139
+ * detection while the user spoke; nothing the user did ended the turn. */
140
+ export type SpeechEndReason = "semantic" | "timeout" | "silence" | "max_duration" | "detector_changed";
112
141
  export interface SpeechStopped {
113
142
  readonly kind: "speech.stopped";
114
143
  readonly itemId: string;
115
144
  readonly audioEndMs: number;
145
+ /** Null means the server did not publish the field. */
146
+ readonly endReason: SpeechEndReason | "unknown" | null;
116
147
  }
117
148
  export interface InputCommitted {
118
149
  readonly kind: "input.committed";
package/docs/realtime.md CHANGED
@@ -275,6 +275,37 @@ Declare tools with plain `ToolDefinition` objects. Names must be nonblank and
275
275
  unique; schemas must contain finite JSON values. The SDK copies schemas before
276
276
  connection setup, so later caller changes cannot alter initialization.
277
277
 
278
+ Set `prismFiller` to control what Prism says while the tool runs:
279
+
280
+ ```ts
281
+ import type { ToolDefinition } from "@rimelabs/sdk";
282
+
283
+ const pressDigits: ToolDefinition = {
284
+ name: "press_digits",
285
+ parameters: { type: "object", properties: { digits: { type: "string" } } },
286
+ prismFiller: { type: "none" },
287
+ };
288
+ ```
289
+
290
+ | Setting | Behavior |
291
+ | --- | --- |
292
+ | Omitted | Use the server default, currently `auto`. |
293
+ | `{ type: "none" }` | Suppress filler for this tool. |
294
+ | `{ type: "text", text: "One moment." }` | Speak the supplied text. |
295
+ | `{ type: "auto" }` | Let Prism write a line from the tool description. |
296
+ | `{ type: "auto", instructions: "Say you are checking the order." }` | Guide the generated line. |
297
+
298
+ Text and instructions must be valid UTF-8, contain at most 500 Unicode scalar
299
+ values, and contain a character outside the Unicode `White_Space` set. This
300
+ matches the engine's validation. The SDK preserves the supplied text and copies
301
+ filler settings before connection setup. Invalid settings raise `RimeInputError` before opening a
302
+ connection, including for JavaScript callers. The exported `PrismFiller` union
303
+ also checks TypeScript callers.
304
+
305
+ Filler applies when the reply has said nothing before calling a tool. If a reply
306
+ calls several tools, the first call's tool controls the filler. `none` suppresses
307
+ filler only; it does not silence other speech. Settings are fixed for the session.
308
+
278
309
  Execute a tool once for each `tool.call` event. Validate its parsed `arguments`,
279
310
  then submit a string result with `submitToolResult(payload.call, output)`.
280
311
  Use `JSON.stringify(...)` for a structured result. Call
@@ -301,7 +332,8 @@ are exported from `@rimelabs/sdk`.
301
332
  | --- | --- |
302
333
  | `input.ready` | No data fields. Turn methods handle the readiness wait. |
303
334
  | `speech.hint` | `audioStartMs`; the user may have started speaking over a playing reply. It can be a false alarm: lower playback, do not stop it. |
304
- | `speech.started`, `speech.stopped` | `itemId` and `audioStartMs` or `audioEndMs` |
335
+ | `speech.started` | `itemId`, `audioStartMs` |
336
+ | `speech.stopped` | `itemId`, `audioEndMs`, `endReason`: what ended the turn (`semantic`, `timeout`, `silence`, `max_duration` or `detector_changed`), `unknown` for a newer value, or null from a server that does not send it |
305
337
  | `input.committed` | `itemId` |
306
338
  | `transcript.delta`, `transcript.final` | `itemId` and `delta` or `text` |
307
339
  | `transcript.failed` | `itemId`, `error` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelabs/sdk",
3
- "version": "0.1.0-alpha.19",
3
+ "version": "0.1.0-alpha.20",
4
4
  "description": "Rime streaming TTS, STT and Prism realtime SDK for Node.js",
5
5
  "license": "MIT",
6
6
  "type": "module",