@stackbone/cli 0.9.2 → 0.9.3

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