@stackbone/cli 0.9.0 → 0.9.2

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/main.js CHANGED
@@ -3229,7 +3229,7 @@ var init_studio_internal_prompts = __esmMin((() => {
3229
3229
  }));
3230
3230
  //#endregion
3231
3231
  //#region ../../libs/shared/validators/src/lib/studio/studio-model-routing.ts
3232
- var MODEL_PROVIDER_SORTS, MODEL_PROVIDER_SORTS_TEXT, modelProviderSortSchema, isModelProviderSort, studioModelRoutingPathSchema, studioModelRoutingSchema, putStudioModelRoutingRequestSchema, studioModelRoutingDeleteResponseSchema;
3232
+ var MODEL_PROVIDER_SORTS, MODEL_PROVIDER_SORTS_TEXT, modelProviderSortSchema, MODEL_PROVIDER_QUANTIZATIONS, providerSlugSchema, providerSlugsSchema, percentileTargetSchema, dollarsSchema, modelProviderRoutingSchema, modelRoutingInputSchema, toModelProviderRouting, readModelProviderRouting, plainSortOf, studioModelRoutingPathSchema, studioModelRoutingSchema, putStudioModelRoutingRequestSchema, routingOfPutRequest, studioModelRoutingDeleteResponseSchema;
3233
3233
  var init_studio_model_routing = __esmMin((() => {
3234
3234
  init_studio_model_selections();
3235
3235
  MODEL_PROVIDER_SORTS = [
@@ -3239,14 +3239,69 @@ var init_studio_model_routing = __esmMin((() => {
3239
3239
  ];
3240
3240
  MODEL_PROVIDER_SORTS_TEXT = MODEL_PROVIDER_SORTS.map((sort) => `'${sort}'`).join(", ");
3241
3241
  modelProviderSortSchema = z.enum(MODEL_PROVIDER_SORTS);
3242
- isModelProviderSort = (value) => modelProviderSortSchema.safeParse(value).success;
3242
+ MODEL_PROVIDER_QUANTIZATIONS = [
3243
+ "int4",
3244
+ "int8",
3245
+ "fp4",
3246
+ "mxfp4",
3247
+ "nvfp4",
3248
+ "fp6",
3249
+ "fp8",
3250
+ "mxfp8",
3251
+ "fp16",
3252
+ "bf16",
3253
+ "fp32",
3254
+ "unknown"
3255
+ ];
3256
+ providerSlugSchema = z.string().regex(/^\S+$/, "a provider slug has no spaces").max(128);
3257
+ providerSlugsSchema = z.array(providerSlugSchema).min(1).max(32);
3258
+ percentileTargetSchema = z.union([z.number().positive(), z.strictObject({
3259
+ p50: z.number().positive(),
3260
+ p75: z.number().positive(),
3261
+ p90: z.number().positive(),
3262
+ p99: z.number().positive()
3263
+ }).partial().refine((value) => Object.keys(value).length > 0, "name at least one percentile")]);
3264
+ dollarsSchema = z.number().nonnegative();
3265
+ modelProviderRoutingSchema = z.strictObject({
3266
+ order: providerSlugsSchema,
3267
+ only: providerSlugsSchema,
3268
+ ignore: providerSlugsSchema,
3269
+ allow_fallbacks: z.boolean(),
3270
+ require_parameters: z.boolean(),
3271
+ data_collection: z.enum(["allow", "deny"]),
3272
+ zdr: z.boolean(),
3273
+ enforce_distillable_text: z.boolean(),
3274
+ quantizations: z.array(z.enum(MODEL_PROVIDER_QUANTIZATIONS)).min(1),
3275
+ sort: z.union([modelProviderSortSchema, z.strictObject({
3276
+ by: modelProviderSortSchema,
3277
+ partition: z.enum(["model", "none"]).optional()
3278
+ })]),
3279
+ preferred_min_throughput: percentileTargetSchema,
3280
+ preferred_max_latency: percentileTargetSchema,
3281
+ max_price: z.strictObject({
3282
+ prompt: dollarsSchema,
3283
+ completion: dollarsSchema,
3284
+ request: dollarsSchema,
3285
+ image: dollarsSchema
3286
+ }).partial()
3287
+ }).partial().refine((value) => Object.keys(value).length > 0, "name at least one routing field");
3288
+ modelRoutingInputSchema = z.union([modelProviderSortSchema, modelProviderRoutingSchema]);
3289
+ toModelProviderRouting = (input) => typeof input === "string" ? { sort: input } : input;
3290
+ readModelProviderRouting = (value) => {
3291
+ const parsed = modelRoutingInputSchema.safeParse(value);
3292
+ return parsed.success ? toModelProviderRouting(parsed.data) : null;
3293
+ };
3294
+ plainSortOf = (routing) => routing && Object.keys(routing).length === 1 && typeof routing.sort === "string" ? routing.sort : null;
3243
3295
  studioModelRoutingPathSchema = studioModelSelectionPathSchema.extend({ ownerKind: z.literal("agent") });
3244
3296
  studioModelRoutingSchema = studioModelRoutingPathSchema.extend({
3245
- sort: modelProviderSortSchema,
3297
+ routing: modelProviderRoutingSchema,
3298
+ /** `routing` as a plain sort, for a Studio older than the object; `null` otherwise. */
3299
+ sort: modelProviderSortSchema.nullable(),
3246
3300
  updatedBy: z.string().nullable(),
3247
3301
  updatedAt: z.iso.datetime()
3248
3302
  });
3249
- putStudioModelRoutingRequestSchema = z.strictObject({ sort: modelProviderSortSchema });
3303
+ putStudioModelRoutingRequestSchema = z.union([z.strictObject({ routing: modelProviderRoutingSchema }), z.strictObject({ sort: modelProviderSortSchema })]);
3304
+ routingOfPutRequest = (body) => "routing" in body ? body.routing : { sort: body.sort };
3250
3305
  studioModelRoutingDeleteResponseSchema = studioModelRoutingPathSchema.extend({ status: z.literal("deleted") });
3251
3306
  }));
3252
3307
  //#endregion
@@ -3613,7 +3668,7 @@ var init_studio_rag = __esmMin((() => {
3613
3668
  error: z.string().nullable()
3614
3669
  });
3615
3670
  ragRebuildStatusResponseSchema = z.object({ rebuild: ragRebuildStatusSchema.nullable() });
3616
- })), VOICE_STT_MODELS, VOICE_TTS_MODELS, isCartesiaTtsModel, VOICE_BACKGROUND_CLIPS, VOICE_AMBIENT_SOUNDS, VOICE_THINKING_SOUNDS, thresholdSchema, millisecondsSchema, keytermsSchema, vadSchema, turnSchema, interruptionsSchema, ragSchema, volumeSchema, backgroundAudioSchema, VOICE_BACKGROUND_AUDIO_DEFAULTS, effectiveVoiceSettingsObjectSchema, checkCrossFieldRules, effectiveVoiceSettingsSchema, callVoiceSettingsObjectSchema, callVoiceSettingsSchema, toCallVoiceSettings, VOICE_SETTINGS_LEAF_PATHS, VOICE_TTS_PAIR, voiceSettingOriginSchema, effectiveVoiceSettingsViewSchema, checkTtsPair, voiceSettingsSchema, checkStudioTtsPair, studioVoiceSettingsPatchSchema, voiceSettingsResponseSchema, describeVoiceSettingsIssue, deepAgentNameSchema, voiceCallIdSchema, voiceTagsSchema, VOICE_TTS_MARKUP_CONTEXT, VOICE_TTS_MARKUP_GUIDES, isVoiceBoxUrl, VOICE_BOX_URL_MAX, voiceBoxUrlSchema, VOICE_TRACES_PATH, VOICE_WORKER_AGENT_NAME_ENV, VOICE_WORKER_PORT_ENV, VOICE_CALL_TOKEN_MAX, voiceDispatchSchema, voiceRoomRequestSchema, voiceRoomResponseSchema, VOICE_RECORDING_STATUSES, voiceRecordingQuerySchema, VOICE_RECORDING_URL_TTL_SECONDS, voiceRecordingResponseSchema, voiceStatusResponseSchema, HEARD_TEXT_MAX, aguiHeardSchema, VOICE_CALLS_PATH, voiceCallBootstrapSchema, voiceCallTranscriptSchema, VOICE_CALL_TURNS_MAX, VOICE_TURN_LATENCY_MAX_MS, turnLatencyMs, voiceCallTurnMetricsSchema, voiceCallTurnsSchema, readVoiceCallTurns, voiceCallCompleteSchema, voiceCallLifecycleResponseSchema;
3671
+ })), VOICE_STT_MODELS, VOICE_TTS_MODELS, isCartesiaTtsModel, VOICE_BACKGROUND_CLIPS, VOICE_AMBIENT_SOUNDS, VOICE_THINKING_SOUNDS, thresholdSchema, millisecondsSchema, keytermsSchema, vadSchema, turnSchema, VOICE_EAGER_THRESHOLD_DEFAULT, interruptionsSchema, ragSchema, volumeSchema, backgroundAudioSchema, VOICE_BACKGROUND_AUDIO_DEFAULTS, effectiveVoiceSettingsObjectSchema, checkCrossFieldRules, effectiveVoiceSettingsSchema, callVoiceSettingsObjectSchema, callVoiceSettingsSchema, toCallVoiceSettings, VOICE_SETTINGS_LEAF_PATHS, VOICE_TTS_PAIR, voiceSettingOriginSchema, LEAVES_NEWER_THAN_SOME_BOXES, effectiveVoiceSettingsViewSchema, checkTtsPair, voiceSettingsSchema, checkStudioTtsPair, studioVoiceSettingsPatchSchema, voiceSettingsResponseSchema, describeVoiceSettingsIssue, deepAgentNameSchema, voiceCallIdSchema, voiceTagsSchema, VOICE_TTS_MARKUP_CONTEXT, VOICE_TTS_MARKUP_GUIDES, isVoiceBoxUrl, VOICE_BOX_URL_MAX, voiceBoxUrlSchema, VOICE_TRACES_PATH, VOICE_WORKER_AGENT_NAME_ENV, VOICE_WORKER_PORT_ENV, VOICE_CALL_TOKEN_MAX, voiceDispatchSchema, voiceRoomRequestSchema, voiceRoomResponseSchema, VOICE_RECORDING_STATUSES, voiceRecordingQuerySchema, VOICE_RECORDING_URL_TTL_SECONDS, voiceRecordingResponseSchema, voiceStatusResponseSchema, HEARD_TEXT_MAX, aguiHeardSchema, VOICE_CALLS_PATH, voiceCallBootstrapSchema, voiceCallKnowledgeSchema, voiceCallTranscriptSchema, VOICE_CALL_TURNS_MAX, VOICE_TURN_LATENCY_MAX_MS, turnLatencyMs, voiceCallTurnMetricsSchema, voiceCallTurnsSchema, readVoiceCallTurns, voiceCallCompleteSchema, voiceCallLifecycleResponseSchema;
3617
3672
  var init_studio_voice = __esmMin((() => {
3618
3673
  init_memory_identities();
3619
3674
  init_studio_rag();
@@ -3653,8 +3708,10 @@ var init_studio_voice = __esmMin((() => {
3653
3708
  mode: z.enum(["stt", "livekit"]),
3654
3709
  minEndpointingDelayMs: millisecondsSchema,
3655
3710
  maxEndpointingDelayMs: millisecondsSchema,
3656
- detectorThreshold: thresholdSchema
3711
+ detectorThreshold: thresholdSchema,
3712
+ eagerThreshold: z.number().min(.3).max(.7)
3657
3713
  });
3714
+ VOICE_EAGER_THRESHOLD_DEFAULT = .3;
3658
3715
  interruptionsSchema = z.strictObject({
3659
3716
  enabled: z.boolean(),
3660
3717
  minDurationMs: millisecondsSchema,
@@ -3673,13 +3730,21 @@ var init_studio_voice = __esmMin((() => {
3673
3730
  ambientSound: z.enum(VOICE_AMBIENT_SOUNDS),
3674
3731
  ambientVolume: volumeSchema,
3675
3732
  thinkingSound: z.enum(VOICE_THINKING_SOUNDS),
3676
- thinkingVolume: volumeSchema
3733
+ thinkingVolume: volumeSchema,
3734
+ /**
3735
+ * How long a reply's wait runs before the thinking sound starts, when no box tool
3736
+ * call is open (a tool call starts it at once). Every turn waits for the knowledge
3737
+ * search and the model's first token, about 1.4 s at the median on a voice box
3738
+ * (2026-11-09), so a delay under that plays the sound on nearly every reply.
3739
+ */
3740
+ thinkingDelayMs: millisecondsSchema
3677
3741
  });
3678
3742
  VOICE_BACKGROUND_AUDIO_DEFAULTS = Object.freeze({
3679
3743
  ambientSound: "office-ambience",
3680
3744
  ambientVolume: .7,
3681
3745
  thinkingSound: "keyboard-typing",
3682
- thinkingVolume: .6
3746
+ thinkingVolume: .6,
3747
+ thinkingDelayMs: 2e3
3683
3748
  });
3684
3749
  effectiveVoiceSettingsObjectSchema = z.strictObject({
3685
3750
  /** BCP-47-ish tag the STT is hinted with and the TTS speaks (`es`, `en-US`), or `multi`. */
@@ -3687,7 +3752,7 @@ var init_studio_voice = __esmMin((() => {
3687
3752
  sttModel: z.enum(VOICE_STT_MODELS),
3688
3753
  keyterms: keytermsSchema.default(() => []),
3689
3754
  vad: vadSchema,
3690
- turn: turnSchema,
3755
+ turn: turnSchema.extend({ eagerThreshold: turnSchema.shape.eagerThreshold.default(VOICE_EAGER_THRESHOLD_DEFAULT) }),
3691
3756
  interruptions: interruptionsSchema,
3692
3757
  ttsModel: z.enum(VOICE_TTS_MODELS),
3693
3758
  /**
@@ -3749,7 +3814,7 @@ var init_studio_voice = __esmMin((() => {
3749
3814
  recording: true,
3750
3815
  rag: true,
3751
3816
  refusalPhrase: true
3752
- }).extend({ backgroundAudio: backgroundAudioSchema.default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS })) });
3817
+ }).extend({ backgroundAudio: backgroundAudioSchema.extend({ thinkingDelayMs: millisecondsSchema.default(VOICE_BACKGROUND_AUDIO_DEFAULTS.thinkingDelayMs) }).default(() => ({ ...VOICE_BACKGROUND_AUDIO_DEFAULTS })) });
3753
3818
  callVoiceSettingsSchema = callVoiceSettingsObjectSchema.superRefine(checkCrossFieldRules);
3754
3819
  toCallVoiceSettings = ({ recording: _recording, rag: _rag, refusalPhrase: _refusalPhrase, ...call }) => ({
3755
3820
  ...call,
@@ -3766,6 +3831,7 @@ var init_studio_voice = __esmMin((() => {
3766
3831
  "turn.minEndpointingDelayMs",
3767
3832
  "turn.maxEndpointingDelayMs",
3768
3833
  "turn.detectorThreshold",
3834
+ "turn.eagerThreshold",
3769
3835
  "interruptions.enabled",
3770
3836
  "interruptions.minDurationMs",
3771
3837
  "interruptions.minWords",
@@ -3779,6 +3845,7 @@ var init_studio_voice = __esmMin((() => {
3779
3845
  "backgroundAudio.ambientVolume",
3780
3846
  "backgroundAudio.thinkingSound",
3781
3847
  "backgroundAudio.thinkingVolume",
3848
+ "backgroundAudio.thinkingDelayMs",
3782
3849
  "recording",
3783
3850
  "rag.collection",
3784
3851
  "rag.minScore",
@@ -3794,9 +3861,10 @@ var init_studio_voice = __esmMin((() => {
3794
3861
  "code",
3795
3862
  "default"
3796
3863
  ]);
3864
+ LEAVES_NEWER_THAN_SOME_BOXES = new Set(["turn.eagerThreshold"]);
3797
3865
  effectiveVoiceSettingsViewSchema = z.strictObject({
3798
3866
  value: effectiveVoiceSettingsObjectSchema,
3799
- origins: z.strictObject(Object.fromEntries(VOICE_SETTINGS_LEAF_PATHS.map((path) => [path, voiceSettingOriginSchema])))
3867
+ origins: z.strictObject(Object.fromEntries(VOICE_SETTINGS_LEAF_PATHS.map((path) => [path, LEAVES_NEWER_THAN_SOME_BOXES.has(path) ? voiceSettingOriginSchema.default("default") : voiceSettingOriginSchema])))
3800
3868
  });
3801
3869
  checkTtsPair = (value, ctx) => {
3802
3870
  if (value.ttsModel !== void 0 && value.ttsVoice === void 0) ctx.addIssue({
@@ -3906,6 +3974,7 @@ var init_studio_voice = __esmMin((() => {
3906
3974
  aguiHeardSchema = z.strictObject({ text: z.string().min(1).max(HEARD_TEXT_MAX) });
3907
3975
  VOICE_CALLS_PATH = "/voice/v1/calls";
3908
3976
  voiceCallBootstrapSchema = z.strictObject({ identities: integrationIdentitiesSchema.optional() });
3977
+ voiceCallKnowledgeSchema = z.strictObject({ text: z.string().min(1).max(HEARD_TEXT_MAX) });
3909
3978
  voiceCallTranscriptSchema = z.array(z.strictObject({
3910
3979
  role: z.enum(["user", "assistant"]),
3911
3980
  text: z.string().trim().min(1).max(16e3)
@@ -3920,7 +3989,9 @@ var init_studio_voice = __esmMin((() => {
3920
3989
  transcriptionMs: turnLatencyMs,
3921
3990
  llmTtftMs: turnLatencyMs,
3922
3991
  ttsTtfbMs: turnLatencyMs,
3923
- e2eMs: turnLatencyMs
3992
+ e2eMs: turnLatencyMs,
3993
+ /** How long before the final transcript the turn's last eager end of turn left for the box (STA3-94). */
3994
+ preflightLeadMs: turnLatencyMs
3924
3995
  });
3925
3996
  voiceCallTurnsSchema = z.array(voiceCallTurnMetricsSchema).max(VOICE_CALL_TURNS_MAX);
3926
3997
  readVoiceCallTurns = (value) => {
@@ -3956,7 +4027,8 @@ var init_effective_voice_settings = __esmMin((() => {
3956
4027
  mode: "stt",
3957
4028
  minEndpointingDelayMs: 0,
3958
4029
  maxEndpointingDelayMs: 1200,
3959
- detectorThreshold: .35
4030
+ detectorThreshold: .35,
4031
+ eagerThreshold: VOICE_EAGER_THRESHOLD_DEFAULT
3960
4032
  }),
3961
4033
  interruptions: Object.freeze({
3962
4034
  enabled: true,
@@ -6009,6 +6081,15 @@ var init_studio_catalog = __esmMin((() => {
6009
6081
  /** The provider routing an operator picked for this key, or `null` when nobody has. */
6010
6082
  selectedProviderSort: modelProviderSortSchema.nullable().default(null),
6011
6083
  /**
6084
+ * The full provider routing the code writes for this key, or `null` when it writes
6085
+ * none. `defaultProviderSort` is the same routing as a plain sort, for a Studio
6086
+ * older than the object, and `null` whenever the routing is more than one sort.
6087
+ * Absent from a box that predates it.
6088
+ */
6089
+ defaultProviderRouting: modelProviderRoutingSchema.nullable().optional(),
6090
+ /** The full provider routing an operator picked, as `defaultProviderRouting` is to the code's. */
6091
+ selectedProviderRouting: modelProviderRoutingSchema.nullable().optional(),
6092
+ /**
6012
6093
  * `declared` — the code declares this key. `unused` — only a pick exists, for a
6013
6094
  * key no declaration mentions any more; resetting it is the one thing left to do.
6014
6095
  * An entry NOTHING scanned never carries an `unused` row: see the box's
@@ -9470,7 +9551,8 @@ var init_model_routing = __esmMin((() => {
9470
9551
  ownerKind: text("owner_kind").notNull(),
9471
9552
  ownerName: text("owner_name").notNull(),
9472
9553
  key: text("key").notNull(),
9473
- sort: text("sort").notNull(),
9554
+ sort: text("sort"),
9555
+ routing: jsonb("routing"),
9474
9556
  /** The acting user's id, or null on the un-authenticated `stackbone dev` datapath. */
9475
9557
  updatedBy: text("updated_by"),
9476
9558
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
@@ -11397,7 +11479,7 @@ var init_json = __esmMin((() => {
11397
11479
  //#region ../../libs/shared/model-provider-wire/src/lib/common/provider-routing.ts
11398
11480
  var providerRoutingFor, withProviderRouting;
11399
11481
  var init_provider_routing = __esmMin((() => {
11400
- providerRoutingFor = (sort) => Object.freeze({ sort });
11482
+ providerRoutingFor = (routing) => Object.freeze({ ...routing });
11401
11483
  withProviderRouting = (body, kept) => {
11402
11484
  if (kept || !("provider" in body)) return body;
11403
11485
  const next = { ...body };
@@ -19604,6 +19686,7 @@ var init_learn_control_plane_identity = __esmMin((() => {
19604
19686
  }));
19605
19687
  //#endregion
19606
19688
  //#region ../../libs/runtime/workspace-runtime/src/lib/catalog/catalog-projector.ts
19689
+ init_src$26();
19607
19690
  /**
19608
19691
  * `${kind}:${name}` — the identity a catalog row, a declaration, a prompt owner and
19609
19692
  * a selection's owner all agree on. The kind is half of it, never decoration: a deep
@@ -19765,16 +19848,26 @@ var asDeclaredModel = (ref) => ({
19765
19848
  overridable: ref.overridable,
19766
19849
  file: ref.file,
19767
19850
  line: ref.line,
19768
- defaultProviderSort: ref.defaultProviderSort ?? null,
19851
+ ...defaultRoutingFields(declaredRouting(ref)),
19769
19852
  selectedModelId: null,
19770
19853
  selectedProviderId: null,
19771
- selectedProviderSort: null,
19854
+ ...selectedRoutingFields(null),
19772
19855
  state: "declared"
19773
19856
  });
19857
+ /** The routing the code writes; a manifest older than the object carries only its sort. */
19858
+ var declaredRouting = (ref) => readModelProviderRouting(ref.defaultProviderRouting ?? ref.defaultProviderSort);
19859
+ var defaultRoutingFields = (routing) => ({
19860
+ defaultProviderRouting: routing,
19861
+ defaultProviderSort: plainSortOf(routing)
19862
+ });
19863
+ var selectedRoutingFields = (routing) => ({
19864
+ selectedProviderRouting: routing,
19865
+ selectedProviderSort: plainSortOf(routing)
19866
+ });
19774
19867
  var selectedFields = (pick) => ({
19775
19868
  selectedModelId: pick?.modelId ?? null,
19776
19869
  selectedProviderId: pick?.providerId ?? null,
19777
- selectedProviderSort: pick?.providerSort ?? null
19870
+ ...selectedRoutingFields(pick?.providerRouting ?? null)
19778
19871
  });
19779
19872
  /**
19780
19873
  * Cross what the code DECLARES against what an operator PICKED, for every row at
@@ -19808,7 +19901,7 @@ var attachModelSelections = (entries, selections) => entries.map((entry) => {
19808
19901
  overridable: true,
19809
19902
  file: null,
19810
19903
  line: null,
19811
- defaultProviderSort: null,
19904
+ ...defaultRoutingFields(null),
19812
19905
  ...selectedFields(pick),
19813
19906
  state: "unused"
19814
19907
  }));
@@ -22665,7 +22758,7 @@ var init_role = __esmMin((() => {
22665
22758
  var STACKBONE_CLI_BUILD_VERSION, EMULATOR_CAPABILITY_ROUTES, EMULATOR_CAPABILITIES, buildContractResponse;
22666
22759
  var init_contract = __esmMin((() => {
22667
22760
  init_src$26();
22668
- STACKBONE_CLI_BUILD_VERSION = "0.9.0";
22761
+ STACKBONE_CLI_BUILD_VERSION = "0.9.2";
22669
22762
  EMULATOR_CAPABILITY_ROUTES = {
22670
22763
  "database.postgres_direct": "/api/db",
22671
22764
  "rag.basic": "/api/rag",
@@ -28254,7 +28347,7 @@ var init_api_docs = __esmMin((() => {
28254
28347
  },
28255
28348
  {
28256
28349
  name: VOICE_WORKER_TAG,
28257
- description: "What the LiveKit voice worker calls on the box besides AG-UI: identity bootstrap before output, heard-call completion after awaited closure, and the relay that takes a call's spans to the box's OTLP collector. It takes the call token the room route put in the call's dispatch, and nothing else. Never called by Studio or by the SDK."
28350
+ description: "What the LiveKit voice worker calls on the box besides AG-UI: identity bootstrap before output, the knowledge search started on the caller's eager end of turn, heard-call completion after awaited closure, and the relay that takes a call's spans to the box's OTLP collector. It takes the call token the room route put in the call's dispatch, and nothing else. Never called by Studio or by the SDK."
28258
28351
  }
28259
28352
  ];
28260
28353
  SURFACE_TAG_DESCRIPTIONS = {
@@ -41631,6 +41724,11 @@ var init_mutations_outside_catalog = __esmMin((() => {
41631
41724
  path: "/voice/v1/calls/{sessionKey}/bootstrap",
41632
41725
  reason: "The accepted call freezes structured identity and recalls memory using its own call token, without an operator seat."
41633
41726
  },
41727
+ {
41728
+ method: "POST",
41729
+ path: "/voice/v1/calls/{sessionKey}/knowledge",
41730
+ reason: "The call worker starts the knowledge search on the caller's eager end of turn using its own call token, without an operator seat. It persists nothing; its only caller is the LiveKit worker."
41731
+ },
41634
41732
  {
41635
41733
  method: "POST",
41636
41734
  path: "/voice/v1/calls/{sessionKey}/complete",
@@ -65513,12 +65611,12 @@ var init_registry = __esmMin((() => {
65513
65611
  };
65514
65612
  const instructions = read(() => def.resolvedInstructions?.());
65515
65613
  const model = read(() => def.resolvedModel?.());
65516
- const providerSort = read(() => def.resolvedProviderSort?.());
65517
- const { providerSort: _previous, ...rest } = metadata;
65614
+ const providerRouting = readModelProviderRouting(read(() => def.resolvedProviderRouting?.()) ?? read(() => def.resolvedProviderSort?.()));
65615
+ const { providerRouting: _previous, ...rest } = metadata;
65518
65616
  return {
65519
65617
  ...rest,
65520
65618
  ...typeof model === "string" && model !== "" ? { model } : {},
65521
- ...isModelProviderSort(providerSort) ? { providerSort } : {},
65619
+ ...providerRouting !== null ? { providerRouting } : {},
65522
65620
  ...typeof instructions === "string" && instructions !== "" ? { instructions } : {}
65523
65621
  };
65524
65622
  };
@@ -73118,7 +73216,7 @@ var selectionsSelect = (kinds, names) => sql`
73118
73216
  SELECT COALESCE(s.owner_kind, r.owner_kind) AS owner_kind,
73119
73217
  COALESCE(s.owner_name, r.owner_name) AS owner_name,
73120
73218
  COALESCE(s.key, r.key) AS key,
73121
- s.model_id, s.provider_id, r.sort
73219
+ s.model_id, s.provider_id, r.sort, r.routing
73122
73220
  FROM ${modelSelections} s
73123
73221
  FULL OUTER JOIN ${modelRouting} r
73124
73222
  ON r.owner_kind = s.owner_kind AND r.owner_name = s.owner_name AND r.key = s.key
@@ -73139,7 +73237,7 @@ var createModelSelectionsReader = (opts) => {
73139
73237
  keys.set(row.key, {
73140
73238
  modelId: row.model_id,
73141
73239
  providerId: row.provider_id,
73142
- providerSort: isModelProviderSort(row.sort) ? row.sort : null
73240
+ providerRouting: readModelProviderRouting(row.routing ?? row.sort)
73143
73241
  });
73144
73242
  byOwner.set(id, keys);
73145
73243
  }
@@ -86674,14 +86772,19 @@ var notFound$1 = {
86674
86772
  code: "studio_model_selection_not_found",
86675
86773
  message: "No model is picked for that key. Its code default already applies."
86676
86774
  };
86677
- var rowToRouting = (row) => ({
86678
- ownerKind: "agent",
86679
- ownerName: row.ownerName,
86680
- key: row.key,
86681
- sort: row.sort,
86682
- updatedBy: row.updatedBy,
86683
- updatedAt: row.updatedAt.toISOString()
86684
- });
86775
+ var rowToRouting = (row) => {
86776
+ const routing = readModelProviderRouting(row.routing ?? row.sort);
86777
+ if (!routing) throw new Error("model_routing_row_unreadable");
86778
+ return {
86779
+ ownerKind: "agent",
86780
+ ownerName: row.ownerName,
86781
+ key: row.key,
86782
+ routing,
86783
+ sort: plainSortOf(routing),
86784
+ updatedBy: row.updatedBy,
86785
+ updatedAt: row.updatedAt.toISOString()
86786
+ };
86787
+ };
86685
86788
  var routingNotFound = {
86686
86789
  code: "studio_model_routing_not_found",
86687
86790
  message: "No provider routing is picked for that key. The code's `modelRouting` already applies."
@@ -86811,10 +86914,10 @@ var createModelSelectionsRouter = (opts) => {
86811
86914
  tags: [MODEL_SELECTIONS_TAG],
86812
86915
  middleware: [requireCapability("config:write")],
86813
86916
  summary: "Pick the provider routing for one key",
86814
- description: "Sets which OpenRouter provider one model key of a deep agent prefers (`provider: { sort }` on its model calls): `latency`, `throughput` or `price`. It outranks the code's `modelRouting`, and a subagent key with none follows the agent's. The box drops the field on any gateway that is not OpenRouter. The agent is rebuilt to apply it.",
86917
+ description: "Sets which OpenRouter providers one model key of a deep agent goes to: OpenRouter's `provider` object on its model calls, e.g. `{ \"routing\": { \"only\": [\"deepinfra/fp8\"], \"allow_fallbacks\": false } }`, or `{ \"sort\": \"latency\" }` as shorthand for `{ \"routing\": { \"sort\": \"latency\" } }`. It outranks the code's `modelRouting`, and a subagent key with none follows the agent's. The box drops the field on any gateway that is not OpenRouter. The agent is rebuilt to apply it.",
86815
86918
  request: {
86816
86919
  params: studioModelRoutingPathSchema,
86817
- body: jsonBody(named("PutStudioModelRoutingRequest", putStudioModelRoutingRequestSchema), "The provider sort.")
86920
+ body: jsonBody(named("PutStudioModelRoutingRequest", putStudioModelRoutingRequestSchema), "The provider routing, or a plain sort.")
86818
86921
  },
86819
86922
  responses: {
86820
86923
  200: jsonResponse$1(named("StudioModelRouting", studioModelRoutingSchema), "The routing as it now stands."),
@@ -86823,12 +86926,14 @@ var createModelSelectionsRouter = (opts) => {
86823
86926
  }
86824
86927
  }), async (c) => {
86825
86928
  const path = c.req.valid("param");
86826
- const { sort } = c.req.valid("json");
86929
+ const routing = routingOfPutRequest(c.req.valid("json"));
86930
+ const sort = plainSortOf(routing);
86827
86931
  const actor = readActorUserId(c);
86828
86932
  const row = await opts.db.transaction(async (tx) => {
86829
86933
  const [written] = await tx.insert(modelRouting).values({
86830
86934
  ...path,
86831
86935
  sort,
86936
+ routing,
86832
86937
  updatedBy: actor
86833
86938
  }).onConflictDoUpdate({
86834
86939
  target: [
@@ -86838,6 +86943,7 @@ var createModelSelectionsRouter = (opts) => {
86838
86943
  ],
86839
86944
  set: {
86840
86945
  sort,
86946
+ routing,
86841
86947
  updatedBy: actor,
86842
86948
  updatedAt: sql`now()`
86843
86949
  }
@@ -86848,7 +86954,8 @@ var createModelSelectionsRouter = (opts) => {
86848
86954
  subjectType: "model_selection",
86849
86955
  payload: {
86850
86956
  ...selectionSubject(path),
86851
- sort
86957
+ ...sort !== null ? { sort } : {},
86958
+ routing
86852
86959
  }
86853
86960
  }, tx);
86854
86961
  return written;
@@ -91331,9 +91438,9 @@ var lookUpLiveKitCredentials = async (opts) => {
91331
91438
  credentials = await readLiveKitCredentials(opts);
91332
91439
  } catch (err) {
91333
91440
  if (!(err instanceof SecretUndecryptableError)) throw err;
91334
- return voiceNotConfigured("This box cannot read the saved LiveKit credentials: they were saved with another key. Open the Voice settings screen and save them again.");
91441
+ return voiceNotConfigured("This box cannot read the saved LiveKit credentials: they were saved with another key. Open the Voice provider screen and save them again.");
91335
91442
  }
91336
- if (!credentials) return voiceNotConfigured("This box has no LiveKit credentials. Save the URL, API key and API secret on the Voice settings screen.");
91443
+ if (!credentials) return voiceNotConfigured("This box has no LiveKit credentials. Save the URL, API key and API secret on the Voice provider screen.");
91337
91444
  return { credentials };
91338
91445
  };
91339
91446
  /**
@@ -93377,11 +93484,17 @@ var answerOf = async (storage, row) => {
93377
93484
  };
93378
93485
  //#endregion
93379
93486
  //#region ../../libs/runtime/workspace-runtime/src/lib/voice/voice-rag.ts
93380
- var WORD_CHARACTER, spokenWordCount, knowledgeQuery, selectKnowledge;
93487
+ var WORD_CHARACTER, spokenWordCount, knowledgeQuery, PUNCTUATION_OR_SYMBOL_RUN, prefetchSentence, prefetchKey, selectKnowledge;
93381
93488
  var init_voice_rag = __esmMin((() => {
93382
93489
  WORD_CHARACTER = /[\p{L}\p{N}]/u;
93383
93490
  spokenWordCount = (text) => text.trim().split(/\s+/u).filter((token) => WORD_CHARACTER.test(token)).length;
93384
93491
  knowledgeQuery = ({ exchanges, sentence }) => [...exchanges.flatMap(({ question, reply }) => [question, reply]), sentence].map((piece) => piece.trim()).filter((piece) => piece !== "").join("\n");
93492
+ PUNCTUATION_OR_SYMBOL_RUN = /[\p{P}\p{S}]+/gu;
93493
+ prefetchSentence = (text) => text.normalize("NFC").toLowerCase().replace(PUNCTUATION_OR_SYMBOL_RUN, " ").replace(/\s+/gu, " ").trim();
93494
+ prefetchKey = ({ exchanges, sentence }) => knowledgeQuery({
93495
+ exchanges,
93496
+ sentence: prefetchSentence(sentence)
93497
+ });
93385
93498
  selectKnowledge = (hits, { minScore, topK }) => {
93386
93499
  const passages = hits.filter((hit) => Number.isFinite(hit.score) && hit.score >= minScore).map((hit) => hit.content).filter((content) => typeof content === "string" && content.trim() !== "").slice(0, topK);
93387
93500
  return passages.length === 0 ? void 0 : JSON.stringify(passages);
@@ -93427,7 +93540,7 @@ var init_voice_calls = __esmMin((() => {
93427
93540
  const words = spokenWordCount(text);
93428
93541
  return words !== 0 && words >= rag.minWords;
93429
93542
  };
93430
- const searchKnowledge = async (call, text) => {
93543
+ const searchKnowledge = async (call, text, origin = "turn") => {
93431
93544
  const { rag } = call;
93432
93545
  if (!searchable(rag, text)) return void 0;
93433
93546
  const { collection } = rag;
@@ -93435,6 +93548,7 @@ var init_voice_calls = __esmMin((() => {
93435
93548
  exchanges: call.exchanges,
93436
93549
  sentence: text
93437
93550
  });
93551
+ const search = origin === "prefetch" ? "rag_prefetch" : "rag_search";
93438
93552
  const warn = (code) => options.logger.warn(`voice ${call.input.agent}/${call.input.sessionKey}: ${code}.`);
93439
93553
  try {
93440
93554
  const result = await withTimeout(retrieve({
@@ -93443,7 +93557,7 @@ var init_voice_calls = __esmMin((() => {
93443
93557
  topK: rag.topK
93444
93558
  }), rag.timeoutMs);
93445
93559
  if (result.error) {
93446
- warn(`rag_search_failed (${result.error.code})`);
93560
+ warn(`${search}_failed (${result.error.code})`);
93447
93561
  return;
93448
93562
  }
93449
93563
  if (result.data.length === 0 && !call.ragEmptyWarned) {
@@ -93452,11 +93566,31 @@ var init_voice_calls = __esmMin((() => {
93452
93566
  }
93453
93567
  return selectKnowledge(result.data, rag);
93454
93568
  } catch (error) {
93455
- if (error instanceof ActionTimeoutError) warn("rag_search_timeout");
93456
- else warn(`rag_search_failed (${error instanceof Error ? error.name : typeof error})`);
93569
+ if (error instanceof ActionTimeoutError) warn(`${search}_timeout`);
93570
+ else warn(`${search}_failed (${error instanceof Error ? error.name : typeof error})`);
93457
93571
  }
93458
93572
  };
93573
+ let inputGuard;
93459
93574
  return {
93575
+ useInputGuard: (guard) => {
93576
+ inputGuard = guard;
93577
+ },
93578
+ prefetchKnowledge: async (agent, sessionKey, text) => {
93579
+ const call = find(agent, sessionKey);
93580
+ const sequence = ++call.prefetches;
93581
+ const guard = inputGuard;
93582
+ if (!guard || call.completion || !searchable(call.rag, text)) return;
93583
+ const guarded = await Promise.resolve().then(() => guard(agent, text)).catch(() => "skip");
93584
+ if (sequence !== call.prefetches || call.completion) return;
93585
+ if (guarded === "skip" || guarded.blocked || !searchable(call.rag, guarded.content)) return;
93586
+ call.prefetch = {
93587
+ key: prefetchKey({
93588
+ exchanges: call.exchanges,
93589
+ sentence: guarded.content
93590
+ }),
93591
+ promise: searchKnowledge(call, guarded.content, "prefetch")
93592
+ };
93593
+ },
93460
93594
  accept: async (input) => {
93461
93595
  const registry = options.getRegistry();
93462
93596
  if (!registry) throw new Error("Voice call memory service is unavailable.");
@@ -93470,6 +93604,7 @@ var init_voice_calls = __esmMin((() => {
93470
93604
  rag: input.rag && Object.freeze({ ...input.rag }),
93471
93605
  refusalPhrase: input.refusalPhrase ?? VOICE_SETTINGS_DEFAULTS.refusalPhrase,
93472
93606
  exchanges: [],
93607
+ prefetches: 0,
93473
93608
  startedAt,
93474
93609
  expiresAt: startedAt + CALL_TOKEN_TTL_SECONDS * 1e3,
93475
93610
  work: /* @__PURE__ */ new Set()
@@ -93536,7 +93671,21 @@ var init_voice_calls = __esmMin((() => {
93536
93671
  done();
93537
93672
  };
93538
93673
  },
93539
- knowledgeFor: (text) => searchKnowledge(call, text)
93674
+ knowledgeFor: (text, notePrefetch) => {
93675
+ const { prefetch } = call;
93676
+ delete call.prefetch;
93677
+ call.prefetches++;
93678
+ if (!searchable(call.rag, text)) return Promise.resolve(void 0);
93679
+ if (prefetch?.key === prefetchKey({
93680
+ exchanges: call.exchanges,
93681
+ sentence: text
93682
+ })) {
93683
+ notePrefetch?.("hit");
93684
+ return prefetch.promise;
93685
+ }
93686
+ notePrefetch?.(prefetch ? "miss" : "none");
93687
+ return searchKnowledge(call, text);
93688
+ }
93540
93689
  });
93541
93690
  call.context = context;
93542
93691
  if (call.input.startRecording) Promise.resolve().then(() => call.input.startRecording(sessionAddressFor(agent, sessionKey, void 0, sessionIdentityFor(identity)))).catch(() => options.logger.warn(`voice ${agent}/${sessionKey}: recording_start_failed.`));
@@ -93625,7 +93774,7 @@ var createVoiceRoomsRouter = (options) => {
93625
93774
  middleware: [requireCapability(VOICE_CAPABILITY$1)],
93626
93775
  "x-stackbone-catalog": excludeFromCatalog("Answers a LiveKit participant token. The operation catalog never offers an operation whose answer is a credential."),
93627
93776
  summary: "Open a voice call with a deep agent",
93628
- description: "Opens a fresh room `call-<agent>-<8 hex>` and answers the LiveKit URL, the browser's token to join it (it joins within five minutes; a call already running is not cut) and the settings the call runs with. The token carries the room's agent dispatch: joining the room sends the voice worker in with the agent's name, its effective voice settings, the session key `voice-<room>` every turn lands in, the tags each turn's run carries (`channel: voice`, `room`, and `origin`, which the caller states and which defaults to `studio`), the box's public URL the worker posts its turns to, and a call token: the worker's bearer for this call, valid two hours, until the box restarts, on the AG-UI chat of this agent and this session key on the trace relay `POST /voice/v1/traces`, and on this call's identity bootstrap and heard completion at `/voice/v1/calls/{sessionKey}/bootstrap` and `/voice/v1/calls/{sessionKey}/complete`, nowhere else. The box freezes memory policy, the agent's knowledge settings (`rag`) and its refusal phrase (`refusalPhrase`, what the caller hears when a guardrail stops their sentence or the agent's reply), none of them in the dispatch, at room acceptance, so a change saved in Studio reaches the next call; the worker supplies the original SIP participant's phoneNumber before output, or the browser call uses explicit identities supplied on this request. The bootstrap response contains no recalled facts or policy. On a box that exports traces (`OTEL_EXPORTER_OTLP_*`), the dispatch also asks the worker to send the call's spans to that relay with that token. The call is recorded to the box bucket as audio-only OGG only when the agent's `recording` voice setting is on (it is off by default) and the box bucket is not on a loopback or private host; a recording that fails never fails the call. The LiveKit API key and secret are never answered. The call token is answered only inside the room token's dispatch, which anyone holding the room token can read.",
93777
+ description: "Opens a fresh room `call-<agent>-<8 hex>` and answers the LiveKit URL, the browser's token to join it (it joins within five minutes; a call already running is not cut) and the settings the call runs with. The token carries the room's agent dispatch: joining the room sends the voice worker in with the agent's name, its effective voice settings, the session key `voice-<room>` every turn lands in, the tags each turn's run carries (`channel: voice`, `room`, and `origin`, which the caller states and which defaults to `studio`), the box's public URL the worker posts its turns to, and a call token: the worker's bearer for this call, valid two hours, until the box restarts, on the AG-UI chat of this agent and this session key on the trace relay `POST /voice/v1/traces`, and on this call's identity bootstrap, knowledge prefetch and heard completion at `/voice/v1/calls/{sessionKey}/bootstrap`, `/voice/v1/calls/{sessionKey}/knowledge` and `/voice/v1/calls/{sessionKey}/complete`, nowhere else. The box freezes memory policy, the agent's knowledge settings (`rag`) and its refusal phrase (`refusalPhrase`, what the caller hears when a guardrail stops their sentence or the agent's reply), none of them in the dispatch, at room acceptance, so a change saved in Studio reaches the next call; the worker supplies the original SIP participant's phoneNumber before output, or the browser call uses explicit identities supplied on this request. The bootstrap response contains no recalled facts or policy. On a box that exports traces (`OTEL_EXPORTER_OTLP_*`), the dispatch also asks the worker to send the call's spans to that relay with that token. The call is recorded to the box bucket as audio-only OGG only when the agent's `recording` voice setting is on (it is off by default) and the box bucket is not on a loopback or private host; a recording that fails never fails the call. The LiveKit API key and secret are never answered. The call token is answered only inside the room token's dispatch, which anyone holding the room token can read.",
93629
93778
  request: { body: jsonBody(named("VoiceRoomRequest", voiceRoomRequestSchema), "The agent to call.") },
93630
93779
  responses: {
93631
93780
  200: jsonResponse$1(named("VoiceRoomResponse", voiceRoomResponseSchema), "The room, its participant token, and what the dispatch carries."),
@@ -94083,18 +94232,21 @@ var createVoiceCallsRouter = (options) => {
94083
94232
  options.logger.warn(principal?.type === "call" ? `voice ${principal.agent}/${principal.sessionKey}: lifecycle_failed.` : "voice lifecycle request failed.");
94084
94233
  return c.json(errorEnvelope("voice_lifecycle_failed", "The voice lifecycle operation failed."), 500);
94085
94234
  });
94235
+ const callResponses = {
94236
+ 200: jsonResponse$1(named("VoiceCallLifecycleResponse", voiceCallLifecycleResponseSchema), "The call operation completed."),
94237
+ 400: errorResponse("Invalid bounded call payload."),
94238
+ 401: errorResponse("Missing, invalid or expired call token."),
94239
+ 403: errorResponse("Only the call token for this session is admitted."),
94240
+ 404: errorResponse("No accepted call for this credential."),
94241
+ 413: errorResponse("The payload exceeds 1 MiB.")
94242
+ };
94086
94243
  const shared = {
94087
94244
  method: "post",
94088
94245
  tags: ["voice-worker"],
94089
94246
  security: [{ voiceCallToken: [] }],
94090
94247
  responses: {
94091
- 200: jsonResponse$1(named("VoiceCallLifecycleResponse", voiceCallLifecycleResponseSchema), "The call operation completed."),
94092
- 400: errorResponse("Invalid bounded call payload."),
94093
- 401: errorResponse("Missing, invalid or expired call token."),
94094
- 403: errorResponse("Only the call token for this session is admitted."),
94095
- 404: errorResponse("No accepted call for this credential."),
94248
+ ...callResponses,
94096
94249
  409: errorResponse("The call identity is frozen or the call is completing."),
94097
- 413: errorResponse("The payload exceeds 1 MiB."),
94098
94250
  500: errorResponse("Settings, recall or extraction failed.")
94099
94251
  }
94100
94252
  };
@@ -94112,6 +94264,22 @@ var createVoiceCallsRouter = (options) => {
94112
94264
  await options.calls.bootstrap(principal.agent, principal.sessionKey, c.req.valid("json").identities);
94113
94265
  return c.json({ ok: true }, 200);
94114
94266
  });
94267
+ app.openapi(createRoute({
94268
+ ...shared,
94269
+ responses: callResponses,
94270
+ path: "/{sessionKey}/knowledge",
94271
+ summary: "Start the knowledge search on the caller's eager end of turn",
94272
+ description: "Runs the agent's deterministic input guardrails over the sentence the speech model guessed ends the caller's turn, starts the knowledge search on the masked text and answers without waiting for it. The turn whose sentence matches it, ignoring case, spacing and punctuation, takes those passages instead of searching again. An agent without a knowledge collection or with a model-backed input guardrail, a sentence too short to search, a blocked sentence and a completing call answer the same and start nothing. Writes nothing to the conversation and calls no chat model.",
94273
+ request: {
94274
+ params: z$1.object({ sessionKey: z$1.string() }),
94275
+ body: jsonBody(named("VoiceCallKnowledge", voiceCallKnowledgeSchema), "The sentence the speech model guessed ends the caller's turn.")
94276
+ }
94277
+ }), async (c) => {
94278
+ const principal = readRequestPrincipal(c);
94279
+ if (principal.type !== "call") throw new Error("Missing call principal");
94280
+ await options.calls.prefetchKnowledge(principal.agent, principal.sessionKey, c.req.valid("json").text);
94281
+ return c.json({ ok: true }, 200);
94282
+ });
94115
94283
  app.openapi(createRoute({
94116
94284
  ...shared,
94117
94285
  path: "/{sessionKey}/complete",
@@ -94326,7 +94494,7 @@ var modelWarmupBody = (target) => ({
94326
94494
  content: WARMUP_USER_TEXT
94327
94495
  }],
94328
94496
  reasoning: { enabled: true },
94329
- ...target.providerSort ? { provider: providerRoutingFor(target.providerSort) } : {}
94497
+ ...target.providerRouting ? { provider: providerRoutingFor(target.providerRouting) } : {}
94330
94498
  });
94331
94499
  var createModelWarmup = (deps) => {
94332
94500
  const timeoutMs = deps.timeoutMs ?? 2e4;
@@ -94337,7 +94505,7 @@ var createModelWarmup = (deps) => {
94337
94505
  const body = modelWarmupBody({
94338
94506
  model: entry.metadata.model,
94339
94507
  instructions: entry.metadata.instructions,
94340
- providerSort: entry.metadata.providerSort
94508
+ providerRouting: entry.metadata.providerRouting
94341
94509
  });
94342
94510
  const signal = AbortSignal.timeout(timeoutMs);
94343
94511
  try {
@@ -95137,7 +95305,7 @@ async function probeDurableInterruptState(graph, threadId, logger) {
95137
95305
  return { supported: false };
95138
95306
  }
95139
95307
  }
95140
- var nowIso, CLIENT_MOVED_ON, CLIENT_TOOL_ANSWER_ROUNDS, createDeepAgentChat, hintDurableSessions, voiceCallConfigurable, guardrailHoldReply, toDecisionStep, toRedactionStep, toParkStep;
95308
+ var nowIso, CLIENT_MOVED_ON, CLIENT_TOOL_ANSWER_ROUNDS, createDeepAgentChat, hintDurableSessions, voiceCallConfigurable, guardrailHoldReply, toDecisionStep, toRedactionStep, toParkStep, agentPolicy, evaluateInput;
95141
95309
  var init_chat = __esmMin((() => {
95142
95310
  init_studio_continuation();
95143
95311
  init_abortable();
@@ -95209,10 +95377,7 @@ var init_chat = __esmMin((() => {
95209
95377
  async function readPolicy(agentName) {
95210
95378
  if (!opts.guardrails) return [];
95211
95379
  try {
95212
- return await opts.guardrails.effectiveGuardrails({
95213
- kind: "agent",
95214
- name: agentName
95215
- });
95380
+ return await agentPolicy(opts.guardrails, agentName);
95216
95381
  } catch (err) {
95217
95382
  logger.warn(`deep-agent guardrail policy read failed for "${agentName}" — the turn runs unguarded: ${messageOf$6(err)}`);
95218
95383
  return [];
@@ -95240,13 +95405,7 @@ var init_chat = __esmMin((() => {
95240
95405
  if (guardrails.length === 0) return unguarded;
95241
95406
  try {
95242
95407
  const original = lastUserMessage(turn.messages);
95243
- const classify = classifierFor(signal);
95244
- const result = await evaluateGuardrails({
95245
- guardrails,
95246
- content: original,
95247
- target: "input",
95248
- ...classify ? { classify } : {}
95249
- });
95408
+ const result = await evaluateInput(guardrails, original, classifierFor(signal));
95250
95409
  return {
95251
95410
  turn: result.content === original ? turn : withLastUserMessage(turn, result.content),
95252
95411
  blocked: result.allowed ? null : result.decision,
@@ -95259,6 +95418,27 @@ var init_chat = __esmMin((() => {
95259
95418
  }
95260
95419
  }
95261
95420
  /**
95421
+ * The input border's deterministic rules over one sentence that is not a turn yet:
95422
+ * the guess a voice call's knowledge prefetch searches (STA3-94 D2). It never asks
95423
+ * the classifier, which the guardrail ADR spends once per turn, and writes no run
95424
+ * step: the turn that follows evaluates the same rules and records them.
95425
+ */
95426
+ const guardInput = async (agentName, text) => {
95427
+ let guardrails = [];
95428
+ if (opts.guardrails) try {
95429
+ guardrails = await agentPolicy(opts.guardrails, agentName);
95430
+ } catch {
95431
+ return "skip";
95432
+ }
95433
+ const input = guardrails.filter((rule) => rule.enabled && rule.target === "input");
95434
+ if (input.some((rule) => isModelBackedCheck(rule.checkType))) return "skip";
95435
+ const result = await evaluateInput(input, text);
95436
+ return {
95437
+ blocked: !result.allowed,
95438
+ content: result.content
95439
+ };
95440
+ };
95441
+ /**
95262
95442
  * Wrap a turn's stream with the OUTPUT border: the reply is held until the policy
95263
95443
  * has judged it, then released, released masked, or dropped with a reason.
95264
95444
  *
@@ -95564,7 +95744,10 @@ var init_chat = __esmMin((() => {
95564
95744
  const guardedTurn = guarded.turn;
95565
95745
  const blocked = guarded.blocked;
95566
95746
  const userMessage = lastUserMessage(guardedTurn.messages);
95567
- const knowledge = registered && blocked === null ? voiceCall?.knowledgeFor(userMessage) : void 0;
95747
+ let prefetch;
95748
+ const knowledge = registered && blocked === null ? voiceCall?.knowledgeFor(userMessage, (outcome) => {
95749
+ prefetch = outcome;
95750
+ }) : void 0;
95568
95751
  const runInput = {
95569
95752
  trigger,
95570
95753
  input: { message: userMessage },
@@ -95574,6 +95757,7 @@ var init_chat = __esmMin((() => {
95574
95757
  memoryIdentity,
95575
95758
  ...voiceCall ? { voiceCall: true } : {},
95576
95759
  ...voiceCall?.searchesKnowledge ? { voiceKnowledgeRule: true } : {},
95760
+ ...prefetch ? { voiceKnowledgePrefetch: prefetch } : {},
95577
95761
  ...groupingKey !== void 0 ? { sessionKey: groupingKey } : {},
95578
95762
  ...turn.evalRunId !== void 0 ? evalRunStamp({
95579
95763
  evalRunId: turn.evalRunId,
@@ -96308,6 +96492,7 @@ var init_chat = __esmMin((() => {
96308
96492
  getMessageHistory,
96309
96493
  seedState,
96310
96494
  reconcileHeard,
96495
+ guardInput,
96311
96496
  runTurn: (turn) => ownedByAgent(turn.agent, runTurn(turn)),
96312
96497
  resumeTurn: ownedResumeTurn
96313
96498
  };
@@ -96334,6 +96519,16 @@ var init_chat = __esmMin((() => {
96334
96519
  ...guardrailParkRow(decision, approvalId),
96335
96520
  status: "running"
96336
96521
  });
96522
+ agentPolicy = (reader, agentName) => reader.effectiveGuardrails({
96523
+ kind: "agent",
96524
+ name: agentName
96525
+ });
96526
+ evaluateInput = (guardrails, content, classify) => evaluateGuardrails({
96527
+ guardrails,
96528
+ content,
96529
+ target: "input",
96530
+ ...classify ? { classify } : {}
96531
+ });
96337
96532
  }));
96338
96533
  //#endregion
96339
96534
  //#region ../../libs/runtime/workspace-runtime/src/lib/deep-agents/http/run-tags-header.ts
@@ -96992,10 +97187,12 @@ var init_mount$1 = __esmMin((() => {
96992
97187
  ...options.classify ? { classify: options.classify } : {},
96993
97188
  ...options.plantAmbientBridges ? { plantAmbientBridges: options.plantAmbientBridges } : {}
96994
97189
  });
97190
+ const voiceCalls = voiceCallsForApp(app);
97191
+ voiceCalls?.useInputGuard(chat.guardInput);
96995
97192
  const wire = {
96996
97193
  sessions: options.sessions,
96997
97194
  invocations: options.invocations,
96998
- voiceCalls: voiceCallsForApp(app),
97195
+ voiceCalls,
96999
97196
  chat,
97000
97197
  registry,
97001
97198
  logger,
@@ -104326,7 +104523,7 @@ var calls_workflow_ts_default = "import { z } from \"zod\";\nimport { streamDeep
104326
104523
  var plain_workflow_ts_default = "import { z } from \"zod\";\n\n/**\n * Workflow \"{{name}}\" — a durable pipeline.\n *\n * Each `\"use step\"` is a durable checkpoint: kill the runtime mid-run and it\n * resumes from the last completed step instead of replaying everything.\n */\n\n// THE contract — sibling exports next to the bare workflow function. The build\n// harvests these (by convention) into the manifest + a live validator. The\n// parameter type is DERIVED from `inputSchema` and the RETURN type from\n// `outputSchema` (both `z.infer`), so neither side can drift from the contract\n// the build harvests. Without the return annotation the schema is published to\n// Studio and the generated types and never compared to what the body returns\n// (PLA-1272).\nexport const inputSchema = z.object({\n message: z.string().describe(\"The input to process\"),\n});\n\nexport const outputSchema = z.object({\n message: z.string(),\n processed: z.boolean(),\n});\n\ntype {{Name}}Input = z.infer<typeof inputSchema>;\n\nexport async function {{export}}(input: {{Name}}Input): Promise<z.infer<typeof outputSchema>> {\n \"use workflow\";\n\n const result = await processMessage(input); // step 1 — durable checkpoint\n return result;\n}\n\nasync function processMessage(input: {{Name}}Input) {\n \"use step\";\n // Replace this with the deterministic work of your step (DB write, HTTP call,\n // an LLM call, …). Returning a value records it as the step's checkpoint.\n return { message: input.message, processed: true };\n}\n";
104327
104524
  //#endregion
104328
104525
  //#region src/services/scaffold/template-loader.ts
104329
- var SDK_TEMPLATE_VERSION = "0.10.0";
104526
+ var SDK_TEMPLATE_VERSION = "0.10.2";
104330
104527
  var PREFIX = "./templates/";
104331
104528
  var templates = new Map(Object.entries(/* @__PURE__ */ Object.assign({
104332
104529
  "./templates/deep-agents/index.ts.tmpl": index_ts_default,
@@ -106449,7 +106646,7 @@ var buildWorkspaceBundle = async (options) => {
106449
106646
  const artifactPath = resolve(outDir, CONFIG_SCHEMA_ARTIFACT_PATH);
106450
106647
  await mkdir(dirname(artifactPath), { recursive: true });
106451
106648
  await writeFile(artifactPath, JSON.stringify(configSchema), "utf8");
106452
- } else logger?.warn(`This workspace declares ${CONFIG_SCHEMA_SOURCE}, but no config JSON Schema was produced for the bundle. Studio's config editor will fall back to a raw JSON field and writes will not be validated.`);
106649
+ } else logger?.warn(`This workspace declares ${CONFIG_SCHEMA_SOURCE}, but no config JSON Schema was produced for the bundle. Studio shows no Dynamic config section (edit the values with \`stackbone config\`) and writes will not be validated.`);
106453
106650
  }
106454
106651
  const manifest = deriveWorkspaceManifest({
106455
106652
  workspace,
@@ -106552,7 +106749,7 @@ var extractConfigSchema = async (projectRoot, opts = {}) => {
106552
106749
  try {
106553
106750
  loaded = await loader(projectRoot);
106554
106751
  } catch (err) {
106555
- emit(`[config-schema] config schema extraction bundle failed; no config schema is available — the Config editor will fall back to a raw JSON field. Cause: ${errMessage$3(err)}`);
106752
+ emit(`[config-schema] config schema extraction bundle failed; no config schema is available — Studio shows no Dynamic config section; edit the values with \`stackbone config\`. Cause: ${errMessage$3(err)}`);
106556
106753
  return {
106557
106754
  schema: null,
106558
106755
  warnings
@@ -106568,7 +106765,7 @@ var extractConfigSchema = async (projectRoot, opts = {}) => {
106568
106765
  warnings
106569
106766
  };
106570
106767
  } catch (err) {
106571
- emit(`[config-schema] config.schema.ts could not be converted to JSON Schema — the Config editor will fall back to a raw JSON field. Cause: ${errMessage$3(err)}`);
106768
+ emit(`[config-schema] config.schema.ts could not be converted to JSON Schema — Studio shows no Dynamic config section; edit the values with \`stackbone config\`. Cause: ${errMessage$3(err)}`);
106572
106769
  return {
106573
106770
  schema: null,
106574
106771
  warnings
@@ -106840,6 +107037,57 @@ var literalText = (ts, node) => {
106840
107037
  return null;
106841
107038
  };
106842
107039
  /**
107040
+ * A value written down as JSON would be: strings, numbers (a leading `-` included),
107041
+ * booleans, `null`, and arrays and object literals of those. `undefined` for
107042
+ * anything else ANYWHERE inside it — an identifier, a spread, a computed key, an
107043
+ * interpolation — because a value read in part is a value guessed at. `as const`
107044
+ * and `satisfies` around it are type-only and looked through.
107045
+ */
107046
+ var literalJson = (ts, node) => {
107047
+ const K = ts.SyntaxKind;
107048
+ if (!node) return void 0;
107049
+ const text = literalText(ts, node);
107050
+ if (text !== null) return text;
107051
+ switch (node.kind) {
107052
+ case K["AsExpression"]:
107053
+ case K["SatisfiesExpression"]:
107054
+ case K["ParenthesizedExpression"]: return literalJson(ts, node.expression);
107055
+ case K["NumericLiteral"]: return Number(node.text);
107056
+ case K["TrueKeyword"]: return true;
107057
+ case K["FalseKeyword"]: return false;
107058
+ case K["NullKeyword"]: return null;
107059
+ case K["PrefixUnaryExpression"]: {
107060
+ const unary = node;
107061
+ if (unary.operator !== K["MinusToken"] || unary.operand.kind !== K["NumericLiteral"]) return;
107062
+ return -Number(unary.operand.text);
107063
+ }
107064
+ case K["ArrayLiteralExpression"]: {
107065
+ const items = [];
107066
+ for (const element of node.elements) {
107067
+ const item = literalJson(ts, element);
107068
+ if (item === void 0) return void 0;
107069
+ items.push(item);
107070
+ }
107071
+ return items;
107072
+ }
107073
+ case K["ObjectLiteralExpression"]: {
107074
+ const object = {};
107075
+ for (const property of node.properties) {
107076
+ if (property.kind !== K["PropertyAssignment"]) return void 0;
107077
+ const assignment = property;
107078
+ const key = literalKey(ts, assignment.name);
107079
+ const value = literalJson(ts, assignment.initializer);
107080
+ if (key === null || value === void 0) return void 0;
107081
+ object[key] = value;
107082
+ }
107083
+ return object;
107084
+ }
107085
+ default: return;
107086
+ }
107087
+ };
107088
+ /** An object literal key written as a name or a string, never a computed one. */
107089
+ var literalKey = (ts, name) => name.kind === ts.SyntaxKind["Identifier"] || name.kind === ts.SyntaxKind["StringLiteral"] ? name.text ?? null : null;
107090
+ /**
106843
107091
  * A plain string literal's text, and nothing else: narrower than `literalText`,
106844
107092
  * which also takes a template with no substitution.
106845
107093
  */
@@ -107071,6 +107319,14 @@ var MODELS_USE_METHOD = "use";
107071
107319
  var MODEL_PROPERTY = "model";
107072
107320
  /** The property an agent and each of its subagents names its provider routing in. */
107073
107321
  var ROUTING_PROPERTY = "modelRouting";
107322
+ /**
107323
+ * A key's routing as the manifest records it: the object, and its plain-sort twin,
107324
+ * which is all a runtime older than the object reads.
107325
+ */
107326
+ var routingFields = (routing) => ({
107327
+ defaultProviderRouting: routing,
107328
+ defaultProviderSort: plainSortOf(routing)
107329
+ });
107074
107330
  var MODEL_SCAN_MESSAGES = {
107075
107331
  noCompiler: "[models] cannot resolve typescript from the workspace, so the model inventory was not collected — Studio will not know which models this workspace declares. Install it as a dev dependency of the project.",
107076
107332
  unparsable: (file) => `[models] could not parse ${file}; the models it declares are missing from the inventory.`
@@ -107105,7 +107361,7 @@ var scanSource$1 = (source) => {
107105
107361
  key,
107106
107362
  defaultModelId: literalText(ts, call.arguments[1]),
107107
107363
  overridable: true,
107108
- defaultProviderSort: null,
107364
+ ...routingFields(null),
107109
107365
  file,
107110
107366
  line: lineOf(call)
107111
107367
  });
@@ -107129,7 +107385,7 @@ var recordModel = (existing, ref, warn) => {
107129
107385
  existing.push(ref);
107130
107386
  return;
107131
107387
  }
107132
- if (already.defaultModelId !== ref.defaultModelId || already.overridable !== ref.overridable || already.defaultProviderSort !== ref.defaultProviderSort) warn(`[models] \`${ref.key}\` is declared again in ${ref.file} with a different default. One key is one pick, so the first declaration (${already.file}:${already.line}) is the one an operator sees — make the two agree, or give them different keys.`, {
107388
+ if (already.defaultModelId !== ref.defaultModelId || already.overridable !== ref.overridable || JSON.stringify(already.defaultProviderRouting) !== JSON.stringify(ref.defaultProviderRouting)) warn(`[models] \`${ref.key}\` is declared again in ${ref.file} with a different default. One key is one pick, so the first declaration (${already.file}:${already.line}) is the one an operator sees — make the two agree, or give them different keys.`, {
107133
107389
  file: ref.file,
107134
107390
  line: ref.line
107135
107391
  });
@@ -107147,16 +107403,19 @@ var collectAgentModels = (source) => {
107147
107403
  const { ts, file, lineOf, at, warn } = source;
107148
107404
  const found = [];
107149
107405
  /**
107150
- * The `modelRouting` the config writes. `defineDeepAgent` refuses a value outside
107151
- * the sorts, so the agent will not load; one built at run time cannot be read
107152
- * here. Both are recorded as "no routing in the code", and the first is warned.
107406
+ * The `modelRouting` the config writes: a sort, or OpenRouter's `provider` object
107407
+ * written as a literal. `defineDeepAgent` refuses a value outside the schema, so
107408
+ * the agent will not load; one built at run time cannot be read here. Both are
107409
+ * recorded as "no routing in the code", and the first is warned.
107153
107410
  */
107154
107411
  const routingOf = (label, config) => {
107155
107412
  const value = propertyValue(ts, config, ROUTING_PROPERTY);
107156
107413
  if (value === void 0) return null;
107157
- const written = literalText(ts, value);
107158
- if (written === null || isModelProviderSort(written)) return written;
107159
- warn(`[models] ${label} in ${file} declares \`modelRouting\` '${written}', which is not one of ${MODEL_PROVIDER_SORTS_TEXT}. The agent is refused when it is defined.`, at(value));
107414
+ const written = literalJson(ts, value);
107415
+ if (written === void 0) return null;
107416
+ const parsed = modelRoutingInputSchema.safeParse(written);
107417
+ if (parsed.success) return toModelProviderRouting(parsed.data);
107418
+ warn(`[models] ${label} in ${file} declares \`modelRouting\` ${JSON.stringify(written)}, which is neither one of ${MODEL_PROVIDER_SORTS_TEXT} nor an OpenRouter \`provider\` object. The agent is refused when it is defined.`, at(value));
107160
107419
  return null;
107161
107420
  };
107162
107421
  /**
@@ -107168,13 +107427,13 @@ var collectAgentModels = (source) => {
107168
107427
  const declare = (args) => {
107169
107428
  const { label, key, config, node } = args;
107170
107429
  const declaration = propertyValue(ts, config, MODEL_PROPERTY);
107171
- const defaultProviderSort = routingOf(label, config);
107430
+ const routing = routingFields(routingOf(label, config));
107172
107431
  if (declaration === void 0) {
107173
107432
  found.push({
107174
107433
  key,
107175
107434
  defaultModelId: null,
107176
107435
  overridable: true,
107177
- defaultProviderSort,
107436
+ ...routing,
107178
107437
  file,
107179
107438
  line: lineOf(node)
107180
107439
  });
@@ -107187,7 +107446,7 @@ var collectAgentModels = (source) => {
107187
107446
  key,
107188
107447
  defaultModelId: blank ? null : written,
107189
107448
  overridable: written !== null,
107190
- defaultProviderSort,
107449
+ ...routing,
107191
107450
  file,
107192
107451
  line: lineOf(declaration)
107193
107452
  });