@stackbone/cli 0.9.2 → 0.9.4
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 +67 -0
- package/main.js +1752 -634
- package/package.json +1 -1
- package/stackbone-cli-0.9.4.tgz +0 -0
- package/voice-worker/main.js +796 -298
- package/voice-worker/main.js.map +4 -4
- package/voice-worker/package.json +1 -0
- package/stackbone-cli-0.9.2.tgz +0 -0
package/voice-worker/main.js
CHANGED
|
@@ -152,6 +152,7 @@ var loadVad = async () => {
|
|
|
152
152
|
import { log as log5, voice as voice4 } from "@livekit/agents";
|
|
153
153
|
import {
|
|
154
154
|
BackgroundVoiceCancellation,
|
|
155
|
+
NoiseCancellation,
|
|
155
156
|
TelephonyBackgroundVoiceCancellation
|
|
156
157
|
} from "@livekit/noise-cancellation-node";
|
|
157
158
|
import { ParticipantKind } from "@livekit/rtc-node";
|
|
@@ -177,7 +178,13 @@ var storedOverridesSchema = z2.strictObject({
|
|
|
177
178
|
baseUrl: z2.boolean().optional(),
|
|
178
179
|
apiKey: z2.boolean().optional()
|
|
179
180
|
});
|
|
180
|
-
var modelProviderKindSchema = z2.enum([
|
|
181
|
+
var modelProviderKindSchema = z2.enum([
|
|
182
|
+
"openrouter",
|
|
183
|
+
"ollama",
|
|
184
|
+
"lmstudio",
|
|
185
|
+
"generic",
|
|
186
|
+
"livekit"
|
|
187
|
+
]);
|
|
181
188
|
var modelProviderGetResponseSchema = z2.object({
|
|
182
189
|
configured: z2.boolean(),
|
|
183
190
|
base_url: z2.string().nullable(),
|
|
@@ -275,7 +282,8 @@ var modelProviderListItemSchema = z2.object({
|
|
|
275
282
|
kind: modelProviderKindSchema,
|
|
276
283
|
baseUrl: z2.string(),
|
|
277
284
|
apiKey: z2.string().nullable(),
|
|
278
|
-
|
|
285
|
+
// `voice`: derived from the Voice credentials (PLA-2416); edited there, never removed here.
|
|
286
|
+
source: z2.enum(["env", "local", "voice"]),
|
|
279
287
|
removable: z2.boolean(),
|
|
280
288
|
responding: z2.boolean().nullable(),
|
|
281
289
|
error: z2.string().optional()
|
|
@@ -562,14 +570,255 @@ var schemaResponseSchema = z5.object({
|
|
|
562
570
|
handlers: jsonObject().nullable().optional()
|
|
563
571
|
});
|
|
564
572
|
|
|
573
|
+
// libs/shared/validators/src/lib/agents/queue-jobs.ts
|
|
574
|
+
import cronParser from "cron-parser";
|
|
575
|
+
import { z as z7 } from "zod";
|
|
576
|
+
|
|
577
|
+
// libs/shared/validators/src/lib/common/utf8-byte-length.ts
|
|
578
|
+
var ENCODER = new TextEncoder();
|
|
579
|
+
function utf8ByteLength(value) {
|
|
580
|
+
return ENCODER.encode(value).length;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// libs/shared/validators/src/lib/studio/studio-queues.ts
|
|
584
|
+
import { z as z6 } from "zod";
|
|
585
|
+
var QUEUE_NAME_REGEX = /^[A-Za-z0-9_.-]+$/;
|
|
586
|
+
var queueNameSchema = z6.string().min(1).max(128).regex(QUEUE_NAME_REGEX, "queue name must be a BullMQ queue identifier ([A-Za-z0-9_.-])");
|
|
587
|
+
var jobSourceSchema = z6.enum(["bullmq", "journal"]);
|
|
588
|
+
var bullmqJobStatusSchema = z6.enum(["pending", "active", "delayed", "failed"]);
|
|
589
|
+
var journalTerminalStatusSchema = z6.enum(["done", "failed"]);
|
|
590
|
+
var journalJobStatusSchema = z6.enum([
|
|
591
|
+
...journalTerminalStatusSchema.options,
|
|
592
|
+
"running",
|
|
593
|
+
"lost"
|
|
594
|
+
]);
|
|
595
|
+
var jobStatusSchema = z6.enum([
|
|
596
|
+
...bullmqJobStatusSchema.options,
|
|
597
|
+
...journalJobStatusSchema.options
|
|
598
|
+
]);
|
|
599
|
+
var jobStatusFilterSchema = z6.enum([
|
|
600
|
+
...bullmqJobStatusSchema.options,
|
|
601
|
+
...journalTerminalStatusSchema.options
|
|
602
|
+
]);
|
|
603
|
+
var queueSummarySchema = z6.object({
|
|
604
|
+
name: z6.string(),
|
|
605
|
+
// `is_dlq: true` flags queues whose name follows the `*-dlq` / `*_dlq`
|
|
606
|
+
// convention. Studio renders them under a "Dead-letter" subgroup; the
|
|
607
|
+
// server-side actions (retry/discard) are unchanged.
|
|
608
|
+
is_dlq: z6.boolean(),
|
|
609
|
+
// BullMQ live counts. `failed` is the dispatch-failure / exhausted-retry
|
|
610
|
+
// bucket the inspector surfaces for manual retry. Completed jobs are not
|
|
611
|
+
// counted here — their record lives in the agent journal.
|
|
612
|
+
pending: z6.number().int().nonnegative(),
|
|
613
|
+
active: z6.number().int().nonnegative(),
|
|
614
|
+
delayed: z6.number().int().nonnegative(),
|
|
615
|
+
failed: z6.number().int().nonnegative()
|
|
616
|
+
}).strict();
|
|
617
|
+
var queueListResponseSchema = z6.object({
|
|
618
|
+
items: z6.array(queueSummarySchema)
|
|
619
|
+
});
|
|
620
|
+
var jobCommonFields = {
|
|
621
|
+
id: z6.string(),
|
|
622
|
+
queue: z6.string(),
|
|
623
|
+
// Opaque job name the agent chose at publish time (e.g. `send-email`).
|
|
624
|
+
// Null for legacy/anonymous rows that predate named jobs.
|
|
625
|
+
name: z6.string().nullable(),
|
|
626
|
+
// BullMQ `attemptsMade` for in-flight rows; the journal's recorded attempt
|
|
627
|
+
// count for execution rows.
|
|
628
|
+
attempts: z6.number().int().nonnegative(),
|
|
629
|
+
enqueued_at: z6.iso.datetime(),
|
|
630
|
+
// Failure reason: the BullMQ `failedReason` (dispatch failure) or the
|
|
631
|
+
// application error the agent returned (also persisted in the journal).
|
|
632
|
+
error: z6.string().nullable(),
|
|
633
|
+
// Correlation key between a BullMQ job and its journal execution row.
|
|
634
|
+
// BullMQ rows carry their own id here; journal rows carry the originating
|
|
635
|
+
// BullMQ job id. Null when uncorrelated.
|
|
636
|
+
dispatch_id: z6.string().nullable(),
|
|
637
|
+
payload: jsonObject()
|
|
638
|
+
};
|
|
639
|
+
var bullmqJobSchema = z6.object({
|
|
640
|
+
...jobCommonFields,
|
|
641
|
+
source: z6.literal("bullmq"),
|
|
642
|
+
status: bullmqJobStatusSchema,
|
|
643
|
+
// When the job is next expected to run: BullMQ `delay`/next-retry timestamp
|
|
644
|
+
// for `delayed`/`pending` rows, null when it runs immediately.
|
|
645
|
+
next_run_at: z6.iso.datetime().nullable(),
|
|
646
|
+
// BullMQ does not own an execution window; null until/unless the journal
|
|
647
|
+
// correlates one. A dispatch failure has neither.
|
|
648
|
+
started_at: z6.iso.datetime().nullable(),
|
|
649
|
+
finished_at: z6.iso.datetime().nullable()
|
|
650
|
+
}).strict();
|
|
651
|
+
var journalJobSchema = z6.object({
|
|
652
|
+
...jobCommonFields,
|
|
653
|
+
source: z6.literal("journal"),
|
|
654
|
+
status: journalJobStatusSchema,
|
|
655
|
+
// A journal row has no live scheduling state, so `next_run_at` is always
|
|
656
|
+
// null. Modelled as a nullable date-time pinned to null (rather than a bare
|
|
657
|
+
// `z.null()`) so it emits the `anyOf:[date-time, null]` shape the OpenAPI
|
|
658
|
+
// 3.0 downgrader already collapses to `nullable: true` — a standalone
|
|
659
|
+
// `{ type: 'null' }` is not valid OpenAPI 3.0 and orval rejects it.
|
|
660
|
+
next_run_at: z6.iso.datetime().nullable().refine((value) => value === null, {
|
|
661
|
+
message: "a journal row never carries a live next_run_at"
|
|
662
|
+
}),
|
|
663
|
+
started_at: z6.iso.datetime(),
|
|
664
|
+
finished_at: z6.iso.datetime().nullable()
|
|
665
|
+
}).strict();
|
|
666
|
+
var jobSchema = z6.discriminatedUnion("source", [bullmqJobSchema, journalJobSchema]);
|
|
667
|
+
var listJobsQuerySchema = forwardPageQuerySchema.extend({
|
|
668
|
+
queue: queueNameSchema,
|
|
669
|
+
// Filter by the BullMQ-live or journal-terminal states only. The journal
|
|
670
|
+
// `running` row exists in the DB as a NULL status, so a `WHERE status =
|
|
671
|
+
// 'running'` filter could never match it — it is reachable only on an
|
|
672
|
+
// unfiltered page, never as a filter value.
|
|
673
|
+
status: jobStatusFilterSchema.optional(),
|
|
674
|
+
limit: limitParamSchema(200).default(50)
|
|
675
|
+
});
|
|
676
|
+
var jobListResponseSchema = forwardPageResponseSchema(jobSchema);
|
|
677
|
+
var jobActionParamsSchema = z6.object({
|
|
678
|
+
queue: queueNameSchema,
|
|
679
|
+
jobId: z6.string().min(1, "job id must be a non-empty BullMQ job id")
|
|
680
|
+
});
|
|
681
|
+
var jobActionResponseSchema = z6.object({
|
|
682
|
+
// Echoes the job id the action operated on. Retry re-enqueues the same job;
|
|
683
|
+
// discard only drops it.
|
|
684
|
+
id: z6.string(),
|
|
685
|
+
queue: z6.string(),
|
|
686
|
+
status: jobStatusSchema
|
|
687
|
+
});
|
|
688
|
+
var peekQueueResponseSchema = z6.object({
|
|
689
|
+
items: z6.array(bullmqJobSchema)
|
|
690
|
+
});
|
|
691
|
+
var purgeQueueResponseSchema = z6.object({
|
|
692
|
+
purged: z6.number().int().nonnegative()
|
|
693
|
+
});
|
|
694
|
+
var pushJobRequestSchema = z6.object({
|
|
695
|
+
payload: jsonObject(),
|
|
696
|
+
retries: z6.number().int().nonnegative().optional(),
|
|
697
|
+
delay: z6.number().int().nonnegative().optional()
|
|
698
|
+
});
|
|
699
|
+
var pushJobResponseSchema = z6.object({
|
|
700
|
+
message_id: z6.string(),
|
|
701
|
+
queue: queueNameSchema
|
|
702
|
+
});
|
|
703
|
+
var queueNameParamsSchema = z6.object({
|
|
704
|
+
name: queueNameSchema
|
|
705
|
+
});
|
|
706
|
+
var peekQueueQuerySchema = z6.object({
|
|
707
|
+
limit: limitParamSchema(50).default(10)
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// libs/shared/validators/src/lib/agents/queue-jobs.ts
|
|
711
|
+
var MAX_JOB_ATTEMPTS = 20;
|
|
712
|
+
var CRON_FIELD_COUNT = 5;
|
|
713
|
+
var MAX_DELAY_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
714
|
+
var MAX_JOB_PAYLOAD_BYTES = 100 * 1024;
|
|
715
|
+
var MAX_JOB_REQUEST_BYTES = MAX_JOB_PAYLOAD_BYTES + 16 * 1024;
|
|
716
|
+
var boundedJobPayload = jsonObject().refine(
|
|
717
|
+
(payload) => utf8ByteLength(JSON.stringify(payload)) <= MAX_JOB_PAYLOAD_BYTES,
|
|
718
|
+
{ message: `payload exceeds the ${MAX_JOB_PAYLOAD_BYTES}-byte limit` }
|
|
719
|
+
);
|
|
720
|
+
var baseJobFields = {
|
|
721
|
+
// The job name the agent chose. Surfaced in the Studio inspector and used as
|
|
722
|
+
// the BullMQ job name. Same character class as a queue name so it stays a
|
|
723
|
+
// clean Redis-keyed identifier.
|
|
724
|
+
name: queueNameSchema,
|
|
725
|
+
payload: boundedJobPayload,
|
|
726
|
+
// Per-job override of the platform retry default. Omitted → DEFAULT_JOB_ATTEMPTS.
|
|
727
|
+
retries: z7.number().int().min(0).max(MAX_JOB_ATTEMPTS).optional()
|
|
728
|
+
};
|
|
729
|
+
var publishJobRequestSchema = z7.object({
|
|
730
|
+
...baseJobFields,
|
|
731
|
+
// Deferred delivery in milliseconds. Omitted / 0 → immediate. Capped at
|
|
732
|
+
// MAX_DELAY_MS (30 days) so a unit slip can't pin a delayed job indefinitely.
|
|
733
|
+
delay: z7.number().int().min(0).max(MAX_DELAY_MS).optional(),
|
|
734
|
+
// Optional idempotency hint. When set the dispatcher uses it as the BullMQ
|
|
735
|
+
// job id so a duplicate publish with the same key collapses to one job.
|
|
736
|
+
deduplicationId: z7.string().min(1).max(255).optional()
|
|
737
|
+
}).strict();
|
|
738
|
+
var publishJobResponseSchema = z7.object({
|
|
739
|
+
// The BullMQ job id the dispatcher minted (or the dedupe id when supplied).
|
|
740
|
+
messageId: z7.string()
|
|
741
|
+
}).strict();
|
|
742
|
+
var cronExpressionSchema = z7.string().min(1).max(120);
|
|
743
|
+
function isKnownTimeZone(tz) {
|
|
744
|
+
try {
|
|
745
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
746
|
+
return true;
|
|
747
|
+
} catch {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function validateCronAndTz(cron, tz) {
|
|
752
|
+
const issues = [];
|
|
753
|
+
const trimmed = cron.trim();
|
|
754
|
+
const isMacro = trimmed.startsWith("@");
|
|
755
|
+
const fields = trimmed.split(/\s+/).filter(Boolean);
|
|
756
|
+
const shapeValid = isMacro || fields.length === CRON_FIELD_COUNT;
|
|
757
|
+
if (!shapeValid) {
|
|
758
|
+
issues.push({
|
|
759
|
+
path: "cron",
|
|
760
|
+
message: `cron must have exactly ${CRON_FIELD_COUNT} fields (minute hour day month weekday) or a macro (@hourly, @daily, @weekly, @monthly, @yearly); sub-minute schedules are not allowed`
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
const tzValid = tz === void 0 || isKnownTimeZone(tz);
|
|
764
|
+
if (!tzValid) {
|
|
765
|
+
issues.push({ path: "tz", message: `unknown IANA timezone: ${tz}` });
|
|
766
|
+
}
|
|
767
|
+
if (shapeValid && tzValid) {
|
|
768
|
+
try {
|
|
769
|
+
cronParser.parseExpression(trimmed, tz === void 0 ? void 0 : { tz }).next();
|
|
770
|
+
} catch {
|
|
771
|
+
issues.push({ path: "cron", message: `invalid cron expression: ${cron}` });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return issues;
|
|
775
|
+
}
|
|
776
|
+
var scheduleJobRequestSchema = z7.object({
|
|
777
|
+
...baseJobFields,
|
|
778
|
+
cron: cronExpressionSchema,
|
|
779
|
+
// IANA timezone the cron pattern is evaluated in. Defaults to UTC.
|
|
780
|
+
tz: z7.string().min(1).max(64).optional()
|
|
781
|
+
}).strict().superRefine((value, ctx) => {
|
|
782
|
+
for (const issue of validateCronAndTz(value.cron, value.tz)) {
|
|
783
|
+
ctx.addIssue({ code: "custom", path: [issue.path], message: issue.message });
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
var scheduleJobResponseSchema = z7.object({
|
|
787
|
+
// The scheduler id (unique per installation) the dispatcher upserted.
|
|
788
|
+
scheduleId: z7.string(),
|
|
789
|
+
name: z7.string(),
|
|
790
|
+
cron: z7.string(),
|
|
791
|
+
tz: z7.string()
|
|
792
|
+
}).strict();
|
|
793
|
+
var unscheduleJobRequestSchema = z7.object({
|
|
794
|
+
name: queueNameSchema
|
|
795
|
+
}).strict();
|
|
796
|
+
var unscheduleJobResponseSchema = z7.object({
|
|
797
|
+
name: z7.string(),
|
|
798
|
+
// True when a schedule with that name existed and was removed; false when
|
|
799
|
+
// nothing matched (the call stays idempotent either way).
|
|
800
|
+
removed: z7.boolean()
|
|
801
|
+
}).strict();
|
|
802
|
+
var scheduleSummarySchema = z7.object({
|
|
803
|
+
scheduleId: z7.string(),
|
|
804
|
+
name: z7.string(),
|
|
805
|
+
cron: z7.string(),
|
|
806
|
+
tz: z7.string(),
|
|
807
|
+
// Next planned fire time (ISO), null when BullMQ has not computed one yet.
|
|
808
|
+
next_run_at: z7.iso.datetime().nullable()
|
|
809
|
+
}).strict();
|
|
810
|
+
var listSchedulesResponseSchema = z7.object({
|
|
811
|
+
items: z7.array(scheduleSummarySchema)
|
|
812
|
+
}).strict();
|
|
813
|
+
|
|
565
814
|
// libs/shared/validators/src/lib/studio/studio-voice.ts
|
|
566
|
-
import { z as
|
|
815
|
+
import { z as z11 } from "zod";
|
|
567
816
|
|
|
568
817
|
// libs/shared/validators/src/lib/studio/memory-identities.ts
|
|
569
|
-
import { z as
|
|
570
|
-
var identityKeySchema =
|
|
571
|
-
var identityValueSchema =
|
|
572
|
-
var integrationIdentitiesSchema =
|
|
818
|
+
import { z as z8 } from "zod";
|
|
819
|
+
var identityKeySchema = z8.string().min(1).max(100);
|
|
820
|
+
var identityValueSchema = z8.string().min(1).max(500);
|
|
821
|
+
var integrationIdentitiesSchema = z8.unknown().superRefine((input, ctx) => {
|
|
573
822
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
574
823
|
ctx.addIssue({ code: "custom", message: "Identities must be a string map." });
|
|
575
824
|
return;
|
|
@@ -586,18 +835,45 @@ var integrationIdentitiesSchema = z6.unknown().superRefine((input, ctx) => {
|
|
|
586
835
|
});
|
|
587
836
|
}
|
|
588
837
|
}).transform((input) => Object.fromEntries(Object.entries(input)));
|
|
589
|
-
var memoryIdentitySnapshotSchema =
|
|
590
|
-
|
|
591
|
-
|
|
838
|
+
var memoryIdentitySnapshotSchema = z8.union([
|
|
839
|
+
z8.strictObject({ key: z8.null(), value: z8.null() }),
|
|
840
|
+
z8.strictObject({ key: identityKeySchema, value: identityValueSchema.nullable() })
|
|
592
841
|
]);
|
|
593
|
-
var memoryIdentityBindingSchema =
|
|
842
|
+
var memoryIdentityBindingSchema = z8.strictObject({
|
|
594
843
|
identityKey: identityKeySchema,
|
|
595
|
-
source:
|
|
596
|
-
fixedAt:
|
|
844
|
+
source: z8.enum(["code", "studio", "legacy"]),
|
|
845
|
+
fixedAt: z8.iso.datetime()
|
|
597
846
|
});
|
|
598
847
|
|
|
848
|
+
// libs/shared/validators/src/lib/studio/voice-greeting.ts
|
|
849
|
+
import { z as z9 } from "zod";
|
|
850
|
+
var VOICE_GREETING_MAX = 500;
|
|
851
|
+
var VOICE_CALL_VARIABLES_MAX = 20;
|
|
852
|
+
var VOICE_CALL_VARIABLE_VALUE_MAX = 200;
|
|
853
|
+
var VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
854
|
+
var voiceCallVariablesSchema = z9.unknown().superRefine((input, ctx) => {
|
|
855
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
856
|
+
ctx.addIssue({ code: "custom", message: "Call variables must be a string map." });
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const entries = Object.entries(input);
|
|
860
|
+
if (entries.length > VOICE_CALL_VARIABLES_MAX)
|
|
861
|
+
ctx.addIssue({
|
|
862
|
+
code: "custom",
|
|
863
|
+
message: `At most ${VOICE_CALL_VARIABLES_MAX} call variables are allowed.`
|
|
864
|
+
});
|
|
865
|
+
for (const [key, value] of entries) {
|
|
866
|
+
if (!VARIABLE_NAME.test(key) || typeof value !== "string" || value.trim() === "" || value.length > VOICE_CALL_VARIABLE_VALUE_MAX)
|
|
867
|
+
ctx.addIssue({
|
|
868
|
+
code: "custom",
|
|
869
|
+
path: [key],
|
|
870
|
+
message: `Call variable names are letters, digits and _ (not starting with a digit); values are 1\u2013${VOICE_CALL_VARIABLE_VALUE_MAX} characters.`
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}).transform((input) => Object.fromEntries(Object.entries(input)));
|
|
874
|
+
|
|
599
875
|
// libs/shared/validators/src/lib/studio/studio-rag.ts
|
|
600
|
-
import { z as
|
|
876
|
+
import { z as z10 } from "zod";
|
|
601
877
|
var STUDIO_RAG_DOCS_LIMIT_DEFAULT = 50;
|
|
602
878
|
var STUDIO_RAG_DOCS_LIMIT_MAX = 200;
|
|
603
879
|
var STUDIO_RAG_TOPK_DEFAULT = 10;
|
|
@@ -605,63 +881,63 @@ var STUDIO_RAG_TOPK_MAX = 50;
|
|
|
605
881
|
var STUDIO_RAG_JOBS_LIMIT_DEFAULT = 50;
|
|
606
882
|
var STUDIO_RAG_JOBS_LIMIT_MAX = 200;
|
|
607
883
|
var STUDIO_RAG_COLLECTION_REGEX = /^[A-Za-z0-9][A-Za-z0-9._\-/:]{0,127}$/;
|
|
608
|
-
var ragCollectionNameParamSchema =
|
|
609
|
-
var ragDistanceSchema =
|
|
610
|
-
var ragIndexKindSchema =
|
|
611
|
-
var ragNumberFormatSchema =
|
|
612
|
-
var ragRangeFieldSchema =
|
|
884
|
+
var ragCollectionNameParamSchema = z10.string().min(1).max(128).regex(STUDIO_RAG_COLLECTION_REGEX, "collection must be 1..128 ASCII identifier-safe chars");
|
|
885
|
+
var ragDistanceSchema = z10.enum(["cosine", "l2", "ip"]);
|
|
886
|
+
var ragIndexKindSchema = z10.enum(["hnsw", "exact"]);
|
|
887
|
+
var ragNumberFormatSchema = z10.enum(["eu", "us"]);
|
|
888
|
+
var ragRangeFieldSchema = z10.string().min(1).refine((field) => !field.includes("."), {
|
|
613
889
|
message: "field must be a literal top-level metadata key without dots"
|
|
614
890
|
});
|
|
615
|
-
var ragExtractFieldSchema =
|
|
616
|
-
name:
|
|
617
|
-
type:
|
|
618
|
-
removed:
|
|
891
|
+
var ragExtractFieldSchema = z10.object({
|
|
892
|
+
name: z10.string().trim().pipe(ragRangeFieldSchema),
|
|
893
|
+
type: z10.enum(["number", "date", "text"]),
|
|
894
|
+
removed: z10.boolean().optional()
|
|
619
895
|
});
|
|
620
896
|
var uniqueFieldNames = (fields) => new Set(fields.map((field) => field.name)).size === fields.length;
|
|
621
|
-
var ragExtractFieldsRequestSchema =
|
|
897
|
+
var ragExtractFieldsRequestSchema = z10.array(ragExtractFieldSchema).refine(uniqueFieldNames, {
|
|
622
898
|
message: "Field names must be unique"
|
|
623
899
|
});
|
|
624
|
-
var ragCollectionSchema =
|
|
625
|
-
name:
|
|
626
|
-
dimensions:
|
|
627
|
-
embedding_state:
|
|
900
|
+
var ragCollectionSchema = z10.object({
|
|
901
|
+
name: z10.string(),
|
|
902
|
+
dimensions: z10.number().int().min(1),
|
|
903
|
+
embedding_state: z10.enum(["ready", "rebuilding", "failed"]).optional(),
|
|
628
904
|
// Optional for boxes that predate collection-owned embeddings.
|
|
629
|
-
embedding_provider:
|
|
630
|
-
embedding_model:
|
|
905
|
+
embedding_provider: z10.string().min(1).optional(),
|
|
906
|
+
embedding_model: z10.string().min(1).optional(),
|
|
631
907
|
distance: ragDistanceSchema,
|
|
632
908
|
index_kind: ragIndexKindSchema,
|
|
633
|
-
chunk_count:
|
|
634
|
-
doc_count:
|
|
909
|
+
chunk_count: z10.number().int().nonnegative(),
|
|
910
|
+
doc_count: z10.number().int().nonnegative(),
|
|
635
911
|
// Maximum `created_at` across the collection's documents. Null when the
|
|
636
912
|
// collection has zero documents (an empty collection row created before its
|
|
637
913
|
// first ingest).
|
|
638
|
-
last_ingested_at:
|
|
914
|
+
last_ingested_at: z10.iso.datetime().nullable(),
|
|
639
915
|
// STA3-57. Optional so a list from a box older than hybrid search still parses.
|
|
640
|
-
hybrid_enabled:
|
|
916
|
+
hybrid_enabled: z10.boolean().optional(),
|
|
641
917
|
number_format: ragNumberFormatSchema.optional(),
|
|
642
|
-
extract_enabled:
|
|
643
|
-
extract_fields:
|
|
918
|
+
extract_enabled: z10.boolean().optional(),
|
|
919
|
+
extract_fields: z10.array(ragExtractFieldSchema).optional()
|
|
644
920
|
});
|
|
645
|
-
var ragCollectionListResponseSchema =
|
|
646
|
-
items:
|
|
921
|
+
var ragCollectionListResponseSchema = z10.object({
|
|
922
|
+
items: z10.array(ragCollectionSchema)
|
|
647
923
|
});
|
|
648
|
-
var ragJobStatusSchema =
|
|
649
|
-
var ragDocumentSchema =
|
|
650
|
-
id:
|
|
651
|
-
namespace:
|
|
924
|
+
var ragJobStatusSchema = z10.enum(["queued", "running", "succeeded", "failed", "cancelled"]);
|
|
925
|
+
var ragDocumentSchema = z10.object({
|
|
926
|
+
id: z10.string(),
|
|
927
|
+
namespace: z10.string(),
|
|
652
928
|
// Zero while the document is still ingesting (staged row, no chunks yet) or
|
|
653
929
|
// when its ingest run failed before writing any chunk. A ready document has
|
|
654
930
|
// at least one chunk.
|
|
655
|
-
chunks_count:
|
|
931
|
+
chunks_count: z10.number().int().nonnegative(),
|
|
656
932
|
// Document-level metadata copied onto the chunks at ingest time. We surface
|
|
657
933
|
// the document's `metadata` so the UI can render filterable tags without
|
|
658
934
|
// paging through chunks.
|
|
659
935
|
metadata: jsonObject(),
|
|
660
936
|
// Truncated content of the first chunk. Full body is one click away via
|
|
661
937
|
// the docs drawer in the component.
|
|
662
|
-
preview:
|
|
663
|
-
first_seen_at:
|
|
664
|
-
last_seen_at:
|
|
938
|
+
preview: z10.string(),
|
|
939
|
+
first_seen_at: z10.iso.datetime(),
|
|
940
|
+
last_seen_at: z10.iso.datetime(),
|
|
665
941
|
// Ingest status of the run that produced this document, when the document
|
|
666
942
|
// was uploaded through Studio's managed ingest (which fires a durable
|
|
667
943
|
// `rag-ingest` run). `null` for documents the agent wrote directly via
|
|
@@ -672,22 +948,22 @@ var ragDocumentSchema = z7.object({
|
|
|
672
948
|
// Id of the ingest run that produced this document, for deep-linking to the
|
|
673
949
|
// run details screen (`studio/runs/:runId`). `null`/absent when there is no
|
|
674
950
|
// tracked run (direct SDK ingest, or the cloud surface).
|
|
675
|
-
run_id:
|
|
951
|
+
run_id: z10.string().nullable().optional(),
|
|
676
952
|
// Error message copied off the ingest run when it failed, so the docs table
|
|
677
953
|
// can surface it inline without opening the run. `null`/absent otherwise.
|
|
678
|
-
ingest_error:
|
|
954
|
+
ingest_error: z10.string().nullable().optional(),
|
|
679
955
|
// STA3-8. Every version of a document is its own row; the list shows the
|
|
680
956
|
// published one, so `id` is that version's id. `version_count` counts every
|
|
681
957
|
// version, published or not. `pending_version` is the newest version past the
|
|
682
958
|
// published one whose ingest finished, the "v2 ready to publish" on the row;
|
|
683
959
|
// `null` when there is none. Optional, like the ingest fields above, so a box
|
|
684
960
|
// from before versioning still parses.
|
|
685
|
-
version:
|
|
686
|
-
version_count:
|
|
687
|
-
pending_version:
|
|
961
|
+
version: z10.number().int().min(1).optional(),
|
|
962
|
+
version_count: z10.number().int().min(1).optional(),
|
|
963
|
+
pending_version: z10.number().int().min(1).nullable().optional()
|
|
688
964
|
});
|
|
689
965
|
var ragFilterSchema = jsonObject();
|
|
690
|
-
var ragDateValueSchema =
|
|
966
|
+
var ragDateValueSchema = z10.string().length(10).regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/).refine((value) => {
|
|
691
967
|
const year = Number(value.slice(0, 4));
|
|
692
968
|
const month = Number(value.slice(5, 7));
|
|
693
969
|
const day = Number(value.slice(8, 10));
|
|
@@ -695,18 +971,18 @@ var ragDateValueSchema = z7.string().length(10).regex(/^[0-9]{4}-[0-9]{2}-[0-9]{
|
|
|
695
971
|
date.setUTCFullYear(year, month - 1, day);
|
|
696
972
|
return year >= 1 && year <= 9999 && date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
697
973
|
}, "date must be a real YYYY-MM-DD day in years 0001\u20139999");
|
|
698
|
-
var ragRangeSchema =
|
|
699
|
-
|
|
974
|
+
var ragRangeSchema = z10.discriminatedUnion("type", [
|
|
975
|
+
z10.strictObject({
|
|
700
976
|
field: ragRangeFieldSchema,
|
|
701
|
-
type:
|
|
702
|
-
gt:
|
|
703
|
-
gte:
|
|
704
|
-
lt:
|
|
705
|
-
lte:
|
|
977
|
+
type: z10.literal("number"),
|
|
978
|
+
gt: z10.number().finite().optional(),
|
|
979
|
+
gte: z10.number().finite().optional(),
|
|
980
|
+
lt: z10.number().finite().optional(),
|
|
981
|
+
lte: z10.number().finite().optional()
|
|
706
982
|
}),
|
|
707
|
-
|
|
983
|
+
z10.strictObject({
|
|
708
984
|
field: ragRangeFieldSchema,
|
|
709
|
-
type:
|
|
985
|
+
type: z10.literal("date"),
|
|
710
986
|
gt: ragDateValueSchema.optional(),
|
|
711
987
|
gte: ragDateValueSchema.optional(),
|
|
712
988
|
lt: ragDateValueSchema.optional(),
|
|
@@ -728,121 +1004,121 @@ var ragRangeSchema = z7.discriminatedUnion("type", [
|
|
|
728
1004
|
ctx.addIssue({ code: "custom", message: "lower bound must not exceed upper bound" });
|
|
729
1005
|
}
|
|
730
1006
|
});
|
|
731
|
-
var ragRangesSchema =
|
|
1007
|
+
var ragRangesSchema = z10.array(ragRangeSchema).max(10);
|
|
732
1008
|
var listRagDocsQuerySchema = forwardPageQuerySchema.extend({
|
|
733
1009
|
limit: limitParamSchema(STUDIO_RAG_DOCS_LIMIT_MAX).default(STUDIO_RAG_DOCS_LIMIT_DEFAULT),
|
|
734
1010
|
// Filter is sent JSON-encoded inside a single query param so the UI can
|
|
735
1011
|
// pass an arbitrarily nested object without colliding with the URL syntax.
|
|
736
1012
|
// Empty/absent => no filter.
|
|
737
|
-
filter:
|
|
1013
|
+
filter: z10.string().optional()
|
|
738
1014
|
});
|
|
739
1015
|
var ragDocumentListResponseSchema = forwardPageResponseSchema(ragDocumentSchema);
|
|
740
|
-
var ragDocumentVersionSchema =
|
|
741
|
-
id:
|
|
742
|
-
version:
|
|
743
|
-
is_active:
|
|
744
|
-
ready:
|
|
745
|
-
chunks_count:
|
|
1016
|
+
var ragDocumentVersionSchema = z10.object({
|
|
1017
|
+
id: z10.string(),
|
|
1018
|
+
version: z10.number().int().min(1),
|
|
1019
|
+
is_active: z10.boolean(),
|
|
1020
|
+
ready: z10.boolean(),
|
|
1021
|
+
chunks_count: z10.number().int().nonnegative(),
|
|
746
1022
|
ingest_status: ragJobStatusSchema.nullable(),
|
|
747
|
-
run_id:
|
|
748
|
-
ingest_error:
|
|
749
|
-
filename:
|
|
750
|
-
created_at:
|
|
1023
|
+
run_id: z10.string().nullable(),
|
|
1024
|
+
ingest_error: z10.string().nullable(),
|
|
1025
|
+
filename: z10.string().nullable(),
|
|
1026
|
+
created_at: z10.iso.datetime()
|
|
751
1027
|
});
|
|
752
|
-
var ragDocumentVersionListResponseSchema =
|
|
753
|
-
items:
|
|
1028
|
+
var ragDocumentVersionListResponseSchema = z10.object({
|
|
1029
|
+
items: z10.array(ragDocumentVersionSchema)
|
|
754
1030
|
});
|
|
755
|
-
var ragDocumentVersionNumberSchema =
|
|
756
|
-
var updateRagDocumentValuesRequestSchema =
|
|
1031
|
+
var ragDocumentVersionNumberSchema = z10.number().int().min(1).max(2147483647);
|
|
1032
|
+
var updateRagDocumentValuesRequestSchema = z10.strictObject({
|
|
757
1033
|
version: ragDocumentVersionNumberSchema.optional(),
|
|
758
1034
|
values: jsonObject(),
|
|
759
|
-
add_fields:
|
|
1035
|
+
add_fields: z10.array(ragExtractFieldSchema.omit({ removed: true })).refine(uniqueFieldNames, {
|
|
760
1036
|
message: "Field names must be unique"
|
|
761
1037
|
}).optional()
|
|
762
1038
|
});
|
|
763
|
-
var updateRagDocumentValuesResponseSchema =
|
|
764
|
-
id:
|
|
765
|
-
collection:
|
|
1039
|
+
var updateRagDocumentValuesResponseSchema = z10.object({
|
|
1040
|
+
id: z10.string(),
|
|
1041
|
+
collection: z10.string(),
|
|
766
1042
|
version: ragDocumentVersionNumberSchema,
|
|
767
1043
|
metadata: jsonObject()
|
|
768
1044
|
});
|
|
769
|
-
var extractRagDocumentRequestSchema =
|
|
1045
|
+
var extractRagDocumentRequestSchema = z10.strictObject({
|
|
770
1046
|
version: ragDocumentVersionNumberSchema.optional()
|
|
771
1047
|
});
|
|
772
|
-
var extractRagDocumentResponseSchema =
|
|
773
|
-
proposal:
|
|
1048
|
+
var extractRagDocumentResponseSchema = z10.object({
|
|
1049
|
+
proposal: z10.array(
|
|
774
1050
|
ragExtractFieldSchema.omit({ removed: true }).extend({
|
|
775
|
-
value:
|
|
776
|
-
new:
|
|
1051
|
+
value: z10.union([z10.number().finite(), z10.string()]),
|
|
1052
|
+
new: z10.boolean()
|
|
777
1053
|
})
|
|
778
1054
|
),
|
|
779
|
-
failed:
|
|
780
|
-
truncated:
|
|
781
|
-
});
|
|
782
|
-
var ragDocumentVersionParamSchema =
|
|
783
|
-
var activateRagDocumentVersionResponseSchema =
|
|
784
|
-
id:
|
|
785
|
-
collection:
|
|
786
|
-
version:
|
|
787
|
-
previous_version:
|
|
788
|
-
changed:
|
|
789
|
-
});
|
|
790
|
-
var ragQueryRequestSchema =
|
|
791
|
-
text:
|
|
792
|
-
embedding:
|
|
793
|
-
model:
|
|
794
|
-
topK:
|
|
1055
|
+
failed: z10.boolean(),
|
|
1056
|
+
truncated: z10.boolean()
|
|
1057
|
+
});
|
|
1058
|
+
var ragDocumentVersionParamSchema = z10.string().regex(/^\d+$/, { message: "version must be a positive integer" }).transform((value) => Number.parseInt(value, 10)).pipe(ragDocumentVersionNumberSchema);
|
|
1059
|
+
var activateRagDocumentVersionResponseSchema = z10.object({
|
|
1060
|
+
id: z10.string(),
|
|
1061
|
+
collection: z10.string(),
|
|
1062
|
+
version: z10.number().int().min(1),
|
|
1063
|
+
previous_version: z10.number().int().min(1).nullable(),
|
|
1064
|
+
changed: z10.boolean()
|
|
1065
|
+
});
|
|
1066
|
+
var ragQueryRequestSchema = z10.object({
|
|
1067
|
+
text: z10.string().min(1).optional(),
|
|
1068
|
+
embedding: z10.array(z10.number().finite()).min(1).optional(),
|
|
1069
|
+
model: z10.string().min(1).optional(),
|
|
1070
|
+
topK: z10.coerce.number().int().min(1).max(STUDIO_RAG_TOPK_MAX).default(STUDIO_RAG_TOPK_DEFAULT),
|
|
795
1071
|
filter: ragFilterSchema.optional(),
|
|
796
1072
|
ranges: ragRangesSchema.optional(),
|
|
797
|
-
preview:
|
|
798
|
-
hybrid:
|
|
1073
|
+
preview: z10.object({ docId: z10.string().min(1), version: ragDocumentVersionNumberSchema }).optional(),
|
|
1074
|
+
hybrid: z10.boolean().optional()
|
|
799
1075
|
}).refine((value) => value.embedding !== void 0 || value.text !== void 0, {
|
|
800
1076
|
message: "either `embedding` or `text` must be provided",
|
|
801
1077
|
path: ["embedding"]
|
|
802
1078
|
});
|
|
803
|
-
var ragQueryHitSchema =
|
|
804
|
-
id:
|
|
805
|
-
chunk_idx:
|
|
1079
|
+
var ragQueryHitSchema = z10.object({
|
|
1080
|
+
id: z10.string(),
|
|
1081
|
+
chunk_idx: z10.number().int().nonnegative(),
|
|
806
1082
|
// Cosine similarity in [-1, 1]. The query handler emits `1 - (embedding <=>
|
|
807
1083
|
// query)` over `stackbone_rag_chunks`, so higher is closer. We surface the
|
|
808
1084
|
// raw value; the UI formats it.
|
|
809
|
-
score:
|
|
1085
|
+
score: z10.number(),
|
|
810
1086
|
// The reciprocal rank fusion score a hybrid search ordered by (STA3-57 D10).
|
|
811
1087
|
// Absent when only the search by meaning ran, and from a box older than it.
|
|
812
|
-
mixed_score:
|
|
813
|
-
dense_rank:
|
|
814
|
-
lexical_rank:
|
|
815
|
-
content:
|
|
1088
|
+
mixed_score: z10.number().optional(),
|
|
1089
|
+
dense_rank: z10.number().int().positive().optional(),
|
|
1090
|
+
lexical_rank: z10.number().int().positive().optional(),
|
|
1091
|
+
content: z10.string(),
|
|
816
1092
|
metadata: jsonObject()
|
|
817
1093
|
});
|
|
818
|
-
var ragQueryResponseSchema =
|
|
819
|
-
items:
|
|
1094
|
+
var ragQueryResponseSchema = z10.object({
|
|
1095
|
+
items: z10.array(ragQueryHitSchema),
|
|
820
1096
|
// Echoes the dimensions used at retrieval time so the UI can flag a
|
|
821
1097
|
// dim-mismatch before it round-trips to a 4xx.
|
|
822
|
-
dimensions:
|
|
1098
|
+
dimensions: z10.number().int().min(1),
|
|
823
1099
|
// Provider-pinned model (`provider::model`) used to embed `text`. Null when
|
|
824
1100
|
// the caller passed `embedding` directly.
|
|
825
|
-
embedded_with:
|
|
1101
|
+
embedded_with: z10.string().nullable()
|
|
826
1102
|
});
|
|
827
|
-
var ragJobSchema =
|
|
828
|
-
id:
|
|
829
|
-
collection:
|
|
1103
|
+
var ragJobSchema = z10.object({
|
|
1104
|
+
id: z10.string(),
|
|
1105
|
+
collection: z10.string(),
|
|
830
1106
|
status: ragJobStatusSchema,
|
|
831
|
-
attempts:
|
|
832
|
-
error:
|
|
833
|
-
created_at:
|
|
834
|
-
finished_at:
|
|
1107
|
+
attempts: z10.number().int().nonnegative(),
|
|
1108
|
+
error: z10.string().nullable(),
|
|
1109
|
+
created_at: z10.iso.datetime(),
|
|
1110
|
+
finished_at: z10.iso.datetime().nullable()
|
|
835
1111
|
});
|
|
836
1112
|
var listRagJobsQuerySchema = forwardPageQuerySchema.extend({
|
|
837
1113
|
collection: ragCollectionNameParamSchema.optional(),
|
|
838
1114
|
limit: limitParamSchema(STUDIO_RAG_JOBS_LIMIT_MAX).default(STUDIO_RAG_JOBS_LIMIT_DEFAULT)
|
|
839
1115
|
});
|
|
840
1116
|
var ragJobListResponseSchema = forwardPageResponseSchema(ragJobSchema);
|
|
841
|
-
var ragJobIdParamSchema =
|
|
842
|
-
jobId:
|
|
1117
|
+
var ragJobIdParamSchema = z10.object({
|
|
1118
|
+
jobId: z10.string().min(1).max(128)
|
|
843
1119
|
});
|
|
844
|
-
var ragJobActionResponseSchema =
|
|
845
|
-
id:
|
|
1120
|
+
var ragJobActionResponseSchema = z10.object({
|
|
1121
|
+
id: z10.string(),
|
|
846
1122
|
status: ragJobStatusSchema
|
|
847
1123
|
});
|
|
848
1124
|
var STUDIO_RAG_SUPPORTED_CONTENT_TYPES = [
|
|
@@ -850,16 +1126,16 @@ var STUDIO_RAG_SUPPORTED_CONTENT_TYPES = [
|
|
|
850
1126
|
"text/markdown",
|
|
851
1127
|
"application/pdf"
|
|
852
1128
|
];
|
|
853
|
-
var ragSupportedContentTypeSchema =
|
|
854
|
-
var createRagCollectionRequestSchema =
|
|
1129
|
+
var ragSupportedContentTypeSchema = z10.enum(STUDIO_RAG_SUPPORTED_CONTENT_TYPES);
|
|
1130
|
+
var createRagCollectionRequestSchema = z10.object({
|
|
855
1131
|
name: ragCollectionNameParamSchema,
|
|
856
1132
|
metadata: jsonObject().optional(),
|
|
857
1133
|
embedding_provider: pickedModelProviderIdSchema.optional(),
|
|
858
|
-
embedding_model:
|
|
1134
|
+
embedding_model: z10.string().trim().min(1).optional(),
|
|
859
1135
|
// Omitted, the box creates the collection with hybrid search off and `eu`.
|
|
860
|
-
hybrid_enabled:
|
|
1136
|
+
hybrid_enabled: z10.boolean().optional(),
|
|
861
1137
|
number_format: ragNumberFormatSchema.optional(),
|
|
862
|
-
extract_enabled:
|
|
1138
|
+
extract_enabled: z10.boolean().optional(),
|
|
863
1139
|
extract_fields: ragExtractFieldsRequestSchema.optional()
|
|
864
1140
|
}).refine(
|
|
865
1141
|
(value) => value.embedding_provider === void 0 === (value.embedding_model === void 0),
|
|
@@ -868,78 +1144,78 @@ var createRagCollectionRequestSchema = z7.object({
|
|
|
868
1144
|
path: ["embedding_model"]
|
|
869
1145
|
}
|
|
870
1146
|
);
|
|
871
|
-
var updateRagCollectionRequestSchema =
|
|
872
|
-
hybrid_enabled:
|
|
1147
|
+
var updateRagCollectionRequestSchema = z10.strictObject({
|
|
1148
|
+
hybrid_enabled: z10.boolean().optional(),
|
|
873
1149
|
number_format: ragNumberFormatSchema.optional(),
|
|
874
|
-
extract_enabled:
|
|
1150
|
+
extract_enabled: z10.boolean().optional(),
|
|
875
1151
|
extract_fields: ragExtractFieldsRequestSchema.optional()
|
|
876
1152
|
}).refine((value) => Object.values(value).some((setting) => setting !== void 0), {
|
|
877
1153
|
message: "Send at least one collection setting"
|
|
878
1154
|
});
|
|
879
|
-
var updateRagCollectionResponseSchema =
|
|
880
|
-
name:
|
|
881
|
-
hybrid_enabled:
|
|
1155
|
+
var updateRagCollectionResponseSchema = z10.object({
|
|
1156
|
+
name: z10.string(),
|
|
1157
|
+
hybrid_enabled: z10.boolean(),
|
|
882
1158
|
number_format: ragNumberFormatSchema,
|
|
883
|
-
extract_enabled:
|
|
884
|
-
extract_fields:
|
|
885
|
-
unconverted:
|
|
1159
|
+
extract_enabled: z10.boolean().optional(),
|
|
1160
|
+
extract_fields: z10.array(ragExtractFieldSchema).optional(),
|
|
1161
|
+
unconverted: z10.number().int().nonnegative().optional()
|
|
886
1162
|
});
|
|
887
|
-
var deleteRagCollectionResponseSchema =
|
|
888
|
-
name:
|
|
889
|
-
deleted:
|
|
1163
|
+
var deleteRagCollectionResponseSchema = z10.object({
|
|
1164
|
+
name: z10.string(),
|
|
1165
|
+
deleted: z10.literal(true)
|
|
890
1166
|
});
|
|
891
|
-
var uploadRagDocumentRequestSchema =
|
|
1167
|
+
var uploadRagDocumentRequestSchema = z10.object({
|
|
892
1168
|
collection: ragCollectionNameParamSchema,
|
|
893
|
-
filename:
|
|
1169
|
+
filename: z10.string().min(1).max(255),
|
|
894
1170
|
content_type: ragSupportedContentTypeSchema,
|
|
895
1171
|
metadata: jsonObject().optional(),
|
|
896
|
-
replaces:
|
|
1172
|
+
replaces: z10.string().min(1).optional()
|
|
897
1173
|
});
|
|
898
|
-
var uploadRagDocumentResponseSchema =
|
|
899
|
-
job_id:
|
|
1174
|
+
var uploadRagDocumentResponseSchema = z10.object({
|
|
1175
|
+
job_id: z10.string(),
|
|
900
1176
|
status: ragJobStatusSchema,
|
|
901
|
-
version:
|
|
1177
|
+
version: z10.number().int().min(1)
|
|
902
1178
|
});
|
|
903
|
-
var deleteRagDocumentResponseSchema =
|
|
904
|
-
id:
|
|
905
|
-
collection:
|
|
906
|
-
deleted:
|
|
1179
|
+
var deleteRagDocumentResponseSchema = z10.object({
|
|
1180
|
+
id: z10.string(),
|
|
1181
|
+
collection: z10.string(),
|
|
1182
|
+
deleted: z10.literal(true)
|
|
907
1183
|
});
|
|
908
|
-
var deleteRagDocumentVersionResponseSchema =
|
|
909
|
-
id:
|
|
910
|
-
collection:
|
|
1184
|
+
var deleteRagDocumentVersionResponseSchema = z10.object({
|
|
1185
|
+
id: z10.string(),
|
|
1186
|
+
collection: z10.string(),
|
|
911
1187
|
version: ragDocumentVersionNumberSchema,
|
|
912
|
-
deleted:
|
|
1188
|
+
deleted: z10.literal(true)
|
|
913
1189
|
});
|
|
914
|
-
var downloadRagDocumentResponseSchema =
|
|
915
|
-
url:
|
|
916
|
-
filename:
|
|
917
|
-
content_type:
|
|
1190
|
+
var downloadRagDocumentResponseSchema = z10.object({
|
|
1191
|
+
url: z10.string(),
|
|
1192
|
+
filename: z10.string(),
|
|
1193
|
+
content_type: z10.string()
|
|
918
1194
|
});
|
|
919
|
-
var changeRagEmbeddingRequestSchema =
|
|
1195
|
+
var changeRagEmbeddingRequestSchema = z10.object({
|
|
920
1196
|
provider: pickedModelProviderIdSchema,
|
|
921
|
-
model:
|
|
922
|
-
});
|
|
923
|
-
var retryRagRebuildRequestSchema =
|
|
924
|
-
var ragEmbeddingIdentitySchema =
|
|
925
|
-
provider:
|
|
926
|
-
model:
|
|
927
|
-
dimension:
|
|
928
|
-
});
|
|
929
|
-
var ragRebuildStatusSchema =
|
|
930
|
-
id:
|
|
931
|
-
collection:
|
|
932
|
-
attempt:
|
|
933
|
-
status:
|
|
1197
|
+
model: z10.string().trim().min(1)
|
|
1198
|
+
});
|
|
1199
|
+
var retryRagRebuildRequestSchema = z10.object({ id: z10.uuid() });
|
|
1200
|
+
var ragEmbeddingIdentitySchema = z10.object({
|
|
1201
|
+
provider: z10.string().min(1),
|
|
1202
|
+
model: z10.string().min(1),
|
|
1203
|
+
dimension: z10.number().int().positive()
|
|
1204
|
+
});
|
|
1205
|
+
var ragRebuildStatusSchema = z10.object({
|
|
1206
|
+
id: z10.uuid(),
|
|
1207
|
+
collection: z10.string(),
|
|
1208
|
+
attempt: z10.number().int().positive(),
|
|
1209
|
+
status: z10.enum(["queued", "running", "failed", "interrupted", "complete"]),
|
|
934
1210
|
current: ragEmbeddingIdentitySchema,
|
|
935
1211
|
target: ragEmbeddingIdentitySchema,
|
|
936
|
-
completed_versions:
|
|
937
|
-
total_versions:
|
|
938
|
-
run_id:
|
|
939
|
-
world_run_id:
|
|
940
|
-
error:
|
|
1212
|
+
completed_versions: z10.number().int().nonnegative(),
|
|
1213
|
+
total_versions: z10.number().int().nonnegative(),
|
|
1214
|
+
run_id: z10.string().nullable(),
|
|
1215
|
+
world_run_id: z10.string().nullable(),
|
|
1216
|
+
error: z10.string().nullable()
|
|
941
1217
|
});
|
|
942
|
-
var ragRebuildStatusResponseSchema =
|
|
1218
|
+
var ragRebuildStatusResponseSchema = z10.object({
|
|
943
1219
|
rebuild: ragRebuildStatusSchema.nullable()
|
|
944
1220
|
});
|
|
945
1221
|
|
|
@@ -963,62 +1239,81 @@ var VOICE_BACKGROUND_CLIPS = [
|
|
|
963
1239
|
];
|
|
964
1240
|
var VOICE_AMBIENT_SOUNDS = ["none", ...VOICE_BACKGROUND_CLIPS];
|
|
965
1241
|
var VOICE_THINKING_SOUNDS = ["none", "chime", ...VOICE_BACKGROUND_CLIPS];
|
|
966
|
-
var thresholdSchema =
|
|
967
|
-
var millisecondsSchema =
|
|
968
|
-
var keytermsSchema =
|
|
969
|
-
var vadSchema =
|
|
1242
|
+
var thresholdSchema = z11.number().min(0).max(1);
|
|
1243
|
+
var millisecondsSchema = z11.int().min(0).max(1e4);
|
|
1244
|
+
var keytermsSchema = z11.array(z11.string().trim().min(1).max(64)).max(50);
|
|
1245
|
+
var vadSchema = z11.strictObject({
|
|
970
1246
|
activationThreshold: thresholdSchema,
|
|
971
1247
|
minSpeechMs: millisecondsSchema,
|
|
972
1248
|
minSilenceMs: millisecondsSchema
|
|
973
1249
|
});
|
|
974
|
-
var turnSchema =
|
|
975
|
-
mode:
|
|
1250
|
+
var turnSchema = z11.strictObject({
|
|
1251
|
+
mode: z11.enum(["stt", "livekit"]),
|
|
976
1252
|
minEndpointingDelayMs: millisecondsSchema,
|
|
977
1253
|
maxEndpointingDelayMs: millisecondsSchema,
|
|
978
1254
|
detectorThreshold: thresholdSchema,
|
|
979
1255
|
// Flux refuses under 0.3 and above its own end-of-turn threshold (0.7 by default,
|
|
980
1256
|
// not a setting), so the bound is the range Flux takes with that default.
|
|
981
|
-
eagerThreshold:
|
|
1257
|
+
eagerThreshold: z11.number().min(0.3).max(0.7)
|
|
982
1258
|
});
|
|
983
1259
|
var VOICE_EAGER_THRESHOLD_DEFAULT = 0.3;
|
|
984
|
-
var
|
|
985
|
-
|
|
1260
|
+
var VOICE_SPEAK_FIRST_CLAUSE_DEFAULT = false;
|
|
1261
|
+
var timeZoneSchema = z11.string().max(64).refine(isKnownTimeZone, { message: "timeZone must be an IANA zone such as Europe/Madrid" }).nullable();
|
|
1262
|
+
var VOICE_TIME_ZONE_DEFAULT = null;
|
|
1263
|
+
var VOICE_NOISE_CANCELLATIONS = ["background", "standard", "off"];
|
|
1264
|
+
var noiseCancellationSchema = z11.enum(VOICE_NOISE_CANCELLATIONS);
|
|
1265
|
+
var VOICE_NOISE_CANCELLATION_DEFAULT = "background";
|
|
1266
|
+
var VOICE_INTERRUPTION_MODES = ["adaptive", "vad"];
|
|
1267
|
+
var VOICE_INTERRUPTION_MODE_DEFAULT = "adaptive";
|
|
1268
|
+
var interruptionsSchema = z11.strictObject({
|
|
1269
|
+
enabled: z11.boolean(),
|
|
1270
|
+
mode: z11.enum(VOICE_INTERRUPTION_MODES),
|
|
986
1271
|
minDurationMs: millisecondsSchema,
|
|
987
|
-
minWords:
|
|
1272
|
+
minWords: z11.int().min(0).max(20)
|
|
988
1273
|
});
|
|
989
|
-
var ragSchema =
|
|
1274
|
+
var ragSchema = z11.strictObject({
|
|
990
1275
|
collection: ragCollectionNameParamSchema.nullable(),
|
|
991
|
-
minScore:
|
|
992
|
-
minWords:
|
|
993
|
-
topK:
|
|
994
|
-
timeoutMs:
|
|
995
|
-
previousExchanges:
|
|
996
|
-
});
|
|
997
|
-
var volumeSchema =
|
|
998
|
-
var backgroundAudioSchema =
|
|
999
|
-
ambientSound:
|
|
1276
|
+
minScore: z11.number().min(0).max(1),
|
|
1277
|
+
minWords: z11.int().min(0).max(20),
|
|
1278
|
+
topK: z11.int().min(1).max(10),
|
|
1279
|
+
timeoutMs: z11.int().min(200).max(1e4),
|
|
1280
|
+
previousExchanges: z11.int().min(0).max(5)
|
|
1281
|
+
});
|
|
1282
|
+
var volumeSchema = z11.number().min(0).max(1);
|
|
1283
|
+
var backgroundAudioSchema = z11.strictObject({
|
|
1284
|
+
ambientSound: z11.enum(VOICE_AMBIENT_SOUNDS),
|
|
1000
1285
|
ambientVolume: volumeSchema,
|
|
1001
|
-
thinkingSound:
|
|
1286
|
+
thinkingSound: z11.enum(VOICE_THINKING_SOUNDS),
|
|
1002
1287
|
thinkingVolume: volumeSchema,
|
|
1003
1288
|
/**
|
|
1004
1289
|
* How long a reply's wait runs before the thinking sound starts, when no box tool
|
|
1005
|
-
* call is open
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1290
|
+
* call is open. A box tool starts it at once, or, after a holding line, 300 ms after
|
|
1291
|
+
* the line has played out (at once when its TTS is lost, at most
|
|
1292
|
+
* `holdingLineMaxWaitMs` after the tool start), so the caller hears the line first
|
|
1293
|
+
* (STA3-99). Every turn waits for the knowledge search and the model's first token,
|
|
1294
|
+
* about 1.4 s at the median on a voice box (2026-11-09), so a delay under that plays
|
|
1295
|
+
* the sound on nearly every reply.
|
|
1296
|
+
*/
|
|
1297
|
+
thinkingDelayMs: millisecondsSchema,
|
|
1298
|
+
/**
|
|
1299
|
+
* The cap on the thinking sound's wait for a holding line to play out, from the tool
|
|
1300
|
+
* start (STA3-99): a long line, or a TTS that never reports its end, must not keep
|
|
1301
|
+
* the caller in silence while the tool runs.
|
|
1008
1302
|
*/
|
|
1009
|
-
|
|
1303
|
+
holdingLineMaxWaitMs: millisecondsSchema
|
|
1010
1304
|
});
|
|
1011
1305
|
var VOICE_BACKGROUND_AUDIO_DEFAULTS = Object.freeze({
|
|
1012
1306
|
ambientSound: "office-ambience",
|
|
1013
1307
|
ambientVolume: 0.7,
|
|
1014
1308
|
thinkingSound: "keyboard-typing",
|
|
1015
1309
|
thinkingVolume: 0.6,
|
|
1016
|
-
thinkingDelayMs: 2e3
|
|
1310
|
+
thinkingDelayMs: 2e3,
|
|
1311
|
+
holdingLineMaxWaitMs: 1e4
|
|
1017
1312
|
});
|
|
1018
|
-
var effectiveVoiceSettingsObjectSchema =
|
|
1313
|
+
var effectiveVoiceSettingsObjectSchema = z11.strictObject({
|
|
1019
1314
|
/** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */
|
|
1020
|
-
language:
|
|
1021
|
-
sttModel:
|
|
1315
|
+
language: z11.string().max(16).regex(/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/),
|
|
1316
|
+
sttModel: z11.enum(VOICE_STT_MODELS),
|
|
1022
1317
|
keyterms: keytermsSchema.default(() => []),
|
|
1023
1318
|
vad: vadSchema,
|
|
1024
1319
|
// Defaulted here, on the read side only: a box older than STA3-94 answers, and
|
|
@@ -1027,32 +1322,57 @@ var effectiveVoiceSettingsObjectSchema = z8.strictObject({
|
|
|
1027
1322
|
turn: turnSchema.extend({
|
|
1028
1323
|
eagerThreshold: turnSchema.shape.eagerThreshold.default(VOICE_EAGER_THRESHOLD_DEFAULT)
|
|
1029
1324
|
}),
|
|
1030
|
-
|
|
1031
|
-
|
|
1325
|
+
// Read side only, like `turn.eagerThreshold`: a box older than the setting answers,
|
|
1326
|
+
// and dispatches, a group without it.
|
|
1327
|
+
interruptions: interruptionsSchema.extend({
|
|
1328
|
+
mode: interruptionsSchema.shape.mode.default(VOICE_INTERRUPTION_MODE_DEFAULT)
|
|
1329
|
+
}),
|
|
1330
|
+
/** The filter on the caller's track (`VOICE_NOISE_CANCELLATIONS`). */
|
|
1331
|
+
// Read side only, like `speakFirstClause`.
|
|
1332
|
+
noiseCancellation: noiseCancellationSchema.default(VOICE_NOISE_CANCELLATION_DEFAULT),
|
|
1333
|
+
ttsModel: z11.enum(VOICE_TTS_MODELS),
|
|
1032
1334
|
/**
|
|
1033
1335
|
* The provider's own voice id (a Cartesia UUID, an ElevenLabs id, a Deepgram name),
|
|
1034
1336
|
* so only its outline is checked: no surrounding whitespace, no control characters.
|
|
1035
1337
|
*/
|
|
1036
|
-
ttsVoice:
|
|
1338
|
+
ttsVoice: z11.string().max(128).regex(/^[^\s\p{Cc}](?:[^\p{Cc}]*[^\s\p{Cc}])?$/u),
|
|
1037
1339
|
/** Cartesia only (D18): another model keeps its own pace, so it takes only 1. */
|
|
1038
|
-
ttsSpeed:
|
|
1340
|
+
ttsSpeed: z11.number().min(0.5).max(2),
|
|
1039
1341
|
/**
|
|
1040
1342
|
* What the agent says when the call connects. `null`: it waits for the caller. A
|
|
1041
1343
|
* blank string is refused rather than read as either: it would be an empty
|
|
1042
|
-
* utterance, not a wait.
|
|
1344
|
+
* utterance, not a wait. It may hold `{{name}}` placeholders, filled per call from
|
|
1345
|
+
* the call's variables (`renderVoiceGreeting`).
|
|
1043
1346
|
*/
|
|
1044
|
-
greeting:
|
|
1347
|
+
greeting: z11.string().trim().min(1).max(VOICE_GREETING_MAX).nullable(),
|
|
1045
1348
|
/** Band-pass the agent's voice to 300-3400 Hz so it sounds like a phone line (D5). */
|
|
1046
|
-
phoneLine:
|
|
1349
|
+
phoneLine: z11.boolean(),
|
|
1047
1350
|
/** LiveKit expressive mode: the turn gets Cartesia's markup guide (D5). Cartesia only. */
|
|
1048
|
-
expressive:
|
|
1049
|
-
|
|
1351
|
+
expressive: z11.boolean(),
|
|
1352
|
+
/**
|
|
1353
|
+
* The reply's first clause goes to the TTS at its first comma or sentence end,
|
|
1354
|
+
* instead of when the next sentence starts or the reply ends: LiveKit's sentence
|
|
1355
|
+
* tokenizer holds a one-sentence reply until the model has finished it, 550 to 790 ms
|
|
1356
|
+
* of silence on a model that writes 25 to 45 tokens/s (2026-11-11). The clause is its
|
|
1357
|
+
* own TTS segment, so the intonation can change at that pause.
|
|
1358
|
+
*/
|
|
1359
|
+
// Defaulted on the read side only, like `turn.eagerThreshold`: a box older than the
|
|
1360
|
+
// setting answers, and dispatches, settings without it. The layer schema drops the
|
|
1361
|
+
// default, or every layer would declare it.
|
|
1362
|
+
speakFirstClause: z11.boolean().default(VOICE_SPEAK_FIRST_CLAUSE_DEFAULT),
|
|
1363
|
+
// Read side only, like `turn.eagerThreshold`: a box older than STA3-99 answers a
|
|
1364
|
+
// group without it.
|
|
1365
|
+
backgroundAudio: backgroundAudioSchema.extend({
|
|
1366
|
+
holdingLineMaxWaitMs: backgroundAudioSchema.shape.holdingLineMaxWaitMs.default(
|
|
1367
|
+
VOICE_BACKGROUND_AUDIO_DEFAULTS.holdingLineMaxWaitMs
|
|
1368
|
+
)
|
|
1369
|
+
}),
|
|
1050
1370
|
/**
|
|
1051
1371
|
* Whether the box records the call to its bucket (D30). Off, no egress starts: a
|
|
1052
1372
|
* box may not be allowed to keep a caller's audio. The box alone reads it, so the
|
|
1053
1373
|
* dispatch to the worker leaves it out (`callVoiceSettingsSchema`).
|
|
1054
1374
|
*/
|
|
1055
|
-
recording:
|
|
1375
|
+
recording: z11.boolean(),
|
|
1056
1376
|
/** The box runs the search before the model's turn, so the worker never reads it. */
|
|
1057
1377
|
rag: ragSchema,
|
|
1058
1378
|
/**
|
|
@@ -1062,7 +1382,16 @@ var effectiveVoiceSettingsObjectSchema = z8.strictObject({
|
|
|
1062
1382
|
* The box answers the turn itself, so the worker never reads it. Not nullable: a
|
|
1063
1383
|
* blocked turn always says something.
|
|
1064
1384
|
*/
|
|
1065
|
-
refusalPhrase:
|
|
1385
|
+
refusalPhrase: z11.string().trim().min(1).max(300),
|
|
1386
|
+
/**
|
|
1387
|
+
* The zone of the date every model call of a call is told as today. The agent's
|
|
1388
|
+
* prompt is rendered once when its graph is built and kept for days, so a date
|
|
1389
|
+
* written into it goes stale, and a `today` tool costs the caller a second model
|
|
1390
|
+
* call. The box writes the date into the turn itself, so the worker never reads it.
|
|
1391
|
+
*/
|
|
1392
|
+
// Defaulted on the read side only, like `speakFirstClause`: a box older than the
|
|
1393
|
+
// setting answers settings without it.
|
|
1394
|
+
timeZone: timeZoneSchema.default(VOICE_TIME_ZONE_DEFAULT)
|
|
1066
1395
|
});
|
|
1067
1396
|
var checkCrossFieldRules = (value, ctx) => {
|
|
1068
1397
|
const min = value.turn?.minEndpointingDelayMs;
|
|
@@ -1093,10 +1422,12 @@ var effectiveVoiceSettingsSchema = effectiveVoiceSettingsObjectSchema.superRefin
|
|
|
1093
1422
|
var callVoiceSettingsObjectSchema = effectiveVoiceSettingsObjectSchema.omit({
|
|
1094
1423
|
recording: true,
|
|
1095
1424
|
rag: true,
|
|
1096
|
-
refusalPhrase: true
|
|
1425
|
+
refusalPhrase: true,
|
|
1426
|
+
timeZone: true
|
|
1097
1427
|
}).extend({
|
|
1098
|
-
// A box older than `thinkingDelayMs` dispatches the group
|
|
1099
|
-
|
|
1428
|
+
// A box older than `thinkingDelayMs` or `holdingLineMaxWaitMs` dispatches the group
|
|
1429
|
+
// without it; the effective schema already defaults the second.
|
|
1430
|
+
backgroundAudio: effectiveVoiceSettingsObjectSchema.shape.backgroundAudio.extend({
|
|
1100
1431
|
thinkingDelayMs: millisecondsSchema.default(
|
|
1101
1432
|
VOICE_BACKGROUND_AUDIO_DEFAULTS.thinkingDelayMs
|
|
1102
1433
|
)
|
|
@@ -1116,19 +1447,23 @@ var VOICE_SETTINGS_LEAF_PATHS = [
|
|
|
1116
1447
|
"turn.detectorThreshold",
|
|
1117
1448
|
"turn.eagerThreshold",
|
|
1118
1449
|
"interruptions.enabled",
|
|
1450
|
+
"interruptions.mode",
|
|
1119
1451
|
"interruptions.minDurationMs",
|
|
1120
1452
|
"interruptions.minWords",
|
|
1453
|
+
"noiseCancellation",
|
|
1121
1454
|
"ttsModel",
|
|
1122
1455
|
"ttsVoice",
|
|
1123
1456
|
"ttsSpeed",
|
|
1124
1457
|
"greeting",
|
|
1125
1458
|
"phoneLine",
|
|
1126
1459
|
"expressive",
|
|
1460
|
+
"speakFirstClause",
|
|
1127
1461
|
"backgroundAudio.ambientSound",
|
|
1128
1462
|
"backgroundAudio.ambientVolume",
|
|
1129
1463
|
"backgroundAudio.thinkingSound",
|
|
1130
1464
|
"backgroundAudio.thinkingVolume",
|
|
1131
1465
|
"backgroundAudio.thinkingDelayMs",
|
|
1466
|
+
"backgroundAudio.holdingLineMaxWaitMs",
|
|
1132
1467
|
"recording",
|
|
1133
1468
|
"rag.collection",
|
|
1134
1469
|
"rag.minScore",
|
|
@@ -1136,15 +1471,21 @@ var VOICE_SETTINGS_LEAF_PATHS = [
|
|
|
1136
1471
|
"rag.topK",
|
|
1137
1472
|
"rag.timeoutMs",
|
|
1138
1473
|
"rag.previousExchanges",
|
|
1139
|
-
"refusalPhrase"
|
|
1474
|
+
"refusalPhrase",
|
|
1475
|
+
"timeZone"
|
|
1140
1476
|
];
|
|
1141
|
-
var voiceSettingOriginSchema =
|
|
1477
|
+
var voiceSettingOriginSchema = z11.enum(["studio", "code", "default"]);
|
|
1142
1478
|
var LEAVES_NEWER_THAN_SOME_BOXES = /* @__PURE__ */ new Set([
|
|
1143
|
-
"turn.eagerThreshold"
|
|
1479
|
+
"turn.eagerThreshold",
|
|
1480
|
+
"interruptions.mode",
|
|
1481
|
+
"noiseCancellation",
|
|
1482
|
+
"speakFirstClause",
|
|
1483
|
+
"backgroundAudio.holdingLineMaxWaitMs",
|
|
1484
|
+
"timeZone"
|
|
1144
1485
|
]);
|
|
1145
|
-
var effectiveVoiceSettingsViewSchema =
|
|
1486
|
+
var effectiveVoiceSettingsViewSchema = z11.strictObject({
|
|
1146
1487
|
value: effectiveVoiceSettingsObjectSchema,
|
|
1147
|
-
origins:
|
|
1488
|
+
origins: z11.strictObject(
|
|
1148
1489
|
Object.fromEntries(
|
|
1149
1490
|
// A leaf newer than the box answering does not come back: that box runs its default.
|
|
1150
1491
|
// Every other leaf stays required, so a box that drops one is still caught.
|
|
@@ -1166,6 +1507,9 @@ var checkTtsPair = (value, ctx) => {
|
|
|
1166
1507
|
};
|
|
1167
1508
|
var voiceSettingsSchema = effectiveVoiceSettingsObjectSchema.extend({
|
|
1168
1509
|
keyterms: keytermsSchema,
|
|
1510
|
+
noiseCancellation: noiseCancellationSchema,
|
|
1511
|
+
speakFirstClause: z11.boolean(),
|
|
1512
|
+
timeZone: timeZoneSchema,
|
|
1169
1513
|
vad: vadSchema.partial(),
|
|
1170
1514
|
turn: turnSchema.partial(),
|
|
1171
1515
|
interruptions: interruptionsSchema.partial(),
|
|
@@ -1183,11 +1527,11 @@ var checkStudioTtsPair = (value, ctx) => {
|
|
|
1183
1527
|
};
|
|
1184
1528
|
var studioVoiceSettingsPatchSchema = voiceSettingsSchema.superRefine(checkStudioTtsPair);
|
|
1185
1529
|
var voiceSettingsResponseSchema = effectiveVoiceSettingsViewSchema.extend({
|
|
1186
|
-
issue:
|
|
1530
|
+
issue: z11.string().nullable()
|
|
1187
1531
|
});
|
|
1188
|
-
var deepAgentNameSchema =
|
|
1189
|
-
var voiceCallIdSchema =
|
|
1190
|
-
var voiceTagsSchema =
|
|
1532
|
+
var deepAgentNameSchema = z11.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/);
|
|
1533
|
+
var voiceCallIdSchema = z11.string().min(1).max(RUN_TAG_VALUE_MAX);
|
|
1534
|
+
var voiceTagsSchema = z11.record(runTagKeySchema, runTagValueSchema).refine((tags) => Object.keys(tags).length <= RUN_TAGS_MAX, {
|
|
1191
1535
|
message: `A call carries at most ${RUN_TAGS_MAX} tags.`
|
|
1192
1536
|
});
|
|
1193
1537
|
var VOICE_TTS_MARKUP_CONTEXT = "voice-tts-markup";
|
|
@@ -1201,7 +1545,7 @@ var isVoiceBoxUrl = (raw) => {
|
|
|
1201
1545
|
}
|
|
1202
1546
|
};
|
|
1203
1547
|
var VOICE_BOX_URL_MAX = 2048;
|
|
1204
|
-
var voiceBoxUrlSchema =
|
|
1548
|
+
var voiceBoxUrlSchema = z11.string().max(VOICE_BOX_URL_MAX).refine(isVoiceBoxUrl, {
|
|
1205
1549
|
message: "boxUrl must be an absolute http(s) URL with no user part, query or fragment."
|
|
1206
1550
|
});
|
|
1207
1551
|
var VOICE_TRACES_PATH = "/voice/v1/traces";
|
|
@@ -1210,7 +1554,7 @@ var VOICE_WORKER_AGENT_NAME_ENV = "STACKBONE_VOICE_AGENT_NAME";
|
|
|
1210
1554
|
var VOICE_WORKER_PORT_ENV = "STACKBONE_VOICE_WORKER_PORT";
|
|
1211
1555
|
var VOICE_TURN_METRICS_MESSAGE = "voice turn metrics";
|
|
1212
1556
|
var VOICE_CALL_TOKEN_MAX = 2048;
|
|
1213
|
-
var voiceDispatchSchema =
|
|
1557
|
+
var voiceDispatchSchema = z11.strictObject({
|
|
1214
1558
|
agent: deepAgentNameSchema,
|
|
1215
1559
|
/**
|
|
1216
1560
|
* The merged value (Studio > code > box default), cross-field rules included,
|
|
@@ -1221,57 +1565,59 @@ var voiceDispatchSchema = z8.strictObject({
|
|
|
1221
1565
|
tags: voiceTagsSchema,
|
|
1222
1566
|
boxUrl: voiceBoxUrlSchema,
|
|
1223
1567
|
/** The call token: the bearer on every AG-UI request of this call, for this agent and session only. */
|
|
1224
|
-
callToken:
|
|
1568
|
+
callToken: z11.string().max(VOICE_CALL_TOKEN_MAX).regex(/^\S+$/, { message: "callToken must be a non-empty token with no whitespace." }),
|
|
1225
1569
|
/**
|
|
1226
1570
|
* Set only when the box exports traces to an OTLP endpoint of its own (FDE-62 S11):
|
|
1227
1571
|
* the worker then sends each call's spans to `<boxUrl>` + {@link VOICE_TRACES_PATH} with the
|
|
1228
1572
|
* call token. Off is the absent key, never `false`.
|
|
1229
1573
|
*/
|
|
1230
|
-
traces:
|
|
1574
|
+
traces: z11.literal(true).optional()
|
|
1231
1575
|
});
|
|
1232
|
-
var voiceRoomRequestSchema =
|
|
1576
|
+
var voiceRoomRequestSchema = z11.strictObject({
|
|
1233
1577
|
agent: deepAgentNameSchema,
|
|
1234
1578
|
origin: runTagKeySchema.default("studio"),
|
|
1235
|
-
identities: integrationIdentitiesSchema.optional()
|
|
1579
|
+
identities: integrationIdentitiesSchema.optional(),
|
|
1580
|
+
/** Fill the greeting's placeholders and reach the agent as data (PLA-2409). */
|
|
1581
|
+
variables: voiceCallVariablesSchema.optional()
|
|
1236
1582
|
});
|
|
1237
|
-
var voiceRoomResponseSchema =
|
|
1238
|
-
url:
|
|
1239
|
-
token:
|
|
1583
|
+
var voiceRoomResponseSchema = z11.strictObject({
|
|
1584
|
+
url: z11.string().min(1),
|
|
1585
|
+
token: z11.string().min(1),
|
|
1240
1586
|
room: voiceCallIdSchema,
|
|
1241
1587
|
agent: deepAgentNameSchema,
|
|
1242
1588
|
sessionKey: voiceCallIdSchema,
|
|
1243
1589
|
voiceSettings: callVoiceSettingsObjectSchema
|
|
1244
1590
|
});
|
|
1245
1591
|
var VOICE_RECORDING_STATUSES = ["recording", "ready", "failed"];
|
|
1246
|
-
var voiceRecordingQuerySchema =
|
|
1247
|
-
var voiceRecordingResponseSchema =
|
|
1592
|
+
var voiceRecordingQuerySchema = z11.object({ session_id: z11.uuid() });
|
|
1593
|
+
var voiceRecordingResponseSchema = z11.strictObject({
|
|
1248
1594
|
room: voiceCallIdSchema,
|
|
1249
|
-
status:
|
|
1250
|
-
url:
|
|
1251
|
-
expires_at:
|
|
1252
|
-
started_at:
|
|
1253
|
-
ended_at:
|
|
1595
|
+
status: z11.enum(VOICE_RECORDING_STATUSES),
|
|
1596
|
+
url: z11.string().nullable(),
|
|
1597
|
+
expires_at: z11.iso.datetime().nullable(),
|
|
1598
|
+
started_at: z11.iso.datetime(),
|
|
1599
|
+
ended_at: z11.iso.datetime().nullable()
|
|
1254
1600
|
});
|
|
1255
|
-
var voiceStatusResponseSchema =
|
|
1256
|
-
configured:
|
|
1257
|
-
reachable:
|
|
1258
|
-
checkedAt:
|
|
1601
|
+
var voiceStatusResponseSchema = z11.strictObject({
|
|
1602
|
+
configured: z11.literal(true),
|
|
1603
|
+
reachable: z11.boolean(),
|
|
1604
|
+
checkedAt: z11.iso.datetime()
|
|
1259
1605
|
});
|
|
1260
1606
|
var HEARD_TEXT_MAX = 4e3;
|
|
1261
|
-
var aguiHeardSchema =
|
|
1262
|
-
text:
|
|
1607
|
+
var aguiHeardSchema = z11.strictObject({
|
|
1608
|
+
text: z11.string().min(1).max(HEARD_TEXT_MAX)
|
|
1263
1609
|
});
|
|
1264
1610
|
var VOICE_CALLS_PATH = "/voice/v1/calls";
|
|
1265
|
-
var voiceCallBootstrapSchema =
|
|
1611
|
+
var voiceCallBootstrapSchema = z11.strictObject({
|
|
1266
1612
|
identities: integrationIdentitiesSchema.optional()
|
|
1267
1613
|
});
|
|
1268
|
-
var voiceCallKnowledgeSchema =
|
|
1269
|
-
text:
|
|
1614
|
+
var voiceCallKnowledgeSchema = z11.strictObject({
|
|
1615
|
+
text: z11.string().min(1).max(HEARD_TEXT_MAX)
|
|
1270
1616
|
});
|
|
1271
|
-
var voiceCallTranscriptSchema =
|
|
1272
|
-
|
|
1273
|
-
role:
|
|
1274
|
-
text:
|
|
1617
|
+
var voiceCallTranscriptSchema = z11.array(
|
|
1618
|
+
z11.strictObject({
|
|
1619
|
+
role: z11.enum(["user", "assistant"]),
|
|
1620
|
+
text: z11.string().trim().min(1).max(16e3)
|
|
1275
1621
|
})
|
|
1276
1622
|
).max(1e3).refine((items) => items.reduce((size, item) => size + item.text.length, 0) <= 2e5, {
|
|
1277
1623
|
message: "A call transcript may contain at most 200000 characters."
|
|
@@ -1279,10 +1625,10 @@ var voiceCallTranscriptSchema = z8.array(
|
|
|
1279
1625
|
var VOICE_CALL_TURNS_MAX = 1e3;
|
|
1280
1626
|
var AGUI_RUN_ID_MAX = 200;
|
|
1281
1627
|
var VOICE_TURN_LATENCY_MAX_MS = 36e5;
|
|
1282
|
-
var turnLatencyMs =
|
|
1283
|
-
var voiceCallTurnMetricsSchema =
|
|
1628
|
+
var turnLatencyMs = z11.number().int().min(0).max(VOICE_TURN_LATENCY_MAX_MS).optional();
|
|
1629
|
+
var voiceCallTurnMetricsSchema = z11.strictObject({
|
|
1284
1630
|
/** The AG-UI `runId` of the box run that answered; absent on a reply the box did not run. */
|
|
1285
|
-
runId:
|
|
1631
|
+
runId: z11.string().min(1).max(AGUI_RUN_ID_MAX).optional(),
|
|
1286
1632
|
endOfTurnMs: turnLatencyMs,
|
|
1287
1633
|
transcriptionMs: turnLatencyMs,
|
|
1288
1634
|
llmTtftMs: turnLatencyMs,
|
|
@@ -1291,21 +1637,26 @@ var voiceCallTurnMetricsSchema = z8.strictObject({
|
|
|
1291
1637
|
/** How long before the final transcript the turn's last eager end of turn left for the box (STA3-94). */
|
|
1292
1638
|
preflightLeadMs: turnLatencyMs
|
|
1293
1639
|
});
|
|
1294
|
-
var voiceCallTurnsSchema =
|
|
1295
|
-
var voiceCallCompleteSchema =
|
|
1640
|
+
var voiceCallTurnsSchema = z11.array(voiceCallTurnMetricsSchema).max(VOICE_CALL_TURNS_MAX);
|
|
1641
|
+
var voiceCallCompleteSchema = z11.strictObject({
|
|
1296
1642
|
transcript: voiceCallTranscriptSchema,
|
|
1297
|
-
outcome:
|
|
1643
|
+
outcome: z11.enum(["completed", "disconnected", "failed"]),
|
|
1298
1644
|
// The transcript, the memory and the `onEnd` start this body carries must not be
|
|
1299
1645
|
// refused over a bad latency, so `turns` is any value here and read apart through
|
|
1300
1646
|
// `readVoiceCallTurns`. Not `.catch()` on the array: this schema is a documented
|
|
1301
1647
|
// route's body, and the OpenAPI generator cannot describe a catch (STA3-56).
|
|
1302
|
-
turns:
|
|
1648
|
+
turns: z11.unknown().optional()
|
|
1303
1649
|
});
|
|
1304
|
-
var voiceCallLifecycleResponseSchema =
|
|
1650
|
+
var voiceCallLifecycleResponseSchema = z11.strictObject({ ok: z11.literal(true) });
|
|
1305
1651
|
|
|
1306
1652
|
// libs/runtime/voice-worker/src/box-llm.ts
|
|
1307
1653
|
import { randomUUID } from "node:crypto";
|
|
1308
|
-
import {
|
|
1654
|
+
import {
|
|
1655
|
+
DEFAULT_API_CONNECT_OPTIONS,
|
|
1656
|
+
FlushSentinel,
|
|
1657
|
+
llm as llm2,
|
|
1658
|
+
log
|
|
1659
|
+
} from "@livekit/agents";
|
|
1309
1660
|
|
|
1310
1661
|
// libs/shared/llm-wire/src/lib/sse/read-sse-data.ts
|
|
1311
1662
|
async function* readSseData(body) {
|
|
@@ -1486,7 +1837,8 @@ var voiceCallKnowledge = async (wire, text) => {
|
|
|
1486
1837
|
var VOICE_RESPONSE_MARKER = "stackboneVoiceResponse";
|
|
1487
1838
|
var WORD_CHAR = /[\p{L}\p{N}]/u;
|
|
1488
1839
|
var SPACE = /\s/u;
|
|
1489
|
-
var
|
|
1840
|
+
var TAG_NAME_START = /[\p{L}/]/u;
|
|
1841
|
+
var TAG = new RegExp(`^<${TAG_NAME_START.source}[^>]*>`, "u");
|
|
1490
1842
|
var charAt = (text, index) => String.fromCodePoint(text.codePointAt(index) ?? 0);
|
|
1491
1843
|
var heardPrefixOf = (boxText, heardText) => {
|
|
1492
1844
|
let b = 0;
|
|
@@ -1649,6 +2001,60 @@ var lastNewAssistantMessage = (chatCtx, seen) => {
|
|
|
1649
2001
|
return void 0;
|
|
1650
2002
|
};
|
|
1651
2003
|
|
|
2004
|
+
// libs/runtime/voice-worker/src/first-clause.ts
|
|
2005
|
+
var FIRST_CLAUSE_MIN_CHARS = 25;
|
|
2006
|
+
var CLAUSE_END = /* @__PURE__ */ new Set([",", ";", ":", ".", "?", "!", "\u2026"]);
|
|
2007
|
+
var FirstClause = class {
|
|
2008
|
+
#done = false;
|
|
2009
|
+
#spoken = 0;
|
|
2010
|
+
/** The text of the tag being read, from its `<`. */
|
|
2011
|
+
#tag;
|
|
2012
|
+
#openTags = 0;
|
|
2013
|
+
/** The last character was a mark that can end the clause: the next one decides. */
|
|
2014
|
+
#markPending = false;
|
|
2015
|
+
push(text) {
|
|
2016
|
+
if (!this.#done) {
|
|
2017
|
+
let at = 0;
|
|
2018
|
+
for (const char of text) {
|
|
2019
|
+
if (this.#markPending && SPACE.test(char)) {
|
|
2020
|
+
this.#done = true;
|
|
2021
|
+
return { head: text.slice(0, at), cut: true, tail: text.slice(at) };
|
|
2022
|
+
}
|
|
2023
|
+
this.#read(char);
|
|
2024
|
+
at += char.length;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return { head: text, cut: false, tail: "" };
|
|
2028
|
+
}
|
|
2029
|
+
#read(char) {
|
|
2030
|
+
this.#markPending = false;
|
|
2031
|
+
if (this.#tag !== void 0) {
|
|
2032
|
+
if (this.#tag !== "<" || TAG_NAME_START.test(char)) {
|
|
2033
|
+
this.#tag += char;
|
|
2034
|
+
if (char === ">") this.#closeTag(this.#tag);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
this.#tag = void 0;
|
|
2038
|
+
this.#spoken += 1;
|
|
2039
|
+
}
|
|
2040
|
+
if (char === "<") {
|
|
2041
|
+
this.#tag = "<";
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (SPACE.test(char)) {
|
|
2045
|
+
if (this.#spoken > 0) this.#spoken += 1;
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
this.#spoken += 1;
|
|
2049
|
+
this.#markPending = CLAUSE_END.has(char) && this.#openTags === 0 && this.#spoken >= FIRST_CLAUSE_MIN_CHARS;
|
|
2050
|
+
}
|
|
2051
|
+
#closeTag(tag) {
|
|
2052
|
+
this.#tag = void 0;
|
|
2053
|
+
if (tag.startsWith("</")) this.#openTags = Math.max(0, this.#openTags - 1);
|
|
2054
|
+
else if (!tag.endsWith("/>")) this.#openTags += 1;
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
|
|
1652
2058
|
// libs/runtime/voice-worker/src/turn-metrics.ts
|
|
1653
2059
|
import { llm, voice as voice2 } from "@livekit/agents";
|
|
1654
2060
|
var AGUI_RUN_ID_EXTRA = "stackboneAguiRunId";
|
|
@@ -1808,6 +2214,8 @@ var BoxLLMStream = class extends llm2.LLMStream {
|
|
|
1808
2214
|
async runTurn() {
|
|
1809
2215
|
const {
|
|
1810
2216
|
onToolCall,
|
|
2217
|
+
speakFirstClause = false,
|
|
2218
|
+
onSpeechSegments,
|
|
1811
2219
|
onTurnError,
|
|
1812
2220
|
onRunFinished,
|
|
1813
2221
|
onClientToolCall,
|
|
@@ -1833,6 +2241,28 @@ var BoxLLMStream = class extends llm2.LLMStream {
|
|
|
1833
2241
|
timer = setTimeout(() => idle.abort(), turnIdleTimeoutMs);
|
|
1834
2242
|
};
|
|
1835
2243
|
const openToolCalls = /* @__PURE__ */ new Set();
|
|
2244
|
+
let textSinceFlush = false;
|
|
2245
|
+
let speechSinceTool = false;
|
|
2246
|
+
let lastChar = "";
|
|
2247
|
+
let flushed = false;
|
|
2248
|
+
let segments = 0;
|
|
2249
|
+
const newClause = () => speakFirstClause ? new FirstClause() : void 0;
|
|
2250
|
+
let clause = newClause();
|
|
2251
|
+
const put = (content) => this.queue.put({
|
|
2252
|
+
id,
|
|
2253
|
+
delta: {
|
|
2254
|
+
role: "assistant",
|
|
2255
|
+
content,
|
|
2256
|
+
extra: { [VOICE_RESPONSE_MARKER]: id, [AGUI_RUN_ID_EXTRA]: runId }
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
const flushSegment = () => {
|
|
2260
|
+
this.queue.put(FlushSentinel);
|
|
2261
|
+
textSinceFlush = false;
|
|
2262
|
+
segments += 1;
|
|
2263
|
+
onSpeechSegments?.(segments);
|
|
2264
|
+
};
|
|
2265
|
+
onSpeechSegments?.(0);
|
|
1836
2266
|
let finished;
|
|
1837
2267
|
let failure;
|
|
1838
2268
|
try {
|
|
@@ -1849,22 +2279,38 @@ var BoxLLMStream = class extends llm2.LLMStream {
|
|
|
1849
2279
|
restartIdle();
|
|
1850
2280
|
reply.observe(ev);
|
|
1851
2281
|
switch (ev.kind) {
|
|
1852
|
-
case "text":
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2282
|
+
case "text": {
|
|
2283
|
+
const content = flushed && !/^\s/.test(ev.text) && !/\s$/.test(lastChar) ? ` ${ev.text}` : ev.text;
|
|
2284
|
+
flushed = false;
|
|
2285
|
+
if (/\S/.test(ev.text)) {
|
|
2286
|
+
textSinceFlush = true;
|
|
2287
|
+
speechSinceTool = true;
|
|
2288
|
+
}
|
|
2289
|
+
lastChar = content.slice(-1);
|
|
2290
|
+
const split = clause?.push(content);
|
|
2291
|
+
if (!split?.cut) {
|
|
2292
|
+
put(content);
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2295
|
+
if (split.head) put(split.head);
|
|
2296
|
+
flushSegment();
|
|
2297
|
+
textSinceFlush = /\S/.test(split.tail);
|
|
2298
|
+
put(split.tail);
|
|
1861
2299
|
break;
|
|
2300
|
+
}
|
|
1862
2301
|
case "tool_call_start":
|
|
1863
2302
|
if (clientToolNames.has(ev.name)) {
|
|
1864
2303
|
clientToolCalls.set(ev.id, { id: ev.id, name: ev.name });
|
|
1865
2304
|
} else if (!openToolCalls.has(ev.id)) {
|
|
2305
|
+
const afterSpeech = speechSinceTool;
|
|
2306
|
+
if (textSinceFlush) {
|
|
2307
|
+
flushSegment();
|
|
2308
|
+
flushed = true;
|
|
2309
|
+
}
|
|
2310
|
+
speechSinceTool = false;
|
|
2311
|
+
clause = newClause();
|
|
1866
2312
|
openToolCalls.add(ev.id);
|
|
1867
|
-
onToolCall?.("start", ev.id);
|
|
2313
|
+
onToolCall?.("start", ev.id, afterSpeech);
|
|
1868
2314
|
}
|
|
1869
2315
|
break;
|
|
1870
2316
|
case "tool_call_result":
|
|
@@ -2252,13 +2698,18 @@ var voiceSessionOptions = (settings, pieces) => {
|
|
|
2252
2698
|
endpointing: { minDelay: turn.minEndpointingDelayMs, maxDelay: turn.maxEndpointingDelayMs },
|
|
2253
2699
|
// LiveKit 1.9.0 reads `enabled` as the session's `allowInterruptions`, the
|
|
2254
2700
|
// default of every speech; the old top-level option is deprecated.
|
|
2255
|
-
//
|
|
2256
|
-
// turns adaptive interruption off unless the worker runs on LiveKit Cloud or in
|
|
2257
|
-
// deprecated `dev` mode, and `stackbone dev` runs `start` since S15
|
|
2258
|
-
//
|
|
2259
|
-
//
|
|
2701
|
+
// The mode is always sent: with none, LiveKit 1.9.0 (`resolveInterruptionDetector`)
|
|
2702
|
+
// turns adaptive interruption off unless the worker runs on LiveKit Cloud or in
|
|
2703
|
+
// its deprecated `dev` mode, and `stackbone dev` runs `start` since S15.
|
|
2704
|
+
// `adaptive`: shortly after the agent starts speaking (LiveKit's backchannel
|
|
2705
|
+
// boundary) LiveKit stops cutting on voice activity and cuts only when its model
|
|
2706
|
+
// calls the overlap a barge-in, so a "sí", a "vale" or a cough do not cut the
|
|
2707
|
+
// agent off, and a real barge-in can be missed. LiveKit still skips adaptive, with
|
|
2708
|
+
// a warning, when the STT gives no word-aligned transcript, and when interruptions
|
|
2709
|
+
// are off. `vad`: any speech of `minDuration` (and `minWords`, when set) cuts, so
|
|
2710
|
+
// a short acknowledgement or a cough can cut too.
|
|
2260
2711
|
interruption: {
|
|
2261
|
-
mode:
|
|
2712
|
+
mode: interruptions.mode,
|
|
2262
2713
|
enabled: interruptions.enabled,
|
|
2263
2714
|
minDuration: interruptions.minDurationMs,
|
|
2264
2715
|
minWords: interruptions.minWords
|
|
@@ -2346,19 +2797,35 @@ var CALL_CLOSING_PHRASE = Object.freeze({
|
|
|
2346
2797
|
});
|
|
2347
2798
|
var MAX_FAILED_TURNS_IN_A_ROW = 3;
|
|
2348
2799
|
var inCallLanguage = (phrase, language) => language.split("-")[0]?.toLowerCase() === "es" ? phrase.es : phrase.en;
|
|
2349
|
-
var
|
|
2800
|
+
var CUE_AFTER_HOLDING_LINE_MS = 300;
|
|
2801
|
+
var thinkingCue = (delayMs, holdingLineMaxWaitMs) => {
|
|
2350
2802
|
let cue;
|
|
2351
2803
|
let open = 0;
|
|
2352
2804
|
let waitedLong = false;
|
|
2353
2805
|
let wait;
|
|
2354
2806
|
let playing = false;
|
|
2807
|
+
let holding = false;
|
|
2808
|
+
let flushedSegments = 0;
|
|
2809
|
+
let playedSegments = 0;
|
|
2810
|
+
let beat;
|
|
2811
|
+
let cap;
|
|
2355
2812
|
const sync = () => {
|
|
2356
|
-
const play = waitedLong || open > 0;
|
|
2813
|
+
const play = waitedLong || open > 0 && !holding;
|
|
2357
2814
|
if (play === playing || !cue) return;
|
|
2358
2815
|
playing = play;
|
|
2359
2816
|
if (play) cue.start();
|
|
2360
2817
|
else cue.stop();
|
|
2361
2818
|
};
|
|
2819
|
+
const clearHold = () => {
|
|
2820
|
+
clearTimeout(beat);
|
|
2821
|
+
clearTimeout(cap);
|
|
2822
|
+
beat = cap = void 0;
|
|
2823
|
+
holding = false;
|
|
2824
|
+
};
|
|
2825
|
+
const releaseHold = () => {
|
|
2826
|
+
clearHold();
|
|
2827
|
+
sync();
|
|
2828
|
+
};
|
|
2362
2829
|
return {
|
|
2363
2830
|
// A turn can start before the track is up.
|
|
2364
2831
|
attach: (ready) => {
|
|
@@ -2380,25 +2847,49 @@ var thinkingCue = (delayMs) => {
|
|
|
2380
2847
|
sync();
|
|
2381
2848
|
}, delayMs);
|
|
2382
2849
|
},
|
|
2383
|
-
onToolCall: (phase) => {
|
|
2384
|
-
if (phase === "start")
|
|
2385
|
-
|
|
2850
|
+
onToolCall: (phase, _id, afterSpeech = false) => {
|
|
2851
|
+
if (phase === "start") {
|
|
2852
|
+
open += 1;
|
|
2853
|
+
if (afterSpeech && holdingLineMaxWaitMs > 0 && playedSegments < flushedSegments) {
|
|
2854
|
+
clearHold();
|
|
2855
|
+
holding = true;
|
|
2856
|
+
cap = setTimeout(releaseHold, holdingLineMaxWaitMs);
|
|
2857
|
+
}
|
|
2858
|
+
} else if (open > 0) open -= 1;
|
|
2859
|
+
if (open === 0) clearHold();
|
|
2386
2860
|
sync();
|
|
2387
2861
|
},
|
|
2862
|
+
/** BoxLLM's count of the turn's flushed segments; a turn starts at 0 (`onSpeechSegments`). */
|
|
2863
|
+
onSpeechSegments: (count) => {
|
|
2864
|
+
if (count === 0) playedSegments = 0;
|
|
2865
|
+
flushedSegments = count;
|
|
2866
|
+
},
|
|
2867
|
+
/** A segment of a reply finished playing out, or was cut. */
|
|
2868
|
+
onPlaybackFinished: () => {
|
|
2869
|
+
if (playedSegments < flushedSegments) playedSegments += 1;
|
|
2870
|
+
if (!holding || beat !== void 0 || playedSegments < flushedSegments) return;
|
|
2871
|
+
beat = setTimeout(releaseHold, CUE_AFTER_HOLDING_LINE_MS);
|
|
2872
|
+
},
|
|
2873
|
+
onSessionError: ({ error }) => {
|
|
2874
|
+
if (holding && error.type === "tts_error" && !error.recoverable) releaseHold();
|
|
2875
|
+
},
|
|
2388
2876
|
/** The call is ending: a pending delay must not start the sound in a closed room. */
|
|
2389
2877
|
close: () => {
|
|
2390
2878
|
clearTimeout(wait);
|
|
2391
2879
|
wait = void 0;
|
|
2392
2880
|
waitedLong = false;
|
|
2393
2881
|
open = 0;
|
|
2882
|
+
clearHold();
|
|
2394
2883
|
sync();
|
|
2395
2884
|
}
|
|
2396
2885
|
};
|
|
2397
2886
|
};
|
|
2398
2887
|
var messageOf = (err) => err instanceof Error ? err.message : String(err);
|
|
2399
|
-
var callerNoiseCancellation = (caller) => {
|
|
2888
|
+
var callerNoiseCancellation = (caller, mode) => {
|
|
2889
|
+
if (mode === "off") return void 0;
|
|
2400
2890
|
try {
|
|
2401
|
-
|
|
2891
|
+
if (caller.kind === ParticipantKind.SIP) return TelephonyBackgroundVoiceCancellation();
|
|
2892
|
+
return mode === "standard" ? NoiseCancellation() : BackgroundVoiceCancellation();
|
|
2402
2893
|
} catch (err) {
|
|
2403
2894
|
log5().warn({ error: messageOf(err) }, "voice worker: no noise cancellation on this call");
|
|
2404
2895
|
return void 0;
|
|
@@ -2436,7 +2927,10 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2436
2927
|
deps
|
|
2437
2928
|
);
|
|
2438
2929
|
const settings = dispatch.voiceSettings;
|
|
2439
|
-
const cue = thinkingCue(
|
|
2930
|
+
const cue = thinkingCue(
|
|
2931
|
+
settings.backgroundAudio.thinkingDelayMs,
|
|
2932
|
+
settings.backgroundAudio.holdingLineMaxWaitMs
|
|
2933
|
+
);
|
|
2440
2934
|
const fallback = inCallLanguage(TURN_FALLBACK_PHRASE, settings.language);
|
|
2441
2935
|
let failedInARow = 0;
|
|
2442
2936
|
let ending = false;
|
|
@@ -2535,6 +3029,8 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2535
3029
|
pendingResume: hangUp.pendingResume,
|
|
2536
3030
|
isEnding: () => ending,
|
|
2537
3031
|
onToolCall: cue.onToolCall,
|
|
3032
|
+
speakFirstClause: settings.speakFirstClause,
|
|
3033
|
+
onSpeechSegments: cue.onSpeechSegments,
|
|
2538
3034
|
onTurnError,
|
|
2539
3035
|
onRunFinished
|
|
2540
3036
|
});
|
|
@@ -2577,6 +3073,7 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2577
3073
|
voice4.AgentSessionEventTypes.AgentStateChanged,
|
|
2578
3074
|
({ newState }) => cue.onAgentState(newState)
|
|
2579
3075
|
);
|
|
3076
|
+
session.on(voice4.AgentSessionEventTypes.Error, cue.onSessionError);
|
|
2580
3077
|
session.on(voice4.AgentSessionEventTypes.Close, () => {
|
|
2581
3078
|
if (completion || ending) return;
|
|
2582
3079
|
void finishCall().catch(() => void 0).finally(() => ctx.shutdown("caller left"));
|
|
@@ -2595,12 +3092,13 @@ var runVoiceJob = async (ctx, deps) => {
|
|
|
2595
3092
|
preflightLeadMs: () => plan.agent.takePreflightLead()
|
|
2596
3093
|
}
|
|
2597
3094
|
);
|
|
2598
|
-
const noiseCancellation = callerNoiseCancellation(participant);
|
|
3095
|
+
const noiseCancellation = callerNoiseCancellation(participant, settings.noiseCancellation);
|
|
2599
3096
|
await session.start({
|
|
2600
3097
|
agent: plan.agent,
|
|
2601
3098
|
room: ctx.room,
|
|
2602
3099
|
inputOptions: noiseCancellation ? { noiseCancellation } : {}
|
|
2603
3100
|
});
|
|
3101
|
+
session.output?.audio?.on("playbackFinished", cue.onPlaybackFinished);
|
|
2604
3102
|
if (settings.greeting !== null) sayIfOpen(settings.greeting, "greeting");
|
|
2605
3103
|
await deps.createCue({ room: ctx.room, session, audio: settings.backgroundAudio }).then(cue.attach, (err) => {
|
|
2606
3104
|
log5().warn({ error: messageOf(err) }, "voice worker: the background audio did not start");
|