@stackbone/cli 0.8.0 → 0.9.0

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,19 @@ 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
+ var VOICE_BACKGROUND_AUDIO_DEFAULTS = Object.freeze({
1001
+ ambientSound: "office-ambience",
1002
+ ambientVolume: 0.7,
1003
+ thinkingSound: "keyboard-typing",
1004
+ thinkingVolume: 0.6
1005
+ });
937
1006
  var effectiveVoiceSettingsObjectSchema = z8.strictObject({
938
1007
  /** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */
939
1008
  language: z8.string().max(16).regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/),
@@ -960,6 +1029,7 @@ var effectiveVoiceSettingsObjectSchema = z8.strictObject({
960
1029
  phoneLine: z8.boolean(),
961
1030
  /** LiveKit expressive mode: the turn gets Cartesia's markup guide (D5). Cartesia only. */
962
1031
  expressive: z8.boolean(),
1032
+ backgroundAudio: backgroundAudioSchema,
963
1033
  /**
964
1034
  * Whether the box records the call to its bucket (D30). Off, no egress starts: a
965
1035
  * box may not be allowed to keep a caller's audio. The box alone reads it, so the
@@ -1007,6 +1077,8 @@ var callVoiceSettingsObjectSchema = effectiveVoiceSettingsObjectSchema.omit({
1007
1077
  recording: true,
1008
1078
  rag: true,
1009
1079
  refusalPhrase: true
1080
+ }).extend({
1081
+ backgroundAudio: backgroundAudioSchema.default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS }))
1010
1082
  });
1011
1083
  var callVoiceSettingsSchema = callVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);
1012
1084
  var VOICE_SETTINGS_LEAF_PATHS = [
@@ -1029,6 +1101,10 @@ var VOICE_SETTINGS_LEAF_PATHS = [
1029
1101
  "greeting",
1030
1102
  "phoneLine",
1031
1103
  "expressive",
1104
+ "backgroundAudio.ambientSound",
1105
+ "backgroundAudio.ambientVolume",
1106
+ "backgroundAudio.thinkingSound",
1107
+ "backgroundAudio.thinkingVolume",
1032
1108
  "recording",
1033
1109
  "rag.collection",
1034
1110
  "rag.minScore",
@@ -1061,7 +1137,8 @@ var voiceSettingsSchema = effectiveVoiceSettingsObjectSchema.extend({
1061
1137
  vad: vadSchema.partial(),
1062
1138
  turn: turnSchema.partial(),
1063
1139
  interruptions: interruptionsSchema.partial(),
1064
- rag: ragSchema.partial()
1140
+ rag: ragSchema.partial(),
1141
+ backgroundAudio: backgroundAudioSchema.partial()
1065
1142
  }).partial().superRefine(checkCrossFieldRules).superRefine(checkTtsPair);
1066
1143
  var checkStudioTtsPair = (value, ctx) => {
1067
1144
  if (value.ttsVoice !== void 0 && value.ttsModel === void 0) {
@@ -2143,23 +2220,32 @@ var CALL_CLOSING_PHRASE = Object.freeze({
2143
2220
  });
2144
2221
  var MAX_FAILED_TURNS_IN_A_ROW = 3;
2145
2222
  var inCallLanguage = (phrase, language) => language.split("-")[0]?.toLowerCase() === "es" ? phrase.es : phrase.en;
2146
- var toolCallCue = () => {
2223
+ var thinkingCue = () => {
2147
2224
  let cue;
2148
2225
  let open = 0;
2226
+ let thinking = false;
2227
+ let playing = false;
2228
+ const sync = () => {
2229
+ const play = thinking || open > 0;
2230
+ if (play === playing || !cue) return;
2231
+ playing = play;
2232
+ if (play) cue.start();
2233
+ else cue.stop();
2234
+ };
2149
2235
  return {
2150
- // A call can open before the cue's track is up.
2236
+ // A turn can start before the track is up.
2151
2237
  attach: (ready) => {
2152
2238
  cue = ready;
2153
- if (open > 0) cue.start();
2239
+ sync();
2240
+ },
2241
+ onAgentState: (state) => {
2242
+ thinking = state === "thinking";
2243
+ sync();
2154
2244
  },
2155
2245
  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();
2162
- }
2246
+ if (phase === "start") open += 1;
2247
+ else if (open > 0) open -= 1;
2248
+ sync();
2163
2249
  }
2164
2250
  };
2165
2251
  };
@@ -2204,7 +2290,7 @@ var runVoiceJob = async (ctx, deps) => {
2204
2290
  deps
2205
2291
  );
2206
2292
  const settings = dispatch.voiceSettings;
2207
- const cue = toolCallCue();
2293
+ const cue = thinkingCue();
2208
2294
  const fallback = inCallLanguage(TURN_FALLBACK_PHRASE, settings.language);
2209
2295
  let failedInARow = 0;
2210
2296
  let ending = false;
@@ -2333,6 +2419,10 @@ var runVoiceJob = async (ctx, deps) => {
2333
2419
  return completion;
2334
2420
  };
2335
2421
  ctx.addShutdownCallback(() => finishCall());
2422
+ session.on(
2423
+ voice4.AgentSessionEventTypes.AgentStateChanged,
2424
+ ({ newState }) => cue.onAgentState(newState)
2425
+ );
2336
2426
  session.on(voice4.AgentSessionEventTypes.Close, () => {
2337
2427
  if (completion || ending) return;
2338
2428
  void finishCall().catch(() => void 0).finally(() => ctx.shutdown("caller left"));
@@ -2355,8 +2445,8 @@ var runVoiceJob = async (ctx, deps) => {
2355
2445
  inputOptions: noiseCancellation ? { noiseCancellation } : {}
2356
2446
  });
2357
2447
  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");
2448
+ await deps.createCue({ room: ctx.room, session, audio: settings.backgroundAudio }).then(cue.attach, (err) => {
2449
+ log4().warn({ error: messageOf(err) }, "voice worker: the background audio did not start");
2360
2450
  });
2361
2451
  };
2362
2452
 
@@ -2366,7 +2456,7 @@ var liveDeps = () => ({
2366
2456
  createStt: (options) => new inference.STT(options),
2367
2457
  createTts: (options) => new inference.TTS(options),
2368
2458
  createTurnDetector: (options) => new inference.TurnDetector(options),
2369
- createCue: startThinkingCue,
2459
+ createCue: startBackgroundAudio,
2370
2460
  // OTLP/HTTP JSON, uncompressed: the encoding the box relay takes and forwards as is.
2371
2461
  // `compression` is set so an `OTEL_EXPORTER_OTLP_COMPRESSION=gzip` in the worker's
2372
2462
  // environment cannot turn every export into a body the relay refuses.