@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.
- package/CHANGELOG.md +69 -0
- package/main.js +1189 -526
- package/package.json +1 -1
- package/stackbone-cli-0.9.0.tgz +0 -0
- package/voice-worker/main.js +339 -249
- package/voice-worker/main.js.map +3 -3
- package/stackbone-cli-0.8.0.tgz +0 -0
package/voice-worker/main.js
CHANGED
|
@@ -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/
|
|
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
|
|
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
|
|
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 *
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
|
59
|
-
|
|
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
|
|
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/
|
|
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 =
|
|
119
|
-
var limitParamSchema = (max = PAGE_LIMIT_MAX) =>
|
|
120
|
-
var orderParamSchema =
|
|
121
|
-
var pageQuerySchema =
|
|
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 =
|
|
128
|
-
var pageResponseSchema = (item) =>
|
|
129
|
-
var forwardPageResponseSchema = (item) =>
|
|
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
|
|
316
|
+
import { z as z5 } from "zod";
|
|
133
317
|
|
|
134
318
|
// libs/shared/validators/src/lib/common/system-entries.ts
|
|
135
|
-
import { z as
|
|
136
|
-
var systemEntriesParamSchema =
|
|
137
|
-
var systemEntriesQuerySchema =
|
|
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 =
|
|
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 =
|
|
164
|
-
var runEntityKindSchema =
|
|
165
|
-
var runTriggerLinkSchema =
|
|
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:
|
|
351
|
+
subscriptionId: z5.string(),
|
|
168
352
|
/** The connector id, e.g. `gmail`. */
|
|
169
|
-
connector:
|
|
353
|
+
connector: z5.string(),
|
|
170
354
|
/** The trigger id within the connector, e.g. `gmail_new_email_received`. */
|
|
171
|
-
trigger:
|
|
172
|
-
outcome:
|
|
355
|
+
trigger: z5.string(),
|
|
356
|
+
outcome: z5.enum(["started", "resumed"]).optional(),
|
|
173
357
|
/** The connector's brand name, e.g. `Gmail`. */
|
|
174
|
-
connectorDisplayName:
|
|
358
|
+
connectorDisplayName: z5.string().optional(),
|
|
175
359
|
/** The trigger's name, e.g. `New Email`. */
|
|
176
|
-
triggerDisplayName:
|
|
177
|
-
connectorLogoUrl:
|
|
360
|
+
triggerDisplayName: z5.string().optional(),
|
|
361
|
+
connectorLogoUrl: z5.string().optional()
|
|
178
362
|
});
|
|
179
|
-
var runSchema =
|
|
180
|
-
id:
|
|
363
|
+
var runSchema = z5.object({
|
|
364
|
+
id: z5.uuid(),
|
|
181
365
|
status: runStatusSchema,
|
|
182
366
|
trigger: runTriggerSchema,
|
|
183
|
-
createdAt:
|
|
184
|
-
startedAt:
|
|
185
|
-
finishedAt:
|
|
186
|
-
durationMs:
|
|
187
|
-
traceId:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
223
|
-
outputTokens:
|
|
224
|
-
cacheReadTokens:
|
|
225
|
-
cacheWriteTokens:
|
|
226
|
-
totalTokens:
|
|
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 =
|
|
431
|
+
var schemaIssueSchema = z5.object({
|
|
248
432
|
/** Path segments to the offending field (`[]` for the root value). */
|
|
249
|
-
path:
|
|
250
|
-
message:
|
|
433
|
+
path: z5.array(z5.union([z5.string(), z5.number()])),
|
|
434
|
+
message: z5.string()
|
|
251
435
|
});
|
|
252
|
-
var runOutputViolationSchema =
|
|
436
|
+
var runOutputViolationSchema = z5.object({
|
|
253
437
|
/** The workflow whose declared `outputSchema` the return value broke. */
|
|
254
|
-
workflowName:
|
|
255
|
-
issues:
|
|
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:
|
|
444
|
+
recordedAt: z5.string()
|
|
261
445
|
});
|
|
262
|
-
var runIdParamSchema =
|
|
263
|
-
var runTagKeySchema =
|
|
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 =
|
|
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 =
|
|
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:
|
|
300
|
-
to:
|
|
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:
|
|
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:
|
|
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:
|
|
324
|
-
input_value:
|
|
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 =
|
|
345
|
-
name:
|
|
528
|
+
var runEntityEntrySchema = z5.object({
|
|
529
|
+
name: z5.string(),
|
|
346
530
|
kind: runEntityKindSchema.nullable()
|
|
347
531
|
});
|
|
348
|
-
var runEntityNamesResponseSchema =
|
|
349
|
-
names:
|
|
350
|
-
entries:
|
|
532
|
+
var runEntityNamesResponseSchema = z5.object({
|
|
533
|
+
names: z5.array(z5.string()),
|
|
534
|
+
entries: z5.array(runEntityEntrySchema).optional()
|
|
351
535
|
});
|
|
352
|
-
var cancelRunResponseSchema =
|
|
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 =
|
|
358
|
-
id:
|
|
359
|
-
name:
|
|
541
|
+
var fixtureSchema = z5.object({
|
|
542
|
+
id: z5.string(),
|
|
543
|
+
name: z5.string(),
|
|
360
544
|
input: jsonObject().nullable(),
|
|
361
|
-
description:
|
|
362
|
-
createdAt:
|
|
545
|
+
description: z5.string().nullable(),
|
|
546
|
+
createdAt: z5.iso.datetime()
|
|
363
547
|
});
|
|
364
|
-
var createFixtureRequestSchema =
|
|
365
|
-
name:
|
|
548
|
+
var createFixtureRequestSchema = z5.object({
|
|
549
|
+
name: z5.string().min(1).max(120),
|
|
366
550
|
input: jsonObject(),
|
|
367
|
-
description:
|
|
551
|
+
description: z5.string().max(2e3).optional().nullable()
|
|
368
552
|
});
|
|
369
553
|
var fixtureListResponseSchema = pageResponseSchema(fixtureSchema);
|
|
370
|
-
var fixtureIdParamSchema =
|
|
371
|
-
var handlerSchemaSchema =
|
|
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 =
|
|
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
|
|
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
|
|
2236
|
+
// A turn can start before the track is up.
|
|
2151
2237
|
attach: (ready) => {
|
|
2152
2238
|
cue = ready;
|
|
2153
|
-
|
|
2239
|
+
sync();
|
|
2240
|
+
},
|
|
2241
|
+
onAgentState: (state) => {
|
|
2242
|
+
thinking = state === "thinking";
|
|
2243
|
+
sync();
|
|
2154
2244
|
},
|
|
2155
2245
|
onToolCall: (phase) => {
|
|
2156
|
-
if (phase === "start")
|
|
2157
|
-
|
|
2158
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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.
|