@stackbone/cli 0.8.0 → 0.9.1

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.
@@ -10,20 +10,20 @@ import {
10
10
  } from "@livekit/agents";
11
11
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
12
12
 
13
- // libs/runtime/voice-worker/src/thinking-sound.ts
14
- import { voice } from "@livekit/agents";
13
+ // libs/runtime/voice-worker/src/background-audio.ts
14
+ import { audioFramesFromFile, voice } from "@livekit/agents";
15
15
  import { AudioFrame } from "@livekit/rtc-node";
16
16
  var SAMPLE_RATE = 48e3;
17
17
  var FRAME_MS = 20;
18
18
  var SAMPLES_PER_FRAME = SAMPLE_RATE * FRAME_MS / 1e3;
19
- var VOLUME = 0.12;
19
+ var CHIME_LEVEL = 0.12;
20
20
  var PATTERN = [
21
21
  [660, 140],
22
22
  [0, 60],
23
23
  [880, 160],
24
24
  [0, 900]
25
25
  ];
26
- var renderPattern = () => {
26
+ var renderChime = () => {
27
27
  const total = PATTERN.reduce((n, [, ms2]) => n + SAMPLE_RATE * ms2 / 1e3, 0);
28
28
  const pcm = new Int16Array(total);
29
29
  let offset = 0;
@@ -31,41 +31,89 @@ var renderPattern = () => {
31
31
  const length = SAMPLE_RATE * ms2 / 1e3;
32
32
  for (let i = 0; hz > 0 && i < length; i++) {
33
33
  const envelope = 0.5 - 0.5 * Math.cos(2 * Math.PI * i / length);
34
- const sample = Math.sin(2 * Math.PI * hz * i / SAMPLE_RATE) * envelope * VOLUME;
34
+ const sample = Math.sin(2 * Math.PI * hz * i / SAMPLE_RATE) * envelope * CHIME_LEVEL;
35
35
  pcm[offset + i] = Math.round(sample * 32767);
36
36
  }
37
37
  offset += length;
38
38
  }
39
- return pcm;
40
- };
41
- var LOOP = renderPattern();
42
- async function* thinkingCue() {
43
- for (; ; ) {
44
- for (let start = 0; start + SAMPLES_PER_FRAME <= LOOP.length; start += SAMPLES_PER_FRAME) {
45
- yield new AudioFrame(
46
- LOOP.slice(start, start + SAMPLES_PER_FRAME),
39
+ const frames = [];
40
+ for (let start = 0; start + SAMPLES_PER_FRAME <= total; start += SAMPLES_PER_FRAME) {
41
+ frames.push(
42
+ new AudioFrame(
43
+ pcm.slice(start, start + SAMPLES_PER_FRAME),
47
44
  SAMPLE_RATE,
48
45
  1,
49
46
  SAMPLES_PER_FRAME
50
- );
51
- }
47
+ )
48
+ );
52
49
  }
53
- }
54
- var startThinkingCue = async ({
50
+ return frames;
51
+ };
52
+ var looped = (frames) => ({
53
+ async *[Symbol.asyncIterator]() {
54
+ const loop = await frames();
55
+ if (loop.length === 0) return;
56
+ for (; ; ) yield* loop;
57
+ }
58
+ });
59
+ var CHIME_FRAMES = renderChime();
60
+ var CHIME = looped(async () => CHIME_FRAMES);
61
+ var LIVEKIT_CLIPS = {
62
+ "office-ambience": voice.BuiltinAudioClip.OFFICE_AMBIENCE,
63
+ "keyboard-typing": voice.BuiltinAudioClip.KEYBOARD_TYPING,
64
+ "keyboard-typing2": voice.BuiltinAudioClip.KEYBOARD_TYPING2,
65
+ "hold-music": voice.BuiltinAudioClip.HOLD_MUSIC
66
+ };
67
+ var decoded = /* @__PURE__ */ new Map();
68
+ var clipFrames = (clip) => {
69
+ let frames = decoded.get(clip);
70
+ if (!frames) {
71
+ frames = (async () => {
72
+ const all = [];
73
+ const path = voice.getBuiltinAudioPath(LIVEKIT_CLIPS[clip]);
74
+ for await (const frame of audioFramesFromFile(path, { sampleRate: SAMPLE_RATE })) {
75
+ all.push(frame);
76
+ }
77
+ return all;
78
+ })();
79
+ frames.catch(() => decoded.delete(clip));
80
+ decoded.set(clip, frames);
81
+ }
82
+ return frames;
83
+ };
84
+ var CLIPS = new Map(
85
+ Object.keys(LIVEKIT_CLIPS).map((clip) => [
86
+ clip,
87
+ looped(() => clipFrames(clip))
88
+ ])
89
+ );
90
+ var backgroundSource = (sound, volume) => {
91
+ if (sound === "none" || volume <= 0) return void 0;
92
+ const source = sound === "chime" ? CHIME : CLIPS.get(sound);
93
+ return source && { source, volume };
94
+ };
95
+ var SILENT = Object.freeze({ start: () => void 0, stop: () => void 0 });
96
+ var startBackgroundAudio = async ({
55
97
  room,
56
- session
98
+ session,
99
+ audio
57
100
  }) => {
58
- const player = new voice.BackgroundAudioPlayer();
59
- await player.start({ room, agentSession: session });
101
+ const ambientSound = backgroundSource(audio.ambientSound, audio.ambientVolume);
102
+ const thinking = backgroundSource(audio.thinkingSound, audio.thinkingVolume);
103
+ if (!ambientSound && !thinking) return SILENT;
104
+ const player = new voice.BackgroundAudioPlayer(ambientSound ? { ambientSound } : {});
105
+ const clip = audio.thinkingSound;
106
+ await Promise.all([
107
+ player.start({ room, agentSession: session }),
108
+ thinking && clip !== "chime" && clip !== "none" ? clipFrames(clip) : void 0
109
+ ]);
110
+ if (!thinking) return SILENT;
60
111
  let playing;
61
112
  return {
62
113
  start: () => {
63
- playing ??= player.play(thinkingCue());
114
+ playing = player.play(thinking);
64
115
  },
65
- stop: () => {
66
- playing?.stop();
67
- playing = void 0;
68
- }
116
+ stop: () => playing?.stop()
69
117
  };
70
118
  };
71
119
 
@@ -112,29 +160,165 @@ import { ParticipantKind } from "@livekit/rtc-node";
112
160
  import { z } from "zod";
113
161
  var jsonObject = () => z.looseObject({});
114
162
 
115
- // libs/shared/validators/src/lib/common/page.ts
163
+ // libs/shared/validators/src/lib/studio/studio-model-provider.ts
116
164
  import { z as z2 } from "zod";
165
+ var modelProviderSlotSchema = z2.enum(["baseUrl", "apiKey"]);
166
+ var MODEL_PROVIDER_SLOTS = {
167
+ baseUrl: ["MODEL_PROVIDER_BASE_URL", "OPENROUTER_BASE_URL"],
168
+ apiKey: ["MODEL_PROVIDER_API_KEY", "OPENROUTER_API_KEY"]
169
+ };
170
+ var SLOT_NAMES = Object.keys(MODEL_PROVIDER_SLOTS);
171
+ var MODEL_PROVIDER_DEPRECATED_ENV_NAMES = SLOT_NAMES.map(
172
+ (slot) => MODEL_PROVIDER_SLOTS[slot][1]
173
+ );
174
+ var modelProviderSourceSchema = z2.enum(["environment", "stored", "none"]);
175
+ var isModelProviderValuePresent = (value) => typeof value === "string" && value.trim() !== "";
176
+ var storedOverridesSchema = z2.strictObject({
177
+ baseUrl: z2.boolean().optional(),
178
+ apiKey: z2.boolean().optional()
179
+ });
180
+ var modelProviderKindSchema = z2.enum(["openrouter", "ollama", "lmstudio", "generic"]);
181
+ var modelProviderGetResponseSchema = z2.object({
182
+ configured: z2.boolean(),
183
+ base_url: z2.string().nullable(),
184
+ api_key_masked: z2.string().nullable(),
185
+ source: modelProviderSourceSchema.optional(),
186
+ env_override: z2.array(z2.string()).optional(),
187
+ effective_base_url: z2.string().nullable().optional(),
188
+ // The gateway kind of the endpoint IN EFFECT (the one `effective_base_url`
189
+ // names), resolved by the box; `null` when nothing is configured. Optional for
190
+ // the same version-skew reason as `source`: a box that predates the field.
191
+ kind: modelProviderKindSchema.nullable().optional(),
192
+ // The slots where a Studio override beats an exported value (PLA-1825, ADR
193
+ // D8), by SLOT name, never the variable's (D4). Such a slot drops out of
194
+ // `env_override`, which keeps naming only the variables that actually win.
195
+ // A box that knows overrides always sends the list, empty or not; an absent
196
+ // list is a box that predates them, and would drop an `override` it cannot
197
+ // read, so Studio offers no Override against it.
198
+ overridden: z2.array(modelProviderSlotSchema).optional()
199
+ });
200
+ var modelProviderPutRequestSchema = z2.object({
201
+ base_url: z2.url(),
202
+ api_key: z2.string().optional().nullable(),
203
+ kind: modelProviderKindSchema.optional(),
204
+ override: z2.object({ baseUrl: z2.boolean().optional(), apiKey: z2.boolean().optional() }).optional()
205
+ }).refine((body) => body.override?.apiKey !== true || isModelProviderValuePresent(body.api_key), {
206
+ message: "Overriding the API key needs the key to use.",
207
+ path: ["api_key"]
208
+ });
209
+ var modelProviderAgentsOutcomeSchema = z2.object({
210
+ /** Agents that were degraded and are now serving. */
211
+ ready: z2.number().int().nonnegative(),
212
+ /** Agents that are still degraded (their reason was refreshed by the retry). */
213
+ degraded: z2.number().int().nonnegative()
214
+ });
215
+ var modelProviderPutResponseSchema = modelProviderGetResponseSchema.extend({
216
+ agents: modelProviderAgentsOutcomeSchema.optional()
217
+ });
218
+ var modelProviderFirstRunResponseSchema = z2.object({
219
+ completed: z2.boolean()
220
+ });
221
+ var modelProviderKeyCheckSchema = z2.enum(["verified", "unverified"]);
222
+ var modelProviderTestResponseSchema = z2.object({
223
+ ok: z2.boolean(),
224
+ model_count: z2.number().int().nonnegative().optional(),
225
+ error: z2.string().optional(),
226
+ key_checked: modelProviderKeyCheckSchema.optional()
227
+ });
228
+ var modelProviderModelTypeSchema = z2.enum(["chat", "image", "embedding", "other"]);
229
+ var modelProviderModelSchema = z2.object({
230
+ id: z2.string().min(1),
231
+ name: z2.string().optional(),
232
+ context_length: z2.number().int().positive().optional(),
233
+ pricing: z2.object({
234
+ prompt: z2.string().optional(),
235
+ completion: z2.string().optional()
236
+ }).optional(),
237
+ owned_by: z2.string().optional(),
238
+ type: modelProviderModelTypeSchema.optional(),
239
+ // What the gateway says the model can do (PLA-1605): `true`, `false`, or
240
+ // `null` for "this gateway does not say". The three values are distinct on
241
+ // purpose — Studio hides a model that reports no tool support and only WARNS
242
+ // about one that reports nothing, so collapsing `null` into `false` would hide
243
+ // every model on a custom OpenAI-compatible endpoint.
244
+ //
245
+ // Nullable AND optional: a box on an older runtime sends neither field, and
246
+ // that must keep validating, exactly as `type` already does.
247
+ tools: z2.boolean().nullable().optional(),
248
+ reasoning: z2.boolean().nullable().optional(),
249
+ // STA3-17: on a box with several providers, the one that serves this entry.
250
+ // `responding: false` marks an entry remembered from a provider that is down.
251
+ // Declared so the parse keeps it: an object schema strips what it does not name.
252
+ provider: z2.object({ id: z2.string(), name: z2.string(), responding: z2.boolean() }).optional()
253
+ });
254
+ var modelProviderModelsRequestSchema = z2.object({
255
+ base_url: z2.url().optional().nullable(),
256
+ api_key: z2.string().optional().nullable()
257
+ });
258
+ var modelProviderModelsResponseSchema = z2.object({
259
+ ok: z2.boolean(),
260
+ models: z2.array(modelProviderModelSchema).optional(),
261
+ error: z2.string().optional(),
262
+ warning: z2.string().optional()
263
+ });
264
+ var MODEL_PROVIDER_DEFAULT_ID = "default";
265
+ var MODEL_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,30}[a-z0-9]$/;
266
+ var modelProviderIdSchema = z2.string().regex(MODEL_PROVIDER_ID_PATTERN, {
267
+ message: "Lowercase letters, digits and dashes, 2 to 32, starting with a letter."
268
+ }).refine((id) => id !== MODEL_PROVIDER_DEFAULT_ID, {
269
+ message: `'${MODEL_PROVIDER_DEFAULT_ID}' is provider 1's id.`
270
+ });
271
+ var pickedModelProviderIdSchema = z2.string().regex(MODEL_PROVIDER_ID_PATTERN);
272
+ var modelProviderListItemSchema = z2.object({
273
+ id: z2.string(),
274
+ name: z2.string(),
275
+ kind: modelProviderKindSchema,
276
+ baseUrl: z2.string(),
277
+ apiKey: z2.string().nullable(),
278
+ source: z2.enum(["env", "local"]),
279
+ removable: z2.boolean(),
280
+ responding: z2.boolean().nullable(),
281
+ error: z2.string().optional()
282
+ });
283
+ var modelProviderListResponseSchema = z2.object({
284
+ providers: z2.array(modelProviderListItemSchema),
285
+ duplicatedIds: z2.array(z2.object({ modelId: z2.string(), providerIds: z2.array(z2.string()) }))
286
+ });
287
+ var providerFields = {
288
+ name: z2.string().trim().min(1),
289
+ kind: modelProviderKindSchema,
290
+ baseUrl: z2.url(),
291
+ apiKey: z2.string().optional()
292
+ };
293
+ var modelProviderCreateRequestSchema = z2.strictObject({
294
+ id: modelProviderIdSchema,
295
+ ...providerFields
296
+ });
297
+ var modelProviderUpdateRequestSchema = z2.strictObject(providerFields);
298
+
299
+ // libs/shared/validators/src/lib/common/page.ts
300
+ import { z as z3 } from "zod";
117
301
  var PAGE_LIMIT_MAX = 100;
118
- var cursorParamSchema = z2.string().min(1);
119
- var limitParamSchema = (max = PAGE_LIMIT_MAX) => z2.coerce.number().int().min(1).max(max);
120
- var orderParamSchema = z2.enum(["asc", "desc"]);
121
- var pageQuerySchema = z2.object({
302
+ var cursorParamSchema = z3.string().min(1);
303
+ var limitParamSchema = (max = PAGE_LIMIT_MAX) => z3.coerce.number().int().min(1).max(max);
304
+ var orderParamSchema = z3.enum(["asc", "desc"]);
305
+ var pageQuerySchema = z3.object({
122
306
  cursor: cursorParamSchema.optional(),
123
307
  limit: limitParamSchema().optional(),
124
308
  order: orderParamSchema.optional()
125
309
  });
126
310
  var forwardPageQuerySchema = pageQuerySchema.omit({ order: true });
127
- var pageCursorSchema = z2.string().nullable();
128
- var pageResponseSchema = (item) => z2.object({ items: z2.array(item), nextCursor: pageCursorSchema, prevCursor: pageCursorSchema });
129
- var forwardPageResponseSchema = (item) => z2.object({ items: z2.array(item), nextCursor: pageCursorSchema });
311
+ var pageCursorSchema = z3.string().nullable();
312
+ var pageResponseSchema = (item) => z3.object({ items: z3.array(item), nextCursor: pageCursorSchema, prevCursor: pageCursorSchema });
313
+ var forwardPageResponseSchema = (item) => z3.object({ items: z3.array(item), nextCursor: pageCursorSchema });
130
314
 
131
315
  // libs/shared/validators/src/lib/agents/runs.ts
132
- import { z as z4 } from "zod";
316
+ import { z as z5 } from "zod";
133
317
 
134
318
  // libs/shared/validators/src/lib/common/system-entries.ts
135
- import { z as z3 } from "zod";
136
- var systemEntriesParamSchema = z3.enum(["include", "exclude"]);
137
- var systemEntriesQuerySchema = z3.object({
319
+ import { z as z4 } from "zod";
320
+ var systemEntriesParamSchema = z4.enum(["include", "exclude"]);
321
+ var systemEntriesQuerySchema = z4.object({
138
322
  system: systemEntriesParamSchema.optional()
139
323
  });
140
324
 
@@ -147,7 +331,7 @@ var RUN_STATUSES = [
147
331
  "waiting",
148
332
  "cancelled"
149
333
  ];
150
- var runStatusSchema = z4.enum(RUN_STATUSES);
334
+ var runStatusSchema = z5.enum(RUN_STATUSES);
151
335
  var RUN_TRIGGERS = [
152
336
  "manual",
153
337
  "webhook",
@@ -160,40 +344,40 @@ var RUN_TRIGGERS = [
160
344
  "platform",
161
345
  "unknown"
162
346
  ];
163
- var runTriggerSchema = z4.enum(RUN_TRIGGERS);
164
- var runEntityKindSchema = z4.enum(["agent", "workflow"]);
165
- var runTriggerLinkSchema = z4.object({
347
+ var runTriggerSchema = z5.enum(RUN_TRIGGERS);
348
+ var runEntityKindSchema = z5.enum(["agent", "workflow"]);
349
+ var runTriggerLinkSchema = z5.object({
166
350
  /** The `trigger_subscriptions.id`: the link the Studio trigger page routes to. */
167
- subscriptionId: z4.string(),
351
+ subscriptionId: z5.string(),
168
352
  /** The connector id, e.g. `gmail`. */
169
- connector: z4.string(),
353
+ connector: z5.string(),
170
354
  /** The trigger id within the connector, e.g. `gmail_new_email_received`. */
171
- trigger: z4.string(),
172
- outcome: z4.enum(["started", "resumed"]).optional(),
355
+ trigger: z5.string(),
356
+ outcome: z5.enum(["started", "resumed"]).optional(),
173
357
  /** The connector's brand name, e.g. `Gmail`. */
174
- connectorDisplayName: z4.string().optional(),
358
+ connectorDisplayName: z5.string().optional(),
175
359
  /** The trigger's name, e.g. `New Email`. */
176
- triggerDisplayName: z4.string().optional(),
177
- connectorLogoUrl: z4.string().optional()
360
+ triggerDisplayName: z5.string().optional(),
361
+ connectorLogoUrl: z5.string().optional()
178
362
  });
179
- var runSchema = z4.object({
180
- id: z4.uuid(),
363
+ var runSchema = z5.object({
364
+ id: z5.uuid(),
181
365
  status: runStatusSchema,
182
366
  trigger: runTriggerSchema,
183
- createdAt: z4.iso.datetime(),
184
- startedAt: z4.iso.datetime().nullable(),
185
- finishedAt: z4.iso.datetime().nullable(),
186
- durationMs: z4.number().int().nullable(),
187
- traceId: z4.string().nullable(),
367
+ createdAt: z5.iso.datetime(),
368
+ startedAt: z5.iso.datetime().nullable(),
369
+ finishedAt: z5.iso.datetime().nullable(),
370
+ durationMs: z5.number().int().nullable(),
371
+ traceId: z5.string().nullable(),
188
372
  // True when the run was triggered from the Studio Playground (header
189
373
  // `STACKBONE_PLAYGROUND_HEADER` on `POST /api/invoke`). Billing and the Trust
190
374
  // Layer's verified metrics filter on this column to exclude stress-tests.
191
- isPlayground: z4.boolean(),
375
+ isPlayground: z5.boolean(),
192
376
  // Name of the agent or workflow this run belongs to (migration 0020). Stamped
193
377
  // at run creation: the agent name for `chat` runs, the workflow name for
194
378
  // `workflow` runs. Combined with `trigger` the UI knows the kind. Null for
195
379
  // classic/manual cloud runs (deep-agent chat is dev-only), so the Runs screen renders `—`.
196
- entityName: z4.string().nullable(),
380
+ entityName: z5.string().nullable(),
197
381
  // Which KIND of entity `entityName` names (STA-426, migration 0037). Stamped at
198
382
  // run creation next to the name, so an agent and a workflow that share a name stop
199
383
  // sharing one row set. Null for a run written before the column existed (no
@@ -208,22 +392,22 @@ var runSchema = z4.object({
208
392
  // `trigger_subscriptions.id`, stamped at start for `trigger: 'event'` and null
209
393
  // for every other gate. Optional as well as nullable for the reason `entityKind`
210
394
  // is: an older box does not send the field.
211
- triggerRef: z4.string().nullable().optional(),
395
+ triggerRef: z5.string().nullable().optional(),
212
396
  // The Connect link that started or resumed the run, with its names (PLA-1898).
213
397
  // Null for a run no link started; optional for the reason `entityKind` is.
214
398
  triggerLink: runTriggerLinkSchema.nullable().optional(),
215
399
  input: jsonObject().nullable(),
216
400
  output: jsonObject().nullable(),
217
- error: z4.object({ message: z4.string(), stack: z4.string().optional() }).nullable(),
401
+ error: z5.object({ message: z5.string(), stack: z5.string().optional() }).nullable(),
218
402
  // Per-turn token usage for deep-agent CHAT runs (feature 68, A5): the chat
219
403
  // recorder folds the turn's usage and stamps the four token counters onto the run.
220
404
  // Null when the run had no model call; `totalTokens` is derived at read
221
405
  // (`input + output`), never stored. No $ cost (D5).
222
- inputTokens: z4.number().int().nullable(),
223
- outputTokens: z4.number().int().nullable(),
224
- cacheReadTokens: z4.number().int().nullable(),
225
- cacheWriteTokens: z4.number().int().nullable(),
226
- totalTokens: z4.number().int().nullable(),
406
+ inputTokens: z5.number().int().nullable(),
407
+ outputTokens: z5.number().int().nullable(),
408
+ cacheReadTokens: z5.number().int().nullable(),
409
+ cacheWriteTokens: z5.number().int().nullable(),
410
+ totalTokens: z5.number().int().nullable(),
227
411
  /**
228
412
  * The run's own `metadata` jsonb, verbatim (PLA-1272 slice 4).
229
413
  *
@@ -244,37 +428,37 @@ var runSchema = z4.object({
244
428
  */
245
429
  metadata: jsonObject().nullable().optional()
246
430
  });
247
- var schemaIssueSchema = z4.object({
431
+ var schemaIssueSchema = z5.object({
248
432
  /** Path segments to the offending field (`[]` for the root value). */
249
- path: z4.array(z4.union([z4.string(), z4.number()])),
250
- message: z4.string()
433
+ path: z5.array(z5.union([z5.string(), z5.number()])),
434
+ message: z5.string()
251
435
  });
252
- var runOutputViolationSchema = z4.object({
436
+ var runOutputViolationSchema = z5.object({
253
437
  /** The workflow whose declared `outputSchema` the return value broke. */
254
- workflowName: z4.string(),
255
- issues: z4.array(schemaIssueSchema),
438
+ workflowName: z5.string(),
439
+ issues: z5.array(schemaIssueSchema),
256
440
  /**
257
441
  * When the projector observed it (ISO). Not the run's `finishedAt`: the run
258
442
  * closed at that instant whether or not anything validated it.
259
443
  */
260
- recordedAt: z4.string()
444
+ recordedAt: z5.string()
261
445
  });
262
- var runIdParamSchema = z4.object({ runId: z4.uuid() });
263
- var runTagKeySchema = z4.string().regex(/^[a-z][a-z0-9_.-]{0,63}$/);
446
+ var runIdParamSchema = z5.object({ runId: z5.uuid() });
447
+ var runTagKeySchema = z5.string().regex(/^[a-z][a-z0-9_.-]{0,63}$/);
264
448
  var RUN_TAG_VALUE_MAX = 256;
265
449
  var RUN_TAGS_MAX = 16;
266
450
  var UNSTORABLE_TAG_CHAR = (
267
451
  // eslint-disable-next-line no-control-regex
268
452
  /[\u0000-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/
269
453
  );
270
- var runTagValueSchema = z4.string().min(1).max(RUN_TAG_VALUE_MAX).refine((value) => !UNSTORABLE_TAG_CHAR.test(value), {
454
+ var runTagValueSchema = z5.string().min(1).max(RUN_TAG_VALUE_MAX).refine((value) => !UNSTORABLE_TAG_CHAR.test(value), {
271
455
  message: "A tag value holds no control character and no lone surrogate."
272
456
  });
273
457
  var splitRunTagFilter = (tag) => {
274
458
  const colon = tag.indexOf(":");
275
459
  return colon < 0 ? null : { key: tag.slice(0, colon), value: tag.slice(colon + 1) };
276
460
  };
277
- var runTagFilterSchema = z4.string().superRefine((tag, ctx) => {
461
+ var runTagFilterSchema = z5.string().superRefine((tag, ctx) => {
278
462
  const filter = splitRunTagFilter(tag);
279
463
  if (!filter) {
280
464
  ctx.addIssue({ code: "custom", message: "A tag filter is `key:value`." });
@@ -296,17 +480,17 @@ var runTagFilterSchema = z4.string().superRefine((tag, ctx) => {
296
480
  var listRunsQuerySchema = pageQuerySchema.extend({
297
481
  status: runStatusSchema.optional(),
298
482
  trigger: runTriggerSchema.optional(),
299
- from: z4.iso.datetime().optional(),
300
- to: z4.iso.datetime().optional(),
483
+ from: z5.iso.datetime().optional(),
484
+ to: z5.iso.datetime().optional(),
301
485
  // Feature 69 — the Sessions detail screen reads a conversation's turns with
302
486
  // `GET /runs?session_id=<id>&order=asc`. `session_id` is the real
303
487
  // `runs.session_id` join column (the deterministic UUIDv5 of the deep-agent
304
488
  // session key), so it is exact-match on the indexed column.
305
- session_id: z4.uuid().optional(),
489
+ session_id: z5.uuid().optional(),
306
490
  // Migration 0020 — the Runs screen filters to one agent/workflow by its
307
491
  // exact name (the value picked from the name dropdown, or deep-linked from
308
492
  // the agent/workflow detail's "View runs"). Exact-match on `runs.entity_name`.
309
- name: z4.string().min(1).optional(),
493
+ name: z5.string().min(1).optional(),
310
494
  // STA-426 — narrow the name filter to ONE kind of entity, so "the runs of the
311
495
  // workflow named sync" excludes the agent that shares the name. A row that
312
496
  // DECLARES the other kind is always excluded; a row whose kind was never stamped
@@ -320,8 +504,8 @@ var listRunsQuerySchema = pageQuerySchema.extend({
320
504
  // or boolean matches its JSON spelling (`3`, `true`). The ONE filter here that is
321
505
  // not exact-match: it serves a search box, and a search box that needs the whole
322
506
  // address typed without a typo is not one. Nested paths are out of scope.
323
- input_key: z4.string().min(1).optional(),
324
- input_value: z4.string().min(1).optional(),
507
+ input_key: z5.string().min(1).optional(),
508
+ input_value: z5.string().min(1).optional(),
325
509
  // PLA-1785 — `exclude` leaves out the runs of the platform's own entries
326
510
  // (`studio-copilot`, `mapping-suggest`, `rag-ingest`), decided by the box's
327
511
  // `isSystemEntry` and applied before the cursor and the LIMIT, so a page stays
@@ -341,179 +525,43 @@ var listRunsQuerySchema = pageQuerySchema.extend({
341
525
  path: ["input_key"]
342
526
  });
343
527
  var runListResponseSchema = pageResponseSchema(runSchema);
344
- var runEntityEntrySchema = z4.object({
345
- name: z4.string(),
528
+ var runEntityEntrySchema = z5.object({
529
+ name: z5.string(),
346
530
  kind: runEntityKindSchema.nullable()
347
531
  });
348
- var runEntityNamesResponseSchema = z4.object({
349
- names: z4.array(z4.string()),
350
- entries: z4.array(runEntityEntrySchema).optional()
532
+ var runEntityNamesResponseSchema = z5.object({
533
+ names: z5.array(z5.string()),
534
+ entries: z5.array(runEntityEntrySchema).optional()
351
535
  });
352
- var cancelRunResponseSchema = z4.object({
536
+ var cancelRunResponseSchema = z5.object({
353
537
  run: runSchema
354
538
  });
355
539
  var STACKBONE_SESSION_HEADER = "x-stackbone-session";
356
540
  var RUN_TAGS_HEADER = "x-stackbone-run-tags";
357
- var fixtureSchema = z4.object({
358
- id: z4.string(),
359
- name: z4.string(),
541
+ var fixtureSchema = z5.object({
542
+ id: z5.string(),
543
+ name: z5.string(),
360
544
  input: jsonObject().nullable(),
361
- description: z4.string().nullable(),
362
- createdAt: z4.iso.datetime()
545
+ description: z5.string().nullable(),
546
+ createdAt: z5.iso.datetime()
363
547
  });
364
- var createFixtureRequestSchema = z4.object({
365
- name: z4.string().min(1).max(120),
548
+ var createFixtureRequestSchema = z5.object({
549
+ name: z5.string().min(1).max(120),
366
550
  input: jsonObject(),
367
- description: z4.string().max(2e3).optional().nullable()
551
+ description: z5.string().max(2e3).optional().nullable()
368
552
  });
369
553
  var fixtureListResponseSchema = pageResponseSchema(fixtureSchema);
370
- var fixtureIdParamSchema = z4.object({ fxtId: z4.uuid() });
371
- var handlerSchemaSchema = z4.object({
554
+ var fixtureIdParamSchema = z5.object({ fxtId: z5.uuid() });
555
+ var handlerSchemaSchema = z5.object({
372
556
  input: jsonObject().nullable(),
373
557
  output: jsonObject().nullable()
374
558
  });
375
- var schemaResponseSchema = z4.object({
559
+ var schemaResponseSchema = z5.object({
376
560
  input: jsonObject().nullable(),
377
561
  output: jsonObject().nullable(),
378
562
  handlers: jsonObject().nullable().optional()
379
563
  });
380
564
 
381
- // libs/shared/validators/src/lib/studio/studio-model-provider.ts
382
- import { z as z5 } from "zod";
383
- var modelProviderSlotSchema = z5.enum(["baseUrl", "apiKey"]);
384
- var MODEL_PROVIDER_SLOTS = {
385
- baseUrl: ["MODEL_PROVIDER_BASE_URL", "OPENROUTER_BASE_URL"],
386
- apiKey: ["MODEL_PROVIDER_API_KEY", "OPENROUTER_API_KEY"]
387
- };
388
- var SLOT_NAMES = Object.keys(MODEL_PROVIDER_SLOTS);
389
- var MODEL_PROVIDER_DEPRECATED_ENV_NAMES = SLOT_NAMES.map(
390
- (slot) => MODEL_PROVIDER_SLOTS[slot][1]
391
- );
392
- var modelProviderSourceSchema = z5.enum(["environment", "stored", "none"]);
393
- var isModelProviderValuePresent = (value) => typeof value === "string" && value.trim() !== "";
394
- var storedOverridesSchema = z5.strictObject({
395
- baseUrl: z5.boolean().optional(),
396
- apiKey: z5.boolean().optional()
397
- });
398
- var modelProviderKindSchema = z5.enum(["openrouter", "ollama", "lmstudio", "generic"]);
399
- var modelProviderGetResponseSchema = z5.object({
400
- configured: z5.boolean(),
401
- base_url: z5.string().nullable(),
402
- api_key_masked: z5.string().nullable(),
403
- source: modelProviderSourceSchema.optional(),
404
- env_override: z5.array(z5.string()).optional(),
405
- effective_base_url: z5.string().nullable().optional(),
406
- // The gateway kind of the endpoint IN EFFECT (the one `effective_base_url`
407
- // names), resolved by the box; `null` when nothing is configured. Optional for
408
- // the same version-skew reason as `source`: a box that predates the field.
409
- kind: modelProviderKindSchema.nullable().optional(),
410
- // The slots where a Studio override beats an exported value (PLA-1825, ADR
411
- // D8), by SLOT name, never the variable's (D4). Such a slot drops out of
412
- // `env_override`, which keeps naming only the variables that actually win.
413
- // A box that knows overrides always sends the list, empty or not; an absent
414
- // list is a box that predates them, and would drop an `override` it cannot
415
- // read, so Studio offers no Override against it.
416
- overridden: z5.array(modelProviderSlotSchema).optional()
417
- });
418
- var modelProviderPutRequestSchema = z5.object({
419
- base_url: z5.url(),
420
- api_key: z5.string().optional().nullable(),
421
- kind: modelProviderKindSchema.optional(),
422
- override: z5.object({ baseUrl: z5.boolean().optional(), apiKey: z5.boolean().optional() }).optional()
423
- }).refine((body) => body.override?.apiKey !== true || isModelProviderValuePresent(body.api_key), {
424
- message: "Overriding the API key needs the key to use.",
425
- path: ["api_key"]
426
- });
427
- var modelProviderAgentsOutcomeSchema = z5.object({
428
- /** Agents that were degraded and are now serving. */
429
- ready: z5.number().int().nonnegative(),
430
- /** Agents that are still degraded (their reason was refreshed by the retry). */
431
- degraded: z5.number().int().nonnegative()
432
- });
433
- var modelProviderPutResponseSchema = modelProviderGetResponseSchema.extend({
434
- agents: modelProviderAgentsOutcomeSchema.optional()
435
- });
436
- var modelProviderFirstRunResponseSchema = z5.object({
437
- completed: z5.boolean()
438
- });
439
- var modelProviderKeyCheckSchema = z5.enum(["verified", "unverified"]);
440
- var modelProviderTestResponseSchema = z5.object({
441
- ok: z5.boolean(),
442
- model_count: z5.number().int().nonnegative().optional(),
443
- error: z5.string().optional(),
444
- key_checked: modelProviderKeyCheckSchema.optional()
445
- });
446
- var modelProviderModelTypeSchema = z5.enum(["chat", "image", "embedding", "other"]);
447
- var modelProviderModelSchema = z5.object({
448
- id: z5.string().min(1),
449
- name: z5.string().optional(),
450
- context_length: z5.number().int().positive().optional(),
451
- pricing: z5.object({
452
- prompt: z5.string().optional(),
453
- completion: z5.string().optional()
454
- }).optional(),
455
- owned_by: z5.string().optional(),
456
- type: modelProviderModelTypeSchema.optional(),
457
- // What the gateway says the model can do (PLA-1605): `true`, `false`, or
458
- // `null` for "this gateway does not say". The three values are distinct on
459
- // purpose — Studio hides a model that reports no tool support and only WARNS
460
- // about one that reports nothing, so collapsing `null` into `false` would hide
461
- // every model on a custom OpenAI-compatible endpoint.
462
- //
463
- // Nullable AND optional: a box on an older runtime sends neither field, and
464
- // that must keep validating, exactly as `type` already does.
465
- tools: z5.boolean().nullable().optional(),
466
- reasoning: z5.boolean().nullable().optional(),
467
- // STA3-17: on a box with several providers, the one that serves this entry.
468
- // `responding: false` marks an entry remembered from a provider that is down.
469
- // Declared so the parse keeps it: an object schema strips what it does not name.
470
- provider: z5.object({ id: z5.string(), name: z5.string(), responding: z5.boolean() }).optional()
471
- });
472
- var modelProviderModelsRequestSchema = z5.object({
473
- base_url: z5.url().optional().nullable(),
474
- api_key: z5.string().optional().nullable()
475
- });
476
- var modelProviderModelsResponseSchema = z5.object({
477
- ok: z5.boolean(),
478
- models: z5.array(modelProviderModelSchema).optional(),
479
- error: z5.string().optional(),
480
- warning: z5.string().optional()
481
- });
482
- var MODEL_PROVIDER_DEFAULT_ID = "default";
483
- var MODEL_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,30}[a-z0-9]$/;
484
- var modelProviderIdSchema = z5.string().regex(MODEL_PROVIDER_ID_PATTERN, {
485
- message: "Lowercase letters, digits and dashes, 2 to 32, starting with a letter."
486
- }).refine((id) => id !== MODEL_PROVIDER_DEFAULT_ID, {
487
- message: `'${MODEL_PROVIDER_DEFAULT_ID}' is provider 1's id.`
488
- });
489
- var pickedModelProviderIdSchema = z5.string().regex(MODEL_PROVIDER_ID_PATTERN);
490
- var modelProviderListItemSchema = z5.object({
491
- id: z5.string(),
492
- name: z5.string(),
493
- kind: modelProviderKindSchema,
494
- baseUrl: z5.string(),
495
- apiKey: z5.string().nullable(),
496
- source: z5.enum(["env", "local"]),
497
- removable: z5.boolean(),
498
- responding: z5.boolean().nullable(),
499
- error: z5.string().optional()
500
- });
501
- var modelProviderListResponseSchema = z5.object({
502
- providers: z5.array(modelProviderListItemSchema),
503
- duplicatedIds: z5.array(z5.object({ modelId: z5.string(), providerIds: z5.array(z5.string()) }))
504
- });
505
- var providerFields = {
506
- name: z5.string().trim().min(1),
507
- kind: modelProviderKindSchema,
508
- baseUrl: z5.url(),
509
- apiKey: z5.string().optional()
510
- };
511
- var modelProviderCreateRequestSchema = z5.strictObject({
512
- id: modelProviderIdSchema,
513
- ...providerFields
514
- });
515
- var modelProviderUpdateRequestSchema = z5.strictObject(providerFields);
516
-
517
565
  // libs/shared/validators/src/lib/studio/studio-voice.ts
518
566
  import { z as z8 } from "zod";
519
567
 
@@ -907,6 +955,14 @@ var VOICE_TTS_MODELS = [
907
955
  "gradium/default"
908
956
  ];
909
957
  var isCartesiaTtsModel = (model) => model.startsWith("cartesia/") && VOICE_TTS_MODELS.includes(model);
958
+ var VOICE_BACKGROUND_CLIPS = [
959
+ "office-ambience",
960
+ "keyboard-typing",
961
+ "keyboard-typing2",
962
+ "hold-music"
963
+ ];
964
+ var VOICE_AMBIENT_SOUNDS = ["none", ...VOICE_BACKGROUND_CLIPS];
965
+ var VOICE_THINKING_SOUNDS = ["none", "chime", ...VOICE_BACKGROUND_CLIPS];
910
966
  var thresholdSchema = z8.number().min(0).max(1);
911
967
  var millisecondsSchema = z8.int().min(0).max(1e4);
912
968
  var keytermsSchema = z8.array(z8.string().trim().min(1).max(64)).max(50);
@@ -934,6 +990,27 @@ var ragSchema = z8.strictObject({
934
990
  timeoutMs: z8.int().min(200).max(1e4),
935
991
  previousExchanges: z8.int().min(0).max(5)
936
992
  });
993
+ var volumeSchema = z8.number().min(0).max(1);
994
+ var backgroundAudioSchema = z8.strictObject({
995
+ ambientSound: z8.enum(VOICE_AMBIENT_SOUNDS),
996
+ ambientVolume: volumeSchema,
997
+ thinkingSound: z8.enum(VOICE_THINKING_SOUNDS),
998
+ thinkingVolume: volumeSchema,
999
+ /**
1000
+ * How long a reply's wait runs before the thinking sound starts, when no box tool
1001
+ * call is open (a tool call starts it at once). Every turn waits for the knowledge
1002
+ * search and the model's first token, about 1.4 s at the median on a voice box
1003
+ * (2026-11-09), so a delay under that plays the sound on nearly every reply.
1004
+ */
1005
+ thinkingDelayMs: millisecondsSchema
1006
+ });
1007
+ var VOICE_BACKGROUND_AUDIO_DEFAULTS = Object.freeze({
1008
+ ambientSound: "office-ambience",
1009
+ ambientVolume: 0.7,
1010
+ thinkingSound: "keyboard-typing",
1011
+ thinkingVolume: 0.6,
1012
+ thinkingDelayMs: 2e3
1013
+ });
937
1014
  var effectiveVoiceSettingsObjectSchema = z8.strictObject({
938
1015
  /** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */
939
1016
  language: z8.string().max(16).regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/),
@@ -960,6 +1037,7 @@ var effectiveVoiceSettingsObjectSchema = z8.strictObject({
960
1037
  phoneLine: z8.boolean(),
961
1038
  /** LiveKit expressive mode: the turn gets Cartesia's markup guide (D5). Cartesia only. */
962
1039
  expressive: z8.boolean(),
1040
+ backgroundAudio: backgroundAudioSchema,
963
1041
  /**
964
1042
  * Whether the box records the call to its bucket (D30). Off, no egress starts: a
965
1043
  * box may not be allowed to keep a caller's audio. The box alone reads it, so the
@@ -1007,6 +1085,13 @@ var callVoiceSettingsObjectSchema = effectiveVoiceSettingsObjectSchema.omit({
1007
1085
  recording: true,
1008
1086
  rag: true,
1009
1087
  refusalPhrase: true
1088
+ }).extend({
1089
+ // A box older than `thinkingDelayMs` dispatches the group without it.
1090
+ backgroundAudio: backgroundAudioSchema.extend({
1091
+ thinkingDelayMs: millisecondsSchema.default(
1092
+ VOICE_BACKGROUND_AUDIO_DEFAULTS.thinkingDelayMs
1093
+ )
1094
+ }).default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS }))
1010
1095
  });
1011
1096
  var callVoiceSettingsSchema = callVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);
1012
1097
  var VOICE_SETTINGS_LEAF_PATHS = [
@@ -1029,6 +1114,11 @@ var VOICE_SETTINGS_LEAF_PATHS = [
1029
1114
  "greeting",
1030
1115
  "phoneLine",
1031
1116
  "expressive",
1117
+ "backgroundAudio.ambientSound",
1118
+ "backgroundAudio.ambientVolume",
1119
+ "backgroundAudio.thinkingSound",
1120
+ "backgroundAudio.thinkingVolume",
1121
+ "backgroundAudio.thinkingDelayMs",
1032
1122
  "recording",
1033
1123
  "rag.collection",
1034
1124
  "rag.minScore",
@@ -1061,7 +1151,8 @@ var voiceSettingsSchema = effectiveVoiceSettingsObjectSchema.extend({
1061
1151
  vad: vadSchema.partial(),
1062
1152
  turn: turnSchema.partial(),
1063
1153
  interruptions: interruptionsSchema.partial(),
1064
- rag: ragSchema.partial()
1154
+ rag: ragSchema.partial(),
1155
+ backgroundAudio: backgroundAudioSchema.partial()
1065
1156
  }).partial().superRefine(checkCrossFieldRules).superRefine(checkTtsPair);
1066
1157
  var checkStudioTtsPair = (value, ctx) => {
1067
1158
  if (value.ttsVoice !== void 0 && value.ttsModel === void 0) {
@@ -2143,23 +2234,52 @@ var CALL_CLOSING_PHRASE = Object.freeze({
2143
2234
  });
2144
2235
  var MAX_FAILED_TURNS_IN_A_ROW = 3;
2145
2236
  var inCallLanguage = (phrase, language) => language.split("-")[0]?.toLowerCase() === "es" ? phrase.es : phrase.en;
2146
- var toolCallCue = () => {
2237
+ var thinkingCue = (delayMs) => {
2147
2238
  let cue;
2148
2239
  let open = 0;
2240
+ let waitedLong = false;
2241
+ let wait;
2242
+ let playing = false;
2243
+ const sync = () => {
2244
+ const play = waitedLong || open > 0;
2245
+ if (play === playing || !cue) return;
2246
+ playing = play;
2247
+ if (play) cue.start();
2248
+ else cue.stop();
2249
+ };
2149
2250
  return {
2150
- // A call can open before the cue's track is up.
2251
+ // A turn can start before the track is up.
2151
2252
  attach: (ready) => {
2152
2253
  cue = ready;
2153
- if (open > 0) cue.start();
2254
+ sync();
2154
2255
  },
2155
- onToolCall: (phase) => {
2156
- if (phase === "start") {
2157
- open += 1;
2158
- if (open === 1) cue?.start();
2159
- } else if (open > 0) {
2160
- open -= 1;
2161
- if (open === 0) cue?.stop();
2256
+ onAgentState: (state) => {
2257
+ if (state !== "thinking") {
2258
+ clearTimeout(wait);
2259
+ wait = void 0;
2260
+ waitedLong = false;
2261
+ sync();
2262
+ return;
2162
2263
  }
2264
+ if (wait !== void 0 || waitedLong) return;
2265
+ wait = setTimeout(() => {
2266
+ wait = void 0;
2267
+ waitedLong = true;
2268
+ sync();
2269
+ }, delayMs);
2270
+ },
2271
+ onToolCall: (phase) => {
2272
+ if (phase === "start") open += 1;
2273
+ else if (open > 0) open -= 1;
2274
+ sync();
2275
+ },
2276
+ /** The call is ending: a pending delay must not start the sound in a closed room. */
2277
+ close: () => {
2278
+ clearTimeout(wait);
2279
+ wait = void 0;
2280
+ waitedLong = false;
2281
+ open = 0;
2282
+ sync();
2163
2283
  }
2164
2284
  };
2165
2285
  };
@@ -2204,7 +2324,7 @@ var runVoiceJob = async (ctx, deps) => {
2204
2324
  deps
2205
2325
  );
2206
2326
  const settings = dispatch.voiceSettings;
2207
- const cue = toolCallCue();
2327
+ const cue = thinkingCue(settings.backgroundAudio.thinkingDelayMs);
2208
2328
  const fallback = inCallLanguage(TURN_FALLBACK_PHRASE, settings.language);
2209
2329
  let failedInARow = 0;
2210
2330
  let ending = false;
@@ -2305,6 +2425,7 @@ var runVoiceJob = async (ctx, deps) => {
2305
2425
  const finishCall = (outcome = "disconnected") => {
2306
2426
  if (completion) return completion;
2307
2427
  ending = true;
2428
+ cue.close();
2308
2429
  completion = Promise.resolve().then(async () => {
2309
2430
  await hangUp.pendingResume();
2310
2431
  await session.close();
@@ -2333,6 +2454,10 @@ var runVoiceJob = async (ctx, deps) => {
2333
2454
  return completion;
2334
2455
  };
2335
2456
  ctx.addShutdownCallback(() => finishCall());
2457
+ session.on(
2458
+ voice4.AgentSessionEventTypes.AgentStateChanged,
2459
+ ({ newState }) => cue.onAgentState(newState)
2460
+ );
2336
2461
  session.on(voice4.AgentSessionEventTypes.Close, () => {
2337
2462
  if (completion || ending) return;
2338
2463
  void finishCall().catch(() => void 0).finally(() => ctx.shutdown("caller left"));
@@ -2355,8 +2480,8 @@ var runVoiceJob = async (ctx, deps) => {
2355
2480
  inputOptions: noiseCancellation ? { noiseCancellation } : {}
2356
2481
  });
2357
2482
  if (settings.greeting !== null) sayIfOpen(settings.greeting, "greeting");
2358
- await deps.createCue({ room: ctx.room, session }).then(cue.attach, (err) => {
2359
- log4().warn({ error: messageOf(err) }, "voice worker: the thinking cue did not start");
2483
+ await deps.createCue({ room: ctx.room, session, audio: settings.backgroundAudio }).then(cue.attach, (err) => {
2484
+ log4().warn({ error: messageOf(err) }, "voice worker: the background audio did not start");
2360
2485
  });
2361
2486
  };
2362
2487
 
@@ -2366,7 +2491,7 @@ var liveDeps = () => ({
2366
2491
  createStt: (options) => new inference.STT(options),
2367
2492
  createTts: (options) => new inference.TTS(options),
2368
2493
  createTurnDetector: (options) => new inference.TurnDetector(options),
2369
- createCue: startThinkingCue,
2494
+ createCue: startBackgroundAudio,
2370
2495
  // OTLP/HTTP JSON, uncompressed: the encoding the box relay takes and forwards as is.
2371
2496
  // `compression` is set so an `OTEL_EXPORTER_OTLP_COMPRESSION=gzip` in the worker's
2372
2497
  // environment cannot turn every export into a body the relay refuses.