@bike4mind/cli 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -61,6 +61,11 @@ const hearthEventKindSchema = z$1.enum([
61
61
  "gate.resolve",
62
62
  "system"
63
63
  ]);
64
+ const hearthEventOriginSchema = z$1.enum([
65
+ "session",
66
+ "api-key",
67
+ "gateway"
68
+ ]);
64
69
  const hearthHumanBodySchema = z$1.object({
65
70
  text: z$1.string().min(1),
66
71
  format: z$1.enum(["md", "text"])
@@ -81,7 +86,8 @@ z$1.object({
81
86
  kind: hearthEventKindSchema,
82
87
  human: hearthHumanBodySchema,
83
88
  machine: hearthMachineBodySchema.optional(),
84
- refs: hearthEventRefsSchema
89
+ refs: hearthEventRefsSchema,
90
+ origin: hearthEventOriginSchema
85
91
  });
86
92
  /** djb2. Deterministic across processes and restarts, which is the whole point. */
87
93
  function hashOf(value) {
@@ -139,7 +145,18 @@ function lookup(map, kind, fallback) {
139
145
  function actorKindMarker(kind) {
140
146
  return lookup(ACTOR_KIND_MARKERS, kind, "?");
141
147
  }
142
- z$1.object({
148
+ /**
149
+ * Every field optional and unknown keys dropped: a low-disclosure hook tier
150
+ * forwards no activity block at all, and a hand-posted presence event should
151
+ * still refresh lastSeen rather than being rejected. No length caps here on
152
+ * purpose - the projection truncates, because losing a whole presence update
153
+ * over a long workspace name is the worse failure.
154
+ *
155
+ * This is the bare SHAPE only. Validate a stored or wire payload with
156
+ * `presencePayloadWithDefaults`, which reads a null payload as `{}`; this schema
157
+ * alone would reject one.
158
+ */
159
+ const presencePayloadSchema = z$1.object({
143
160
  /** Claude Code lifecycle event name; the reason fallback for tiers 0 and 1. */
144
161
  hook_event_name: z$1.string().nullish(),
145
162
  session_id: z$1.string().nullish(),
@@ -166,6 +183,14 @@ z$1.object({
166
183
  background_tasks: z$1.number().int().min(0).nullish()
167
184
  }).nullish()
168
185
  });
186
+ z$1.preprocess((v) => v ?? {}, presencePayloadSchema);
187
+ /** `machine.schema` for `delegation` events; the only schema that kind accepts. */
188
+ const DELEGATION_PAYLOAD_SCHEMA_NAME = "hearth.delegation@1";
189
+ /** Extra keys are kept: hearth_delegate spreads caller-supplied extras into the payload. */
190
+ const delegationPayloadSchema = z$1.looseObject({
191
+ targetActorId: z$1.string().min(1),
192
+ task: z$1.string().min(1).max(4e3)
193
+ });
169
194
  //#endregion
170
195
  //#region ../../b4m-core/common/dist/index.mjs
171
196
  var HTTPError = class extends Error {
@@ -269,6 +294,7 @@ const b4mLLMTools = z$1.enum([
269
294
  "edit_image",
270
295
  "music_generation",
271
296
  "audio_generation",
297
+ "video_generation",
272
298
  "weather_info",
273
299
  "web_search",
274
300
  "web_fetch",
@@ -637,6 +663,8 @@ let KnowledgeType = /* @__PURE__ */ function(KnowledgeType) {
637
663
  * excluded from every LLM-attachment and vectorization path.
638
664
  */
639
665
  KnowledgeType["AUDIO"] = "AUDIO";
666
+ /** Generated video. Media-only like AUDIO: storable and browsable, never ingested. */
667
+ KnowledgeType["VIDEO"] = "VIDEO";
640
668
  return KnowledgeType;
641
669
  }({});
642
670
  /**
@@ -896,9 +924,14 @@ let ChatModels = /* @__PURE__ */ function(ChatModels) {
896
924
  ChatModels["CLAUDE_4_5_OPUS_BEDROCK"] = "global.anthropic.claude-opus-4-5-20251101-v1:0";
897
925
  ChatModels["CLAUDE_4_6_SONNET_BEDROCK"] = "global.anthropic.claude-sonnet-4-6";
898
926
  ChatModels["CLAUDE_5_SONNET_BEDROCK"] = "global.anthropic.claude-sonnet-5";
927
+ ChatModels["CLAUDE_5_5_SONNET_BEDROCK"] = "global.anthropic.claude-sonnet-5-5";
899
928
  ChatModels["CLAUDE_4_6_OPUS_BEDROCK"] = "global.anthropic.claude-opus-4-6-v1";
900
929
  ChatModels["CLAUDE_4_7_OPUS_BEDROCK"] = "global.anthropic.claude-opus-4-7";
901
930
  ChatModels["CLAUDE_4_8_OPUS_BEDROCK"] = "global.anthropic.claude-opus-4-8";
931
+ ChatModels["CLAUDE_FABLE_5_BEDROCK"] = "global.anthropic.claude-fable-5";
932
+ ChatModels["CLAUDE_FABLE_5_1_BEDROCK"] = "global.anthropic.claude-fable-5-1";
933
+ ChatModels["CLAUDE_5_OPUS_BEDROCK"] = "global.anthropic.claude-opus-5";
934
+ ChatModels["CLAUDE_5_5_OPUS_BEDROCK"] = "global.anthropic.claude-opus-5-5";
902
935
  ChatModels["CLAUDE_3_OPUS"] = "claude-3-opus-20240229";
903
936
  ChatModels["CLAUDE_3_5_HAIKU_ANTHROPIC"] = "claude-3-5-haiku-20241022";
904
937
  ChatModels["CLAUDE_3_5_SONNET_ANTHROPIC"] = "claude-3-5-sonnet-20241022";
@@ -990,39 +1023,10 @@ let SpeechToTextModels = /* @__PURE__ */ function(SpeechToTextModels) {
990
1023
  }({});
991
1024
  Object.values(SpeechToTextModels);
992
1025
  z$1.enum(SpeechToTextModels);
993
- /**
994
- * Video Models
995
- */
996
- let VideoModels = /* @__PURE__ */ function(VideoModels) {
997
- VideoModels["SORA_2"] = "sora-2";
998
- VideoModels["SORA_2_PRO"] = "sora-2-pro";
999
- return VideoModels;
1000
- }({});
1001
- Object.values(VideoModels);
1002
- z$1.enum(VideoModels);
1003
- /**
1004
- * Video size constraints and options for Sora
1005
- */
1006
- const VIDEO_SIZE_CONSTRAINTS = { SORA: {
1007
- durations: [
1008
- 4,
1009
- 8,
1010
- 12
1011
- ],
1012
- sizes: [
1013
- "720x1280",
1014
- "1280x720",
1015
- "1024x1792",
1016
- "1792x1024"
1017
- ],
1018
- defaultDuration: 4,
1019
- defaultSize: "720x1280"
1020
- } };
1021
1026
  z$1.enum({
1022
1027
  ...ChatModels,
1023
1028
  ...ImageModels,
1024
- ...SpeechToTextModels,
1025
- ...VideoModels
1029
+ ...SpeechToTextModels
1026
1030
  });
1027
1031
  Object.freeze({
1028
1032
  requestsPerMinute: 60,
@@ -1589,7 +1593,7 @@ const ModelRecordFields = z$1.strictObject({
1589
1593
  /** Operator-owned block. Discovery may never set or clear it. */
1590
1594
  disabled: z$1.boolean().optional(),
1591
1595
  disabledReason: z$1.string().optional(),
1592
- /** Discovery-owned block ("discovered, awaiting price"). Operators may never clear it. */
1596
+ /** Discovery-owned block (e.g. "discovered, awaiting price"). Operators may never clear it. */
1593
1597
  autoDisabled: z$1.boolean().optional(),
1594
1598
  autoDisabledReason: z$1.string().optional(),
1595
1599
  private: z$1.boolean().optional(),
@@ -2034,6 +2038,8 @@ z$1.object({
2034
2038
  joinCoverage: z$1.array(DiscoveryJoinCoverage).optional(),
2035
2039
  /** Ids no aggregator matched: a work item, not a log line. */
2036
2040
  unmatchedIds: z$1.array(z$1.string()).optional(),
2041
+ /** Bedrock profile ids whose foundation id a covered listing omitted: neither sighted nor missed. */
2042
+ frozenProfileIds: z$1.array(z$1.string()).optional(),
2037
2043
  changes: DiscoveryRunChanges.optional(),
2038
2044
  /** Convergence passes the run made; the cap being hit is worth seeing. */
2039
2045
  passes: z$1.number().int().nonnegative().optional(),
@@ -2436,6 +2442,21 @@ z$1.object({
2436
2442
  onBehalfOfUserId: z$1.string().optional()
2437
2443
  }).optional()
2438
2444
  });
2445
+ const GENERATION_JOB_KINDS = ["video"];
2446
+ const GENERATION_JOB_STATES = [
2447
+ "pending",
2448
+ "running",
2449
+ "storing",
2450
+ "succeeded",
2451
+ "failed",
2452
+ "blocked",
2453
+ "cancelled"
2454
+ ];
2455
+ const GENERATION_JOB_SOURCES = [
2456
+ "api",
2457
+ "studio",
2458
+ "agent"
2459
+ ];
2439
2460
  /**
2440
2461
  * SRE Agent Trio - Shared Types
2441
2462
  *
@@ -2866,6 +2887,47 @@ const AGENT_QUEST_MANIFEST = {
2866
2887
  }
2867
2888
  };
2868
2889
  /**
2890
+ * Click-time template resolution for briefcase prompts. Pure and DOM-free so it
2891
+ * is unit-testable and reusable on any surface.
2892
+ */
2893
+ const PLACEHOLDER_RE = /\{\{(\w+)\}\}/g;
2894
+ /**
2895
+ * Replace every `{{key}}` in `promptText` with the matching context value.
2896
+ *
2897
+ * Invariants (see the briefcase blueprint's Test Patterns):
2898
+ * - SINGLE PASS: each placeholder is resolved exactly once against the ORIGINAL
2899
+ * template - a value that itself contains `{{x}}` is never re-substituted.
2900
+ * - Unknown placeholders are left untouched.
2901
+ * - null/undefined values are skipped (the placeholder is left untouched - no
2902
+ * literal "null"/"undefined" leaks into the prompt).
2903
+ * - The FUNCTION form of replace is used so `$&`, `$1`, `$$` etc. in a value
2904
+ * are inserted literally rather than interpreted as replacement patterns.
2905
+ */
2906
+ function replacePromptVariables(promptText, context) {
2907
+ const ctx = context;
2908
+ return promptText.replace(PLACEHOLDER_RE, (match, key) => {
2909
+ const value = ctx[key];
2910
+ if (value === null || value === void 0) return match;
2911
+ return String(value);
2912
+ });
2913
+ }
2914
+ /**
2915
+ * Build a fresh substitution context from the signed-in user, the selected org,
2916
+ * and the current clock. `now` is injectable for deterministic tests.
2917
+ */
2918
+ function buildPromptContext(user, organizationName, now = /* @__PURE__ */ new Date()) {
2919
+ return {
2920
+ organization: organizationName ?? void 0,
2921
+ userName: user?.name ?? void 0,
2922
+ userEmail: user?.email ?? void 0,
2923
+ userRole: user?.role ?? void 0,
2924
+ currentDateTime: now.toISOString(),
2925
+ currentDate: now.toISOString().slice(0, 10),
2926
+ currentTime: now.toISOString().slice(11, 19),
2927
+ currentYear: String(now.getUTCFullYear())
2928
+ };
2929
+ }
2930
+ /**
2869
2931
  * Input headroom the chat path holds back on top of a request's reserved output.
2870
2932
  * safeInputWindow (ChatCompletionProcess) subtracts it, so a text entry whose output
2871
2933
  * reserve leaves less than this has no room for a prompt at all.
@@ -3029,7 +3091,8 @@ const BRIEFCASE_DISALLOWED_TOOLS = [
3029
3091
  "blog_draft"
3030
3092
  ];
3031
3093
  const BriefcaseRequiredToolsSchema = z$1.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
3032
- z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
3094
+ /** A 24-char hex Mongo ObjectId string (for by-id refetch and personal CRUD). */
3095
+ const BriefcasePromptIdSchema = z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
3033
3096
  /** Shared cap for every free-text prompt body a caller can supply. Exported so the
3034
3097
  * caller-prompt caps on the chat request, the invoke params and the CLI tool import it
3035
3098
  * rather than each restating the literal. */
@@ -3056,813 +3119,1152 @@ const PromptBatchQuerySchema = z$1.object({
3056
3119
  });
3057
3120
  z$1.object({ queries: z$1.array(PromptBatchQuerySchema).min(1).max(32).refine((qs) => new Set(qs.map((q) => q.key)).size === qs.length, { message: "Batch query keys must be unique" }) });
3058
3121
  /**
3059
- * Request schema for POST /api/chat - the simplified external chat surface.
3060
- *
3061
- * Shared between the Next.js API handler (apps/client/pages/api/chat.ts, which
3062
- * validates req.body with this exact object) and the OpenAPI registry
3063
- * (b4m-core/common/src/openapi), so the published contract cannot drift from
3064
- * what the handler actually accepts. The model is optional here and resolved
3065
- * server-side from admin settings when omitted.
3066
- *
3067
- * Public-API rule: no `.catch()` / top-level `.transform()`. Both silently mutate
3068
- * caller input (fail-quiet) and are opaque to zod-to-openapi. `historyCount` uses
3069
- * `.default()` (fail loud on a bad value); unknown tool ids are filtered in the
3070
- * handler (see filterKnownTools) instead of by a schema transform. This keeps the
3071
- * schema fully OpenAPI-representable with no doc projection needed.
3122
+ * The default catalog layout: one group per seeded system category (the `type`
3123
+ * discriminator set by the briefcase seed migration) plus the caller's personal
3124
+ * prompts. Shared by the web launcher panel and the CLI's MCP prompts surface so
3125
+ * both list the same catalog. `as const` lets a consumer derive the key union
3126
+ * (e.g. for a label map) instead of restating it.
3072
3127
  */
3073
- const SimplifiedChatRequestSchema = z$1.object({
3074
- sessionId: z$1.string().nullish(),
3075
- newConversation: z$1.boolean().optional(),
3076
- message: z$1.string(),
3077
- organizationId: z$1.string().optional(),
3078
- model: z$1.string().optional(),
3079
- temperature: z$1.number().min(0).max(2).optional(),
3080
- max_tokens: z$1.number().positive().optional(),
3081
- maxTokens: z$1.number().positive().optional(),
3082
- maxOutputTokens: z$1.number().positive().optional(),
3083
- stream: z$1.boolean().prefault(false),
3084
- historyCount: z$1.number().positive().default(10),
3085
- fileIds: z$1.array(z$1.string()).prefault([]),
3086
- wait: z$1.boolean().prefault(false),
3087
- enableTools: z$1.boolean().prefault(false),
3088
- toolMode: z$1.enum(["fast", "smart"]).optional(),
3089
- tools: z$1.array(z$1.string()).optional().describe("Explicit tool ids to offer the model. A non-empty array enables tools on its own; no companion `toolMode` or `enableTools` is required. Merged with the auto-selected set under `toolMode: \"smart\"`, and ignored under `toolMode: \"fast\"`. This list ADDS to what is offered rather than restricting it - the server still offers tools of its own (for example knowledge retrieval when the session has reachable documents). Unrecognized ids are dropped rather than rejecting the request; the response reports the surviving set as `tools.effectiveTools` and the ids that are not tools in this deployment as `tools.unrecognizedTools`, since no endpoint enumerates the valid ids. `unrecognizedTools` is reported under every `toolMode` - it describes the ids, not what the mode did with them - so under `toolMode: \"fast\"` an id can be absent from `effectiveTools` (the mode discarded it) without being unrecognized. Both reported lists are deduplicated, and `unrecognizedTools` names at most the first 10 distinct ids."),
3090
- enableQuestMaster: z$1.boolean().optional(),
3091
- enableMementos: z$1.boolean().optional(),
3092
- enableAgents: z$1.boolean().optional(),
3093
- promptMode: z$1.enum([
3094
- "raw",
3095
- "grounded",
3096
- "surface"
3097
- ]).optional(),
3098
- skip_auto_offers: z$1.boolean().optional().describe("Suppress tools the server would otherwise attach on its own for this session (the knowledge-base search offer, in-app view navigation, blog drafting/editing/publishing, skill invocation, and every tool from a connected MCP server). Native tools you request explicitly in `tools` are unaffected, but an MCP server tool cannot be named through this field, so with this flag set a caller with an MCP server connected sees none of its tools regardless of `tools`. One system-prompt block goes with them: withholding in-app view navigation also drops the view-registry block that exists only to describe it. No other prompt content changes. This does not switch off retrieval: a session with forced knowledge retrieval still retrieves, and documents already attached to the session are still placed in the prompt directly. Any promptMode suppresses these too, so false has no effect alongside one."),
3099
- includePromptDetails: z$1.boolean().optional(),
3100
- includeSystemPrompt: z$1.boolean().optional(),
3101
- systemPrompt: z$1.string().max(PROMPT_TEXT_MAX).optional().describe("System-prompt text for this request only, never persisted. Rendered as a defended block appended after every other system-prompt source, with prose instructing the model to defer to organization, session and data-lake guidance. Over the cap is a 422, never truncated.")
3128
+ const BRIEFCASE_CATALOG_QUERIES = [
3129
+ {
3130
+ key: "general",
3131
+ type: "general"
3132
+ },
3133
+ {
3134
+ key: "writing",
3135
+ type: "writing"
3136
+ },
3137
+ {
3138
+ key: "learning",
3139
+ type: "learning"
3140
+ },
3141
+ {
3142
+ key: "personal",
3143
+ personal: true
3144
+ }
3145
+ ];
3146
+ const AnonymousSessionIdSchema = z$1.object({
3147
+ /** SHA256(userId|orgId|dailySalt|date) - cannot be reversed */
3148
+ hash: z$1.string(),
3149
+ /** YYYY-MM-DD for salt lookup during deletion */
3150
+ dateKey: z$1.string()
3102
3151
  });
3103
- /**
3104
- * Async ACK returned on the default (wait:false) path of POST /api/chat. The
3105
- * `type`/`errorCode` pair below is the same classifier the `wait: true` body and
3106
- * the polled quest (`GET /api/v1/quests/{id}`) carry, so it is modelled once here -
3107
- * the rest of those two bodies is NOT described by this schema. The
3108
- * handler assembles the ack body inline (apps/client/pages/api/chat.ts), so
3109
- * this schema MUST stay in sync with that `res.json({...})` shape.
3110
- */
3111
- const ChatAckSchema = z$1.object({
3112
- id: z$1.string(),
3113
- status: z$1.string(),
3114
- message_received: z$1.boolean(),
3115
- timestamp: z$1.string(),
3116
- model: z$1.string(),
3117
- sessionId: z$1.string().optional(),
3118
- message: z$1.string().optional(),
3119
- type: z$1.enum(CHAT_HISTORY_ITEM_TYPES).optional(),
3120
- errorCode: z$1.enum(QUEST_ERROR_CODES).optional(),
3121
- tools: z$1.object({
3122
- toolMode: z$1.enum(["fast", "smart"]).optional(),
3123
- autoSelectedTools: z$1.array(z$1.string()).optional(),
3124
- effectiveTools: z$1.array(z$1.string()),
3125
- unrecognizedTools: z$1.array(z$1.string()).optional()
3126
- }).optional(),
3127
- tracking_info: z$1.object({
3128
- quest_id: z$1.string(),
3129
- check_status_url: z$1.string(),
3130
- poll_url: z$1.string().optional()
3131
- })
3152
+ const SpanContextSchema = z$1.object({
3153
+ /** gen_ai.trace_id */
3154
+ traceId: z$1.string(),
3155
+ /** gen_ai.span_id */
3156
+ spanId: z$1.string()
3132
3157
  });
3133
- /**
3134
- * The quest a `wait: false` caller polls at `GET /api/v1/quests/{id}` - the outcome
3135
- * of the turn the ACK above only acknowledged.
3136
- *
3137
- * Deliberately the OUTCOME SUBSET, not the whole quest: it models only what decides
3138
- * whether the turn succeeded. The full response of that endpoint is
3139
- * QuestPollResponseSchema (schemas/quest.ts), which extends this schema and is
3140
- * the 200 body of the `getQuest` contract.
3141
- *
3142
- * A failed turn is still `status: 'done'` with the failure text in `reply`, so
3143
- * `reply` alone cannot tell an answer from a failure - `type` and `errorCode` are
3144
- * what separate a CLASSIFIED failure. A run that died with partial content is not
3145
- * one of those: `terminalRecoveryFor` (questTimeoutRecovery.ts) keeps the surviving
3146
- * content, so it polls back as `type: 'message'`. What marks it unfinished is
3147
- * `promptMeta.finishReason` - RUN_TIMED_OUT_FINISH_REASON or
3148
- * RUN_ABANDONED_FINISH_REASON (utils/stopReasons.ts) - plus a notice appended to
3149
- * the reply.
3150
- */
3151
- const PolledFallbackInfoSchema = FallbackInfoSchema.pick({
3152
- primaryModel: true,
3153
- primaryModelName: true,
3154
- fallbackModel: true,
3155
- fallbackModelName: true,
3156
- reason: true
3158
+ const OperationSchema = z$1.object({
3159
+ name: z$1.enum([
3160
+ "chat_completion",
3161
+ "agent_invoke",
3162
+ "tool_execute"
3163
+ ]),
3164
+ finishReason: z$1.enum([
3165
+ "stop",
3166
+ "length",
3167
+ "tool_use",
3168
+ "content_filter",
3169
+ "error"
3170
+ ]).optional()
3157
3171
  });
3158
- const ChatQuestPollResultSchema = z$1.object({
3159
- id: z$1.string(),
3160
- status: z$1.enum([
3161
- "pending",
3162
- "stopped",
3163
- "running",
3164
- "done"
3165
- ]).optional(),
3166
- type: z$1.enum(CHAT_HISTORY_ITEM_TYPES).optional(),
3167
- errorCode: z$1.enum(QUEST_ERROR_CODES).optional(),
3168
- reply: z$1.string().nullable().optional(),
3169
- replies: z$1.array(z$1.string()).optional(),
3170
- fallbackInfo: PolledFallbackInfoSchema.optional()
3172
+ const ModelTelemetrySchema = z$1.object({
3173
+ /** gen_ai.request.model */
3174
+ modelId: z$1.string(),
3175
+ provider: z$1.enum([
3176
+ "anthropic",
3177
+ "openai",
3178
+ "bedrock",
3179
+ "google",
3180
+ "xai",
3181
+ "ollama",
3182
+ "moonshot",
3183
+ "deepseek"
3184
+ ]),
3185
+ fallbackUsed: z$1.boolean(),
3186
+ fallbackReason: z$1.string().optional(),
3187
+ originalModelId: z$1.string().optional(),
3188
+ usedThinking: z$1.boolean(),
3189
+ /** Extended thinking tokens */
3190
+ thinkingTokensUsed: z$1.number().optional(),
3191
+ usedTools: z$1.boolean()
3171
3192
  });
3172
- /**
3173
- * Reusable JSON error envelope (plain; the OpenAPI layer annotates it).
3174
- *
3175
- * Must stay in sync with the published `ErrorResponse` component
3176
- * (../openapi/schemas.ts) - `openapi/errorEnvelopeParity.test.ts` pins the two
3177
- * together, and apps/client's errorHandler test uses this shape as the stand-in for
3178
- * the component, which is generate-time only and cannot be imported at runtime.
3179
- */
3180
- const ApiErrorSchema = z$1.object({
3181
- error: z$1.string(),
3182
- request_id: z$1.string().optional(),
3193
+ const SystemPromptDetailSchema = z$1.object({
3194
+ source: z$1.enum([
3195
+ "hardcoded",
3196
+ "admin",
3197
+ "user",
3198
+ "project",
3199
+ "session",
3200
+ "org",
3201
+ "caller"
3202
+ ]),
3203
+ /** e.g., "date_context", "tool_guidance" */
3204
+ name: z$1.string(),
3205
+ tokenCount: z$1.number(),
3206
+ wasIncluded: z$1.boolean(),
3207
+ exclusionReason: z$1.enum([
3208
+ "duplicate",
3209
+ "disabled",
3210
+ "token_limit"
3211
+ ]).optional()
3212
+ });
3213
+ const SystemPromptsTelemetrySchema = z$1.object({
3214
+ prompts: z$1.array(SystemPromptDetailSchema),
3215
+ totalTokens: z$1.number(),
3216
+ duplicateCount: z$1.number()
3217
+ });
3218
+ const FeatureContributionSchema = z$1.object({
3219
+ /** e.g., "mementos", "quest_master", "deep_research" */
3220
+ featureName: z$1.string(),
3221
+ messagesAdded: z$1.number(),
3222
+ tokenCount: z$1.number(),
3223
+ executionTimeMs: z$1.number(),
3224
+ success: z$1.boolean(),
3225
+ errorType: z$1.string().optional()
3226
+ });
3227
+ const FeaturesTelemetrySchema = z$1.object({ contributions: z$1.array(FeatureContributionSchema) });
3228
+ const TokensBySourceSchema = z$1.object({
3229
+ systemPrompts: z$1.number(),
3230
+ conversationHistory: z$1.number(),
3231
+ mementos: z$1.number(),
3232
+ fabFiles: z$1.number(),
3233
+ urlContent: z$1.number(),
3234
+ toolSchemas: z$1.number(),
3235
+ userPrompt: z$1.number(),
3183
3236
  /**
3184
- * Deprecated, sunset 2026-12-01. The `name` of whatever was thrown - our own error
3185
- * classes usually, a library/driver class name on an unhandled 500 - added to every
3186
- * body by apps/client's errorHandler. Documented here so the runtime and the spec
3187
- * agree while it is still served; do not build on it. See CONVENTIONS.md section 1.
3237
+ * Lake-sourced content injected this turn (forced retrieval + the lake-memory hot card), split out
3238
+ * of `systemPrompts`. Optional and absent means UNKNOWN, never zero - telemetry captured before
3239
+ * this bucket existed has no value to report and must not be read as "no lake content". Mirrors
3240
+ * PromptMetaTokensBySourceSchema in promptMeta.ts.
3188
3241
  */
3189
- name: z$1.string().optional()
3242
+ lakeRetrieval: z$1.number().optional()
3190
3243
  });
3191
- /**
3192
- * Error envelope for the 422 a credit-metered endpoint returns for two unrelated
3193
- * reasons: "your body is invalid" and "you cannot afford this". `errorCode` is
3194
- * what separates them - `insufficientCreditsError` (see insufficientCredits.ts)
3195
- * tags the credit case, so its absence means an ordinary validation failure.
3196
- *
3197
- * Derived from `ApiErrorSchema` rather than re-declaring `error`/`request_id`:
3198
- * both of those 422s are *thrown*, so errorHandler serves the body and adds
3199
- * `name`. Extending is what keeps that documented here (and what drops it again
3200
- * on the sunset date) instead of leaving a bespoke copy behind to drift.
3201
- */
3202
- const InsufficientCreditsErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.literal("insufficient_credits").optional() });
3203
- /** 503 when this deployment has no usable key for a provider the request needs (data-lake search, voice). */
3204
- const ProviderNotConfiguredErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.literal("provider_not_configured").optional() });
3205
- /**
3206
- * Error envelope for the 403 apiKeyAuth (apps/client/server/middlewares/apiKeyAuth.ts)
3207
- * throws when an API key lacks a route's scopes. It names what the ROUTE requires,
3208
- * never what the key holds, so a caller knows which scope to mint a key with.
3209
- * Values are `ApiKeyScope` strings; plain `z.string()` because the enum's module is
3210
- * not import-safe for the install-only openapi job (see the import note above).
3211
- */
3212
- const ScopeForbiddenErrorSchema = ApiErrorSchema.extend({
3213
- required_scopes: z$1.array(z$1.string()).optional().describe("API key scopes this route accepts. Holding any one of them satisfies it."),
3214
- also_required_scopes: z$1.array(z$1.string()).optional().describe("API key scopes this route also requires. Every one must be held, on top of `required_scopes`.")
3244
+ z$1.object({
3245
+ /** gen_ai.usage.input_tokens */
3246
+ inputTokens: z$1.number(),
3247
+ /** gen_ai.usage.output_tokens */
3248
+ outputTokens: z$1.number(),
3249
+ contextWindowLimit: z$1.number(),
3250
+ utilizationPercentage: z$1.number(),
3251
+ reservedOutputTokens: z$1.number(),
3252
+ overflowDetected: z$1.boolean(),
3253
+ overflowAmount: z$1.number().optional(),
3254
+ /** Token breakdown by source */
3255
+ tokensBySource: TokensBySourceSchema
3215
3256
  });
3216
- const supportedVoiceGenerationVendor = z.enum(["openai", "elevenlabs"]);
3217
- const DEFAULT_TTS_PROVIDER = "openai";
3218
- const voiceOutputFormatSchema = z.enum([
3219
- "mp3",
3220
- "wav",
3221
- "opus",
3222
- "aac",
3223
- "flac",
3224
- "pcm"
3225
- ]);
3226
- const TTS_ABSOLUTE_MAX_INPUT_CHARS = Math.max(...Object.values({
3227
- openai: 4096,
3228
- elevenlabs: 1e4
3229
- }));
3230
- const ttsLanguageCodeSchema = z.string().regex(/^[a-z]{2}$/, "languageCode must be a lowercase ISO 639-1 code, e.g. \"en\" or \"ja\"");
3231
- const ttsRequestSchema = z.object({
3232
- text: z.string().min(1).max(TTS_ABSOLUTE_MAX_INPUT_CHARS),
3233
- provider: supportedVoiceGenerationVendor.optional(),
3234
- model: z.string().optional(),
3235
- voice: z.string().optional(),
3236
- format: voiceOutputFormatSchema.optional(),
3237
- stability: z.number().min(0).max(1).optional(),
3238
- similarityBoost: z.number().min(0).max(1).optional(),
3239
- languageCode: ttsLanguageCodeSchema.optional(),
3240
- ...generatedAudioRequestFields
3257
+ const CacheTelemetrySchema = z$1.object({
3258
+ /** Anthropic cache_read_input_tokens */
3259
+ cacheReadTokens: z$1.number(),
3260
+ /** Anthropic cache_creation_input_tokens */
3261
+ cacheWriteTokens: z$1.number(),
3262
+ /** 0-100% */
3263
+ cacheHitRate: z$1.number(),
3264
+ costSavingsPercent: z$1.number().optional()
3241
3265
  });
3242
- /**
3243
- * JSON body of `POST /api/ai/tts` for `encoding: 'base64'`: the shared generated-audio
3244
- * body plus the TTS fields. `provider`/`fallbackFrom` appear only when the requested
3245
- * provider was unavailable and another one stood in.
3246
- */
3247
- const ttsBase64ResponseSchema = extendGeneratedAudioResponseSchema({
3248
- format: voiceOutputFormatSchema,
3249
- /** The provider that actually produced the audio, present only on a fallback. */
3250
- provider: supportedVoiceGenerationVendor.optional(),
3251
- /** The originally requested provider that could not serve the request. */
3252
- fallbackFrom: supportedVoiceGenerationVendor.optional()
3266
+ const CostsTelemetrySchema = z$1.object({
3267
+ inputCostUsd: z$1.number(),
3268
+ outputCostUsd: z$1.number(),
3269
+ totalCostUsd: z$1.number(),
3270
+ creditsUsed: z$1.number()
3253
3271
  });
3254
- /**
3255
- * Error body for `POST /api/ai/tts`, shared by the 401, 422, 429 and 502; the 413
3256
- * has a shape of its own (`ttsResponseTooLargeSchema`). `errorCode` is present only
3257
- * on the conditions that carry a classifier; an ordinary validation 422 has none.
3258
- *
3259
- * Extends `ApiErrorSchema` because what decides whether a body carries the fields
3260
- * errorHandler adds - `request_id`, and `name` until its 2026-12-01 sunset - is
3261
- * whether the body was THROWN, not which status it wears, and three of these four
3262
- * statuses are reachable both ways:
3263
- *
3264
- * - 401: thrown by apiKeyAuth on a rejected key; written by `auth` when no
3265
- * credential was presented at all, and by the handler for
3266
- * `provider_not_configured` and for an upstream credential rejection
3267
- * (`provider_rejected`, from an upstream 401 or 403).
3268
- * - 422: thrown by request validation and by the char-limit / format guards;
3269
- * written by the handler for `insufficient_credits` and for every upstream 4xx
3270
- * that is neither a credential rejection nor a 429.
3271
- * - 429: thrown by apiKeyRateLimit; written by the handler on an upstream 429.
3272
- * - 502: only ever written.
3273
- *
3274
- * So those two are genuinely optional here, and splitting this per status would be
3275
- * wrong for the first three. The 502 does advertise both without ever sending them;
3276
- * that is not worth a fourth error schema on one route, and `request_id` missing from
3277
- * this handler's written bodies is a gap in the handler rather than something to
3278
- * enshrine in a schema.
3279
- */
3280
- const ttsErrorResponseSchema = ApiErrorSchema.extend({
3281
- provider: supportedVoiceGenerationVendor.optional(),
3282
- errorCode: z.enum([
3283
- "insufficient_credits",
3284
- "provider_not_configured",
3285
- "provider_rejected"
3286
- ]).optional()
3272
+ const TruncationTelemetrySchema = z$1.object({
3273
+ wasTruncated: z$1.boolean(),
3274
+ originalMessageCount: z$1.number(),
3275
+ finalMessageCount: z$1.number(),
3276
+ truncatedMessageCount: z$1.number(),
3277
+ truncationMethod: z$1.enum([
3278
+ "priority",
3279
+ "token-budget",
3280
+ "history-limit",
3281
+ "context-overflow"
3282
+ ]).optional(),
3283
+ truncationPercentage: z$1.number()
3287
3284
  });
3288
- /**
3289
- * TTS 413 body: `generatedAudioTooLargeSchema` plus the provider. The handler never
3290
- * sends the `saved`/`fabFileId`/`fileUrl` fields (a saved copy's `fileUrl` is now
3291
- * served as the oversized-audio URL); they stay so a client still parses a server
3292
- * that predates the offload.
3293
- *
3294
- * Not derived from `ApiErrorSchema`: every 413 on this route is written, never
3295
- * thrown, so `name` is genuinely absent rather than optional - a stronger claim than
3296
- * `ttsErrorResponseSchema` can make for its own statuses, see the note there (a
3297
- * provider 413 is folded onto a 422 by documentedStatusForUpstream4xx). An oversized
3298
- * *request* body is another 413 that never reaches this schema at all - Next's own
3299
- * body parser answers it in plain text before the router runs.
3300
- */
3301
- const ttsResponseTooLargeSchema = generatedAudioTooLargeSchema.extend({
3302
- provider: supportedVoiceGenerationVendor,
3303
- saved: z.literal(true).optional(),
3304
- fabFileId: z.string().optional(),
3305
- fileUrl: z.string().optional()
3285
+ const ToolErrorCategorySchema = z$1.enum([
3286
+ "timeout",
3287
+ "rate_limit",
3288
+ "auth_error",
3289
+ "validation_error",
3290
+ "network_error",
3291
+ "internal_error"
3292
+ ]);
3293
+ const ToolTelemetrySchema = z$1.object({
3294
+ toolName: z$1.string(),
3295
+ /** MCP vs native tool */
3296
+ isMcpTool: z$1.boolean(),
3297
+ /** For MCP tools */
3298
+ mcpServerName: z$1.string().optional(),
3299
+ invocationCount: z$1.number(),
3300
+ successCount: z$1.number(),
3301
+ failureCount: z$1.number(),
3302
+ totalDurationMs: z$1.number(),
3303
+ maxDurationMs: z$1.number(),
3304
+ /** Retry tracking */
3305
+ retryCount: z$1.number(),
3306
+ /** Max 200 chars */
3307
+ lastError: z$1.string().max(200).optional(),
3308
+ errorCategories: z$1.array(ToolErrorCategorySchema).optional(),
3309
+ /**
3310
+ * Content-size metrics for tools that extract text (currently web_fetch, issue #452).
3311
+ * Zero-PII integer counts, so kept at both basic and enhanced capture levels.
3312
+ */
3313
+ /** Count of unique truncated citables at the tool's size cap (citables dedupe by url,
3314
+ * so same-url refetches within a completion collapse to one - see WebFetchContentTelemetry). */
3315
+ truncatedInvocationCount: z$1.number().optional(),
3316
+ /** Largest single extracted (post-cap) content length across invocations. */
3317
+ maxExtractedChars: z$1.number().optional(),
3318
+ /** Sum of extracted (post-cap) content length across invocations. */
3319
+ totalExtractedChars: z$1.number().optional()
3306
3320
  });
3307
- z.enum(["openai"]);
3308
- /**
3309
- * Supported sound-effects generation vendors. Currently only ElevenLabs.
3310
- * New vendors are added here and in the `aiSoundService` factory.
3311
- */
3312
- const supportedSoundGenerationVendor = z.enum(["elevenlabs"]);
3313
- /**
3314
- * Inbound request body for `POST /api/ai/sound-effects`.
3315
- *
3316
- * `durationSeconds` and `promptInfluence` bounds mirror the ElevenLabs
3317
- * sound-generation limits (0.5-30s for the default eleven_text_to_sound_v2
3318
- * model, prompt influence 0-1). `format` is the provider-specific output
3319
- * encoding token (e.g. `mp3_44100_128`).
3320
- */
3321
- const soundEffectsRequestSchema = z.object({
3322
- provider: supportedSoundGenerationVendor.default("elevenlabs"),
3323
- text: z.string().min(1).max(1e3),
3324
- durationSeconds: z.number().min(.5).max(30).optional(),
3325
- promptInfluence: z.number().min(0).max(1).optional(),
3326
- format: z.string().optional(),
3327
- ...generatedAudioRequestFields
3321
+ const SubagentTelemetrySchema = z$1.object({
3322
+ agentName: z$1.string(),
3323
+ delegationCount: z$1.number(),
3324
+ successCount: z$1.number(),
3325
+ failureCount: z$1.number(),
3326
+ timeoutCount: z$1.number(),
3327
+ totalDurationMs: z$1.number(),
3328
+ totalTokensUsed: z$1.number(),
3329
+ thoroughness: z$1.enum([
3330
+ "quick",
3331
+ "medium",
3332
+ "very_thorough"
3333
+ ]).optional()
3328
3334
  });
3329
- /**
3330
- * Supported background-music generation vendors. Currently only ElevenLabs.
3331
- * New vendors are added here and in the `aiMusicService` factory.
3332
- */
3333
- const supportedMusicGenerationVendor = z.enum(["elevenlabs"]);
3334
- /**
3335
- * Supported ElevenLabs music models. Scoped to `music_v1` for now; `music_v2` is
3336
- * a deliberate follow-up (see the parent issue). Adding it here is the only
3337
- * change the request surface needs to accept it.
3338
- */
3339
- const supportedMusicModel = z.enum(["music_v1"]);
3340
- /**
3341
- * Inbound request body for `POST /api/ai/music`.
3342
- *
3343
- * `lengthMs` upper bound is capped below the ElevenLabs Music API ceiling to fit
3344
- * the serving function's time budget (see MAX_MUSIC_LENGTH_MS). It carries a
3345
- * default rather than being optional so the billed
3346
- * length is always known up front (the reserve/settle path needs a deterministic
3347
- * cost before generation) and the route can force that exact length on the
3348
- * provider. `format` is the provider-specific output encoding token (e.g.
3349
- * `mp3_44100_128`).
3350
- */
3351
- const musicRequestSchema = z.object({
3352
- provider: supportedMusicGenerationVendor.default("elevenlabs"),
3353
- prompt: z.string().min(1).max(2e3),
3354
- lengthMs: z.number().int().min(3e3).max(12e4).default(1e4),
3355
- forceInstrumental: z.boolean().optional(),
3356
- modelId: supportedMusicModel.default("music_v1"),
3357
- format: z.string().optional(),
3358
- ...generatedAudioRequestFields
3335
+ const LatencyPercentilesSchema = z$1.object({
3336
+ p50Ms: z$1.number(),
3337
+ p95Ms: z$1.number(),
3338
+ p99Ms: z$1.number()
3359
3339
  });
3360
- const GenerationCallbackUrlSchema = z$1.url({
3361
- protocol: /^https$/,
3362
- error: "callbackUrl must be an https URL"
3363
- }).max(2048).describe("Optional https URL that receives one signed POST when the job settles (success or failure). Requires API-key authentication and a callback signing secret on that key. The body is the GET /api/v1/quests/{id} response. Verify it with the `X-Webhook-Signature-256` header: `sha256=` + hex HMAC-SHA256 of `<X-Webhook-Timestamp>.<raw body>` keyed with the signing secret. Polling keeps working either way.");
3364
- z$1.enum([
3365
- "pending",
3366
- "dispatched",
3367
- "delivered",
3368
- "failed"
3340
+ const PerformanceTelemetrySchema = z$1.object({
3341
+ totalResponseTimeMs: z$1.number(),
3342
+ firstTokenTimeMs: z$1.number().optional(),
3343
+ /**
3344
+ * Elapsed ms until the first chunk of any kind, hidden thinking included. Paired with
3345
+ * firstTokenTimeMs so computeAnomalies can tell a never-rendered turn (streamed, nothing
3346
+ * visible) from one that never streamed at all - see ttfvtState in @bike4mind/common.
3347
+ */
3348
+ firstChunkTimeMs: z$1.number().optional(),
3349
+ contextRetrievalMs: z$1.number().optional(),
3350
+ modelInferenceMs: z$1.number().optional(),
3351
+ toolExecutionMs: z$1.number().optional(),
3352
+ /** Latency percentiles for trend analysis */
3353
+ latencyPercentiles: LatencyPercentilesSchema.optional()
3354
+ });
3355
+ const AnomalySeveritySchema = z$1.enum([
3356
+ "critical",
3357
+ "high",
3358
+ "medium",
3359
+ "low"
3369
3360
  ]);
3370
- /**
3371
- * The 400 clause every queued-generation contract declares for a rejected `callbackUrl`; must stay
3372
- * in sync with the rejections in apps/client/server/generationCallback/armGenerationCallback.ts.
3373
- */
3374
- const GENERATION_CALLBACK_REJECTED_DESCRIPTION = "`callbackUrl` was rejected: the request is not API-key authenticated, the key has no callback signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks.";
3375
- VIDEO_SIZE_CONSTRAINTS.SORA.durations;
3376
- VIDEO_SIZE_CONSTRAINTS.SORA.sizes;
3377
- z$1.object({
3378
- /** Text description of the video to generate */
3379
- prompt: z$1.string().min(1).max(1e4),
3380
- /** Model to use for generation */
3381
- model: z$1.enum(VideoModels).prefault("sora-2"),
3382
- /** Video duration in seconds: 4, 8, or 12 */
3383
- seconds: z$1.union([
3384
- z$1.literal(4),
3385
- z$1.literal(8),
3386
- z$1.literal(12)
3387
- ]).prefault(4),
3388
- /** Video resolution */
3389
- size: z$1.enum([
3390
- "720x1280",
3391
- "1280x720",
3392
- "1024x1792",
3393
- "1792x1024"
3394
- ]).prefault(VIDEO_SIZE_CONSTRAINTS.SORA.defaultSize),
3395
- /** Optional user identifier for abuse tracking */
3396
- user: z$1.string().optional()
3361
+ const PrimaryAnomalySchema = z$1.enum([
3362
+ "none",
3363
+ "context_overflow",
3364
+ "high_utilization",
3365
+ "high_truncation",
3366
+ "tool_failure",
3367
+ "subagent_timeout",
3368
+ "slow_response",
3369
+ "multiple"
3370
+ ]);
3371
+ const AnomaliesTelemetrySchema = z$1.object({
3372
+ contextOverflow: z$1.boolean(),
3373
+ /** >= 90% */
3374
+ highUtilization: z$1.boolean(),
3375
+ /** >= 95% */
3376
+ criticalUtilization: z$1.boolean(),
3377
+ /** >= 50% */
3378
+ highTruncation: z$1.boolean(),
3379
+ /** >= 75% */
3380
+ criticalTruncation: z$1.boolean(),
3381
+ /** >= 3 failures */
3382
+ toolFailureSpike: z$1.boolean(),
3383
+ /** > 30s */
3384
+ toolTimeout: z$1.boolean(),
3385
+ /** > 5min */
3386
+ subagentTimeout: z$1.boolean(),
3387
+ /** > 10s */
3388
+ slowFirstToken: z$1.boolean(),
3389
+ /** > 60s */
3390
+ slowTotalResponse: z$1.boolean(),
3391
+ /** 0-100 */
3392
+ anomalyScore: z$1.number().min(0).max(100),
3393
+ /** Severity mapping */
3394
+ severity: AnomalySeveritySchema,
3395
+ /** Pattern-based dedup key, e.g., "tool_failure_claude-3-5-sonnet_delegate-to-agent" */
3396
+ dedupKey: z$1.string(),
3397
+ primaryAnomaly: PrimaryAnomalySchema
3397
3398
  });
3398
- z$1.object({
3399
- /** Unique identifier for the video generation job */
3400
- id: z$1.string(),
3401
- /** Current status of the job */
3402
- status: z$1.enum([
3403
- "queued",
3404
- "in_progress",
3405
- "completed",
3406
- "failed"
3399
+ const RequestMetadataSchema = z$1.object({
3400
+ queryComplexity: z$1.enum([
3401
+ "simple",
3402
+ "contextual",
3403
+ "complex"
3407
3404
  ]),
3408
- /** Error message if the job failed */
3409
- error: z$1.object({
3410
- message: z$1.string(),
3411
- code: z$1.string().optional()
3412
- }).optional(),
3413
- /** Timestamp when the job was created */
3414
- created_at: z$1.number().optional()
3405
+ historyMessageCount: z$1.number(),
3406
+ attachedFileCount: z$1.number(),
3407
+ mementoCount: z$1.number(),
3408
+ enabledFeatures: z$1.array(z$1.string())
3409
+ });
3410
+ const ContextWindowTelemetryWithOptionalSourceSchema = z$1.object({
3411
+ /** gen_ai.usage.input_tokens */
3412
+ inputTokens: z$1.number(),
3413
+ /** gen_ai.usage.output_tokens */
3414
+ outputTokens: z$1.number(),
3415
+ contextWindowLimit: z$1.number(),
3416
+ utilizationPercentage: z$1.number(),
3417
+ reservedOutputTokens: z$1.number(),
3418
+ overflowDetected: z$1.boolean(),
3419
+ overflowAmount: z$1.number().optional(),
3420
+ /** Token breakdown by source (enhanced only) */
3421
+ tokensBySource: TokensBySourceSchema.optional()
3422
+ });
3423
+ const ContextTelemetrySchema = z$1.object({
3424
+ schemaVersion: z$1.enum([
3425
+ "1.0",
3426
+ "1.1",
3427
+ "1.2"
3428
+ ]),
3429
+ /** ISO 8601 */
3430
+ timestamp: z$1.string(),
3431
+ /** Self-monitoring: time to capture telemetry */
3432
+ captureOverheadMs: z$1.number(),
3433
+ /** Capture level: basic or enhanced */
3434
+ captureLevel: z$1.enum(["basic", "enhanced"]).optional(),
3435
+ anonymousSessionId: AnonymousSessionIdSchema,
3436
+ spanContext: SpanContextSchema.optional(),
3437
+ operation: OperationSchema,
3438
+ model: ModelTelemetrySchema,
3439
+ systemPrompts: SystemPromptsTelemetrySchema.optional(),
3440
+ features: FeaturesTelemetrySchema.optional(),
3441
+ contextWindow: ContextWindowTelemetryWithOptionalSourceSchema,
3442
+ cache: CacheTelemetrySchema.optional(),
3443
+ costs: CostsTelemetrySchema,
3444
+ truncation: TruncationTelemetrySchema.optional(),
3445
+ tools: z$1.array(ToolTelemetrySchema).optional(),
3446
+ subagents: z$1.array(SubagentTelemetrySchema).optional(),
3447
+ performance: PerformanceTelemetrySchema,
3448
+ anomalies: AnomaliesTelemetrySchema,
3449
+ requestMetadata: RequestMetadataSchema.optional(),
3450
+ captureErrors: z$1.array(z$1.string()).optional()
3415
3451
  });
3416
3452
  z$1.object({
3417
- /** Unique identifier for the video */
3418
- id: z$1.string(),
3419
- /** Status should be 'completed' */
3420
- status: z$1.literal("completed"),
3421
- /** URL to download the video (temporary, expires after a period) */
3422
- video_url: z$1.url().optional(),
3423
- /** Video duration in seconds */
3424
- duration: z$1.number().optional(),
3425
- /** Video resolution */
3426
- resolution: z$1.string().optional()
3453
+ avgResponseTimeMs: z$1.number(),
3454
+ p95ResponseTimeMs: z$1.number(),
3455
+ avgUtilization: z$1.number(),
3456
+ utilizationRange: z$1.object({
3457
+ low: z$1.number(),
3458
+ high: z$1.number()
3459
+ }),
3460
+ sampleCount: z$1.number(),
3461
+ windowDays: z$1.number()
3427
3462
  });
3463
+ z$1.enum([
3464
+ "no_action",
3465
+ "monitor",
3466
+ "investigate_soon",
3467
+ "immediate_action"
3468
+ ]);
3428
3469
  /**
3429
- * Schema for video generation API request body
3470
+ * Request schema for POST /api/chat - the simplified external chat surface.
3471
+ *
3472
+ * Shared between the Next.js API handler (apps/client/pages/api/chat.ts, which
3473
+ * validates req.body with this exact object) and the OpenAPI registry
3474
+ * (b4m-core/common/src/openapi), so the published contract cannot drift from
3475
+ * what the handler actually accepts. The model is optional here and resolved
3476
+ * server-side from admin settings when omitted.
3477
+ *
3478
+ * Public-API rule: no `.catch()` / top-level `.transform()`. Both silently mutate
3479
+ * caller input (fail-quiet) and are opaque to zod-to-openapi. `historyCount` uses
3480
+ * `.default()` (fail loud on a bad value); unknown tool ids are filtered in the
3481
+ * handler (see filterKnownTools) instead of by a schema transform. This keeps the
3482
+ * schema fully OpenAPI-representable with no doc projection needed.
3430
3483
  */
3431
- const GenerateVideoRequestBodySchema = z$1.object({
3432
- prompt: z$1.string().min(1),
3433
- model: z$1.enum(VideoModels).optional().prefault("sora-2"),
3434
- seconds: z$1.union([
3435
- z$1.literal(4),
3436
- z$1.literal(8),
3437
- z$1.literal(12)
3438
- ]).optional().prefault(4),
3439
- size: z$1.enum([
3440
- "720x1280",
3441
- "1280x720",
3442
- "1024x1792",
3443
- "1792x1024"
3444
- ]).optional().prefault(VIDEO_SIZE_CONSTRAINTS.SORA.defaultSize),
3445
- sessionId: z$1.string().optional(),
3446
- sessionName: z$1.string().optional(),
3447
- questId: z$1.string().optional(),
3448
- projectId: z$1.string().optional(),
3449
- organizationId: z$1.string().nullable().optional(),
3450
- callbackUrl: GenerationCallbackUrlSchema.optional()
3484
+ const SimplifiedChatRequestSchema = z$1.object({
3485
+ sessionId: z$1.string().nullish(),
3486
+ newConversation: z$1.boolean().optional(),
3487
+ message: z$1.string(),
3488
+ organizationId: z$1.string().optional(),
3489
+ model: z$1.string().optional(),
3490
+ temperature: z$1.number().min(0).max(2).optional(),
3491
+ max_tokens: z$1.number().positive().optional(),
3492
+ maxTokens: z$1.number().positive().optional(),
3493
+ maxOutputTokens: z$1.number().positive().optional(),
3494
+ stream: z$1.boolean().prefault(false),
3495
+ historyCount: z$1.number().positive().default(10),
3496
+ fileIds: z$1.array(z$1.string()).prefault([]),
3497
+ wait: z$1.boolean().prefault(false),
3498
+ enableTools: z$1.boolean().prefault(false),
3499
+ toolMode: z$1.enum(["fast", "smart"]).optional(),
3500
+ tools: z$1.array(z$1.string()).optional().describe("Explicit tool ids to offer the model. A non-empty array enables tools on its own; no companion `toolMode` or `enableTools` is required. Merged with the auto-selected set under `toolMode: \"smart\"`, and ignored under `toolMode: \"fast\"`. This list ADDS to what is offered rather than restricting it - the server still offers tools of its own (for example knowledge retrieval when the session has reachable documents). Unrecognized ids are dropped rather than rejecting the request; the response reports the surviving set as `tools.effectiveTools` and the ids that are not tools in this deployment as `tools.unrecognizedTools`, since no endpoint enumerates the valid ids. `unrecognizedTools` is reported under every `toolMode` - it describes the ids, not what the mode did with them - so under `toolMode: \"fast\"` an id can be absent from `effectiveTools` (the mode discarded it) without being unrecognized. Both reported lists are deduplicated, and `unrecognizedTools` names at most the first 10 distinct ids."),
3501
+ enableQuestMaster: z$1.boolean().optional(),
3502
+ enableMementos: z$1.boolean().optional(),
3503
+ enableAgents: z$1.boolean().optional(),
3504
+ promptMode: z$1.enum([
3505
+ "raw",
3506
+ "grounded",
3507
+ "surface"
3508
+ ]).optional(),
3509
+ skip_auto_offers: z$1.boolean().optional().describe("Suppress tools the server would otherwise attach on its own for this session (the knowledge-base search offer, in-app view navigation, blog drafting/editing/publishing, skill invocation, and every tool from a connected MCP server). Native tools you request explicitly in `tools` are unaffected, but an MCP server tool cannot be named through this field, so with this flag set a caller with an MCP server connected sees none of its tools regardless of `tools`. One system-prompt block goes with them: withholding in-app view navigation also drops the view-registry block that exists only to describe it. No other prompt content changes. This does not switch off retrieval: a session with forced knowledge retrieval still retrieves, and documents already attached to the session are still placed in the prompt directly. Any promptMode suppresses these too, so false has no effect alongside one."),
3510
+ includePromptDetails: z$1.boolean().optional(),
3511
+ includeSystemPrompt: z$1.boolean().optional(),
3512
+ systemPrompt: z$1.string().max(PROMPT_TEXT_MAX).optional().describe("System-prompt text for this request only, never persisted. Rendered as a defended block appended after every other system-prompt source, with prose instructing the model to defer to organization, session and data-lake guidance. Over the cap is a 422, never truncated.")
3451
3513
  });
3452
- GenerateVideoRequestBodySchema.extend({ sessionId: z$1.string() });
3453
3514
  /**
3454
- * Canonical agent execution status tuple. Shared between the database model
3455
- * (`AgentExecutionModel`), the client store (`useAgentExecutionStore`), and
3456
- * wire schemas (`ChildExecutionSnapshotSchema`, etc.) so the value space can't
3457
- * drift across boundaries, and so the Zod schemas can type `status` as
3458
- * `z.enum(AGENT_EXECUTION_STATUSES)` instead of `z.string()`.
3515
+ * Async ACK returned on the default (wait:false) path of POST /api/chat. The
3516
+ * `type`/`errorCode` pair below is the same classifier the `wait: true` body and
3517
+ * the polled quest (`GET /api/v1/quests/{id}`) carry, so it is modelled once here.
3518
+ * The rest of the `wait: true` body is ChatWaitResponseSchema below. The
3519
+ * handler assembles the ack body inline (apps/client/pages/api/chat.ts), so
3520
+ * this schema MUST stay in sync with that `res.json({...})` shape.
3459
3521
  */
3460
- const AGENT_EXECUTION_STATUSES = [
3461
- "pending",
3462
- "running",
3463
- "continuing",
3464
- "awaiting_permission",
3465
- "awaiting_subagent",
3466
- "awaiting_dag_children",
3467
- "paused",
3468
- "completed",
3469
- "failed",
3470
- "aborted"
3471
- ];
3522
+ const ChatAckSchema = z$1.object({
3523
+ id: z$1.string(),
3524
+ status: z$1.string(),
3525
+ message_received: z$1.boolean(),
3526
+ timestamp: z$1.string(),
3527
+ model: z$1.string(),
3528
+ sessionId: z$1.string().optional(),
3529
+ message: z$1.string().optional(),
3530
+ type: z$1.enum(CHAT_HISTORY_ITEM_TYPES).optional(),
3531
+ errorCode: z$1.enum(QUEST_ERROR_CODES).optional(),
3532
+ tools: z$1.object({
3533
+ toolMode: z$1.enum(["fast", "smart"]).optional(),
3534
+ autoSelectedTools: z$1.array(z$1.string()).optional(),
3535
+ effectiveTools: z$1.array(z$1.string()),
3536
+ unrecognizedTools: z$1.array(z$1.string()).optional()
3537
+ }).optional(),
3538
+ tracking_info: z$1.object({
3539
+ quest_id: z$1.string(),
3540
+ check_status_url: z$1.string(),
3541
+ poll_url: z$1.string().optional()
3542
+ })
3543
+ });
3544
+ const ToolPayloadSchema = z$1.object({
3545
+ type: z$1.string(),
3546
+ payload: z$1.unknown()
3547
+ });
3472
3548
  /**
3473
- * Why a session summarization happened, stamped on `ISession.summaryTrigger`. Single source for the
3474
- * places that each used to spell this list out: the Session zod schema (schemas/actions.ts), the
3475
- * entity type (types/entities/SessionTypes.ts), the Mongoose path (packages/database SessionModel)
3476
- * and the session.summarize event payload (apps/client server/utils/eventBus.ts). They drifted -
3477
- * the Mongoose enum said 'milestone'/'growth' for two values nothing produces - and a drift there
3478
- * is invisible on the update path, because BaseModel's findOneAndUpdate writes without
3479
- * runValidators.
3549
+ * The completed-turn body POST /api/chat returns with `wait: true`. Like the ACK it is
3550
+ * assembled inline in apps/client/pages/api/chat.ts and MUST stay in sync with that
3551
+ * `res.json({...})` shape.
3552
+ */
3553
+ const ChatWaitResponseSchema = ChatAckSchema.extend({
3554
+ type: z$1.enum(CHAT_HISTORY_ITEM_TYPES),
3555
+ response: z$1.string().nullable(),
3556
+ responses: z$1.array(z$1.string()),
3557
+ toolPayloads: z$1.array(ToolPayloadSchema),
3558
+ createdAt: z$1.union([z$1.string().datetime(), z$1.date()]),
3559
+ promptDetails: z$1.array(SystemPromptDetailSchema).optional(),
3560
+ promptText: z$1.string().optional(),
3561
+ performance: z$1.object({
3562
+ total_ms: z$1.number(),
3563
+ phases: z$1.record(z$1.string(), z$1.number()),
3564
+ pipeline_phases: z$1.record(z$1.string(), z$1.number()).optional()
3565
+ })
3566
+ });
3567
+ /**
3568
+ * The quest a `wait: false` caller polls at `GET /api/v1/quests/{id}` - the outcome
3569
+ * of the turn the ACK above only acknowledged.
3480
3570
  *
3481
- * Those surfaces now name PERSISTED_SESSION_SUMMARY_TRIGGERS below, not the full union: a stored
3482
- * field may only carry a reason a run HAPPENED. 'throttling' is the exception that forced the
3483
- * split - shouldSummarizeSession (b4m-core/services ChatCompletionFeatures) returns it as the
3484
- * reason it DECLINED to summarize, so it describes no run and belongs to a decision, not a
3485
- * document. It is typed by SummarizationDecision there, not by the session field.
3571
+ * Deliberately the OUTCOME SUBSET, not the whole quest: it models only what decides
3572
+ * whether the turn succeeded. The full response of that endpoint is
3573
+ * QuestPollResponseSchema (schemas/quest.ts), which extends this schema and is
3574
+ * the 200 body of the `getQuest` contract.
3486
3575
  *
3487
- * 'manual' means someone asked for one notebook's summary. The admin sweep
3488
- * (apps/workers/src/events/spider.ts) summarizes every un-summarized notebook of the admin who ran it in one
3489
- * billed pass, so it stamps 'spider' instead: without that, one deliberate click and a whole sweep
3490
- * are indistinguishable when someone investigates unexpected summarization spend.
3491
- */
3492
- /**
3493
- * The triggers a document may actually carry - every reason a summarization HAPPENED. The event
3494
- * payload and createSessionParametersSchema both name this list rather than the full union below,
3495
- * so 'throttling' cannot be published, cannot be stored, and therefore cannot reach a copy path.
3496
- * Add a new reason-it-happened here, not to SESSION_SUMMARY_TRIGGERS, and every one of those
3497
- * boundaries picks it up.
3576
+ * A failed turn is still `status: 'done'` with the failure text in `reply`, so
3577
+ * `reply` alone cannot tell an answer from a failure - `type` and `errorCode` are
3578
+ * what separate a CLASSIFIED failure. A run that died with partial content is not
3579
+ * one of those: `terminalRecoveryFor` (questTimeoutRecovery.ts) keeps the surviving
3580
+ * content, so it polls back as `type: 'message'`. What marks it unfinished is
3581
+ * `promptMeta.finishReason` - RUN_TIMED_OUT_FINISH_REASON or
3582
+ * RUN_ABANDONED_FINISH_REASON (utils/stopReasons.ts) - plus a notice appended to
3583
+ * the reply.
3498
3584
  */
3499
- const PERSISTED_SESSION_SUMMARY_TRIGGERS = [
3500
- "manual",
3501
- "project",
3502
- "earlyMilestone",
3503
- "contentGrowth",
3504
- "spider"
3505
- ];
3506
- [...PERSISTED_SESSION_SUMMARY_TRIGGERS];
3585
+ const PolledFallbackInfoSchema = FallbackInfoSchema.pick({
3586
+ primaryModel: true,
3587
+ primaryModelName: true,
3588
+ fallbackModel: true,
3589
+ fallbackModelName: true,
3590
+ reason: true
3591
+ });
3592
+ const ChatQuestPollResultSchema = z$1.object({
3593
+ id: z$1.string(),
3594
+ status: z$1.enum([
3595
+ "pending",
3596
+ "stopped",
3597
+ "running",
3598
+ "done"
3599
+ ]).optional(),
3600
+ type: z$1.enum(CHAT_HISTORY_ITEM_TYPES).optional(),
3601
+ errorCode: z$1.enum(QUEST_ERROR_CODES).optional(),
3602
+ reply: z$1.string().nullable().optional(),
3603
+ replies: z$1.array(z$1.string()).optional(),
3604
+ fallbackInfo: PolledFallbackInfoSchema.optional()
3605
+ });
3507
3606
  /**
3508
- * Operator allow-list for the client-authored Mongo filter carried on a `subscribe_query` frame.
3509
- *
3510
- * The WS data-subscribe handler forwards that filter to `Model.find` and persists it on the
3511
- * QuerySubscription the separate subscriber-fanout service replays, so an unconstrained
3512
- * `$`-prefixed key lets any authenticated socket ask the database to run server-side JavaScript
3513
- * (`$where`, `$expr` + `$function`) or an unbounded `$regex` - either of which pins a pooled
3514
- * connection for as long as it runs.
3607
+ * Reusable JSON error envelope (plain; the OpenAPI layer annotates it).
3515
3608
  *
3516
- * Live subscriptions only ever need equality, membership and range matching, so anything outside
3517
- * this list is REFUSED rather than stripped: silently dropping an operator would quietly widen the
3518
- * document set the caller ends up subscribed to.
3609
+ * Must stay in sync with the published `ErrorResponse` component
3610
+ * (../openapi/schemas.ts) - `openapi/errorEnvelopeParity.test.ts` pins the two
3611
+ * together, and apps/client's errorHandler test uses this shape as the stand-in for
3612
+ * the component, which is generate-time only and cannot be imported at runtime.
3613
+ */
3614
+ const ApiErrorSchema = z$1.object({
3615
+ error: z$1.string(),
3616
+ request_id: z$1.string().optional(),
3617
+ /**
3618
+ * Deprecated, sunset 2026-12-01. The `name` of whatever was thrown - our own error
3619
+ * classes usually, a library/driver class name on an unhandled 500 - added to every
3620
+ * body by apps/client's errorHandler. Documented here so the runtime and the spec
3621
+ * agree while it is still served; do not build on it. See CONVENTIONS.md section 1.
3622
+ */
3623
+ name: z$1.string().optional()
3624
+ });
3625
+ /**
3626
+ * Error envelope for the 422 a credit-metered endpoint returns for two unrelated
3627
+ * reasons: "your body is invalid" and "you cannot afford this". `errorCode` is
3628
+ * what separates them - `insufficientCreditsError` (see insufficientCredits.ts)
3629
+ * tags the credit case, so its absence means an ordinary validation failure.
3519
3630
  *
3520
- * The scan is deliberately structural rather than a model of Mongo's grammar - it flags any
3521
- * `$`-prefixed key anywhere in the tree that isn't allow-listed. It can therefore accept an
3522
- * allow-listed operator in a position Mongo would treat as a literal sub-document field name, but
3523
- * that is inert; what matters is that no disallowed operator can reach the driver.
3631
+ * Derived from `ApiErrorSchema` rather than re-declaring `error`/`request_id`:
3632
+ * both of those 422s are *thrown*, so errorHandler serves the body and adds
3633
+ * `name`. Extending is what keeps that documented here (and what drops it again
3634
+ * on the sunset date) instead of leaving a bespoke copy behind to drift.
3524
3635
  */
3525
- /** Combinators whose operands are themselves filters. */
3526
- const SUBSCRIPTION_FILTER_LOGICAL_OPERATORS = [
3527
- "$and",
3528
- "$or",
3529
- "$nor",
3530
- "$not"
3531
- ];
3532
- /** Value-level operators a subscription filter may use. */
3533
- const SUBSCRIPTION_FILTER_VALUE_OPERATORS = [
3534
- "$eq",
3535
- "$ne",
3536
- "$gt",
3537
- "$gte",
3538
- "$lt",
3539
- "$lte",
3540
- "$in",
3541
- "$nin",
3542
- "$exists",
3543
- "$type",
3544
- "$size",
3545
- "$all",
3546
- "$elemMatch"
3547
- ];
3548
- const ALLOWED_OPERATORS = /* @__PURE__ */ new Set([...SUBSCRIPTION_FILTER_LOGICAL_OPERATORS, ...SUBSCRIPTION_FILTER_VALUE_OPERATORS]);
3636
+ const InsufficientCreditsErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.literal("insufficient_credits").optional() });
3637
+ /** 503 when this deployment has no usable key for a provider the request needs (data-lake search, voice). */
3638
+ const ProviderNotConfiguredErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.literal("provider_not_configured").optional() });
3549
3639
  /**
3550
- * Returns a dotted path for every part of `filter` a subscription may not send: a disallowed
3551
- * `$`-prefixed key, a `RegExp` operand, or nesting past {@link SUBSCRIPTION_FILTER_MAX_DEPTH}.
3552
- * An empty array means the filter is safe to forward to the database.
3640
+ * Error envelope for the 403 apiKeyAuth (apps/client/server/middlewares/apiKeyAuth.ts)
3641
+ * throws when an API key lacks a route's scopes. It names what the ROUTE requires,
3642
+ * never what the key holds, so a caller knows which scope to mint a key with.
3643
+ * Values are `ApiKeyScope` strings; plain `z.string()` because the enum's module is
3644
+ * not import-safe for the install-only openapi job (see the import note above).
3553
3645
  */
3554
- function findDisallowedSubscriptionFilterKeys(filter) {
3555
- const violations = [];
3556
- const walk = (node, path, depth) => {
3557
- if (depth > 12) {
3558
- violations.push(`${path} (nested deeper than 12)`);
3559
- return;
3560
- }
3561
- if (node instanceof RegExp) {
3562
- violations.push(`${path} (regular expression)`);
3563
- return;
3564
- }
3565
- if (Array.isArray(node)) {
3566
- node.forEach((entry, i) => walk(entry, `${path}[${i}]`, depth + 1));
3567
- return;
3568
- }
3569
- if (node === null || typeof node !== "object") return;
3570
- for (const [key, value] of Object.entries(node)) {
3571
- const childPath = path ? `${path}.${key}` : key;
3572
- if (key.startsWith("$") && !ALLOWED_OPERATORS.has(key)) {
3573
- violations.push(childPath);
3574
- continue;
3575
- }
3576
- walk(value, childPath, depth + 1);
3577
- }
3578
- };
3579
- walk(filter, "", 0);
3580
- return violations;
3581
- }
3582
- const DataSubscribeRequestAction = z$1.object({
3583
- action: z$1.literal("subscribe_query"),
3584
- accessToken: z$1.string().optional(),
3585
- subscriptionId: z$1.string(),
3586
- collectionName: z$1.string(),
3587
- query: z$1.looseObject({}).superRefine((filter, ctx) => {
3588
- for (const key of findDisallowedSubscriptionFilterKeys(filter)) ctx.addIssue({
3589
- code: "custom",
3590
- message: `Disallowed subscription filter: ${key}`
3591
- });
3592
- }),
3593
- fields: z$1.record(z$1.string(), z$1.union([z$1.boolean(), z$1.number()])),
3594
- fetchInitialData: z$1.boolean().prefault(true).optional(),
3595
- clientId: z$1.string().optional()
3596
- });
3597
- const DataUnsubscribeRequestAction = z$1.object({
3598
- action: z$1.literal("unsubscribe_query"),
3599
- accessToken: z$1.string().optional(),
3600
- subscriptionId: z$1.string()
3646
+ const ScopeForbiddenErrorSchema = ApiErrorSchema.extend({
3647
+ required_scopes: z$1.array(z$1.string()).optional().describe("API key scopes this route accepts. Holding any one of them satisfies it."),
3648
+ also_required_scopes: z$1.array(z$1.string()).optional().describe("API key scopes this route also requires. Every one must be held, on top of `required_scopes`.")
3601
3649
  });
3602
- const HeartbeatAction = z$1.object({ action: z$1.literal("heartbeat") });
3603
- const VoiceSessionSendTranscriptAction = z$1.object({
3604
- action: z$1.literal("voice_session_send_transcript"),
3605
- userId: z$1.string(),
3606
- sessionId: z$1.string(),
3607
- transcript: z$1.string(),
3608
- type: z$1.enum(["input", "response"]),
3609
- conversationItemId: z$1.string(),
3610
- timestamp: z$1.coerce.date().optional()
3650
+ const supportedVoiceGenerationVendor = z.enum(["openai", "elevenlabs"]);
3651
+ const DEFAULT_TTS_PROVIDER = "openai";
3652
+ const voiceOutputFormatSchema = z.enum([
3653
+ "mp3",
3654
+ "wav",
3655
+ "opus",
3656
+ "aac",
3657
+ "flac",
3658
+ "pcm"
3659
+ ]);
3660
+ const TTS_ABSOLUTE_MAX_INPUT_CHARS = Math.max(...Object.values({
3661
+ openai: 4096,
3662
+ elevenlabs: 1e4
3663
+ }));
3664
+ const ttsLanguageCodeSchema = z.string().regex(/^[a-z]{2}$/, "languageCode must be a lowercase ISO 639-1 code, e.g. \"en\" or \"ja\"");
3665
+ const ttsRequestSchema = z.object({
3666
+ text: z.string().min(1).max(TTS_ABSOLUTE_MAX_INPUT_CHARS),
3667
+ provider: supportedVoiceGenerationVendor.optional(),
3668
+ model: z.string().optional(),
3669
+ voice: z.string().optional(),
3670
+ format: voiceOutputFormatSchema.optional(),
3671
+ stability: z.number().min(0).max(1).optional(),
3672
+ similarityBoost: z.number().min(0).max(1).optional(),
3673
+ languageCode: ttsLanguageCodeSchema.optional(),
3674
+ ...generatedAudioRequestFields
3611
3675
  });
3612
3676
  /**
3613
- * CLI Completion request over WebSocket (bypasses CloudFront 20s timeout)
3677
+ * JSON body of `POST /api/ai/tts` for `encoding: 'base64'`: the shared generated-audio
3678
+ * body plus the TTS fields. `provider`/`fallbackFrom` appear only when the requested
3679
+ * provider was unavailable and another one stood in.
3614
3680
  */
3615
- const CliCompletionRequestAction = z$1.object({
3616
- action: z$1.literal("cli_completion_request"),
3617
- accessToken: z$1.string(),
3618
- requestId: z$1.uuid(),
3619
- model: z$1.string(),
3620
- messages: z$1.array(z$1.object({
3621
- role: z$1.enum([
3622
- "user",
3623
- "assistant",
3624
- "system"
3625
- ]),
3626
- content: z$1.union([z$1.string(), z$1.array(z$1.unknown())]),
3627
- cache: z$1.boolean().optional()
3628
- })),
3629
- options: z$1.object({
3630
- temperature: z$1.number().optional(),
3631
- maxTokens: z$1.number().optional(),
3632
- stream: z$1.boolean().optional(),
3633
- tools: z$1.array(z$1.unknown()).optional(),
3634
- response_format: z$1.discriminatedUnion("type", [z$1.object({ type: z$1.literal("text") }), z$1.object({
3635
- type: z$1.literal("json_schema"),
3636
- json_schema: z$1.object({
3637
- name: z$1.string(),
3638
- description: z$1.string().optional(),
3639
- schema: z$1.record(z$1.string(), z$1.any()),
3640
- strict: z$1.boolean().optional().default(true)
3641
- })
3642
- })]).optional()
3643
- }).optional()
3681
+ const ttsBase64ResponseSchema = extendGeneratedAudioResponseSchema({
3682
+ format: voiceOutputFormatSchema,
3683
+ /** The provider that actually produced the audio, present only on a fallback. */
3684
+ provider: supportedVoiceGenerationVendor.optional(),
3685
+ /** The originally requested provider that could not serve the request. */
3686
+ fallbackFrom: supportedVoiceGenerationVendor.optional()
3644
3687
  });
3645
3688
  /**
3646
- * CLI Tool execution request over WebSocket
3647
- */
3648
- const CliToolRequestAction = z$1.object({
3649
- action: z$1.literal("cli_tool_request"),
3650
- accessToken: z$1.string(),
3651
- requestId: z$1.uuid(),
3652
- toolName: z$1.string(),
3653
- input: z$1.record(z$1.string(), z$1.unknown())
3654
- });
3655
- const VoiceSessionEndedAction = z$1.object({
3656
- action: z$1.literal("voice_session_ended"),
3657
- userId: z$1.string(),
3658
- model: z$1.string(),
3659
- sessionId: z$1.string(),
3660
- usage: z$1.object({
3661
- audioInputTokens: z$1.number().min(0),
3662
- audioCachedInputTokens: z$1.number().min(0),
3663
- audioOutputTokens: z$1.number().min(0),
3664
- textInputTokens: z$1.number().min(0),
3665
- textCachedInputTokens: z$1.number().min(0),
3666
- textOutputTokens: z$1.number().min(0)
3667
- })
3668
- });
3669
- const DataSubscriptionUpdateAction = z$1.object({
3670
- action: z$1.literal("data_update"),
3671
- subscriptionId: z$1.string(),
3672
- collectionName: z$1.string(),
3673
- operationType: z$1.string(),
3674
- clientId: z$1.string().optional(),
3675
- data: z$1.looseObject({
3676
- _id: z$1.string(),
3677
- id: z$1.string()
3678
- })
3689
+ * Error body for `POST /api/ai/tts`, shared by the 401, 422, 429 and 502; the 413
3690
+ * has a shape of its own (`ttsResponseTooLargeSchema`). `errorCode` is present only
3691
+ * on the conditions that carry a classifier; an ordinary validation 422 has none.
3692
+ *
3693
+ * Extends `ApiErrorSchema` because what decides whether a body carries the fields
3694
+ * errorHandler adds - `request_id`, and `name` until its 2026-12-01 sunset - is
3695
+ * whether the body was THROWN, not which status it wears, and three of these four
3696
+ * statuses are reachable both ways:
3697
+ *
3698
+ * - 401: thrown by apiKeyAuth on a rejected key; written by `auth` when no
3699
+ * credential was presented at all, and by the handler for
3700
+ * `provider_not_configured` and for an upstream credential rejection
3701
+ * (`provider_rejected`, from an upstream 401 or 403).
3702
+ * - 422: thrown by request validation and by the char-limit / format guards;
3703
+ * written by the handler for `insufficient_credits` and for every upstream 4xx
3704
+ * that is neither a credential rejection nor a 429.
3705
+ * - 429: thrown by apiKeyRateLimit; written by the handler on an upstream 429.
3706
+ * - 502: only ever written.
3707
+ *
3708
+ * So those two are genuinely optional here, and splitting this per status would be
3709
+ * wrong for the first three. The 502 does advertise both without ever sending them;
3710
+ * that is not worth a fourth error schema on one route, and `request_id` missing from
3711
+ * this handler's written bodies is a gap in the handler rather than something to
3712
+ * enshrine in a schema.
3713
+ */
3714
+ const ttsErrorResponseSchema = ApiErrorSchema.extend({
3715
+ provider: supportedVoiceGenerationVendor.optional(),
3716
+ errorCode: z.enum([
3717
+ "insufficient_credits",
3718
+ "provider_not_configured",
3719
+ "provider_rejected"
3720
+ ]).optional()
3679
3721
  });
3680
3722
  /**
3681
- * Server -> Client: a `subscribe_query` frame was refused (the operator allow-list, `fields`
3682
- * validation) or its initial fetch was aborted (`maxTimeMS`). Neither failure throws an
3683
- * UnauthorizedError/JsonWebTokenError, so withWebSocketContext's status code never reaches the
3684
- * client as a frame - this is the only signal the caller gets that the subscription never took.
3723
+ * TTS 413 body: `generatedAudioTooLargeSchema` plus the provider. The handler never
3724
+ * sends the `saved`/`fabFileId`/`fileUrl` fields (a saved copy's `fileUrl` is now
3725
+ * served as the oversized-audio URL); they stay so a client still parses a server
3726
+ * that predates the offload.
3727
+ *
3728
+ * Not derived from `ApiErrorSchema`: every 413 on this route is written, never
3729
+ * thrown, so `name` is genuinely absent rather than optional - a stronger claim than
3730
+ * `ttsErrorResponseSchema` can make for its own statuses, see the note there (a
3731
+ * provider 413 is folded onto a 422 by documentedStatusForUpstream4xx). An oversized
3732
+ * *request* body is another 413 that never reaches this schema at all - Next's own
3733
+ * body parser answers it in plain text before the router runs.
3685
3734
  */
3686
- const DataSubscribeErrorAction = z$1.object({
3687
- action: z$1.literal("data_subscribe_error"),
3688
- subscriptionId: z$1.string(),
3689
- error: z$1.string()
3690
- });
3691
- const LLMStatusUpdateAction = z$1.object({
3692
- action: z$1.literal("llm_status_update"),
3693
- status: z$1.string().nullable(),
3694
- clientId: z$1.string().optional()
3695
- });
3696
- const InboxRefetchAction = z$1.object({
3697
- action: z$1.literal("inbox_refetch"),
3698
- status: z$1.string(),
3699
- clientId: z$1.string().optional()
3700
- });
3701
- const InvitesRefetchAction = z$1.object({
3702
- action: z$1.literal("invites_refetch"),
3703
- status: z$1.string(),
3704
- clientId: z$1.string().optional()
3705
- });
3706
- const UpdateCurrentUserAction = z$1.object({
3707
- action: z$1.literal("update_current_user"),
3708
- clientId: z$1.string().optional(),
3709
- user: z$1.object({
3710
- id: z$1.string(),
3711
- username: z$1.string(),
3712
- name: z$1.string(),
3713
- email: z$1.string(),
3714
- groups: z$1.array(z$1.string()),
3715
- isAdmin: z$1.boolean(),
3716
- storageLimit: z$1.number(),
3717
- currentStorageSize: z$1.number()
3718
- }).partial()
3719
- });
3720
- const ResearchTaskStatusUpdateAction = z$1.object({
3721
- action: z$1.literal("research_task_status_update"),
3722
- taskId: z$1.string(),
3723
- status: z$1.string(),
3724
- currentStep: z$1.string(),
3725
- progress: z$1.number()
3726
- });
3727
- const NotebookCurationProgressUpdateAction = z$1.object({
3728
- action: z$1.literal("notebook_curation_progress"),
3729
- curationJobId: z$1.string(),
3730
- sessionId: z$1.string(),
3731
- status: z$1.enum([
3732
- "pending",
3733
- "loading",
3734
- "extracting",
3735
- "generating",
3736
- "storing",
3737
- "completed",
3738
- "failed"
3739
- ]),
3740
- stage: z$1.enum([
3741
- "loading",
3742
- "extracting",
3743
- "generating",
3744
- "storing"
3745
- ]).optional(),
3746
- percentage: z$1.number(),
3747
- message: z$1.string().optional(),
3748
- messagesProcessed: z$1.number().optional(),
3749
- totalMessages: z$1.number().optional(),
3750
- artifactsFound: z$1.number().optional(),
3751
- curatedFileId: z$1.string().optional(),
3752
- errorMessage: z$1.string().optional(),
3753
- tokensDeducted: z$1.number().optional(),
3754
- clientId: z$1.string().optional()
3755
- });
3756
- const QuestExportProgressAction = z$1.object({
3757
- action: z$1.literal("quest_export_progress"),
3758
- exportJobId: z$1.string(),
3759
- planId: z$1.string(),
3760
- status: z$1.enum([
3761
- "assembling",
3762
- "downloading_images",
3763
- "summarizing",
3764
- "zipping",
3765
- "completed",
3766
- "failed"
3767
- ]),
3768
- progress: z$1.number(),
3769
- detail: z$1.string().optional(),
3770
- downloadUrl: z$1.string().optional(),
3771
- filename: z$1.string().optional(),
3772
- errorMessage: z$1.string().optional(),
3773
- droppedQuestCount: z$1.number().optional(),
3774
- clientId: z$1.string().optional()
3775
- });
3776
- const OrgFeedbackSummaryProgressAction = z$1.object({
3777
- action: z$1.literal("org_feedback_summary_progress"),
3778
- summaryJobId: z$1.string(),
3779
- organizationId: z$1.string(),
3780
- status: z$1.enum([
3781
- "processing",
3782
- "completed",
3783
- "failed"
3784
- ]),
3785
- progress: z$1.number(),
3786
- errorMessage: z$1.string().optional(),
3787
- clientId: z$1.string().optional()
3735
+ const ttsResponseTooLargeSchema = generatedAudioTooLargeSchema.extend({
3736
+ provider: supportedVoiceGenerationVendor,
3737
+ saved: z.literal(true).optional(),
3738
+ fabFileId: z.string().optional(),
3739
+ fileUrl: z.string().optional()
3788
3740
  });
3789
- const SpiderProgressUpdateAction = z$1.object({
3790
- action: z$1.literal("spider_progress"),
3791
- spiderJobId: z$1.string(),
3792
- notebooksProcessed: z$1.number(),
3793
- totalNotebooks: z$1.number(),
3794
- currentOperation: z$1.string(),
3795
- currentNotebookId: z$1.string().optional(),
3796
- currentNotebookName: z$1.string().optional(),
3797
- dryRun: z$1.boolean().optional(),
3798
- clientId: z$1.string().optional()
3741
+ const VIDEO_MODES = ["text_to_video", "image_to_video"];
3742
+ const ASPECT_RATIOS = [
3743
+ "16:9",
3744
+ "9:16",
3745
+ "1:1",
3746
+ "4:3",
3747
+ "3:4",
3748
+ "3:2",
3749
+ "2:3",
3750
+ "21:9"
3751
+ ];
3752
+ const RESOLUTION_TIERS = [
3753
+ "360p",
3754
+ "480p",
3755
+ "720p",
3756
+ "1080p",
3757
+ "4k"
3758
+ ];
3759
+ const VideoModelIdSchema = z$1.enum([
3760
+ "test-video",
3761
+ "gemini-omni-1.1-flash",
3762
+ "grok-imagine-video-1.5",
3763
+ "veo-3.1-fast-generate-preview"
3764
+ ]);
3765
+ z$1.object({
3766
+ model: VideoModelIdSchema,
3767
+ mode: z$1.enum(VIDEO_MODES),
3768
+ prompt: z$1.string().trim().min(1).max(4e3),
3769
+ durationSeconds: z$1.number().positive(),
3770
+ aspectRatio: z$1.enum(ASPECT_RATIOS),
3771
+ resolution: z$1.enum(RESOLUTION_TIERS),
3772
+ inputImageFileId: z$1.string().min(1).optional(),
3773
+ inputGeneratedImageKey: z$1.string().min(1).optional(),
3774
+ audio: z$1.boolean().optional()
3775
+ });
3776
+ const VIDEO_VALIDATION_ERROR_CODES = [
3777
+ "unsupported_duration",
3778
+ "unsupported_aspect_ratio",
3779
+ "unsupported_resolution",
3780
+ "unsupported_mode",
3781
+ "missing_input_image",
3782
+ "unexpected_input_image",
3783
+ "unsupported_audio_option"
3784
+ ];
3785
+ /**
3786
+ * Supported sound-effects generation vendors. Currently only ElevenLabs.
3787
+ * New vendors are added here and in the `aiSoundService` factory.
3788
+ */
3789
+ const supportedSoundGenerationVendor = z.enum(["elevenlabs"]);
3790
+ /**
3791
+ * Inbound request body for `POST /api/ai/sound-effects`.
3792
+ *
3793
+ * `durationSeconds` and `promptInfluence` bounds mirror the ElevenLabs
3794
+ * sound-generation limits (0.5-30s for the default eleven_text_to_sound_v2
3795
+ * model, prompt influence 0-1). `format` is the provider-specific output
3796
+ * encoding token (e.g. `mp3_44100_128`).
3797
+ */
3798
+ const soundEffectsRequestSchema = z.object({
3799
+ provider: supportedSoundGenerationVendor.default("elevenlabs"),
3800
+ text: z.string().min(1).max(1e3),
3801
+ durationSeconds: z.number().min(.5).max(30).optional(),
3802
+ promptInfluence: z.number().min(0).max(1).optional(),
3803
+ format: z.string().optional(),
3804
+ ...generatedAudioRequestFields
3799
3805
  });
3800
- const SpiderCompleteAction = z$1.object({
3801
- action: z$1.literal("spider_complete"),
3802
- spiderJobId: z$1.string(),
3803
- totalNotebooks: z$1.number(),
3804
- stats: z$1.object({
3805
- messageCountsUpdated: z$1.number(),
3806
- notebooksCurated: z$1.number(),
3807
- notebooksSummarized: z$1.number(),
3808
- notebooksTagged: z$1.number(),
3809
- messagesEmbedded: z$1.number().optional(),
3810
- errors: z$1.number().optional(),
3811
- skipped: z$1.number().optional()
3812
- }),
3813
- dryRun: z$1.boolean().optional(),
3814
- clientId: z$1.string().optional()
3806
+ /**
3807
+ * Supported background-music generation vendors. Currently only ElevenLabs.
3808
+ * New vendors are added here and in the `aiMusicService` factory.
3809
+ */
3810
+ const supportedMusicGenerationVendor = z.enum(["elevenlabs"]);
3811
+ /**
3812
+ * Supported ElevenLabs music models. Scoped to `music_v1` for now; `music_v2` is
3813
+ * a deliberate follow-up (see the parent issue). Adding it here is the only
3814
+ * change the request surface needs to accept it.
3815
+ */
3816
+ const supportedMusicModel = z.enum(["music_v1"]);
3817
+ /**
3818
+ * Inbound request body for `POST /api/ai/music`.
3819
+ *
3820
+ * `lengthMs` upper bound is capped below the ElevenLabs Music API ceiling to fit
3821
+ * the serving function's time budget (see MAX_MUSIC_LENGTH_MS). It carries a
3822
+ * default rather than being optional so the billed
3823
+ * length is always known up front (the reserve/settle path needs a deterministic
3824
+ * cost before generation) and the route can force that exact length on the
3825
+ * provider. `format` is the provider-specific output encoding token (e.g.
3826
+ * `mp3_44100_128`).
3827
+ */
3828
+ const musicRequestSchema = z.object({
3829
+ provider: supportedMusicGenerationVendor.default("elevenlabs"),
3830
+ prompt: z.string().min(1).max(2e3),
3831
+ lengthMs: z.number().int().min(3e3).max(12e4).default(1e4),
3832
+ forceInstrumental: z.boolean().optional(),
3833
+ modelId: supportedMusicModel.default("music_v1"),
3834
+ format: z.string().optional(),
3835
+ ...generatedAudioRequestFields
3815
3836
  });
3816
- const SpiderErrorAction = z$1.object({
3817
- action: z$1.literal("spider_error"),
3818
- spiderJobId: z$1.string(),
3819
- error: z$1.string(),
3820
- notebooksProcessed: z$1.number().optional(),
3821
- totalNotebooks: z$1.number(),
3822
- dryRun: z$1.boolean().optional(),
3837
+ /**
3838
+ * Canonical agent execution status tuple. Shared between the database model
3839
+ * (`AgentExecutionModel`), the client store (`useAgentExecutionStore`), and
3840
+ * wire schemas (`ChildExecutionSnapshotSchema`, etc.) so the value space can't
3841
+ * drift across boundaries, and so the Zod schemas can type `status` as
3842
+ * `z.enum(AGENT_EXECUTION_STATUSES)` instead of `z.string()`.
3843
+ */
3844
+ const AGENT_EXECUTION_STATUSES = [
3845
+ "pending",
3846
+ "running",
3847
+ "continuing",
3848
+ "awaiting_permission",
3849
+ "awaiting_subagent",
3850
+ "awaiting_dag_children",
3851
+ "paused",
3852
+ "completed",
3853
+ "failed",
3854
+ "aborted"
3855
+ ];
3856
+ /**
3857
+ * Why a session summarization happened, stamped on `ISession.summaryTrigger`. Single source for the
3858
+ * places that each used to spell this list out: the Session zod schema (schemas/actions.ts), the
3859
+ * entity type (types/entities/SessionTypes.ts), the Mongoose path (packages/database SessionModel)
3860
+ * and the session.summarize event payload (apps/client server/utils/eventBus.ts). They drifted -
3861
+ * the Mongoose enum said 'milestone'/'growth' for two values nothing produces - and a drift there
3862
+ * is invisible on the update path, because BaseModel's findOneAndUpdate writes without
3863
+ * runValidators.
3864
+ *
3865
+ * Those surfaces now name PERSISTED_SESSION_SUMMARY_TRIGGERS below, not the full union: a stored
3866
+ * field may only carry a reason a run HAPPENED. 'throttling' is the exception that forced the
3867
+ * split - shouldSummarizeSession (b4m-core/services ChatCompletionFeatures) returns it as the
3868
+ * reason it DECLINED to summarize, so it describes no run and belongs to a decision, not a
3869
+ * document. It is typed by SummarizationDecision there, not by the session field.
3870
+ *
3871
+ * 'manual' means someone asked for one notebook's summary. The admin sweep
3872
+ * (apps/workers/src/events/spider.ts) summarizes every un-summarized notebook of the admin who ran it in one
3873
+ * billed pass, so it stamps 'spider' instead: without that, one deliberate click and a whole sweep
3874
+ * are indistinguishable when someone investigates unexpected summarization spend.
3875
+ */
3876
+ /**
3877
+ * The triggers a document may actually carry - every reason a summarization HAPPENED. The event
3878
+ * payload and createSessionParametersSchema both name this list rather than the full union below,
3879
+ * so 'throttling' cannot be published, cannot be stored, and therefore cannot reach a copy path.
3880
+ * Add a new reason-it-happened here, not to SESSION_SUMMARY_TRIGGERS, and every one of those
3881
+ * boundaries picks it up.
3882
+ */
3883
+ const PERSISTED_SESSION_SUMMARY_TRIGGERS = [
3884
+ "manual",
3885
+ "project",
3886
+ "earlyMilestone",
3887
+ "contentGrowth",
3888
+ "spider"
3889
+ ];
3890
+ [...PERSISTED_SESSION_SUMMARY_TRIGGERS];
3891
+ /**
3892
+ * Operator allow-list for the client-authored Mongo filter carried on a `subscribe_query` frame.
3893
+ *
3894
+ * The WS data-subscribe handler forwards that filter to `Model.find` and persists it on the
3895
+ * QuerySubscription the separate subscriber-fanout service replays, so an unconstrained
3896
+ * `$`-prefixed key lets any authenticated socket ask the database to run server-side JavaScript
3897
+ * (`$where`, `$expr` + `$function`) or an unbounded `$regex` - either of which pins a pooled
3898
+ * connection for as long as it runs.
3899
+ *
3900
+ * Live subscriptions only ever need equality, membership and range matching, so anything outside
3901
+ * this list is REFUSED rather than stripped: silently dropping an operator would quietly widen the
3902
+ * document set the caller ends up subscribed to.
3903
+ *
3904
+ * The scan is deliberately structural rather than a model of Mongo's grammar - it flags any
3905
+ * `$`-prefixed key anywhere in the tree that isn't allow-listed. It can therefore accept an
3906
+ * allow-listed operator in a position Mongo would treat as a literal sub-document field name, but
3907
+ * that is inert; what matters is that no disallowed operator can reach the driver.
3908
+ */
3909
+ /** Combinators whose operands are themselves filters. */
3910
+ const SUBSCRIPTION_FILTER_LOGICAL_OPERATORS = [
3911
+ "$and",
3912
+ "$or",
3913
+ "$nor",
3914
+ "$not"
3915
+ ];
3916
+ /** Value-level operators a subscription filter may use. */
3917
+ const SUBSCRIPTION_FILTER_VALUE_OPERATORS = [
3918
+ "$eq",
3919
+ "$ne",
3920
+ "$gt",
3921
+ "$gte",
3922
+ "$lt",
3923
+ "$lte",
3924
+ "$in",
3925
+ "$nin",
3926
+ "$exists",
3927
+ "$type",
3928
+ "$size",
3929
+ "$all",
3930
+ "$elemMatch"
3931
+ ];
3932
+ const ALLOWED_OPERATORS = /* @__PURE__ */ new Set([...SUBSCRIPTION_FILTER_LOGICAL_OPERATORS, ...SUBSCRIPTION_FILTER_VALUE_OPERATORS]);
3933
+ /**
3934
+ * Returns a dotted path for every part of `filter` a subscription may not send: a disallowed
3935
+ * `$`-prefixed key, a `RegExp` operand, or nesting past {@link SUBSCRIPTION_FILTER_MAX_DEPTH}.
3936
+ * An empty array means the filter is safe to forward to the database.
3937
+ */
3938
+ function findDisallowedSubscriptionFilterKeys(filter) {
3939
+ const violations = [];
3940
+ const walk = (node, path, depth) => {
3941
+ if (depth > 12) {
3942
+ violations.push(`${path} (nested deeper than 12)`);
3943
+ return;
3944
+ }
3945
+ if (node instanceof RegExp) {
3946
+ violations.push(`${path} (regular expression)`);
3947
+ return;
3948
+ }
3949
+ if (Array.isArray(node)) {
3950
+ node.forEach((entry, i) => walk(entry, `${path}[${i}]`, depth + 1));
3951
+ return;
3952
+ }
3953
+ if (node === null || typeof node !== "object") return;
3954
+ for (const [key, value] of Object.entries(node)) {
3955
+ const childPath = path ? `${path}.${key}` : key;
3956
+ if (key.startsWith("$") && !ALLOWED_OPERATORS.has(key)) {
3957
+ violations.push(childPath);
3958
+ continue;
3959
+ }
3960
+ walk(value, childPath, depth + 1);
3961
+ }
3962
+ };
3963
+ walk(filter, "", 0);
3964
+ return violations;
3965
+ }
3966
+ const DataSubscribeRequestAction = z$1.object({
3967
+ action: z$1.literal("subscribe_query"),
3968
+ accessToken: z$1.string().optional(),
3969
+ subscriptionId: z$1.string(),
3970
+ collectionName: z$1.string(),
3971
+ query: z$1.looseObject({}).superRefine((filter, ctx) => {
3972
+ for (const key of findDisallowedSubscriptionFilterKeys(filter)) ctx.addIssue({
3973
+ code: "custom",
3974
+ message: `Disallowed subscription filter: ${key}`
3975
+ });
3976
+ }),
3977
+ fields: z$1.record(z$1.string(), z$1.union([z$1.boolean(), z$1.number()])),
3978
+ fetchInitialData: z$1.boolean().prefault(true).optional(),
3823
3979
  clientId: z$1.string().optional()
3824
3980
  });
3825
- const PiHistoryProgressAction = z$1.object({
3826
- action: z$1.literal("pi_history_progress"),
3827
- analysisJobId: z$1.string(),
3828
- repoFullName: z$1.string(),
3829
- phase: z$1.enum([
3830
- "fetching_issues",
3831
- "fetching_prs",
3832
- "calculating_stats",
3833
- "building_profiles",
3834
- "extracting_keywords",
3835
- "saving"
3836
- ]),
3837
- percentage: z$1.number(),
3838
- message: z$1.string(),
3839
- itemsProcessed: z$1.number().optional(),
3840
- totalItems: z$1.number().optional(),
3841
- clientId: z$1.string().optional()
3981
+ const DataUnsubscribeRequestAction = z$1.object({
3982
+ action: z$1.literal("unsubscribe_query"),
3983
+ accessToken: z$1.string().optional(),
3984
+ subscriptionId: z$1.string()
3842
3985
  });
3843
- const PiHistoryCompleteAction = z$1.object({
3844
- action: z$1.literal("pi_history_complete"),
3845
- analysisJobId: z$1.string(),
3846
- repoFullName: z$1.string(),
3847
- stats: z$1.object({
3848
- closedIssues: z$1.number(),
3849
- mergedPRs: z$1.number(),
3850
- contributors: z$1.number()
3851
- }),
3852
- clientId: z$1.string().optional()
3986
+ const HeartbeatAction = z$1.object({ action: z$1.literal("heartbeat") });
3987
+ const VoiceSessionSendTranscriptAction = z$1.object({
3988
+ action: z$1.literal("voice_session_send_transcript"),
3989
+ userId: z$1.string(),
3990
+ sessionId: z$1.string(),
3991
+ transcript: z$1.string(),
3992
+ type: z$1.enum(["input", "response"]),
3993
+ conversationItemId: z$1.string(),
3994
+ timestamp: z$1.coerce.date().optional()
3853
3995
  });
3854
- const PiHistoryErrorAction = z$1.object({
3855
- action: z$1.literal("pi_history_error"),
3856
- analysisJobId: z$1.string(),
3857
- repoFullName: z$1.string(),
3858
- error: z$1.string(),
3859
- phase: z$1.string(),
3860
- clientId: z$1.string().optional()
3996
+ /**
3997
+ * CLI Completion request over WebSocket (bypasses CloudFront 20s timeout)
3998
+ */
3999
+ const CliCompletionRequestAction = z$1.object({
4000
+ action: z$1.literal("cli_completion_request"),
4001
+ accessToken: z$1.string(),
4002
+ requestId: z$1.uuid(),
4003
+ model: z$1.string(),
4004
+ messages: z$1.array(z$1.object({
4005
+ role: z$1.enum([
4006
+ "user",
4007
+ "assistant",
4008
+ "system"
4009
+ ]),
4010
+ content: z$1.union([z$1.string(), z$1.array(z$1.unknown())]),
4011
+ cache: z$1.boolean().optional()
4012
+ })),
4013
+ options: z$1.object({
4014
+ temperature: z$1.number().optional(),
4015
+ maxTokens: z$1.number().optional(),
4016
+ stream: z$1.boolean().optional(),
4017
+ tools: z$1.array(z$1.unknown()).optional(),
4018
+ response_format: z$1.discriminatedUnion("type", [z$1.object({ type: z$1.literal("text") }), z$1.object({
4019
+ type: z$1.literal("json_schema"),
4020
+ json_schema: z$1.object({
4021
+ name: z$1.string(),
4022
+ description: z$1.string().optional(),
4023
+ schema: z$1.record(z$1.string(), z$1.any()),
4024
+ strict: z$1.boolean().optional().default(true)
4025
+ })
4026
+ })]).optional()
4027
+ }).optional()
3861
4028
  });
3862
- const StreamedChatCompletionAction = z$1.object({
3863
- action: z$1.literal("streamed_chat_completion"),
4029
+ /**
4030
+ * CLI Tool execution request over WebSocket
4031
+ */
4032
+ const CliToolRequestAction = z$1.object({
4033
+ action: z$1.literal("cli_tool_request"),
4034
+ accessToken: z$1.string(),
4035
+ requestId: z$1.uuid(),
4036
+ toolName: z$1.string(),
4037
+ input: z$1.record(z$1.string(), z$1.unknown())
4038
+ });
4039
+ const VoiceSessionEndedAction = z$1.object({
4040
+ action: z$1.literal("voice_session_ended"),
4041
+ userId: z$1.string(),
4042
+ model: z$1.string(),
4043
+ sessionId: z$1.string(),
4044
+ usage: z$1.object({
4045
+ audioInputTokens: z$1.number().min(0),
4046
+ audioCachedInputTokens: z$1.number().min(0),
4047
+ audioOutputTokens: z$1.number().min(0),
4048
+ textInputTokens: z$1.number().min(0),
4049
+ textCachedInputTokens: z$1.number().min(0),
4050
+ textOutputTokens: z$1.number().min(0)
4051
+ })
4052
+ });
4053
+ const DataSubscriptionUpdateAction = z$1.object({
4054
+ action: z$1.literal("data_update"),
4055
+ subscriptionId: z$1.string(),
4056
+ collectionName: z$1.string(),
4057
+ operationType: z$1.string(),
3864
4058
  clientId: z$1.string().optional(),
3865
- statusMessage: z$1.string().nullable().optional(),
4059
+ data: z$1.looseObject({
4060
+ _id: z$1.string(),
4061
+ id: z$1.string()
4062
+ })
4063
+ });
4064
+ /**
4065
+ * Server -> Client: a `subscribe_query` frame was refused (the operator allow-list, `fields`
4066
+ * validation) or its initial fetch was aborted (`maxTimeMS`). Neither failure throws an
4067
+ * UnauthorizedError/JsonWebTokenError, so withWebSocketContext's status code never reaches the
4068
+ * client as a frame - this is the only signal the caller gets that the subscription never took.
4069
+ */
4070
+ const DataSubscribeErrorAction = z$1.object({
4071
+ action: z$1.literal("data_subscribe_error"),
4072
+ subscriptionId: z$1.string(),
4073
+ error: z$1.string()
4074
+ });
4075
+ const LLMStatusUpdateAction = z$1.object({
4076
+ action: z$1.literal("llm_status_update"),
4077
+ status: z$1.string().nullable(),
4078
+ clientId: z$1.string().optional()
4079
+ });
4080
+ const InboxRefetchAction = z$1.object({
4081
+ action: z$1.literal("inbox_refetch"),
4082
+ status: z$1.string(),
4083
+ clientId: z$1.string().optional()
4084
+ });
4085
+ const InvitesRefetchAction = z$1.object({
4086
+ action: z$1.literal("invites_refetch"),
4087
+ status: z$1.string(),
4088
+ clientId: z$1.string().optional()
4089
+ });
4090
+ const UpdateCurrentUserAction = z$1.object({
4091
+ action: z$1.literal("update_current_user"),
4092
+ clientId: z$1.string().optional(),
4093
+ user: z$1.object({
4094
+ id: z$1.string(),
4095
+ username: z$1.string(),
4096
+ name: z$1.string(),
4097
+ email: z$1.string(),
4098
+ groups: z$1.array(z$1.string()),
4099
+ isAdmin: z$1.boolean(),
4100
+ storageLimit: z$1.number(),
4101
+ currentStorageSize: z$1.number()
4102
+ }).partial()
4103
+ });
4104
+ const ResearchTaskStatusUpdateAction = z$1.object({
4105
+ action: z$1.literal("research_task_status_update"),
4106
+ taskId: z$1.string(),
4107
+ status: z$1.string(),
4108
+ currentStep: z$1.string(),
4109
+ progress: z$1.number()
4110
+ });
4111
+ const NotebookCurationProgressUpdateAction = z$1.object({
4112
+ action: z$1.literal("notebook_curation_progress"),
4113
+ curationJobId: z$1.string(),
4114
+ sessionId: z$1.string(),
4115
+ status: z$1.enum([
4116
+ "pending",
4117
+ "loading",
4118
+ "extracting",
4119
+ "generating",
4120
+ "storing",
4121
+ "completed",
4122
+ "failed"
4123
+ ]),
4124
+ stage: z$1.enum([
4125
+ "loading",
4126
+ "extracting",
4127
+ "generating",
4128
+ "storing"
4129
+ ]).optional(),
4130
+ percentage: z$1.number(),
4131
+ message: z$1.string().optional(),
4132
+ messagesProcessed: z$1.number().optional(),
4133
+ totalMessages: z$1.number().optional(),
4134
+ artifactsFound: z$1.number().optional(),
4135
+ curatedFileId: z$1.string().optional(),
4136
+ errorMessage: z$1.string().optional(),
4137
+ tokensDeducted: z$1.number().optional(),
4138
+ clientId: z$1.string().optional()
4139
+ });
4140
+ const QuestExportProgressAction = z$1.object({
4141
+ action: z$1.literal("quest_export_progress"),
4142
+ exportJobId: z$1.string(),
4143
+ planId: z$1.string(),
4144
+ status: z$1.enum([
4145
+ "assembling",
4146
+ "downloading_images",
4147
+ "summarizing",
4148
+ "zipping",
4149
+ "completed",
4150
+ "failed"
4151
+ ]),
4152
+ progress: z$1.number(),
4153
+ detail: z$1.string().optional(),
4154
+ downloadUrl: z$1.string().optional(),
4155
+ filename: z$1.string().optional(),
4156
+ errorMessage: z$1.string().optional(),
4157
+ droppedQuestCount: z$1.number().optional(),
4158
+ clientId: z$1.string().optional()
4159
+ });
4160
+ const OrgFeedbackSummaryProgressAction = z$1.object({
4161
+ action: z$1.literal("org_feedback_summary_progress"),
4162
+ summaryJobId: z$1.string(),
4163
+ organizationId: z$1.string(),
4164
+ status: z$1.enum([
4165
+ "processing",
4166
+ "completed",
4167
+ "failed"
4168
+ ]),
4169
+ progress: z$1.number(),
4170
+ errorMessage: z$1.string().optional(),
4171
+ clientId: z$1.string().optional()
4172
+ });
4173
+ const SpiderProgressUpdateAction = z$1.object({
4174
+ action: z$1.literal("spider_progress"),
4175
+ spiderJobId: z$1.string(),
4176
+ notebooksProcessed: z$1.number(),
4177
+ totalNotebooks: z$1.number(),
4178
+ currentOperation: z$1.string(),
4179
+ currentNotebookId: z$1.string().optional(),
4180
+ currentNotebookName: z$1.string().optional(),
4181
+ dryRun: z$1.boolean().optional(),
4182
+ clientId: z$1.string().optional()
4183
+ });
4184
+ const SpiderCompleteAction = z$1.object({
4185
+ action: z$1.literal("spider_complete"),
4186
+ spiderJobId: z$1.string(),
4187
+ totalNotebooks: z$1.number(),
4188
+ stats: z$1.object({
4189
+ messageCountsUpdated: z$1.number(),
4190
+ notebooksCurated: z$1.number(),
4191
+ notebooksSummarized: z$1.number(),
4192
+ notebooksTagged: z$1.number(),
4193
+ messagesEmbedded: z$1.number().optional(),
4194
+ errors: z$1.number().optional(),
4195
+ skipped: z$1.number().optional()
4196
+ }),
4197
+ dryRun: z$1.boolean().optional(),
4198
+ clientId: z$1.string().optional()
4199
+ });
4200
+ const SpiderErrorAction = z$1.object({
4201
+ action: z$1.literal("spider_error"),
4202
+ spiderJobId: z$1.string(),
4203
+ error: z$1.string(),
4204
+ notebooksProcessed: z$1.number().optional(),
4205
+ totalNotebooks: z$1.number(),
4206
+ dryRun: z$1.boolean().optional(),
4207
+ clientId: z$1.string().optional()
4208
+ });
4209
+ const PiHistoryProgressAction = z$1.object({
4210
+ action: z$1.literal("pi_history_progress"),
4211
+ analysisJobId: z$1.string(),
4212
+ repoFullName: z$1.string(),
4213
+ phase: z$1.enum([
4214
+ "fetching_issues",
4215
+ "fetching_prs",
4216
+ "calculating_stats",
4217
+ "building_profiles",
4218
+ "extracting_keywords",
4219
+ "saving"
4220
+ ]),
4221
+ percentage: z$1.number(),
4222
+ message: z$1.string(),
4223
+ itemsProcessed: z$1.number().optional(),
4224
+ totalItems: z$1.number().optional(),
4225
+ clientId: z$1.string().optional()
4226
+ });
4227
+ const PiHistoryCompleteAction = z$1.object({
4228
+ action: z$1.literal("pi_history_complete"),
4229
+ analysisJobId: z$1.string(),
4230
+ repoFullName: z$1.string(),
4231
+ stats: z$1.object({
4232
+ closedIssues: z$1.number(),
4233
+ mergedPRs: z$1.number(),
4234
+ contributors: z$1.number()
4235
+ }),
4236
+ clientId: z$1.string().optional()
4237
+ });
4238
+ const PiHistoryErrorAction = z$1.object({
4239
+ action: z$1.literal("pi_history_error"),
4240
+ analysisJobId: z$1.string(),
4241
+ repoFullName: z$1.string(),
4242
+ error: z$1.string(),
4243
+ phase: z$1.string(),
4244
+ clientId: z$1.string().optional()
4245
+ });
4246
+ const GenerationJobUpdatedAction = z$1.object({
4247
+ action: z$1.literal("generation_job_updated"),
4248
+ job: z$1.object({
4249
+ id: z$1.string(),
4250
+ kind: z$1.enum(GENERATION_JOB_KINDS),
4251
+ state: z$1.enum(GENERATION_JOB_STATES),
4252
+ progress: z$1.number().min(0).max(1).optional(),
4253
+ error: z$1.object({
4254
+ code: z$1.string(),
4255
+ message: z$1.string()
4256
+ }).optional(),
4257
+ output: z$1.object({
4258
+ fileId: z$1.string().optional(),
4259
+ contentType: z$1.string(),
4260
+ durationSeconds: z$1.number()
4261
+ }).optional()
4262
+ })
4263
+ });
4264
+ const StreamedChatCompletionAction = z$1.object({
4265
+ action: z$1.literal("streamed_chat_completion"),
4266
+ clientId: z$1.string().optional(),
4267
+ statusMessage: z$1.string().nullable().optional(),
3866
4268
  quest: z$1.object({
3867
4269
  id: z$1.string(),
3868
4270
  sessionId: z$1.string(),
@@ -3870,6 +4272,7 @@ const StreamedChatCompletionAction = z$1.object({
3870
4272
  replies: z$1.array(z$1.string()).optional(),
3871
4273
  images: z$1.array(z$1.string()).optional(),
3872
4274
  videos: z$1.array(z$1.string()).optional(),
4275
+ videoJobIds: z$1.array(z$1.string()).optional(),
3873
4276
  type: z$1.enum(CHAT_HISTORY_ITEM_TYPES),
3874
4277
  status: z$1.enum([
3875
4278
  "stopped",
@@ -4415,6 +4818,7 @@ const HearthEventAction = z$1.object({
4415
4818
  }),
4416
4819
  machine: hearthMachineBodySchema.optional(),
4417
4820
  refs: hearthEventRefsSchema.prefault({}),
4821
+ origin: hearthEventOriginSchema.optional(),
4418
4822
  createdAt: z$1.string()
4419
4823
  })
4420
4824
  });
@@ -4893,6 +5297,7 @@ const ReconnectResultAction = z$1.object({
4893
5297
  action: z$1.literal("reconnect_result"),
4894
5298
  found: z$1.boolean(),
4895
5299
  executionId: z$1.string().optional(),
5300
+ sessionId: z$1.string().optional(),
4896
5301
  status: z$1.enum(AGENT_EXECUTION_STATUSES).optional(),
4897
5302
  pendingPermission: z$1.object({
4898
5303
  toolName: z$1.string(),
@@ -4930,6 +5335,7 @@ const OptiHashiRunUpdatedAction = z$1.object({
4930
5335
  status: z$1.string()
4931
5336
  });
4932
5337
  z$1.discriminatedUnion("action", [
5338
+ GenerationJobUpdatedAction,
4933
5339
  DataSubscriptionUpdateAction,
4934
5340
  DataSubscribeErrorAction,
4935
5341
  InboxRefetchAction,
@@ -5226,7 +5632,7 @@ const CompletionRequestSchema = z$1.object({
5226
5632
  stream: z$1.boolean().optional(),
5227
5633
  tools: z$1.array(CompletionToolSchema).optional(),
5228
5634
  response_format: ResponseFormatSchema.optional(),
5229
- reasoningEffort: CompletionReasoningEffortSchema.optional()
5635
+ reasoningEffort: CompletionReasoningEffortSchema.optional().describe("Reasoning effort hint. Honoured only by the OpenAI (reasoning models only, and not alongside tools on some of them), Kimi (K3 only) and DeepSeek adapters; Kimi and DeepSeek map it onto their own scale. Silently ignored by every other adapter (Anthropic, Gemini, Bedrock, xAI, Ollama) and by non-reasoning models, so sending it there is a no-op.")
5230
5636
  }).optional()
5231
5637
  });
5232
5638
  const CompletionUsageSchema = z$1.object({
@@ -6269,6 +6675,24 @@ const DATA_LAKES = [{
6269
6675
  }
6270
6676
  })()];
6271
6677
  new Set(DATA_LAKES.map((l) => l.id));
6678
+ /**
6679
+ * Query parameters for a cursor-paginated public list (CONVENTIONS.md section 8).
6680
+ *
6681
+ * `limit` is `z.coerce` because query values arrive as strings; registerContract publishes it as an
6682
+ * integer. A non-numeric, zero, or over-cap value is a 422, never silently clamped.
6683
+ */
6684
+ const PaginationQuerySchema = z$1.object({
6685
+ limit: z$1.coerce.number().int().min(1).max(100).default(25),
6686
+ cursor: z$1.string().min(1).optional()
6687
+ });
6688
+ /**
6689
+ * The response envelope every cursor-paginated public list returns. `next_cursor` is required and
6690
+ * `null` on the last page, so a caller loops on it rather than on the page length.
6691
+ */
6692
+ const paginatedResponseSchema = (itemSchema) => z$1.object({
6693
+ data: z$1.array(itemSchema),
6694
+ next_cursor: z$1.string().nullable()
6695
+ });
6272
6696
  const SessionTagSchema = z$1.object({
6273
6697
  name: z$1.string(),
6274
6698
  strength: z$1.number()
@@ -6286,14 +6710,15 @@ const SessionUpdateRequestSchema = z$1.object({
6286
6710
  tags: z$1.array(SessionTagSchema).optional(),
6287
6711
  lastUsedModel: z$1.string().min(1).nullish().describe("Pin a specific model id, or omit/send null to leave the current pin unchanged. Sending null does NOT clear it."),
6288
6712
  forceKnowledgeRetrieval: z$1.boolean().optional(),
6289
- lakeScope: z$1.array(z$1.string()).nullable().optional().describe("The data lakes this session grounds on, as lake tags (the `datalakeTag` of each lake from GET /api/data-lakes). Send a list to ground only on those lakes, `[]` to ground on no lake at all, or `null` to clear the choice so retrieval falls back to every lake you can reach. Omit to leave the current choice unchanged. Tags naming a lake you cannot reach are ignored at retrieval time rather than rejected here. Narrowing the scope does not by itself turn retrieval on: pair it with `forceKnowledgeRetrieval: true` for a session that is not already grounded. Conversely `[]` leaves a grounded session nothing to retrieve from, so its forced retrieval is skipped rather than run against every lake."),
6713
+ lakeScope: z$1.array(z$1.string()).nullable().optional().describe("The data lakes this session grounds on, as lake tags (the `datalake_tag` of each lake from GET /api/v1/data-lakes). Send a list to ground only on those lakes, `[]` to ground on no lake at all, or `null` to clear the choice so retrieval falls back to every lake you can reach. Omit to leave the current choice unchanged. Tags naming a lake you cannot reach are ignored at retrieval time rather than rejected here. Narrowing the scope does not by itself turn retrieval on: pair it with `forceKnowledgeRetrieval: true` for a session that is not already grounded. Conversely `[]` leaves a grounded session nothing to retrieve from, so its forced retrieval is skipped rather than run against every lake."),
6714
+ includeLibraryFiles: z$1.boolean().optional().describe("Whether the session grounds on your own library (files you own, or that are shared with you or your groups) alongside its lakes. `false` confines retrieval to lake content, including when every lake is in scope; the session's attached files still reach the model and stay searchable by its knowledge-base tools. Omit to leave it unchanged; while never set, the library is excluded only when a lake was picked for the session (Data Lakes mode or an explicit lake scope), not when lake tags were derived from an attached file. While `forceKnowledgeRetrieval` is `false` the library is included regardless; the stored value applies again once it is turned back on."),
6290
6715
  propagateToProjects: z$1.boolean().optional().describe("Defaults to true when omitted. When knowledgeIds grows, the newly-added file ids are also appended to every project that contains this session, granting every member of that project access to those files. This propagation is append-only and cannot be undone through the UI - pass false if newly-attached files should not be shared with the project.")
6291
6716
  });
6292
6717
  /** Path parameter for session-scoped endpoints, e.g. GET/PUT /api/sessions/{id}. */
6293
6718
  const SessionIdParamSchema = z$1.object({ id: z$1.string().min(1) });
6294
6719
  /**
6295
- * Practical response subset for PUT /api/sessions/{id} - the fields a caller needs to
6296
- * confirm an update took effect. ISession (types/entities/SessionTypes.ts) carries many
6720
+ * Practical response subset for GET and PUT /api/sessions/{id} - the fields a caller needs to
6721
+ * read a session or confirm an update took effect. ISession (types/entities/SessionTypes.ts) carries many
6297
6722
  * more server-internal fields not documented as public API surface here.
6298
6723
  */
6299
6724
  const SessionResponseSchema = z$1.object({
@@ -6307,10 +6732,22 @@ const SessionResponseSchema = z$1.object({
6307
6732
  forceKnowledgeRetrieval: z$1.boolean().optional(),
6308
6733
  retrievalTags: z$1.array(z$1.string()).optional(),
6309
6734
  lakeScopeExplicit: z$1.boolean().optional().describe("True when `retrievalTags` is a deliberate choice, so an empty list means \"no lake\" rather than \"any\"."),
6735
+ includeLibraryFiles: z$1.boolean().optional().describe("Whether the session grounds on your own library alongside its lakes. Absent when never set."),
6310
6736
  lastUsedModel: z$1.string().nullish(),
6311
6737
  firstCreated: z$1.date(),
6312
6738
  lastUpdated: z$1.date()
6313
6739
  });
6740
+ /** Request body for POST /api/v1/sessions/{id}/clone. */
6741
+ const SessionCloneRequestSchema = z$1.preprocess((body) => body === "" ? void 0 : body, z$1.object({ targetSurface: z$1.string().nullable().optional() }).default({}));
6742
+ /** Query for GET /api/v1/sessions: the pagination convention plus flat filters. */
6743
+ const ListSessionsQuerySchema = PaginationQuerySchema.extend({
6744
+ search: z$1.string().min(1).max(200).optional().describe("Case-insensitive substring match on the session name, its summary, or a tag name."),
6745
+ surface: z$1.string().min(1).optional().describe("Lists only sessions of this product surface. When omitted, only sessions with no surface are listed."),
6746
+ origin: z$1.enum(SESSION_ORIGIN_CHANNELS).optional().describe("Lists only sessions created through this channel. `web` also matches sessions with no recorded origin.")
6747
+ });
6748
+ const ListSessionsResponseSchema = paginatedResponseSchema(SessionResponseSchema);
6749
+ /** Response for DELETE /api/sessions/{id}. */
6750
+ const SessionDeleteResponseSchema = z$1.object({ newLastNotebookId: z$1.string().nullable().describe("The caller's most recently updated remaining session, which the product UI opens next; null when none remains.") });
6314
6751
  /**
6315
6752
  * Request schema for POST /api/v1/sessions. Declares every client-settable field of
6316
6753
  * createSessionParametersSchema (b4m-core/services/src/sessionService/create.ts), which stays the
@@ -6321,7 +6758,7 @@ const SessionResponseSchema = z$1.object({
6321
6758
  const CreateSessionRequestSchema = z$1.object({
6322
6759
  name: z$1.string(),
6323
6760
  projectId: z$1.string().optional().describe("Adds the new session to this project."),
6324
- dataLakeId: z$1.string().nullish().describe("Seeds retrieval defaults from this data lake."),
6761
+ dataLakeId: z$1.string().nullish().describe("Seeds retrieval defaults from this data lake and turns on forced retrieval. Get ids from `GET /api/v1/data-lakes`."),
6325
6762
  preauthorizedLakeIds: z$1.unknown().optional().describe("Data lakes the caller manages, armed for this session. At most 10; requires management rights."),
6326
6763
  knowledgeIds: z$1.array(z$1.string()).optional(),
6327
6764
  artifactIds: z$1.array(z$1.string()).optional(),
@@ -6335,6 +6772,7 @@ const CreateSessionRequestSchema = z$1.object({
6335
6772
  forceKnowledgeRetrieval: z$1.boolean().optional(),
6336
6773
  retrievalTags: z$1.array(z$1.string()).optional(),
6337
6774
  lakeScopeExplicit: z$1.boolean().optional(),
6775
+ includeLibraryFiles: z$1.boolean().optional(),
6338
6776
  corpusGroundingMode: z$1.enum(DATA_LAKE_GROUNDING_MODES).optional().describe("Honored only when a lake is named by `retrievalTags` without `dataLakeId`."),
6339
6777
  retrievalExcludeFilenameMarkers: z$1.array(z$1.string()).optional(),
6340
6778
  retrievalVectorizedOnly: z$1.boolean().optional(),
@@ -6590,6 +7028,125 @@ const SupportedEmbeddingModelSchema = z$1.union([
6590
7028
  z$1.enum(OllamaEmbeddingModel)
6591
7029
  ]);
6592
7030
  /**
7031
+ * Absolute floors for the FILE/RAG corpus that forced retrieval scans, by embedding space.
7032
+ *
7033
+ * Backs the `forcedRetrievalMinSimilarityPct` default. An operator's explicit value still wins -
7034
+ * this is what "unset" resolves to, and the reason the setting cannot simply carry one static
7035
+ * default any more.
7036
+ *
7037
+ * ada-002 at 75 is the value production shipped and the only one with a production-lake history
7038
+ * behind it. 3-small at 58 is MEASURED, on a live 520-file / 21,327-chunk capture of the
7039
+ * `opti-knowledge` lake (1536 dims, 2026-09-18), swept against 30 positives authored from that
7040
+ * corpus's own passages plus screened negatives - so recall and false-positive rate come out of ONE
7041
+ * corpus snapshot rather than two. The negatives were screened by reading the served chunk TEXT,
7042
+ * which reclassified 44 of 89 as answerable; the two right-hand columns are that screen's two
7043
+ * defensible readings, counting a PARTIAL answer as answerable or as a false positive.
7044
+ *
7045
+ * floor recall positives emptied FP (45 strict neg) FP (61 partial-as-neg)
7046
+ * 85:49 100.0% 0 71.1% 78.7%
7047
+ * 85:53 100.0% 0 64.4% 73.8%
7048
+ * 85:55 96.7% 1 60.0% 68.9%
7049
+ * 85:58 93.3% 2 42.2% 52.5%
7050
+ * 85:61 86.7% 4 22.2% 36.1%
7051
+ * 85:64 76.7% 7 13.3% 19.7%
7052
+ * 85:75 20.0% 24 0.0% 0.0%
7053
+ *
7054
+ * WHY 58 RATHER THAN THE SEPARATION OPTIMUM, WHICH IS 61-64. Recall here is measured on positives
7055
+ * authored FROM corpus passages, so each supporting document is the easiest possible match for its
7056
+ * own question, and this recall is an UPPER BOUND on recall against a real user's phrasing. The
7057
+ * false-positive rate, measured on screened negatives, carries no matching optimism. An optimistic
7058
+ * recall beside an honest false-positive rate biases the optimum HIGH, so the shipped floor is the
7059
+ * low end of the bracket rather than its peak.
7060
+ *
7061
+ * WHAT THE PREVIOUS 49 GOT WRONG, and it was not the magnitude. 49 emptied ZERO positives - it was
7062
+ * not buying recall protection, it simply was not cutting - while 71-79% of screened true negatives
7063
+ * still got something served. The entire range 0-53 costs no recall on this corpus, which the
7064
+ * 35-file capture 49 was fitted to could not show: its decoy column rested on 6 negatives.
7065
+ *
7066
+ * Recall above is of the ACCEPTED set, so it answers "did the floor cut the supporting document",
7067
+ * which is the floor's own question, and is NOT a claim the model saw it - the char budget and the
7068
+ * 256-chunk candidate pool cap are separate narrowings downstream of this gate.
7069
+ *
7070
+ * ada-002's 75 applied in this space would be an outage in all but name: 20% recall, 24 of 30
7071
+ * positives emptied outright. That is the per-space case above restated as a measurement.
7072
+ *
7073
+ * 3-large is deliberately absent despite having a measured band (0.2104-0.5197): the choice between
7074
+ * it and 3-small is recorded as NOT SETTLED, and guessing a floor for a space nobody has run the
7075
+ * sweep against is what this module exists to stop. Voyage, Bedrock and the Ollama self-host
7076
+ * embedders are absent for the same reason - none has ever been measured, and the 0.75 they inherit
7077
+ * today was fitted to a model none of them is.
7078
+ */
7079
+ const FORCED_RETRIEVAL_MIN_SIMILARITY_PCT_BY_SPACE = {
7080
+ ["text-embedding-ada-002"]: 75,
7081
+ ["text-embedding-3-small"]: 58
7082
+ };
7083
+ const ReleaseNoteCategorySchema = z$1.enum([
7084
+ "new",
7085
+ "improved",
7086
+ "fixed"
7087
+ ]);
7088
+ const ReleaseNoteItemSchema = z$1.object({
7089
+ category: ReleaseNoteCategorySchema,
7090
+ text: z$1.string().min(1),
7091
+ importance: z$1.number().int().min(1).max(3),
7092
+ sourcePrs: z$1.array(z$1.number().int().positive())
7093
+ });
7094
+ const ReleaseNoteStatusSchema = z$1.enum(["scheduled", "hidden"]);
7095
+ z$1.object({
7096
+ releaseTag: z$1.string().min(1),
7097
+ deployedSha: z$1.string().min(1),
7098
+ deployedAt: z$1.coerce.date(),
7099
+ headline: z$1.string(),
7100
+ summary: z$1.string(),
7101
+ items: z$1.array(ReleaseNoteItemSchema),
7102
+ audience: z$1.literal("public"),
7103
+ status: ReleaseNoteStatusSchema,
7104
+ publishAt: z$1.coerce.date(),
7105
+ editedAt: z$1.coerce.date().nullable(),
7106
+ schemaVersion: z$1.number().int()
7107
+ });
7108
+ const ReleaseNotesJobPrSchema = z$1.object({
7109
+ number: z$1.number().int().positive(),
7110
+ title: z$1.string(),
7111
+ labels: z$1.array(z$1.string()),
7112
+ customerNote: z$1.string().optional(),
7113
+ excerpt: z$1.string()
7114
+ });
7115
+ z$1.object({
7116
+ kind: z$1.literal("release-notes"),
7117
+ schemaVersion: z$1.number().int(),
7118
+ releaseTag: z$1.string().min(1),
7119
+ releaseUrl: z$1.string().regex(/^https:\/\/[^\s<>|]+$/),
7120
+ previousTag: z$1.string().nullable(),
7121
+ deployedSha: z$1.string().min(1),
7122
+ deployedAt: z$1.coerce.date(),
7123
+ prs: z$1.array(ReleaseNotesJobPrSchema)
7124
+ });
7125
+ const ReleaseNotesConfigSchema = z$1.object({
7126
+ enabled: z$1.boolean().default(false),
7127
+ modelId: z$1.string().default("gpt-4o-mini"),
7128
+ embargoHours: z$1.number().min(0).max(168).default(12),
7129
+ denylist: z$1.array(z$1.string()).default([]),
7130
+ slackTeamId: z$1.string().optional(),
7131
+ slackChannelId: z$1.string().optional()
7132
+ });
7133
+ /**
7134
+ * A release note as GET /api/v1/whats-new serves it. An allowlist: provenance (sourcePrs, deployedSha),
7135
+ * workflow state (status, editedAt) and storage fields never reach the public wire.
7136
+ */
7137
+ const PublicReleaseNoteSchema = z$1.object({
7138
+ id: z$1.string(),
7139
+ release_tag: z$1.string(),
7140
+ headline: z$1.string(),
7141
+ summary: z$1.string(),
7142
+ published_at: z$1.string(),
7143
+ items: z$1.array(z$1.object({
7144
+ category: ReleaseNoteCategorySchema,
7145
+ text: z$1.string(),
7146
+ importance: z$1.number().int().min(1).max(3)
7147
+ }))
7148
+ });
7149
+ /**
6593
7150
  * The measured per-space floors, rendered for an admin-facing description (e.g. "75 for
6594
7151
  * text-embedding-ada-002, 58 for text-embedding-3-small").
6595
7152
  *
@@ -6597,10 +7154,7 @@ const SupportedEmbeddingModelSchema = z$1.union([
6597
7154
  * against a production lake, and a description that restates the table is a wrong number shown to
6598
7155
  * operators the moment it drifts, with nothing failing.
6599
7156
  */
6600
- const forcedRetrievalFloorsBySpaceSummary = Object.entries({
6601
- ["text-embedding-ada-002"]: 75,
6602
- ["text-embedding-3-small"]: 58
6603
- }).map(([space, pct]) => `${pct} for ${space}`).join(", ");
7157
+ const forcedRetrievalFloorsBySpaceSummary = Object.entries(FORCED_RETRIEVAL_MIN_SIMILARITY_PCT_BY_SPACE).map(([space, pct]) => `${pct} for ${space}`).join(", ");
6604
7158
  /**
6605
7159
  * Default text for the artifact-emission system prompt. Single source of truth used BOTH as the
6606
7160
  * `ArtifactEmissionPrompt` admin setting's default AND as the runtime fallback in
@@ -6827,6 +7381,7 @@ z$1.enum([
6827
7381
  "EnforceLakeReadGrants",
6828
7382
  "EnableDataLakeDrivePoll",
6829
7383
  "EnableDataLakeGitHub",
7384
+ "EnableDataLakeGitHubReconcile",
6830
7385
  "EnforceLakeAdmission",
6831
7386
  "EnforceLakeOriginOnIngest",
6832
7387
  "EnableBriefcase",
@@ -6970,6 +7525,7 @@ z$1.enum([
6970
7525
  "whatsNewAutomationEnabled",
6971
7526
  "whatsNewConfig",
6972
7527
  "whatsNewSyncConfig",
7528
+ "releaseNotesConfig",
6973
7529
  "enableAgentProactiveMessages",
6974
7530
  "EnableEnhancedDateTime",
6975
7531
  "EnableHistoricalFeatures",
@@ -7006,6 +7562,7 @@ z$1.enum([
7006
7562
  "apiRateLimitProPerMin",
7007
7563
  "overwatchRollupSync",
7008
7564
  "orchestrationDefaults",
7565
+ "videoGeneration",
7009
7566
  "enableModelDiscovery",
7010
7567
  "modelDiscoveryMode",
7011
7568
  "modelDiscoveryAutoEnable",
@@ -7252,6 +7809,7 @@ const RapidReplySettingsSchema = z$1.object({
7252
7809
  lastUpdated: /* @__PURE__ */ new Date()
7253
7810
  })
7254
7811
  });
7812
+ const VideoGenerationSettingsSchema = z$1.object({ enabledModels: z$1.record(z$1.string(), z$1.boolean()).prefault({}) });
7255
7813
  /**
7256
7814
  * Canonical repository and branch the What's New generator reads from.
7257
7815
  *
@@ -8899,6 +9457,16 @@ const settingsMap = {
8899
9457
  order: 96,
8900
9458
  dependsOn: "EnableDataLakes"
8901
9459
  }),
9460
+ EnableDataLakeGitHubReconcile: makeBooleanSetting({
9461
+ key: "EnableDataLakeGitHubReconcile",
9462
+ name: "Data Lakes: GitHub scheduled reconcile",
9463
+ defaultValue: false,
9464
+ description: "Server-side gate for the scheduled check that compares each connected GitHub repository's default-branch HEAD with the last synced commit and queues a sync when they differ, so a missed or dropped push webhook still reaches the lake. Off by default - pushes still sync through the webhook and the Re-sync button; turn this on to also reconcile on a schedule.",
9465
+ category: "Experimental",
9466
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
9467
+ order: 96,
9468
+ dependsOn: "EnableDataLakeGitHub"
9469
+ }),
8902
9470
  EnforceLakeAdmission: makeBooleanSetting({
8903
9471
  key: "EnforceLakeAdmission",
8904
9472
  name: "Data Lakes: Enforce the admission contract",
@@ -10652,6 +11220,15 @@ const settingsMap = {
10652
11220
  order: 102,
10653
11221
  schema: WhatsNewSyncConfigSchema
10654
11222
  }),
11223
+ releaseNotesConfig: makeObjectSetting({
11224
+ key: "releaseNotesConfig",
11225
+ name: "Release Notes Configuration",
11226
+ defaultValue: ReleaseNotesConfigSchema.parse({}),
11227
+ description: "Customer-facing release notes generated from each production release. Disabled by default. Sets the model, the embargo before a note becomes visible, a denylist of terms that must never appear, and the Slack channel that gets the pre-publish preview.",
11228
+ category: "Admin",
11229
+ order: 103,
11230
+ schema: ReleaseNotesConfigSchema
11231
+ }),
10655
11232
  enableAgentProactiveMessages: makeBooleanSetting({
10656
11233
  key: "enableAgentProactiveMessages",
10657
11234
  name: "Enable Agent Proactive Messages",
@@ -11004,6 +11581,16 @@ const settingsMap = {
11004
11581
  order: 140,
11005
11582
  schema: OrchestrationDefaultsSchema
11006
11583
  }),
11584
+ videoGeneration: makeObjectSetting({
11585
+ key: "videoGeneration",
11586
+ userReadable: true,
11587
+ name: "Video Generation",
11588
+ defaultValue: VideoGenerationSettingsSchema.parse({}),
11589
+ description: "Enable or disable individual video generation models. Models without an override use their built-in default.",
11590
+ category: "AI",
11591
+ order: 145,
11592
+ schema: VideoGenerationSettingsSchema
11593
+ }),
11007
11594
  enableModelDiscovery: makeBooleanSetting({
11008
11595
  key: "enableModelDiscovery",
11009
11596
  name: "Enable Model Discovery",
@@ -11103,352 +11690,37 @@ const settingsMap = {
11103
11690
  }),
11104
11691
  prReportEgressAllowlist: makeObjectSetting({
11105
11692
  key: "prReportEgressAllowlist",
11106
- name: "PR Report Egress Allowlist",
11107
- defaultValue: { hosts: ["hooks.slack.com"] },
11108
- description: "Hosts the PR digest may post to, checked against the webhook URL its own hostname. FAILS CLOSED: an empty list rejects every send rather than degrading to allow-any, because the post body carries PR titles, author logins and the staffing implied by the role rosters. Slack incoming webhooks live at hooks.slack.com, so that is the default.",
11109
- category: "Slack",
11110
- order: 144,
11111
- schema: z$1.object({ hosts: z$1.array(z$1.string()).default([]) })
11112
- })
11113
- };
11114
- /**
11115
- * Experimental setting keys the client reads but that intentionally live OUTSIDE
11116
- * the `EXPERIMENTAL` group, so the group rule below can't pick them up:
11117
- * - `EnableContextTelemetry` - category `Admin` (rendered in the telemetry card),
11118
- * read as an experimental admin gate.
11119
- *
11120
- * Exported so the guard test asserts against this exact list rather than keeping a
11121
- * second hand-copied array (which would re-introduce two-place drift).
11122
- */
11123
- const experimentalNonGroupSettingKeys = ["EnableContextTelemetry"];
11124
- (() => {
11125
- const groupKeys = Object.values(settingsMap).filter((s) => s.group === API_SERVICE_GROUPS.EXPERIMENTAL.id).map((s) => s.key);
11126
- return Array.from(/* @__PURE__ */ new Set([...groupKeys, ...experimentalNonGroupSettingKeys]));
11127
- })();
11128
- z$1.preprocess((val) => {
11129
- if (typeof val === "string") {
11130
- if (val.toLowerCase() === "true") return true;
11131
- if (val.toLowerCase() === "false") return false;
11132
- }
11133
- return val;
11134
- }, z$1.boolean());
11135
- const AnonymousSessionIdSchema = z$1.object({
11136
- /** SHA256(userId|orgId|dailySalt|date) - cannot be reversed */
11137
- hash: z$1.string(),
11138
- /** YYYY-MM-DD for salt lookup during deletion */
11139
- dateKey: z$1.string()
11140
- });
11141
- const SpanContextSchema = z$1.object({
11142
- /** gen_ai.trace_id */
11143
- traceId: z$1.string(),
11144
- /** gen_ai.span_id */
11145
- spanId: z$1.string()
11146
- });
11147
- const OperationSchema = z$1.object({
11148
- name: z$1.enum([
11149
- "chat_completion",
11150
- "agent_invoke",
11151
- "tool_execute"
11152
- ]),
11153
- finishReason: z$1.enum([
11154
- "stop",
11155
- "length",
11156
- "tool_use",
11157
- "content_filter",
11158
- "error"
11159
- ]).optional()
11160
- });
11161
- const ModelTelemetrySchema = z$1.object({
11162
- /** gen_ai.request.model */
11163
- modelId: z$1.string(),
11164
- provider: z$1.enum([
11165
- "anthropic",
11166
- "openai",
11167
- "bedrock",
11168
- "google",
11169
- "xai",
11170
- "ollama",
11171
- "moonshot",
11172
- "deepseek"
11173
- ]),
11174
- fallbackUsed: z$1.boolean(),
11175
- fallbackReason: z$1.string().optional(),
11176
- originalModelId: z$1.string().optional(),
11177
- usedThinking: z$1.boolean(),
11178
- /** Extended thinking tokens */
11179
- thinkingTokensUsed: z$1.number().optional(),
11180
- usedTools: z$1.boolean()
11181
- });
11182
- const SystemPromptDetailSchema = z$1.object({
11183
- source: z$1.enum([
11184
- "hardcoded",
11185
- "admin",
11186
- "user",
11187
- "project",
11188
- "session",
11189
- "org",
11190
- "caller"
11191
- ]),
11192
- /** e.g., "date_context", "tool_guidance" */
11193
- name: z$1.string(),
11194
- tokenCount: z$1.number(),
11195
- wasIncluded: z$1.boolean(),
11196
- exclusionReason: z$1.enum([
11197
- "duplicate",
11198
- "disabled",
11199
- "token_limit"
11200
- ]).optional()
11201
- });
11202
- const SystemPromptsTelemetrySchema = z$1.object({
11203
- prompts: z$1.array(SystemPromptDetailSchema),
11204
- totalTokens: z$1.number(),
11205
- duplicateCount: z$1.number()
11206
- });
11207
- const FeatureContributionSchema = z$1.object({
11208
- /** e.g., "mementos", "quest_master", "deep_research" */
11209
- featureName: z$1.string(),
11210
- messagesAdded: z$1.number(),
11211
- tokenCount: z$1.number(),
11212
- executionTimeMs: z$1.number(),
11213
- success: z$1.boolean(),
11214
- errorType: z$1.string().optional()
11215
- });
11216
- const FeaturesTelemetrySchema = z$1.object({ contributions: z$1.array(FeatureContributionSchema) });
11217
- const TokensBySourceSchema = z$1.object({
11218
- systemPrompts: z$1.number(),
11219
- conversationHistory: z$1.number(),
11220
- mementos: z$1.number(),
11221
- fabFiles: z$1.number(),
11222
- urlContent: z$1.number(),
11223
- toolSchemas: z$1.number(),
11224
- userPrompt: z$1.number(),
11225
- /**
11226
- * Lake-sourced content injected this turn (forced retrieval + the lake-memory hot card), split out
11227
- * of `systemPrompts`. Optional and absent means UNKNOWN, never zero - telemetry captured before
11228
- * this bucket existed has no value to report and must not be read as "no lake content". Mirrors
11229
- * PromptMetaTokensBySourceSchema in promptMeta.ts.
11230
- */
11231
- lakeRetrieval: z$1.number().optional()
11232
- });
11233
- z$1.object({
11234
- /** gen_ai.usage.input_tokens */
11235
- inputTokens: z$1.number(),
11236
- /** gen_ai.usage.output_tokens */
11237
- outputTokens: z$1.number(),
11238
- contextWindowLimit: z$1.number(),
11239
- utilizationPercentage: z$1.number(),
11240
- reservedOutputTokens: z$1.number(),
11241
- overflowDetected: z$1.boolean(),
11242
- overflowAmount: z$1.number().optional(),
11243
- /** Token breakdown by source */
11244
- tokensBySource: TokensBySourceSchema
11245
- });
11246
- const CacheTelemetrySchema = z$1.object({
11247
- /** Anthropic cache_read_input_tokens */
11248
- cacheReadTokens: z$1.number(),
11249
- /** Anthropic cache_creation_input_tokens */
11250
- cacheWriteTokens: z$1.number(),
11251
- /** 0-100% */
11252
- cacheHitRate: z$1.number(),
11253
- costSavingsPercent: z$1.number().optional()
11254
- });
11255
- const CostsTelemetrySchema = z$1.object({
11256
- inputCostUsd: z$1.number(),
11257
- outputCostUsd: z$1.number(),
11258
- totalCostUsd: z$1.number(),
11259
- creditsUsed: z$1.number()
11260
- });
11261
- const TruncationTelemetrySchema = z$1.object({
11262
- wasTruncated: z$1.boolean(),
11263
- originalMessageCount: z$1.number(),
11264
- finalMessageCount: z$1.number(),
11265
- truncatedMessageCount: z$1.number(),
11266
- truncationMethod: z$1.enum([
11267
- "priority",
11268
- "token-budget",
11269
- "history-limit",
11270
- "context-overflow"
11271
- ]).optional(),
11272
- truncationPercentage: z$1.number()
11273
- });
11274
- const ToolErrorCategorySchema = z$1.enum([
11275
- "timeout",
11276
- "rate_limit",
11277
- "auth_error",
11278
- "validation_error",
11279
- "network_error",
11280
- "internal_error"
11281
- ]);
11282
- const ToolTelemetrySchema = z$1.object({
11283
- toolName: z$1.string(),
11284
- /** MCP vs native tool */
11285
- isMcpTool: z$1.boolean(),
11286
- /** For MCP tools */
11287
- mcpServerName: z$1.string().optional(),
11288
- invocationCount: z$1.number(),
11289
- successCount: z$1.number(),
11290
- failureCount: z$1.number(),
11291
- totalDurationMs: z$1.number(),
11292
- maxDurationMs: z$1.number(),
11293
- /** Retry tracking */
11294
- retryCount: z$1.number(),
11295
- /** Max 200 chars */
11296
- lastError: z$1.string().max(200).optional(),
11297
- errorCategories: z$1.array(ToolErrorCategorySchema).optional(),
11298
- /**
11299
- * Content-size metrics for tools that extract text (currently web_fetch, issue #452).
11300
- * Zero-PII integer counts, so kept at both basic and enhanced capture levels.
11301
- */
11302
- /** Count of unique truncated citables at the tool's size cap (citables dedupe by url,
11303
- * so same-url refetches within a completion collapse to one - see WebFetchContentTelemetry). */
11304
- truncatedInvocationCount: z$1.number().optional(),
11305
- /** Largest single extracted (post-cap) content length across invocations. */
11306
- maxExtractedChars: z$1.number().optional(),
11307
- /** Sum of extracted (post-cap) content length across invocations. */
11308
- totalExtractedChars: z$1.number().optional()
11309
- });
11310
- const SubagentTelemetrySchema = z$1.object({
11311
- agentName: z$1.string(),
11312
- delegationCount: z$1.number(),
11313
- successCount: z$1.number(),
11314
- failureCount: z$1.number(),
11315
- timeoutCount: z$1.number(),
11316
- totalDurationMs: z$1.number(),
11317
- totalTokensUsed: z$1.number(),
11318
- thoroughness: z$1.enum([
11319
- "quick",
11320
- "medium",
11321
- "very_thorough"
11322
- ]).optional()
11323
- });
11324
- const LatencyPercentilesSchema = z$1.object({
11325
- p50Ms: z$1.number(),
11326
- p95Ms: z$1.number(),
11327
- p99Ms: z$1.number()
11328
- });
11329
- const PerformanceTelemetrySchema = z$1.object({
11330
- totalResponseTimeMs: z$1.number(),
11331
- firstTokenTimeMs: z$1.number().optional(),
11332
- contextRetrievalMs: z$1.number().optional(),
11333
- modelInferenceMs: z$1.number().optional(),
11334
- toolExecutionMs: z$1.number().optional(),
11335
- /** Latency percentiles for trend analysis */
11336
- latencyPercentiles: LatencyPercentilesSchema.optional()
11337
- });
11338
- const AnomalySeveritySchema = z$1.enum([
11339
- "critical",
11340
- "high",
11341
- "medium",
11342
- "low"
11343
- ]);
11344
- const PrimaryAnomalySchema = z$1.enum([
11345
- "none",
11346
- "context_overflow",
11347
- "high_utilization",
11348
- "high_truncation",
11349
- "tool_failure",
11350
- "subagent_timeout",
11351
- "slow_response",
11352
- "multiple"
11353
- ]);
11354
- const AnomaliesTelemetrySchema = z$1.object({
11355
- contextOverflow: z$1.boolean(),
11356
- /** >= 90% */
11357
- highUtilization: z$1.boolean(),
11358
- /** >= 95% */
11359
- criticalUtilization: z$1.boolean(),
11360
- /** >= 50% */
11361
- highTruncation: z$1.boolean(),
11362
- /** >= 75% */
11363
- criticalTruncation: z$1.boolean(),
11364
- /** >= 3 failures */
11365
- toolFailureSpike: z$1.boolean(),
11366
- /** > 30s */
11367
- toolTimeout: z$1.boolean(),
11368
- /** > 5min */
11369
- subagentTimeout: z$1.boolean(),
11370
- /** > 10s */
11371
- slowFirstToken: z$1.boolean(),
11372
- /** > 60s */
11373
- slowTotalResponse: z$1.boolean(),
11374
- /** 0-100 */
11375
- anomalyScore: z$1.number().min(0).max(100),
11376
- /** Severity mapping */
11377
- severity: AnomalySeveritySchema,
11378
- /** Pattern-based dedup key, e.g., "tool_failure_claude-3-5-sonnet_delegate-to-agent" */
11379
- dedupKey: z$1.string(),
11380
- primaryAnomaly: PrimaryAnomalySchema
11381
- });
11382
- const RequestMetadataSchema = z$1.object({
11383
- queryComplexity: z$1.enum([
11384
- "simple",
11385
- "contextual",
11386
- "complex"
11387
- ]),
11388
- historyMessageCount: z$1.number(),
11389
- attachedFileCount: z$1.number(),
11390
- mementoCount: z$1.number(),
11391
- enabledFeatures: z$1.array(z$1.string())
11392
- });
11393
- const ContextWindowTelemetryWithOptionalSourceSchema = z$1.object({
11394
- /** gen_ai.usage.input_tokens */
11395
- inputTokens: z$1.number(),
11396
- /** gen_ai.usage.output_tokens */
11397
- outputTokens: z$1.number(),
11398
- contextWindowLimit: z$1.number(),
11399
- utilizationPercentage: z$1.number(),
11400
- reservedOutputTokens: z$1.number(),
11401
- overflowDetected: z$1.boolean(),
11402
- overflowAmount: z$1.number().optional(),
11403
- /** Token breakdown by source (enhanced only) */
11404
- tokensBySource: TokensBySourceSchema.optional()
11405
- });
11406
- const ContextTelemetrySchema = z$1.object({
11407
- schemaVersion: z$1.enum([
11408
- "1.0",
11409
- "1.1",
11410
- "1.2"
11411
- ]),
11412
- /** ISO 8601 */
11413
- timestamp: z$1.string(),
11414
- /** Self-monitoring: time to capture telemetry */
11415
- captureOverheadMs: z$1.number(),
11416
- /** Capture level: basic or enhanced */
11417
- captureLevel: z$1.enum(["basic", "enhanced"]).optional(),
11418
- anonymousSessionId: AnonymousSessionIdSchema,
11419
- spanContext: SpanContextSchema.optional(),
11420
- operation: OperationSchema,
11421
- model: ModelTelemetrySchema,
11422
- systemPrompts: SystemPromptsTelemetrySchema.optional(),
11423
- features: FeaturesTelemetrySchema.optional(),
11424
- contextWindow: ContextWindowTelemetryWithOptionalSourceSchema,
11425
- cache: CacheTelemetrySchema.optional(),
11426
- costs: CostsTelemetrySchema,
11427
- truncation: TruncationTelemetrySchema.optional(),
11428
- tools: z$1.array(ToolTelemetrySchema).optional(),
11429
- subagents: z$1.array(SubagentTelemetrySchema).optional(),
11430
- performance: PerformanceTelemetrySchema,
11431
- anomalies: AnomaliesTelemetrySchema,
11432
- requestMetadata: RequestMetadataSchema.optional(),
11433
- captureErrors: z$1.array(z$1.string()).optional()
11434
- });
11435
- z$1.object({
11436
- avgResponseTimeMs: z$1.number(),
11437
- p95ResponseTimeMs: z$1.number(),
11438
- avgUtilization: z$1.number(),
11439
- utilizationRange: z$1.object({
11440
- low: z$1.number(),
11441
- high: z$1.number()
11442
- }),
11443
- sampleCount: z$1.number(),
11444
- windowDays: z$1.number()
11445
- });
11446
- z$1.enum([
11447
- "no_action",
11448
- "monitor",
11449
- "investigate_soon",
11450
- "immediate_action"
11451
- ]);
11693
+ name: "PR Report Egress Allowlist",
11694
+ defaultValue: { hosts: ["hooks.slack.com"] },
11695
+ description: "Hosts the PR digest may post to, checked against the webhook URL its own hostname. FAILS CLOSED: an empty list rejects every send rather than degrading to allow-any, because the post body carries PR titles, author logins and the staffing implied by the role rosters. Slack incoming webhooks live at hooks.slack.com, so that is the default.",
11696
+ category: "Slack",
11697
+ order: 144,
11698
+ schema: z$1.object({ hosts: z$1.array(z$1.string()).default([]) })
11699
+ })
11700
+ };
11701
+ /**
11702
+ * Experimental setting keys the client reads but that intentionally live OUTSIDE
11703
+ * the `EXPERIMENTAL` group, so the group rule below can't pick them up:
11704
+ * - `EnableContextTelemetry` - category `Admin` (rendered in the telemetry card),
11705
+ * read as an experimental admin gate.
11706
+ *
11707
+ * Exported so the guard test asserts against this exact list rather than keeping a
11708
+ * second hand-copied array (which would re-introduce two-place drift).
11709
+ */
11710
+ const experimentalNonGroupSettingKeys = ["EnableContextTelemetry"];
11711
+ (() => {
11712
+ const groupKeys = Object.values(settingsMap).filter((s) => s.group === API_SERVICE_GROUPS.EXPERIMENTAL.id).map((s) => s.key);
11713
+ return Array.from(/* @__PURE__ */ new Set([...groupKeys, ...experimentalNonGroupSettingKeys]));
11714
+ })();
11715
+ z$1.preprocess((val) => {
11716
+ if (typeof val === "string") {
11717
+ if (val.toLowerCase() === "true") return true;
11718
+ if (val.toLowerCase() === "false") return false;
11719
+ }
11720
+ return val;
11721
+ }, z$1.boolean());
11722
+ /** Splits on the marker tokens, keeping them in the result so a scan can track nesting depth. */
11723
+ const THINK_TAG_TOKENS = /(<think>|<\/think>)/g;
11452
11724
  /**
11453
11725
  * Zero-width space spliced into a defanged marker. It is invisible wherever the text is
11454
11726
  * rendered, so escaping never changes what the reader sees - only what the parser matches.
@@ -11513,6 +11785,52 @@ function createThinkMarkerEscaper() {
11513
11785
  };
11514
11786
  }
11515
11787
  /**
11788
+ * The visible remainder of one reply slot, with hidden reasoning removed.
11789
+ *
11790
+ * This is the rule the chat transcript renders by - `extractReplies` in
11791
+ * apps/client/shared/replyUtils.ts calls straight into it, so the two cannot drift.
11792
+ * Anything deriving "did the user see something yet" (latency metrics in particular) must
11793
+ * use this and not a looser non-empty check: a metric built on a looser rule reports text
11794
+ * as seen while the UI is still hiding it.
11795
+ *
11796
+ * Thinking is removed span by span rather than by keeping the tail after the last close
11797
+ * marker. A turn that answers, calls a tool and then thinks again reopens its thinking
11798
+ * inside the slot that already holds the partial answer (the provider restarts its
11799
+ * content-block indices - see appendStreamedChunk), so a tail rule would drop text the user
11800
+ * has already watched stream in. An unclosed trailing marker hides everything after it, so a
11801
+ * reopened block does not render its raw marker while it streams.
11802
+ *
11803
+ * Markers are tracked by nesting depth rather than matched pairwise, because reasoning text
11804
+ * is provider-authored and can itself contain marker-shaped substrings (a model reasoning
11805
+ * about the `<think>` protocol, say). A naive non-greedy pair match on
11806
+ * `<think>outer<think>inner</think>tail</think>answer` would strip only the innermost pair
11807
+ * and let `tail` leak into the transcript; walking depth instead keeps everything between an
11808
+ * open and its matching close hidden regardless of what markers appear in between, and an
11809
+ * unmatched trailing `</think>` (depth already zero) is treated as ordinary text rather than
11810
+ * closing something that was never open.
11811
+ *
11812
+ * Interior whitespace is preserved: callers concatenate slots with no separator, so trimming
11813
+ * every slot would weld a heading onto the table beneath it.
11814
+ */
11815
+ function visibleReplyText(part) {
11816
+ if (!part || !part.trim()) return "";
11817
+ let depth = 0;
11818
+ let visible = "";
11819
+ for (const token of part.split(THINK_TAG_TOKENS)) if (token === "<think>") depth += 1;
11820
+ else if (token === "</think>") if (depth > 0) depth -= 1;
11821
+ else visible += token;
11822
+ else if (depth === 0) visible += token;
11823
+ return visible.trim() ? visible : "";
11824
+ }
11825
+ /**
11826
+ * Joins visible reply slots into one string. Each slot is a separate content block, so a slot that
11827
+ * does not end in a newline gets a paragraph break; with a bare join a closing code fence would
11828
+ * land on the same line as the next slot's text and never close.
11829
+ */
11830
+ function joinReplySlots(parts) {
11831
+ return parts.reduce((joined, part) => !joined || joined.endsWith("\n") ? joined + part : `${joined}\n\n${part}`, "");
11832
+ }
11833
+ /**
11516
11834
  * Reply choices: the discrete next steps a reply offers, rendered as numbered buttons.
11517
11835
  *
11518
11836
  * The model ends a reply with a fenced `choices` block; the server strips it with
@@ -11532,6 +11850,173 @@ const REPLY_CHOICES_INVALID_REASONS = [
11532
11850
  "unterminated",
11533
11851
  "think_unclosed"
11534
11852
  ];
11853
+ const ABSENT = { status: "absent" };
11854
+ const invalid = (reason) => ({
11855
+ status: "invalid",
11856
+ reason
11857
+ });
11858
+ const FENCE_OPEN_LINE = /^[ \t]*(`{3,}|~{3,})(.*)$/;
11859
+ const FENCE_CLOSE_LINE = /^[ \t]*(`{3,}|~{3,})[ \t]*$/;
11860
+ /**
11861
+ * Offset where a slot's visible answer starts: just past its last top-level `</think>`, 0 when it
11862
+ * has none, or null while a `<think>` is still open. Depth-tracked to match visibleReplyText in
11863
+ * ./streamVisibility.ts, so a marker-shaped string inside reasoning cannot end it early.
11864
+ */
11865
+ function answerStart(reply) {
11866
+ let depth = 0;
11867
+ let start = 0;
11868
+ for (const marker of reply.matchAll(/<\/?think>/g)) if (marker[0] === "<think>") depth += 1;
11869
+ else if (depth > 0) {
11870
+ depth -= 1;
11871
+ if (depth === 0) start = (marker.index ?? 0) + marker[0].length;
11872
+ }
11873
+ return depth === 0 ? start : null;
11874
+ }
11875
+ /**
11876
+ * The last top-level ```choices fence in `text`, walking fences line by line (CommonMark rules: a
11877
+ * fence closes on a bare line of the same character at least as long), so a choices sample nested
11878
+ * inside a longer outer fence is content rather than a block.
11879
+ */
11880
+ function findLastChoicesFence(text) {
11881
+ let last = null;
11882
+ let open = null;
11883
+ let lineStart = 0;
11884
+ while (lineStart <= text.length) {
11885
+ const newline = text.indexOf("\n", lineStart);
11886
+ const lineEnd = newline === -1 ? text.length : newline;
11887
+ const line = text.slice(lineStart, lineEnd).replace(/\r$/, "");
11888
+ if (!open) {
11889
+ const match = line.match(FENCE_OPEN_LINE);
11890
+ if (match && !(match[1][0] === "`" && match[2].includes("`"))) open = {
11891
+ start: lineStart,
11892
+ bodyStart: newline === -1 ? text.length : newline + 1,
11893
+ marker: match[1],
11894
+ isChoices: match[1] === "```" && match[2].trim().toLowerCase() === "choices"
11895
+ };
11896
+ } else {
11897
+ const match = line.match(FENCE_CLOSE_LINE);
11898
+ if (match && match[1][0] === open.marker[0] && match[1].length >= open.marker.length) {
11899
+ if (open.isChoices) last = {
11900
+ start: open.start,
11901
+ end: lineEnd,
11902
+ body: text.slice(open.bodyStart, lineStart)
11903
+ };
11904
+ open = null;
11905
+ }
11906
+ }
11907
+ if (newline === -1) break;
11908
+ lineStart = newline + 1;
11909
+ }
11910
+ if (open?.isChoices) last = {
11911
+ start: open.start,
11912
+ end: null,
11913
+ body: text.slice(open.bodyStart)
11914
+ };
11915
+ return last;
11916
+ }
11917
+ /**
11918
+ * Strips the last ```choices block from a reply's visible answer and validates its options.
11919
+ *
11920
+ * Only the text after the slot's last `</think>` is eligible, so a block the model drafted while
11921
+ * reasoning is never read, and the reasoning prefix is returned byte-for-byte. Nothing visible is
11922
+ * deleted: prose the model added after the block's closing fence is kept, with only the block
11923
+ * removed. A block still open at the end (truncated, or streaming) is hidden through to the end.
11924
+ * Any invalid option rejects the whole block, because dropping one would shift the numbering away
11925
+ * from the prose.
11926
+ */
11927
+ function extractChoicesBlock(reply) {
11928
+ const unchanged = {
11929
+ text: reply,
11930
+ choices: null,
11931
+ found: false,
11932
+ outcome: ABSENT
11933
+ };
11934
+ const start = answerStart(reply);
11935
+ if (start === null) return findLastChoicesFence(reply) ? {
11936
+ ...unchanged,
11937
+ outcome: invalid("think_unclosed")
11938
+ } : unchanged;
11939
+ const answer = reply.slice(start);
11940
+ const fence = findLastChoicesFence(answer);
11941
+ if (!fence) return unchanged;
11942
+ const before = answer.slice(0, fence.start).trimEnd();
11943
+ const after = fence.end === null ? "" : answer.slice(fence.end).replace(/^(?:[ \t]*\r?\n)+/, "");
11944
+ const kept = after.trim() ? before ? `${before}\n\n${after}` : after : before;
11945
+ const parsed = fence.end === null ? { reason: "unterminated" } : parseChoiceOptions(fence.body);
11946
+ const text = reply.slice(0, start) + kept;
11947
+ return "options" in parsed ? {
11948
+ text,
11949
+ choices: parsed.options,
11950
+ found: true,
11951
+ outcome: { status: "parsed" }
11952
+ } : {
11953
+ text,
11954
+ choices: null,
11955
+ found: true,
11956
+ outcome: invalid(parsed.reason)
11957
+ };
11958
+ }
11959
+ /**
11960
+ * {@link extractChoicesBlock} over the reply slots. Every slot's block is stripped (a pre-tool-call
11961
+ * answer must not leak raw JSON), but only the last slot with visible text - the final answer -
11962
+ * supplies the options, so an abandoned earlier draft block can never offer buttons.
11963
+ */
11964
+ function stripChoicesFromReplies(replies) {
11965
+ const stripped = replies.map((slot) => visibleReplyText(slot) ? extractChoicesBlock(slot).text : slot);
11966
+ let answerIndex = replies.length - 1;
11967
+ while (answerIndex >= 0 && !visibleReplyText(replies[answerIndex])) answerIndex -= 1;
11968
+ if (answerIndex < 0) return {
11969
+ replies: stripped,
11970
+ choices: null,
11971
+ found: false,
11972
+ outcome: ABSENT
11973
+ };
11974
+ const { choices, found, outcome } = extractChoicesBlock(replies[answerIndex]);
11975
+ return {
11976
+ replies: stripped,
11977
+ choices,
11978
+ found,
11979
+ outcome
11980
+ };
11981
+ }
11982
+ /**
11983
+ * The visible answer text of a quest, derived from its reply slots (choices stripped, `<think>`
11984
+ * hidden) with the scalar `reply` as the fallback when no slot has visible text. `joinSlots` joins
11985
+ * the visible slots: a bare join by default, matching the scalar the server persists, and a
11986
+ * paragraph join for a reader that renders the text as markdown (see streamVisibility.ts), since a
11987
+ * bare join glues a slot's closing code fence to the next slot's text.
11988
+ */
11989
+ function questReplyText(quest, joinSlots = (slots) => slots.join("")) {
11990
+ return joinSlots(stripChoicesFromReplies(quest.replies ?? []).replies.map((slot) => visibleReplyText(slot)).filter(Boolean)) || (quest.reply ?? null);
11991
+ }
11992
+ function parseChoiceOptions(body) {
11993
+ let parsed;
11994
+ try {
11995
+ parsed = JSON.parse(body.trim());
11996
+ } catch {
11997
+ return { reason: "json" };
11998
+ }
11999
+ const raw = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.options) ? parsed.options : null;
12000
+ if (!raw) return { reason: "shape" };
12001
+ const options = [];
12002
+ const seenLabels = /* @__PURE__ */ new Set();
12003
+ for (const item of raw.slice(0, 4)) {
12004
+ if (!item || typeof item !== "object") return { reason: "shape" };
12005
+ const { label, description } = item;
12006
+ if (typeof label !== "string" || typeof description !== "string") return { reason: "shape" };
12007
+ const cleanLabel = label.trim();
12008
+ const cleanDescription = description.trim();
12009
+ if (!cleanLabel || cleanLabel.length > 40) return { reason: "label_length" };
12010
+ if (!cleanDescription || cleanDescription.length > 300) return { reason: "description_length" };
12011
+ if (seenLabels.has(cleanLabel)) return { reason: "duplicate_label" };
12012
+ seenLabels.add(cleanLabel);
12013
+ options.push({
12014
+ label: cleanLabel,
12015
+ description: cleanDescription
12016
+ });
12017
+ }
12018
+ return options.length >= 2 ? { options } : { reason: "too_few" };
12019
+ }
11535
12020
  [
11536
12021
  "# Reply choices",
11537
12022
  "",
@@ -11749,6 +12234,7 @@ const PromptMetaPerformanceSchema = z$1.object({
11749
12234
  firstTokenTime: z$1.number().optional(),
11750
12235
  /** Elapsed ms until the first chunk of any kind, including a hidden thinking block. */
11751
12236
  firstChunkTime: z$1.number().optional(),
12237
+ /** @deprecated Read/write the quest-level clientFirstTokenTime instead. */
11752
12238
  clientFirstTokenTime: z$1.number().optional(),
11753
12239
  streamingPerformance: z$1.object({
11754
12240
  chunkCount: z$1.number().optional(),
@@ -12273,11 +12759,10 @@ const RetrievalSummarySchema = z$1.object({
12273
12759
  * an archived or quota-limited lake) adds an enum value here rather than a description.
12274
12760
  *
12275
12761
  * 'access' is the only reason today: the caller could see the lake exists (their org membership,
12276
- * the lake's public listing, or having created it; for a lake named by identity, also administering
12277
- * its org; an admin may see any lake) but they hold neither its own gate/entitlement nor an
12278
- * ownership or grant exception for it. A lake the caller could not see is never counted, so the
12279
- * count cannot confirm that a guessed lake tag exists. The account-wide count still bounds on
12280
- * membership only.
12762
+ * the lake's public listing, or having created it; administering its org; an admin may see any
12763
+ * lake) but they hold neither its own gate/entitlement nor an ownership or grant exception for
12764
+ * it. A lake the caller could not see is never counted, so the count cannot confirm that a
12765
+ * guessed lake tag exists.
12281
12766
  *
12282
12767
  * A session-preauthorized lake (unionPreauthorizedLakeAccess) that is ALSO gate-dropped from
12283
12768
  * this account-wide count is corrected, not merely narrow: the seed's targeted measurement
@@ -12705,6 +13190,7 @@ const CORE_TOOL_SIDE_EFFECTS = {
12705
13190
  edit_image: "external",
12706
13191
  music_generation: "external",
12707
13192
  audio_generation: "external",
13193
+ video_generation: "external",
12708
13194
  excel_generation: "external",
12709
13195
  create_data_lake: "external",
12710
13196
  save_content_to_data_lake: "external",
@@ -12745,15 +13231,13 @@ const EXTERNAL_REGISTRY_SIDE_EFFECTS = {
12745
13231
  notebook_status: "none",
12746
13232
  share_curated_file: "external",
12747
13233
  notebook_new: "external",
12748
- confirm_pending_action: "external",
12749
13234
  cancel_pending_action: "external",
12750
13235
  mission_status: "none",
12751
13236
  optihashi_decompose: "local",
12752
13237
  optihashi_solve: "local",
12753
13238
  send_slack_message: "external",
12754
13239
  coordinate_task: "external",
12755
- code_execute: "external",
12756
- video_generation: "external"
13240
+ code_execute: "external"
12757
13241
  };
12758
13242
  ({
12759
13243
  ...CORE_TOOL_SIDE_EFFECTS,
@@ -13369,24 +13853,6 @@ z.object({
13369
13853
  })
13370
13854
  });
13371
13855
  /**
13372
- * Query parameters for a cursor-paginated public list (CONVENTIONS.md section 8).
13373
- *
13374
- * `limit` is `z.coerce` because query values arrive as strings; registerContract publishes it as an
13375
- * integer. A non-numeric, zero, or over-cap value is a 422, never silently clamped.
13376
- */
13377
- const PaginationQuerySchema = z$1.object({
13378
- limit: z$1.coerce.number().int().min(1).max(100).default(25),
13379
- cursor: z$1.string().min(1).optional()
13380
- });
13381
- /**
13382
- * The response envelope every cursor-paginated public list returns. `next_cursor` is required and
13383
- * `null` on the last page, so a caller loops on it rather than on the page length.
13384
- */
13385
- const paginatedResponseSchema = (itemSchema) => z$1.object({
13386
- data: z$1.array(itemSchema),
13387
- next_cursor: z$1.string().nullable()
13388
- });
13389
- /**
13390
13856
  * Wire schemas for the public `/api/v1/data-lakes/*` contracts
13391
13857
  * (api-contract/contracts/dataLakes.contract.ts). Deliberately narrower than the SPA's data-lake
13392
13858
  * payloads: a field published here can be added to later but never removed, so only what an
@@ -13399,6 +13865,11 @@ const DataLakeResourceSchema = z$1.object({
13399
13865
  id: z$1.string(),
13400
13866
  name: z$1.string(),
13401
13867
  slug: z$1.string(),
13868
+ /**
13869
+ * The value to pass in a session's `lakeScope`. Not derivable from `slug` + `organization_id`: a
13870
+ * lake moved into an org keeps the `datalake:<slug>` tag it was created with.
13871
+ */
13872
+ datalake_tag: z$1.string(),
13402
13873
  description: z$1.string().nullable(),
13403
13874
  organization_id: z$1.string().nullable(),
13404
13875
  is_public: z$1.boolean(),
@@ -13464,6 +13935,108 @@ const DataLakeSearchResponseSchema = z$1.object({
13464
13935
  paused_files: z$1.number().int().nonnegative()
13465
13936
  })
13466
13937
  });
13938
+ const ProjectResourceSchema = z$1.object({
13939
+ id: z$1.string(),
13940
+ name: z$1.string(),
13941
+ description: z$1.string(),
13942
+ session_ids: z$1.array(z$1.string()).describe("Sessions grouped in the project. Anyone the project is shared with can read them."),
13943
+ file_ids: z$1.array(z$1.string()).describe("Files grouped in the project. Anyone the project is shared with can read them."),
13944
+ created_at: z$1.string().nullable().describe("ISO 8601 timestamp."),
13945
+ updated_at: z$1.string().nullable().describe("ISO 8601 timestamp.")
13946
+ });
13947
+ const ListProjectsResponseSchema = paginatedResponseSchema(ProjectResourceSchema);
13948
+ /**
13949
+ * `id` is a plain string on purpose: a malformed id must answer 404 (CONVENTIONS.md status table),
13950
+ * and a path-param schema failure would answer 422.
13951
+ */
13952
+ const ProjectIdParamSchema = z$1.object({ id: z$1.string().min(1) });
13953
+ /**
13954
+ * Strict, so a camelCase `sessionIds`/`fileIds` (the SPA route's spelling) is a 422 rather than
13955
+ * silently dropped, which would create a project without the content the caller meant to attach.
13956
+ */
13957
+ const CreateProjectRequestSchema = z$1.object({
13958
+ name: z$1.string().min(1).describe("Unique among your live projects."),
13959
+ description: z$1.string().min(1),
13960
+ session_ids: z$1.array(z$1.string().min(1)).max(500).optional().describe("Sessions to group in the project. Each must be readable by the caller."),
13961
+ file_ids: z$1.array(z$1.string().min(1)).max(500).optional().describe("Files to group in the project. Each must be readable by the caller.")
13962
+ }).strict();
13963
+ /**
13964
+ * The public projection of `ModelInfo` served by `GET /api/v1/models`.
13965
+ *
13966
+ * Deliberately narrower than `ModelInfo`: every field here is one an integrator can pin, so
13967
+ * internal catalog fields (`adapterFamily`, `dispatchProfile`, `rank`, `pricing`, `logoFile`)
13968
+ * stay off the wire. The legacy `GET /api/models` keeps serving the full internal type to the
13969
+ * SPA and CLI. Wire fields are snake_case (CONVENTIONS.md section 2).
13970
+ */
13971
+ const PublicImageSizingSchema = z$1.discriminatedUnion("kind", [
13972
+ z$1.object({
13973
+ kind: z$1.literal("presets"),
13974
+ presets: z$1.array(z$1.string()).describe("Accepted `size` values, as `WxH`."),
13975
+ default_size: z$1.string()
13976
+ }),
13977
+ z$1.object({
13978
+ kind: z$1.literal("constrained"),
13979
+ presets: z$1.array(z$1.string()).describe("Suggested `size` values; any `WxH` meeting `constraints` is accepted."),
13980
+ default_size: z$1.string(),
13981
+ auto_size: z$1.string().describe("The `size` value that lets the provider choose."),
13982
+ constraints: z$1.object({
13983
+ max_edge: z$1.number().int(),
13984
+ min_total_pixels: z$1.number().int(),
13985
+ max_total_pixels: z$1.number().int(),
13986
+ edge_multiple: z$1.number().int().describe("Both edges must be a multiple of this."),
13987
+ max_aspect_ratio: z$1.number().describe("Longest edge divided by shortest edge.")
13988
+ })
13989
+ }),
13990
+ z$1.object({
13991
+ kind: z$1.literal("dimensions"),
13992
+ presets: z$1.array(z$1.string()),
13993
+ default_size: z$1.string(),
13994
+ min_width: z$1.number().int(),
13995
+ max_width: z$1.number().int(),
13996
+ min_height: z$1.number().int(),
13997
+ max_height: z$1.number().int(),
13998
+ step: z$1.number().int().describe("`width` and `height` are rounded to the nearest multiple of this.")
13999
+ }),
14000
+ z$1.object({ kind: z$1.literal("aspect_ratio").describe("Takes an `aspect_ratio` such as `16:9`, not a `size`.") }),
14001
+ z$1.object({
14002
+ kind: z$1.literal("input_image").describe("Sized by the input image; takes no `size`."),
14003
+ aspect_ratio: z$1.boolean().describe("Whether an `aspect_ratio` override is honoured.")
14004
+ }),
14005
+ z$1.object({ kind: z$1.literal("fixed").describe("The provider takes no size at all.") })
14006
+ ]);
14007
+ const PublicImageCapabilitiesSchema = z$1.object({
14008
+ sizing: PublicImageSizingSchema,
14009
+ supports: z$1.object({
14010
+ transparent_background: z$1.boolean().describe("`background: 'transparent'` yields real alpha."),
14011
+ seed: z$1.boolean(),
14012
+ qualities: z$1.array(OpenAIImageQualitySchema).describe("Accepted `quality` tiers; empty when none is taken."),
14013
+ max_images: z$1.number().int().describe("Most images one request may ask for via `n`."),
14014
+ max_reference_images: z$1.number().int().describe("Most `referenceImageFabFileIds` honoured; 0 when the model takes none."),
14015
+ edit: z$1.boolean().describe("Accepted by `POST /api/v1/image-edits`."),
14016
+ requires_input_image: z$1.boolean()
14017
+ })
14018
+ });
14019
+ const ListModelsResponseSchema = paginatedResponseSchema(z$1.object({
14020
+ id: z$1.string().describe("Pass as `model` on the generation endpoints."),
14021
+ name: z$1.string(),
14022
+ type: z$1.enum([
14023
+ "text",
14024
+ "image",
14025
+ "speech-to-text",
14026
+ "video"
14027
+ ]),
14028
+ backend: z$1.enum(ModelBackend).describe("The provider that serves the model."),
14029
+ description: z$1.string().nullable(),
14030
+ context_window: z$1.number().int().describe("Input context, in tokens."),
14031
+ max_output_tokens: z$1.number().int(),
14032
+ supports_streaming: z$1.boolean(),
14033
+ supports_thinking: z$1.boolean(),
14034
+ supports_tools: z$1.boolean(),
14035
+ supports_vision: z$1.boolean(),
14036
+ deprecation_date: z$1.string().nullable().describe("`YYYY-MM-DD`; null when no retirement is scheduled."),
14037
+ replaced_by: z$1.string().nullable().describe("The id of the recommended successor, when there is one."),
14038
+ image: PublicImageCapabilitiesSchema.nullable().describe("Size rules and supported parameters. Present on image models only; null for every other type.")
14039
+ }));
13467
14040
  z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid template id");
13468
14041
  /**
13469
14042
  * The bound model. Reuses the legacy-remap preprocess so a template saved under
@@ -13625,6 +14198,21 @@ z$1.object({
13625
14198
  }))
13626
14199
  });
13627
14200
  [...OPENAI_IMAGE_MODELS, ...GEMINI_IMAGE_MODELS];
14201
+ const GenerationCallbackUrlSchema = z$1.url({
14202
+ protocol: /^https$/,
14203
+ error: "callbackUrl must be an https URL"
14204
+ }).max(2048).describe("Optional https URL that receives one signed POST when the job settles (success or failure). Requires API-key authentication and a callback signing secret on that key. The body is the GET /api/v1/quests/{id} response. Verify it with the `X-Webhook-Signature-256` header: `sha256=` + hex HMAC-SHA256 of `<X-Webhook-Timestamp>.<raw body>` keyed with the signing secret. Polling keeps working either way.");
14205
+ z$1.enum([
14206
+ "pending",
14207
+ "dispatched",
14208
+ "delivered",
14209
+ "failed"
14210
+ ]);
14211
+ /**
14212
+ * The 400 clause every queued-generation contract declares for a rejected `callbackUrl`; must stay
14213
+ * in sync with the rejections in apps/client/server/generationCallback/armGenerationCallback.ts.
14214
+ */
14215
+ const GENERATION_CALLBACK_REJECTED_DESCRIPTION = "`callbackUrl` was rejected: the request is not API-key authenticated, the key has no callback signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks.";
13628
14216
  const DashboardParamsSchema = z$1.object({
13629
14217
  dashboardDataSources: z$1.array(z$1.object({
13630
14218
  sourceName: z$1.string(),
@@ -13958,24 +14546,108 @@ const GenerateImageResponseSchema = z$1.object({
13958
14546
  promptWasEnhanced: z$1.boolean(),
13959
14547
  intent: PromptIntentSchema
13960
14548
  });
13961
- /**
13962
- * Response shape for the public video endpoint (`generateVideo`,
13963
- * api-contract/contracts/videoGeneration.contract.ts). The request schema lives in
13964
- * `../schemas/sora` next to the service-side ones it extends.
13965
- *
13966
- * Mirrors `ImageQuestSchema` (imageApi.ts): the ACK body IS the quest document,
13967
- * returned before the queued render has run. A render that fails afterwards polls
13968
- * back as `type: 'error'` with the reason in `reply` - hence the chat poll
13969
- * vocabulary for `type` / `errorCode`.
13970
- */
13971
- const VideoQuestSchema = ChatQuestPollResultSchema.extend({
13972
- sessionId: z$1.string(),
13973
- videos: z$1.array(z$1.string()).optional()
14549
+ const VIDEO_GENERATION_API_ERROR_CODES = [
14550
+ ...VIDEO_VALIDATION_ERROR_CODES,
14551
+ "invalid_request",
14552
+ "model_disabled",
14553
+ "model_unavailable",
14554
+ "insufficient_credits",
14555
+ "input_image_not_found",
14556
+ "idempotency_key_reused",
14557
+ "invalid_idempotency_key"
14558
+ ];
14559
+ const VIDEO_JOB_PUBLIC_ERROR_CODES = [
14560
+ "content_blocked",
14561
+ "provider_timeout",
14562
+ "provider_error",
14563
+ "region_unavailable",
14564
+ "output_too_large",
14565
+ "input_image_not_found",
14566
+ "cancelled"
14567
+ ];
14568
+ const VideoGenerationErrorResponseSchema = ApiErrorSchema.extend({ errorCode: z$1.enum(VIDEO_GENERATION_API_ERROR_CODES).optional() });
14569
+ const CreateVideoGenerationBodySchema = z$1.strictObject({
14570
+ model: z$1.string().min(1).describe("A model id from GET /api/v1/video-models."),
14571
+ prompt: z$1.string().min(1).max(4e3),
14572
+ mode: z$1.enum(VIDEO_MODES).optional().describe("Defaults to image_to_video when input_image_file_id is set, else text_to_video."),
14573
+ duration_seconds: z$1.number().positive().optional().describe("Defaults to the model's default duration."),
14574
+ aspect_ratio: z$1.enum(ASPECT_RATIOS).optional(),
14575
+ resolution: z$1.enum(RESOLUTION_TIERS).optional(),
14576
+ input_image_file_id: z$1.string().min(1).optional().describe("An image file you own, for image_to_video."),
14577
+ audio: z$1.boolean().optional()
14578
+ });
14579
+ const VideoGenerationSchema = z$1.object({
14580
+ id: z$1.string(),
14581
+ object: z$1.literal("video_generation"),
14582
+ state: z$1.enum(GENERATION_JOB_STATES),
14583
+ model: z$1.string(),
14584
+ mode: z$1.enum(VIDEO_MODES),
14585
+ prompt: z$1.string(),
14586
+ duration_seconds: z$1.number(),
14587
+ aspect_ratio: z$1.enum(ASPECT_RATIOS),
14588
+ resolution: z$1.enum(RESOLUTION_TIERS),
14589
+ source: z$1.enum(GENERATION_JOB_SOURCES),
14590
+ progress: z$1.number().min(0).max(1).nullable(),
14591
+ error: z$1.object({
14592
+ code: z$1.enum(VIDEO_JOB_PUBLIC_ERROR_CODES),
14593
+ message: z$1.string()
14594
+ }).nullable(),
14595
+ output: z$1.object({
14596
+ availability: z$1.enum([
14597
+ "ready",
14598
+ "pending_scan",
14599
+ "unavailable"
14600
+ ]).describe("`ready`: url is set. `pending_scan`: the saved file is still being scanned; re-fetch the job until it changes (self-hosted installs can stay here for up to ~30 minutes). `unavailable`: the file was blocked by the scan or deleted and url will never be set; stop polling."),
14601
+ url: z$1.string().nullable().describe("Signed download URL, valid until expires_at; re-fetch the job for a fresh one. Set only when availability is `ready`, otherwise null."),
14602
+ expires_at: z$1.string().nullable().describe("ISO 8601. When url stops working; null whenever url is."),
14603
+ content_type: z$1.string(),
14604
+ duration_seconds: z$1.number(),
14605
+ file_id: z$1.string().nullable().describe("The Files entry, or null when the clip was stored outside Files.")
14606
+ }).nullable(),
14607
+ credits: z$1.object({
14608
+ reserved: z$1.number().nullable(),
14609
+ settled: z$1.number().nullable()
14610
+ }),
14611
+ created_at: z$1.string(),
14612
+ updated_at: z$1.string()
14613
+ });
14614
+ const VideoModelSchema = z$1.object({
14615
+ id: z$1.string(),
14616
+ object: z$1.literal("video_model"),
14617
+ display_name: z$1.string(),
14618
+ provider: z$1.string(),
14619
+ modes: z$1.array(z$1.enum(VIDEO_MODES)),
14620
+ duration: z$1.union([z$1.object({
14621
+ kind: z$1.literal("range"),
14622
+ min: z$1.number(),
14623
+ max: z$1.number(),
14624
+ step: z$1.number()
14625
+ }), z$1.object({
14626
+ kind: z$1.literal("discrete"),
14627
+ values: z$1.array(z$1.number())
14628
+ })]),
14629
+ aspect_ratios: z$1.array(z$1.enum(ASPECT_RATIOS)),
14630
+ resolutions: z$1.array(z$1.enum(RESOLUTION_TIERS)),
14631
+ defaults: z$1.object({
14632
+ duration_seconds: z$1.number(),
14633
+ aspect_ratio: z$1.enum(ASPECT_RATIOS),
14634
+ resolution: z$1.enum(RESOLUTION_TIERS)
14635
+ }),
14636
+ audio: z$1.enum([
14637
+ "always",
14638
+ "optional",
14639
+ "none"
14640
+ ]),
14641
+ credits_per_second: z$1.record(z$1.string(), z$1.number()).nullable().describe("Credits per second of output by resolution; null for per-clip priced models."),
14642
+ deprecation_date: z$1.string().nullable()
13974
14643
  });
13975
- const GenerateVideoResponseSchema = z$1.object({
13976
- quest: VideoQuestSchema,
13977
- session: z$1.object({ id: z$1.string() })
14644
+ const ListVideoModelsResponseSchema = z$1.object({ models: z$1.array(VideoModelSchema) });
14645
+ const ListVideoGenerationsQuerySchema = PaginationQuerySchema.extend({
14646
+ state: z$1.enum(GENERATION_JOB_STATES).optional(),
14647
+ source: z$1.enum(GENERATION_JOB_SOURCES).optional()
13978
14648
  });
14649
+ const ListVideoGenerationsResponseSchema = paginatedResponseSchema(VideoGenerationSchema);
14650
+ const VideoGenerationIdParamSchema = z$1.object({ id: z$1.string().min(1) });
13979
14651
  /**
13980
14652
  * Wire schemas for the public `/api/v1/voice/*` contracts (api-contract/contracts/voice.contract.ts).
13981
14653
  * The legacy `/api/voice/v2/*` paths serve the same handlers, so these shapes are also what the SPA
@@ -14067,8 +14739,8 @@ defineEndpoint({
14067
14739
  emitsRateLimitHeaders: true,
14068
14740
  responses: {
14069
14741
  200: {
14070
- description: "Message accepted - NOT a completed turn. The default (async) path returns this queued ACK; the outcome arrives on `GET /api/v1/quests/{id}` (see the `sendChatMessage200PollResult` schema). With `wait: true` the body additionally carries the completed reply (`response`/`responses`), `toolPayloads`, `createdAt`, and `performance` timings - fields not modelled here yet; the synchronous response shape is a follow-up. A turn that FAILS still resolves with `200`, never a 4xx, on both that `wait: true` body and the polled quest (`GET /api/v1/quests/{id}`) - the prose explaining why lands in `reply`/`response` like any other answer, so the reply text alone cannot tell a failure from an answer. `type` is the field that can: both surfaces carry it unconditionally, so match on `type: \"error\"` first - it covers credit exhaustion, a provider timeout or overload, and an in-process aborted turn; a real answer carries the turn's actual completion type instead (`\"message\"` for an ordinary reply). Two related states do NOT set `type: \"error\"`: a user-cancelled turn resolves as `status: \"stopped\"` with `type` left at `\"message\"`, and a recovered stuck quest that still has renderable content resolves as a success (`status: \"done\"`, no error) by design, to avoid destroying content to report a failure. `errorCode` then names the failure reason, but only for the billing failures that have one - `\"insufficient_credits\"` today; it is absent on every other `type: \"error\"` turn, so never use its absence to infer success. On a real answer `errorCode` is absent from the `wait: true` body. The body also carries `sessionId`, the notebook the turn was recorded in - for an API-key caller that sent none (or any caller sending `newConversation: true`), that is the freshly created notebook's id; pass it back as the request's `sessionId` to continue. Contrast the tts/music/soundEffects contracts, which reject synchronously with a 422 carrying the same `errorCode` vocabulary.",
14071
- schema: ChatAckSchema,
14742
+ description: "Message accepted - NOT a completed turn. The default (async) path returns this queued ACK; the outcome arrives on `GET /api/v1/quests/{id}` (see the `sendChatMessage200PollResult` schema). With `wait: true` the body is instead the completed turn: the ACK fields plus the reply (`response`/`responses`), `toolPayloads`, `createdAt`, `performance` timings and, when requested, `promptDetails`/`promptText`. A turn that FAILS still resolves with `200`, never a 4xx, on both that `wait: true` body and the polled quest (`GET /api/v1/quests/{id}`) - the prose explaining why lands in `reply`/`response` like any other answer, so the reply text alone cannot tell a failure from an answer. `type` is the field that can: both surfaces carry it unconditionally, so match on `type: \"error\"` first - it covers credit exhaustion, a provider timeout or overload, and an in-process aborted turn; a real answer carries the turn's actual completion type instead (`\"message\"` for an ordinary reply). Two related states do NOT set `type: \"error\"`: a user-cancelled turn resolves as `status: \"stopped\"` with `type` left at `\"message\"`, and a recovered stuck quest that still has renderable content resolves as a success (`status: \"done\"`, no error) by design, to avoid destroying content to report a failure. `errorCode` then names the failure reason, but only for the billing failures that have one - `\"insufficient_credits\"` today; it is absent on every other `type: \"error\"` turn, so never use its absence to infer success. On a real answer `errorCode` is absent from the `wait: true` body. The body also carries `sessionId`, the notebook the turn was recorded in - for an API-key caller that sent none (or any caller sending `newConversation: true`), that is the freshly created notebook's id; pass it back as the request's `sessionId` to continue. Contrast the tts/music/soundEffects contracts, which reject synchronously with a 422 carrying the same `errorCode` vocabulary.",
14743
+ schema: z$1.union([ChatWaitResponseSchema, ChatAckSchema]),
14072
14744
  pollResult: {
14073
14745
  schema: ChatQuestPollResultSchema,
14074
14746
  description: "Outcome fields of the quest polled at `GET /api/v1/quests/{id}` after this ACK. A finished turn that failed is `status: \"done\"` with `type: \"error\"` and the failure text in `reply`, so a caller reading `reply` alone cannot tell a failure from an answer - check `type` first, and also treat a terminal `status: \"stopped\"` (a missing session, a user-cancelled turn) as failure even though it never sets `type`. `errorCode` is an optional refinement of `type: \"error\"`, present only for a classified billing failure; credit exhaustion arrives here as `insufficient_credits`, the same vocabulary the synchronous 422s on `/api/ai/music`, `/api/ai/sound-effects` and `/api/ai/tts` use. `QUEST_ERROR_CODES` publishes a second member, `spend_cap_exceeded`, but no current throw site on this endpoint raises it as a quest errorCode: its only one, the embed chat route's pre-flight 422, fires outside the process try/catch that would classify it onto the quest. Most `type: \"error\"` turns - an abort, a provider timeout or overload - have NO `errorCode`; its absence does not mean success, only that the failure is unclassified. A recovered stuck quest that still has renderable content is not a failure at all: it keeps `type: \"message\"` even though it did not finish, so a caller gets the content rather than an error. `fallbackInfo` is present only when the requested model failed and another model produced the reply: `fallbackModel` answered, `primaryModel` is what was asked for, and `reason` is the error from the first failed attempt on the requested model. The poll body carries further fields not modelled here, including `images`, `files`, `toolPayloads`, `promptMeta`, and the attachment report (`attachmentNotices`/`attachmentDelivery`) - only the outcome subset is modelled here.",
@@ -14142,39 +14814,143 @@ defineEndpoint({
14142
14814
  429: {
14143
14815
  description: "Per-user rate limit exceeded.",
14144
14816
  schema: ApiErrorSchema
14145
- },
14146
- 502: {
14147
- description: "The executor could not be dispatched. The run did not start, so a retry is safe.",
14148
- schema: ApiErrorSchema
14817
+ },
14818
+ 502: {
14819
+ description: "The executor could not be dispatched. The run did not start, so a retry is safe.",
14820
+ schema: ApiErrorSchema
14821
+ }
14822
+ },
14823
+ codeSample: {
14824
+ authToken: "b4m_live_<key>",
14825
+ streaming: false,
14826
+ body: {
14827
+ session_id: "<sessionId>",
14828
+ message: "Audit this data set and summarize what stands out."
14829
+ }
14830
+ }
14831
+ });
14832
+ defineEndpoint({
14833
+ method: "get",
14834
+ path: "/api/v1/agent-executions/{id}",
14835
+ operationId: "getAgentExecution",
14836
+ summary: "Get an agent execution",
14837
+ description: "Returns the status, reasoning trace, and (once terminal) the final answer of a run started by `POST /api/v1/agent-executions`. `steps` grows while the run is in flight, so polling this endpoint is also how a REST caller follows the loop. Safe (GET) requests on this route are exempt from the per-day API-key quota: a poll consumes no daily slot, and only the per-minute burst limit applies.",
14838
+ tags: ["AI"],
14839
+ auth: "apiKeyOrJwt",
14840
+ scopes: ["ai:chat", "ai:generate"],
14841
+ pathParams: AgentExecutionIdParamSchema,
14842
+ emitsRateLimitHeaders: true,
14843
+ responses: {
14844
+ 200: {
14845
+ description: "The execution, its trace, and its answer if it has finished.",
14846
+ schema: AgentExecutionStatusResponseSchema
14847
+ },
14848
+ 404: {
14849
+ description: "No execution with that id is visible to the caller.",
14850
+ schema: ApiErrorSchema
14851
+ },
14852
+ 429: {
14853
+ description: "Per-user rate limit exceeded.",
14854
+ schema: ApiErrorSchema
14855
+ }
14856
+ },
14857
+ codeSample: {
14858
+ authToken: "b4m_live_<key>",
14859
+ streaming: false,
14860
+ body: {}
14861
+ }
14862
+ });
14863
+ defineEndpoint({
14864
+ method: "put",
14865
+ path: "/api/sessions/{id}",
14866
+ operationId: "updateSession",
14867
+ summary: "Update a session",
14868
+ description: "Updates a session (called a \"notebook\" in the product UI): its name, attached knowledge files, tags, or retrieval settings. Set `knowledgeIds` and `forceKnowledgeRetrieval: true` together to enable grounded retrieval for `POST /api/chat` against this session - retrieval is gated by these session fields, not by the chat request. `lakeScope` narrows that retrieval to a chosen set of data lakes; omit it to leave the current choice unchanged, or send `null` to clear it so retrieval reaches every lake you can access. `includeLibraryFiles` sets whether the session grounds on your own library alongside its lakes; `false` confines it to lake content even when every lake is in scope (files attached to the session still reach the model and its knowledge-base tools), and is set aside (library included) while `forceKnowledgeRetrieval` is `false`. Authenticate with an API key (`b4m_live_`) or a JWT. Warning: adding to `knowledgeIds` shares those files with every member of every project containing this session by default (see `propagateToProjects`), and that sharing cannot be undone through the UI.",
14869
+ tags: ["Sessions"],
14870
+ auth: "apiKeyOrJwt",
14871
+ scopes: ["notebooks:write"],
14872
+ pathParams: SessionIdParamSchema,
14873
+ request: SessionUpdateRequestSchema,
14874
+ emitsRateLimitHeaders: true,
14875
+ requestExample: {
14876
+ knowledgeIds: ["<fabFileId>"],
14877
+ forceKnowledgeRetrieval: true
14878
+ },
14879
+ responses: {
14880
+ 200: {
14881
+ description: "The updated session.",
14882
+ schema: SessionResponseSchema
14883
+ },
14884
+ 404: {
14885
+ description: "No session exists with the given id.",
14886
+ schema: ApiErrorSchema
14887
+ },
14888
+ 429: {
14889
+ description: "Per-user rate limit exceeded.",
14890
+ schema: ApiErrorSchema
14891
+ }
14892
+ },
14893
+ codeSample: {
14894
+ authToken: "b4m_live_<key>",
14895
+ streaming: false,
14896
+ body: {
14897
+ knowledgeIds: ["<fabFileId>"],
14898
+ forceKnowledgeRetrieval: true
14899
+ }
14900
+ }
14901
+ });
14902
+ defineEndpoint({
14903
+ method: "get",
14904
+ path: "/api/sessions/{id}",
14905
+ operationId: "getSession",
14906
+ summary: "Get a session",
14907
+ description: "Reads a session you own or that was shared with you (called a \"notebook\" in the product UI): its name, attached knowledge files, tags, and retrieval settings. Use it to confirm what a `PUT` on the same path stored. A session not visible to you, or one that was deleted, is `404`. An API key needs `notebooks:read` or `notebooks:write`.",
14908
+ tags: ["Sessions"],
14909
+ auth: "apiKeyOrJwt",
14910
+ scopes: ["notebooks:read", "notebooks:write"],
14911
+ pathParams: SessionIdParamSchema,
14912
+ emitsRateLimitHeaders: true,
14913
+ responses: {
14914
+ 200: {
14915
+ description: "The session.",
14916
+ schema: SessionResponseSchema
14917
+ },
14918
+ 404: {
14919
+ description: "No session with that id is visible to the caller.",
14920
+ schema: ApiErrorSchema
14921
+ },
14922
+ 429: {
14923
+ description: "Per-user rate limit exceeded.",
14924
+ schema: ApiErrorSchema
14149
14925
  }
14150
14926
  },
14151
14927
  codeSample: {
14152
14928
  authToken: "b4m_live_<key>",
14153
- streaming: false,
14154
- body: {
14155
- session_id: "<sessionId>",
14156
- message: "Audit this data set and summarize what stands out."
14157
- }
14929
+ streaming: false
14158
14930
  }
14159
14931
  });
14160
14932
  defineEndpoint({
14161
- method: "get",
14162
- path: "/api/v1/agent-executions/{id}",
14163
- operationId: "getAgentExecution",
14164
- summary: "Get an agent execution",
14165
- description: "Returns the status, reasoning trace, and (once terminal) the final answer of a run started by `POST /api/v1/agent-executions`. `steps` grows while the run is in flight, so polling this endpoint is also how a REST caller follows the loop. Safe (GET) requests on this route are exempt from the per-day API-key quota: a poll consumes no daily slot, and only the per-minute burst limit applies.",
14166
- tags: ["AI"],
14933
+ method: "delete",
14934
+ path: "/api/sessions/{id}",
14935
+ operationId: "deleteSession",
14936
+ summary: "Delete a session",
14937
+ description: "Deletes one of your sessions (called a \"notebook\" in the product UI). This cannot be undone through the API. Files you uploaded into the session are deleted with it; files other people uploaded into it are kept, and every access grant this session gave out on a file is revoked. A session you do not own, or one already deleted, is `404`. A `409` means a concurrent change touched one of those files mid-delete; nothing was deleted, so retry. An API key needs `notebooks:write`.",
14938
+ tags: ["Sessions"],
14167
14939
  auth: "apiKeyOrJwt",
14168
- scopes: ["ai:chat", "ai:generate"],
14169
- pathParams: AgentExecutionIdParamSchema,
14940
+ scopes: ["notebooks:write"],
14941
+ pathParams: SessionIdParamSchema,
14170
14942
  emitsRateLimitHeaders: true,
14171
14943
  responses: {
14172
14944
  200: {
14173
- description: "The execution, its trace, and its answer if it has finished.",
14174
- schema: AgentExecutionStatusResponseSchema
14945
+ description: "The session was deleted.",
14946
+ schema: SessionDeleteResponseSchema
14175
14947
  },
14176
14948
  404: {
14177
- description: "No execution with that id is visible to the caller.",
14949
+ description: "No session with that id is visible to the caller.",
14950
+ schema: ApiErrorSchema
14951
+ },
14952
+ 409: {
14953
+ description: "A concurrent change touched a file this session shares; nothing was deleted. Retry.",
14178
14954
  schema: ApiErrorSchema
14179
14955
  },
14180
14956
  429: {
@@ -14184,43 +14960,51 @@ defineEndpoint({
14184
14960
  },
14185
14961
  codeSample: {
14186
14962
  authToken: "b4m_live_<key>",
14187
- streaming: false,
14188
- body: {}
14963
+ streaming: false
14189
14964
  }
14190
14965
  });
14191
14966
  defineEndpoint({
14192
- method: "put",
14193
- path: "/api/sessions/{id}",
14194
- operationId: "updateSession",
14195
- summary: "Update a session",
14196
- description: "Updates a session (called a \"notebook\" in the product UI): its name, attached knowledge files, tags, or retrieval settings. Set `knowledgeIds` and `forceKnowledgeRetrieval: true` together to enable grounded retrieval for `POST /api/chat` against this session - retrieval is gated by these session fields, not by the chat request. `lakeScope` narrows that retrieval to a chosen set of data lakes; omit it to leave the current choice unchanged, or send `null` to clear it so retrieval reaches every lake you can access. Authenticate with an API key (`b4m_live_`) or a JWT. Warning: adding to `knowledgeIds` shares those files with every member of every project containing this session by default (see `propagateToProjects`), and that sharing cannot be undone through the UI.",
14967
+ method: "post",
14968
+ path: "/api/v1/sessions/{id}/clone",
14969
+ operationId: "cloneSession",
14970
+ summary: "Clone a session",
14971
+ description: "Clones a session (called a \"notebook\" in the product UI), including its messages and attachments. Omit `targetSurface` to inherit the source workspace, send `null` for the main notebook list, or send a surface id for another workspace. An API key needs `notebooks:write`.",
14197
14972
  tags: ["Sessions"],
14198
14973
  auth: "apiKeyOrJwt",
14199
14974
  scopes: ["notebooks:write"],
14200
14975
  pathParams: SessionIdParamSchema,
14201
- request: SessionUpdateRequestSchema,
14976
+ request: SessionCloneRequestSchema,
14977
+ requestBodyRequired: false,
14978
+ validationErrorStatus: 400,
14202
14979
  emitsRateLimitHeaders: true,
14203
- requestExample: {
14204
- knowledgeIds: ["<fabFileId>"],
14205
- forceKnowledgeRetrieval: true
14206
- },
14980
+ requestExample: {},
14207
14981
  responses: {
14208
14982
  200: {
14209
- description: "The updated session.",
14983
+ description: "The cloned session.",
14210
14984
  schema: SessionResponseSchema
14211
14985
  },
14986
+ 400: {
14987
+ description: "The session id is missing or `targetSurface` is malformed.",
14988
+ schema: ApiErrorSchema
14989
+ },
14990
+ 403: {
14991
+ description: "The API key lacks `notebooks:write` or the caller is not allowed to clone sessions.",
14992
+ schema: ScopeForbiddenErrorSchema
14993
+ },
14212
14994
  404: {
14213
- description: "No session exists with the given id.",
14995
+ description: "No session with that id is visible to the caller.",
14214
14996
  schema: ApiErrorSchema
14997
+ },
14998
+ 429: {
14999
+ description: "The clone limit of 10 requests per minute per caller was exceeded.",
15000
+ schema: ApiErrorSchema,
15001
+ headers: { "Retry-After": "Seconds until the caller can retry." }
14215
15002
  }
14216
15003
  },
14217
15004
  codeSample: {
14218
15005
  authToken: "b4m_live_<key>",
14219
15006
  streaming: false,
14220
- body: {
14221
- knowledgeIds: ["<fabFileId>"],
14222
- forceKnowledgeRetrieval: true
14223
- }
15007
+ body: {}
14224
15008
  }
14225
15009
  });
14226
15010
  defineEndpoint({
@@ -14255,6 +15039,10 @@ defineEndpoint({
14255
15039
  description: "Missing or invalid JWT (an API key is rejected here).",
14256
15040
  schema: ApiErrorSchema
14257
15041
  },
15042
+ 403: {
15043
+ description: "Valid JWT, but the account is suspended or has a dispute pending.",
15044
+ schema: ApiErrorSchema
15045
+ },
14258
15046
  429: {
14259
15047
  description: "Rate limit exceeded (100 requests/hour per user for this endpoint).",
14260
15048
  schema: ApiErrorSchema
@@ -14651,6 +15439,80 @@ defineEndpoint({
14651
15439
  body: {}
14652
15440
  }
14653
15441
  });
15442
+ defineEndpoint({
15443
+ method: "get",
15444
+ path: "/api/v1/models",
15445
+ operationId: "listModels",
15446
+ summary: "List models",
15447
+ description: "Lists the models the caller can use right now - the platform catalog filtered to the providers the caller has a key for, their own or the platform's. Use it to choose a `model` by capability before calling a generation endpoint: `context_window` and `max_output_tokens` for text models, and for image models the `image` block, which lists the accepted `size` values (or the rule they must meet) and whether `background`, `seed`, `quality`, `n` and reference images are honoured. The list is cached for up to a minute per caller. Cursor-paginated (see the pagination convention): pass `next_cursor` back as `cursor` until it is `null`. Authenticate with an API key (`b4m_live_`) carrying `ai:chat` or `ai:generate`, or a JWT.",
15448
+ tags: ["Models"],
15449
+ auth: "apiKeyOrJwt",
15450
+ scopes: ["ai:chat", "ai:generate"],
15451
+ queryParams: PaginationQuerySchema,
15452
+ emitsRateLimitHeaders: true,
15453
+ responses: {
15454
+ 200: {
15455
+ description: "One page of models, ordered by `id`.",
15456
+ schema: ListModelsResponseSchema,
15457
+ example: {
15458
+ data: [{
15459
+ id: "gpt-image-1",
15460
+ name: "GPT Image 1",
15461
+ type: "image",
15462
+ backend: "openai",
15463
+ description: null,
15464
+ context_window: 0,
15465
+ max_output_tokens: 0,
15466
+ supports_streaming: false,
15467
+ supports_thinking: false,
15468
+ supports_tools: false,
15469
+ supports_vision: false,
15470
+ deprecation_date: null,
15471
+ replaced_by: null,
15472
+ image: {
15473
+ sizing: {
15474
+ kind: "presets",
15475
+ presets: [
15476
+ "1024x1024",
15477
+ "1024x1536",
15478
+ "1536x1024"
15479
+ ],
15480
+ default_size: "1024x1024"
15481
+ },
15482
+ supports: {
15483
+ transparent_background: true,
15484
+ seed: false,
15485
+ qualities: [
15486
+ "low",
15487
+ "medium",
15488
+ "high",
15489
+ "auto"
15490
+ ],
15491
+ max_images: 10,
15492
+ max_reference_images: 4,
15493
+ edit: true,
15494
+ requires_input_image: false
15495
+ }
15496
+ }
15497
+ }],
15498
+ next_cursor: null
15499
+ }
15500
+ },
15501
+ 422: {
15502
+ description: "`limit` is out of range, or `cursor` is malformed or was issued by a different endpoint. A cursor is opaque: pass back exactly the `next_cursor` you were given.",
15503
+ schema: ApiErrorSchema
15504
+ },
15505
+ 429: {
15506
+ description: "Per-user rate limit exceeded.",
15507
+ schema: ApiErrorSchema
15508
+ }
15509
+ },
15510
+ codeSample: {
15511
+ authToken: "b4m_live_<key>",
15512
+ streaming: false,
15513
+ body: {}
15514
+ }
15515
+ });
14654
15516
  /** Shared by generateImage and editImage: both hand off to the same quest poll. */
14655
15517
  const imageQuestPollResult = {
14656
15518
  schema: ImageQuestSchema,
@@ -14668,7 +15530,7 @@ defineEndpoint({
14668
15530
  path: "/api/v1/image-generations",
14669
15531
  operationId: "generateImage",
14670
15532
  summary: "Generate an image",
14671
- description: "Queues an image generation and returns immediately with the quest that will carry the result - no image yet, and the call never blocks on the render. Poll `GET /api/v1/quests/{id}` until `status` is `\"done\"` (see the `generateImage200PollResult` schema). Omit `sessionId` to create a new session. The prompt is resolved against the session history first, so a follow-up such as \"make it darker\" binds to the previous image; `enhancedPrompt` reports the prompt actually sent to the model. Send `prompt_resolution: \"literal\"` to skip that step and have the prompt sent as written (apart from truncation to the model's prompt limit). `referenceImageFabFileIds` passes up to 4 already-uploaded images as style anchors, after the input image taken from `fabFileIds`. Only gpt-image models accept them: sending any with another model is rejected with a 400 rather than ignored. Which `size` values a model accepts, and whether it honours `background`, `seed`, `quality`, `n` and reference images, is listed per model in the `image` block of `GET /api/models`. Credits are checked when the render runs, so insufficient credits arrive on the polled quest rather than as a 422. `POST /api/ai/generate-image` is a legacy alias of this endpoint. Authenticate with an API key (`b4m_live_`) or a JWT.\n\nOptional `callbackUrl`: an https URL that receives a signed POST when the quest settles (success, failure, or a stop; a failed render is `type: \"error\"` in the body, same as the poll). The body is the `GET /api/v1/quests/{id}` response. Headers: `X-Webhook-Event-ID` (the same on every attempt for one settlement; delivery is at-least-once, so dedupe on it, and a retried quest settles again under a new one), `X-Webhook-Delivery-ID` (unique per attempt), `X-Webhook-Timestamp` (unix seconds), `X-Webhook-Signature-256` (`sha256=` + hex HMAC-SHA256 of `<timestamp>.<raw body>` keyed with the API key's callback signing secret), and `X-Event-Type: quest.settled`. Delivery makes up to 5 attempts, retrying on a 5xx, 408, 429, timeout or network error - never on any other 4xx, and redirects are not followed; your endpoint must answer 2xx within 10 seconds. Requires API-key authentication and a signing secret on that key (returned once at key creation, or via `POST /api/user-api-keys/{id}/callback-secret`). A `callbackUrl` is rejected with a 400 when the request is not API-key authenticated, the key has no signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks; a URL that is not https fails request validation with a 422. Polling still works whether or not `callbackUrl` is set.",
15533
+ description: "Queues an image generation and returns immediately with the quest that will carry the result - no image yet, and the call never blocks on the render. Poll `GET /api/v1/quests/{id}` until `status` is `\"done\"` (see the `generateImage200PollResult` schema). Omit `sessionId` to create a new session. The prompt is resolved against the session history first, so a follow-up such as \"make it darker\" binds to the previous image; `enhancedPrompt` reports the prompt actually sent to the model. Send `prompt_resolution: \"literal\"` to skip that step and have the prompt sent as written (apart from truncation to the model's prompt limit). `referenceImageFabFileIds` passes up to 4 already-uploaded images as style anchors, after the input image taken from `fabFileIds`. Only gpt-image models accept them: sending any with another model is rejected with a 400 rather than ignored. Which `size` values a model accepts, and whether it honours `background`, `seed`, `quality`, `n` and reference images, is listed per model in the `image` block of `GET /api/v1/models`. Credits are checked when the render runs, so insufficient credits arrive on the polled quest rather than as a 422. `POST /api/ai/generate-image` is a legacy alias of this endpoint. Authenticate with an API key (`b4m_live_`) or a JWT.\n\nOptional `callbackUrl`: an https URL that receives a signed POST when the quest settles (success, failure, or a stop; a failed render is `type: \"error\"` in the body, same as the poll). The body is the `GET /api/v1/quests/{id}` response. Headers: `X-Webhook-Event-ID` (the same on every attempt for one settlement; delivery is at-least-once, so dedupe on it, and a retried quest settles again under a new one), `X-Webhook-Delivery-ID` (unique per attempt), `X-Webhook-Timestamp` (unix seconds), `X-Webhook-Signature-256` (`sha256=` + hex HMAC-SHA256 of `<timestamp>.<raw body>` keyed with the API key's callback signing secret), and `X-Event-Type: quest.settled`. Delivery makes up to 5 attempts, retrying on a 5xx, 408, 429, timeout or network error - never on any other 4xx, and redirects are not followed; your endpoint must answer 2xx within 10 seconds. Requires API-key authentication and a signing secret on that key (returned once at key creation, or via `POST /api/user-api-keys/{id}/callback-secret`). A `callbackUrl` is rejected with a 400 when the request is not API-key authenticated, the key has no signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks; a URL that is not https fails request validation with a 422. Polling still works whether or not `callbackUrl` is set.",
14672
15534
  tags: ["Images"],
14673
15535
  auth: "apiKeyOrJwt",
14674
15536
  scopes: ["ai:generate"],
@@ -14953,7 +15815,7 @@ defineEndpoint({
14953
15815
  path: "/api/v1/sessions",
14954
15816
  operationId: "createSession",
14955
15817
  summary: "Create a session",
14956
- description: "Creates a session (called a \"notebook\" in the product UI). Pass the returned `id` as `session_id` to `POST /api/chat`, then poll `GET /api/v1/quests/{id}` for the reply. An API key needs `notebooks:write` for this call, so a chat round trip needs that scope as well as `ai:chat`. Unknown body fields are ignored. Naming a data lake with `dataLakeId` seeds the session with that lake's retrieval defaults.",
15818
+ description: "Creates a session (called a \"notebook\" in the product UI). Pass the returned `id` as `sessionId` to `POST /api/chat`, then poll `GET /api/v1/quests/{id}` for the reply. An API key needs `notebooks:write` for this call, so a chat round trip needs that scope as well as `ai:chat`. Unknown body fields are ignored. Naming a data lake with `dataLakeId` (ids come from `GET /api/v1/data-lakes`) seeds the session with that lake's retrieval defaults and turns on forced retrieval.",
14957
15819
  tags: ["Sessions"],
14958
15820
  auth: "apiKeyOrJwt",
14959
15821
  scopes: ["notebooks:write"],
@@ -14984,6 +15846,68 @@ defineEndpoint({
14984
15846
  body: { name: "Quarterly analysis" }
14985
15847
  }
14986
15848
  });
15849
+ defineEndpoint({
15850
+ method: "get",
15851
+ path: "/api/v1/sessions",
15852
+ operationId: "listSessions",
15853
+ summary: "List sessions",
15854
+ description: "Lists the sessions you own (called \"notebooks\" in the product UI), newest first. Sessions shared with you are not included; read one of those by id with `GET /api/sessions/{id}`. Each item has the same shape `GET /api/sessions/{id}` returns. Cursor-paginated (see the pagination convention): pass `next_cursor` back as `cursor` until it is `null`. Changing `search`, `surface` or `origin` starts a new listing, so drop the cursor when you do. An API key needs `notebooks:read` or `notebooks:write`.",
15855
+ tags: ["Sessions"],
15856
+ auth: "apiKeyOrJwt",
15857
+ scopes: ["notebooks:read", "notebooks:write"],
15858
+ queryParams: ListSessionsQuerySchema,
15859
+ emitsRateLimitHeaders: true,
15860
+ responses: {
15861
+ 200: {
15862
+ description: "One page of sessions, newest first by `id`.",
15863
+ schema: ListSessionsResponseSchema
15864
+ },
15865
+ 403: {
15866
+ description: "The API key holds neither `notebooks:read` nor `notebooks:write`.",
15867
+ schema: ScopeForbiddenErrorSchema
15868
+ },
15869
+ 422: {
15870
+ description: "`limit` is out of range, a filter is malformed, or `cursor` is malformed or was issued by a different endpoint. A cursor is opaque: pass back exactly the `next_cursor` you were given.",
15871
+ schema: ApiErrorSchema
15872
+ },
15873
+ 429: {
15874
+ description: "Per-user rate limit exceeded.",
15875
+ schema: ApiErrorSchema
15876
+ }
15877
+ },
15878
+ codeSample: {
15879
+ authToken: "b4m_live_<key>",
15880
+ streaming: false
15881
+ }
15882
+ });
15883
+ defineEndpoint({
15884
+ method: "get",
15885
+ path: "/api/v1/whats-new",
15886
+ operationId: "listWhatsNew",
15887
+ summary: "List release notes",
15888
+ description: "Lists published release notes, newest first. Public: no credentials are needed, and the response is cacheable for up to about 15 minutes (5 fresh plus 10 stale-while-revalidate), so a newly published or withdrawn note can take that long to show. A deployment that proxies an upstream feed stacks its own caching on the upstream's, so there it can take about 31 minutes. Cursor-paginated (see the pagination convention): pass `next_cursor` back as `cursor` until it is `null`. Rate limited per client IP.",
15889
+ tags: ["Release notes"],
15890
+ auth: "public",
15891
+ queryParams: PaginationQuerySchema,
15892
+ responses: {
15893
+ 200: {
15894
+ description: "One page of published release notes, newest first by `published_at`.",
15895
+ schema: paginatedResponseSchema(PublicReleaseNoteSchema)
15896
+ },
15897
+ 422: {
15898
+ description: "`limit` is out of range, or `cursor` is malformed or was issued by a different endpoint. A cursor is opaque: pass back exactly the `next_cursor` you were given. On a deployment that proxies an upstream feed, a cursor issued by that upstream which can no longer be served there ends the list (200 with empty `data`) rather than returning 422.",
15899
+ schema: ApiErrorSchema
15900
+ },
15901
+ 429: {
15902
+ description: "Per-IP rate limit exceeded.",
15903
+ schema: ApiErrorSchema
15904
+ },
15905
+ 500: {
15906
+ description: "Unexpected server error.",
15907
+ schema: ApiErrorSchema
15908
+ }
15909
+ }
15910
+ });
14987
15911
  /**
14988
15912
  * The read/write/query API-key scope sets the data-lake doors accept (OR semantics), shared by the
14989
15913
  * SPA routes (`apps/client/server/dataLakes/dataLakeScopes.ts` re-exports these) and the public
@@ -15173,22 +16097,202 @@ defineEndpoint({
15173
16097
  emitsRateLimitHeaders: true,
15174
16098
  responses: {
15175
16099
  200: {
15176
- description: "The file is no longer a member; the lake's updated totals.",
15177
- schema: DataLakeFileMembershipResponseSchema
15178
- },
15179
- 403: {
15180
- description: WRITE_FORBIDDEN,
15181
- schema: ScopeForbiddenErrorSchema
15182
- },
15183
- 404: {
15184
- description: "The lake is not visible to the caller, or the file is not a current member of it.",
15185
- schema: ApiErrorSchema
16100
+ description: "The file is no longer a member; the lake's updated totals.",
16101
+ schema: DataLakeFileMembershipResponseSchema
16102
+ },
16103
+ 403: {
16104
+ description: WRITE_FORBIDDEN,
16105
+ schema: ScopeForbiddenErrorSchema
16106
+ },
16107
+ 404: {
16108
+ description: "The lake is not visible to the caller, or the file is not a current member of it.",
16109
+ schema: ApiErrorSchema
16110
+ },
16111
+ 429: {
16112
+ description: "Per-user rate limit exceeded.",
16113
+ schema: ApiErrorSchema
16114
+ }
16115
+ },
16116
+ codeSample: {
16117
+ authToken: "b4m_live_<key>",
16118
+ streaming: false,
16119
+ body: {}
16120
+ }
16121
+ });
16122
+ defineEndpoint({
16123
+ method: "post",
16124
+ path: "/api/v1/data-lakes/{id}/search",
16125
+ operationId: "searchDataLake",
16126
+ summary: "Search a data lake",
16127
+ description: "Semantic (vector) search over one lake: the query is embedded with the model the corpus was indexed with and the top `top_k` passages at or above `min_score` are returned. Only members of this lake are searched - never the caller's other files. `tags` narrows to files carrying any of the listed tags. Files still being indexed or paused are withheld rather than searched stale: `partial_results` is then true and `retrieval_unavailable` counts them (poll `GET /api/v1/data-lakes/{id}/files/{file_id}` for a specific file). The query embedding is billed as credits. Limited to 10 searches per minute per caller, shared with the product UI.",
16128
+ tags: ["Data Lakes"],
16129
+ auth: "apiKeyOrJwt",
16130
+ scopes: DATA_LAKE_QUERY_API_KEY_SCOPES,
16131
+ pathParams: DataLakeIdParamSchema,
16132
+ request: DataLakeSearchRequestSchema,
16133
+ requestExample: {
16134
+ query: "What is our refund policy for annual plans?",
16135
+ top_k: 5
16136
+ },
16137
+ emitsRateLimitHeaders: true,
16138
+ responses: {
16139
+ 200: {
16140
+ description: "The ranked passages and what, if anything, was withheld.",
16141
+ schema: DataLakeSearchResponseSchema
16142
+ },
16143
+ 400: {
16144
+ description: "The API key bills an organization that no longer exists, or one its owner is no longer a member of.",
16145
+ schema: ApiErrorSchema
16146
+ },
16147
+ 403: {
16148
+ description: `The API key lacks \`datalake:query\`, ${FEATURE_DISABLED_NOTE}.`,
16149
+ schema: ScopeForbiddenErrorSchema
16150
+ },
16151
+ 404: {
16152
+ description: `${NOT_VISIBLE_LAKE} A lake the caller can read but is not entitled to search is reported the same way, as is a lake that is not \`active\` yet (a draft lake is readable but not yet searchable).`,
16153
+ schema: ApiErrorSchema
16154
+ },
16155
+ 422: {
16156
+ description: "Request body failed validation, or the caller cannot afford the query embedding - the latter is tagged `errorCode: \"insufficient_credits\"` (the balance is short, or the org member credit cap is exhausted).",
16157
+ schema: InsufficientCreditsErrorSchema
16158
+ },
16159
+ 429: {
16160
+ description: "Search rate limit (10 per minute) or per-user API rate limit exceeded.",
16161
+ schema: ApiErrorSchema
16162
+ },
16163
+ 503: {
16164
+ description: "No usable key is configured on this deployment for the embedding provider the query needs (`errorCode: \"provider_not_configured\"`).",
16165
+ schema: ProviderNotConfiguredErrorSchema
16166
+ }
16167
+ },
16168
+ codeSample: {
16169
+ authToken: "b4m_live_<key>",
16170
+ streaming: false,
16171
+ body: {
16172
+ query: "What is our refund policy for annual plans?",
16173
+ top_k: 5
16174
+ }
16175
+ }
16176
+ });
16177
+ const exampleJob = {
16178
+ id: EXAMPLE_RESOURCE_ID,
16179
+ object: "video_generation",
16180
+ state: "pending",
16181
+ model: "grok-imagine-video-1.5",
16182
+ mode: "text_to_video",
16183
+ prompt: "A slow dolly shot of a red lighthouse at dusk",
16184
+ duration_seconds: 6,
16185
+ aspect_ratio: "16:9",
16186
+ resolution: "480p",
16187
+ source: "api",
16188
+ progress: null,
16189
+ error: null,
16190
+ output: null,
16191
+ credits: {
16192
+ reserved: 960,
16193
+ settled: null
16194
+ },
16195
+ created_at: "2026-10-06T00:00:00.000Z",
16196
+ updated_at: "2026-10-06T00:00:00.000Z"
16197
+ };
16198
+ const scopeForbidden = {
16199
+ description: "The API key lacks the `ai:generate` scope.",
16200
+ schema: ScopeForbiddenErrorSchema
16201
+ };
16202
+ const notFound = {
16203
+ description: "No video generation with that id was requested by the caller (a malformed id is also a 404).",
16204
+ schema: ApiErrorSchema
16205
+ };
16206
+ const rateLimited = {
16207
+ description: "Per-user rate limit exceeded.",
16208
+ schema: ApiErrorSchema
16209
+ };
16210
+ defineEndpoint({
16211
+ method: "get",
16212
+ path: "/api/v1/video-models",
16213
+ operationId: "listVideoModels",
16214
+ summary: "List the video models you can use",
16215
+ description: "Returns the video models that are enabled, available in this deployment and have a usable provider key for the caller, with their capabilities and per-second credit price. Validate requests against these capabilities: an unsupported duration, aspect ratio or resolution is a 422, never silently adjusted.",
16216
+ tags: ["Videos"],
16217
+ auth: "apiKeyOrJwt",
16218
+ scopes: ["ai:generate"],
16219
+ responses: {
16220
+ 200: {
16221
+ description: "The usable models.",
16222
+ schema: ListVideoModelsResponseSchema
16223
+ },
16224
+ 403: scopeForbidden,
16225
+ 429: rateLimited
16226
+ },
16227
+ emitsRateLimitHeaders: true,
16228
+ codeSample: {
16229
+ authToken: "b4m_live_<key>",
16230
+ streaming: false,
16231
+ body: {}
16232
+ }
16233
+ });
16234
+ defineEndpoint({
16235
+ method: "post",
16236
+ path: "/api/v1/video-generations",
16237
+ operationId: "createVideoGeneration",
16238
+ summary: "Start a video generation",
16239
+ description: "Queues a video generation and returns `202` with the job. Poll `GET /api/v1/video-generations/{id}` until `state` is `succeeded`, `failed`, `blocked` or `cancelled`. On success, `output.availability` says whether the clip can be downloaded: `ready` means `output.url` is a signed download URL valid for 15 minutes (re-fetch the job for a fresh one); `pending_scan` means the saved file is still being scanned, so keep re-fetching the job (self-hosted installs can stay here for up to ~30 minutes); `unavailable` means the file was blocked or deleted and `output.url` will never be set, so stop polling. Credits for the requested duration are reserved up front and settled on completion; a failed or cancelled job is not charged. A `blocked` job is not charged either, unless the provider generated the clip before rejecting it and billed it: then the requested duration is charged and `credits.settled` shows it. Send an `Idempotency-Key` header (1-255 printable ASCII characters) to make retries safe: a repeat with the same key and body returns the original job, and the same key with a different body is a 422 `idempotency_key_reused`. Omitted fields take the model defaults from `GET /api/v1/video-models`.",
16240
+ tags: ["Videos"],
16241
+ auth: "apiKeyOrJwt",
16242
+ scopes: ["ai:generate"],
16243
+ request: CreateVideoGenerationBodySchema,
16244
+ requestExample: {
16245
+ model: "grok-imagine-video-1.5",
16246
+ prompt: "A slow dolly shot of a red lighthouse at dusk"
16247
+ },
16248
+ responses: {
16249
+ 202: {
16250
+ description: "Accepted; the job is queued (or replayed for a repeated Idempotency-Key).",
16251
+ schema: VideoGenerationSchema,
16252
+ example: exampleJob
16253
+ },
16254
+ 403: scopeForbidden,
16255
+ 404: {
16256
+ description: "`input_image_file_id` does not name an image you own (`errorCode: input_image_not_found`).",
16257
+ schema: VideoGenerationErrorResponseSchema
16258
+ },
16259
+ 422: {
16260
+ description: "The request cannot run: a body or capability violation (`errorCode` names it, e.g. `unsupported_duration`), an unknown or unavailable model (`model_unavailable`), a model your administrator disabled (`model_disabled`), too few credits (`insufficient_credits`), or an Idempotency-Key problem (`invalid_idempotency_key`, `idempotency_key_reused`).",
16261
+ schema: VideoGenerationErrorResponseSchema
16262
+ },
16263
+ 429: rateLimited
16264
+ },
16265
+ emitsRateLimitHeaders: true,
16266
+ codeSample: {
16267
+ authToken: "b4m_live_<key>",
16268
+ streaming: false,
16269
+ body: {
16270
+ model: "grok-imagine-video-1.5",
16271
+ prompt: "A slow dolly shot of a red lighthouse at dusk"
16272
+ }
16273
+ }
16274
+ });
16275
+ defineEndpoint({
16276
+ method: "get",
16277
+ path: "/api/v1/video-generations/{id}",
16278
+ operationId: "getVideoGeneration",
16279
+ summary: "Get a video generation",
16280
+ description: "Returns one job you requested. Each read signs a fresh `output.url` (valid 15 minutes, see `expires_at`).",
16281
+ tags: ["Videos"],
16282
+ auth: "apiKeyOrJwt",
16283
+ scopes: ["ai:generate"],
16284
+ pathParams: VideoGenerationIdParamSchema,
16285
+ responses: {
16286
+ 200: {
16287
+ description: "The job.",
16288
+ schema: VideoGenerationSchema,
16289
+ example: exampleJob
15186
16290
  },
15187
- 429: {
15188
- description: "Per-user rate limit exceeded.",
15189
- schema: ApiErrorSchema
15190
- }
16291
+ 403: scopeForbidden,
16292
+ 404: notFound,
16293
+ 429: rateLimited
15191
16294
  },
16295
+ emitsRateLimitHeaders: true,
15192
16296
  codeSample: {
15193
16297
  authToken: "b4m_live_<key>",
15194
16298
  streaming: false,
@@ -15196,105 +16300,59 @@ defineEndpoint({
15196
16300
  }
15197
16301
  });
15198
16302
  defineEndpoint({
15199
- method: "post",
15200
- path: "/api/v1/data-lakes/{id}/search",
15201
- operationId: "searchDataLake",
15202
- summary: "Search a data lake",
15203
- description: "Semantic (vector) search over one lake: the query is embedded with the model the corpus was indexed with and the top `top_k` passages at or above `min_score` are returned. Only members of this lake are searched - never the caller's other files. `tags` narrows to files carrying any of the listed tags. Files still being indexed or paused are withheld rather than searched stale: `partial_results` is then true and `retrieval_unavailable` counts them (poll `GET /api/v1/data-lakes/{id}/files/{file_id}` for a specific file). The query embedding is billed as credits. Limited to 10 searches per minute per caller, shared with the product UI.",
15204
- tags: ["Data Lakes"],
16303
+ method: "get",
16304
+ path: "/api/v1/video-generations",
16305
+ operationId: "listVideoGenerations",
16306
+ summary: "List your video generations",
16307
+ description: "Your jobs, newest first, optionally filtered by `state` and `source`. Cursor-paginated (see the pagination convention): pass `next_cursor` back as `cursor` until it is `null`.",
16308
+ tags: ["Videos"],
15205
16309
  auth: "apiKeyOrJwt",
15206
- scopes: DATA_LAKE_QUERY_API_KEY_SCOPES,
15207
- pathParams: DataLakeIdParamSchema,
15208
- request: DataLakeSearchRequestSchema,
15209
- requestExample: {
15210
- query: "What is our refund policy for annual plans?",
15211
- top_k: 5
15212
- },
15213
- emitsRateLimitHeaders: true,
16310
+ scopes: ["ai:generate"],
16311
+ queryParams: ListVideoGenerationsQuerySchema,
15214
16312
  responses: {
15215
16313
  200: {
15216
- description: "The ranked passages and what, if anything, was withheld.",
15217
- schema: DataLakeSearchResponseSchema
15218
- },
15219
- 403: {
15220
- description: `The API key lacks \`datalake:query\`, ${FEATURE_DISABLED_NOTE}.`,
15221
- schema: ScopeForbiddenErrorSchema
15222
- },
15223
- 404: {
15224
- description: `${NOT_VISIBLE_LAKE} A lake the caller can read but is not entitled to search is reported the same way, as is a lake that is not \`active\` yet (a draft lake is readable but not yet searchable).`,
15225
- schema: ApiErrorSchema
16314
+ description: "A page of jobs.",
16315
+ schema: ListVideoGenerationsResponseSchema
15226
16316
  },
16317
+ 403: scopeForbidden,
15227
16318
  422: {
15228
- description: "Request body failed validation, or the caller cannot afford the query embedding - the latter is tagged `errorCode: \"insufficient_credits\"` (the balance is short, or the org member credit cap is exhausted).",
15229
- schema: InsufficientCreditsErrorSchema
15230
- },
15231
- 429: {
15232
- description: "Search rate limit (10 per minute) or per-user API rate limit exceeded.",
16319
+ description: "A malformed `cursor`, `limit`, `state` or `source`.",
15233
16320
  schema: ApiErrorSchema
15234
16321
  },
15235
- 503: {
15236
- description: "No usable key is configured on this deployment for the embedding provider the query needs (`errorCode: \"provider_not_configured\"`).",
15237
- schema: ProviderNotConfiguredErrorSchema
15238
- }
16322
+ 429: rateLimited
15239
16323
  },
16324
+ emitsRateLimitHeaders: true,
15240
16325
  codeSample: {
15241
16326
  authToken: "b4m_live_<key>",
15242
16327
  streaming: false,
15243
- body: {
15244
- query: "What is our refund policy for annual plans?",
15245
- top_k: 5
15246
- }
16328
+ body: {}
15247
16329
  }
15248
16330
  });
15249
16331
  defineEndpoint({
15250
16332
  method: "post",
15251
- path: "/api/v1/video-generations",
15252
- operationId: "generateVideo",
15253
- summary: "Generate a video",
15254
- description: "Queues a Sora video generation and returns immediately with the quest that will carry the result - no video yet, and the call never blocks on the render. Poll `GET /api/v1/quests/{id}` until `status` is `\"done\"` (see the `generateVideo200PollResult` schema). Omit `sessionId` to create a new session. Credits are checked when the render runs, so insufficient credits arrive on the polled quest rather than as a 422. `POST /api/ai/generate-video` is a legacy alias of this endpoint. Authenticate with an API key (`b4m_live_`) or a JWT.\n\nOptional `callbackUrl`: an https URL that receives a signed POST when the quest settles (success, failure, or a stop; a failed render is `type: \"error\"` in the body, same as the poll). The body is the `GET /api/v1/quests/{id}` response. Headers: `X-Webhook-Event-ID` (the same on every attempt for one settlement; delivery is at-least-once, so dedupe on it, and a retried quest settles again under a new one), `X-Webhook-Delivery-ID` (unique per attempt), `X-Webhook-Timestamp` (unix seconds), `X-Webhook-Signature-256` (`sha256=` + hex HMAC-SHA256 of `<timestamp>.<raw body>` keyed with the API key's callback signing secret), and `X-Event-Type: quest.settled`. Delivery makes up to 5 attempts, retrying on a 5xx, 408, 429, timeout or network error - never on any other 4xx, and redirects are not followed; your endpoint must answer 2xx within 10 seconds. Requires API-key authentication and a signing secret on that key (returned once at key creation, or via `POST /api/user-api-keys/{id}/callback-secret`). A `callbackUrl` is rejected with a 400 when the request is not API-key authenticated, the key has no signing secret, the URL resolves to a private address, or the deployment does not deliver callbacks; a URL that is not https fails request validation with a 422. Polling still works whether or not `callbackUrl` is set.",
16333
+ path: "/api/v1/video-generations/{id}/cancel",
16334
+ operationId: "cancelVideoGeneration",
16335
+ summary: "Cancel a video generation",
16336
+ description: "Requests cancellation and returns the job. A `pending` or `running` job moves to `cancelled` shortly after and its reserved credits are returned. A job already `storing` or finished is returned unchanged (the provider has already produced, and charged for, the video).",
15255
16337
  tags: ["Videos"],
15256
16338
  auth: "apiKeyOrJwt",
15257
16339
  scopes: ["ai:generate"],
15258
- request: GenerateVideoRequestBodySchema,
15259
- requestExample: {
15260
- prompt: "a drone shot over a foggy pine forest at sunrise",
15261
- model: "sora-2",
15262
- seconds: 4
15263
- },
16340
+ pathParams: VideoGenerationIdParamSchema,
15264
16341
  responses: {
15265
16342
  200: {
15266
- description: "Generation queued - NOT a finished render. `quest` has no videos yet; its outcome arrives on `GET /api/v1/quests/{id}`.",
15267
- schema: GenerateVideoResponseSchema,
15268
- pollResult: {
15269
- schema: VideoQuestSchema,
15270
- description: "The quest polled at `GET /api/v1/quests/{id}` after this ACK. The render is finished once `status` is `\"done\"`: on success `videos` holds the rendered videos; a render that FAILED is `type: \"error\"` with the reason in `reply`, never a 4xx. That covers credit exhaustion (tagged `errorCode: \"insufficient_credits\"`, the same vocabulary as the synchronous 422s on the audio endpoints) and a provider error. Check `type` before reading `videos`; `errorCode` is absent on unclassified failures, so its absence does not mean success.",
15271
- example: {
15272
- id: EXAMPLE_RESOURCE_ID,
15273
- sessionId: EXAMPLE_SESSION_ID,
15274
- status: "done",
15275
- type: "message",
15276
- videos: ["3f6c1a52-9d1e-4b7a-8c2f-5e0d4a9b1c7e.mp4"]
15277
- }
15278
- }
15279
- },
15280
- 400: {
15281
- description: `The ${GENERATION_CALLBACK_REJECTED_DESCRIPTION}`,
15282
- schema: ApiErrorSchema
16343
+ description: "The job after the cancel request.",
16344
+ schema: VideoGenerationSchema,
16345
+ example: exampleJob
15283
16346
  },
15284
- 404: {
15285
- description: "The session, or the quest being retried, does not exist or is not accessible to the caller. A nonexistent billing `organizationId` is `404` only for an admin caller; for anyone else, an invalid or inaccessible `organizationId` is `403` instead.",
15286
- schema: ApiErrorSchema
15287
- }
16347
+ 403: scopeForbidden,
16348
+ 404: notFound,
16349
+ 429: rateLimited
15288
16350
  },
15289
16351
  emitsRateLimitHeaders: true,
15290
16352
  codeSample: {
15291
16353
  authToken: "b4m_live_<key>",
15292
16354
  streaming: false,
15293
- body: {
15294
- prompt: "a drone shot over a foggy pine forest at sunrise",
15295
- model: "sora-2",
15296
- seconds: 4
15297
- }
16355
+ body: {}
15298
16356
  }
15299
16357
  });
15300
16358
  /**
@@ -15433,12 +16491,136 @@ defineEndpoint({
15433
16491
  }
15434
16492
  },
15435
16493
  emitsRateLimitHeaders: true,
16494
+ codeSample: {
16495
+ authToken: "b4m_live_<key>",
16496
+ streaming: false
16497
+ }
16498
+ });
16499
+ /**
16500
+ * The integrator-facing subset of the project API. Each route is a `/api/v1` twin of an SPA route
16501
+ * under `/api/projects/*` and reuses its service logic; the SPA routes are unchanged. Update, delete
16502
+ * and every sub-resource (files, sessions, members, invites, system prompts) are deliberately not
16503
+ * published yet.
16504
+ */
16505
+ const READ_SCOPES = ["projects:read", "projects:write"];
16506
+ const READ_FORBIDDEN = "The API key holds neither `projects:read` nor `projects:write`.";
16507
+ const MEMBERSHIP_NOTE = "A project's `file_ids` and `session_ids` are shared with it: anyone the project is shared with can read every file and session it groups.";
16508
+ defineEndpoint({
16509
+ method: "get",
16510
+ path: "/api/v1/projects",
16511
+ operationId: "listProjects",
16512
+ summary: "List projects",
16513
+ description: `Lists the projects the caller can read: projects they own and projects shared with them. ${MEMBERSHIP_NOTE} Cursor-paginated (see the pagination convention): pass \`next_cursor\` back as \`cursor\` until it is \`null\`.`,
16514
+ tags: ["Projects"],
16515
+ auth: "apiKeyOrJwt",
16516
+ scopes: READ_SCOPES,
16517
+ queryParams: PaginationQuerySchema,
16518
+ emitsRateLimitHeaders: true,
16519
+ responses: {
16520
+ 200: {
16521
+ description: "One page of projects, ordered by `id`.",
16522
+ schema: ListProjectsResponseSchema
16523
+ },
16524
+ 403: {
16525
+ description: READ_FORBIDDEN,
16526
+ schema: ScopeForbiddenErrorSchema
16527
+ },
16528
+ 422: {
16529
+ description: "`limit` is out of range, or `cursor` is malformed or was issued by a different endpoint. A cursor is opaque: pass back exactly the `next_cursor` you were given.",
16530
+ schema: ApiErrorSchema
16531
+ },
16532
+ 429: {
16533
+ description: "Per-user rate limit exceeded.",
16534
+ schema: ApiErrorSchema
16535
+ }
16536
+ },
16537
+ codeSample: {
16538
+ authToken: "b4m_live_<key>",
16539
+ streaming: false,
16540
+ body: {}
16541
+ }
16542
+ });
16543
+ defineEndpoint({
16544
+ method: "get",
16545
+ path: "/api/v1/projects/{id}",
16546
+ operationId: "getProject",
16547
+ summary: "Get a project",
16548
+ description: `Returns one project the caller can read. ${MEMBERSHIP_NOTE}`,
16549
+ tags: ["Projects"],
16550
+ auth: "apiKeyOrJwt",
16551
+ scopes: READ_SCOPES,
16552
+ pathParams: ProjectIdParamSchema,
16553
+ emitsRateLimitHeaders: true,
16554
+ responses: {
16555
+ 200: {
16556
+ description: "The project.",
16557
+ schema: ProjectResourceSchema
16558
+ },
16559
+ 403: {
16560
+ description: READ_FORBIDDEN,
16561
+ schema: ScopeForbiddenErrorSchema
16562
+ },
16563
+ 404: {
16564
+ description: "No project with that id is visible to the caller. A project that exists but is not yours to read, a deleted project and a malformed id are all reported as 404, so project ids cannot be probed through this endpoint.",
16565
+ schema: ApiErrorSchema
16566
+ },
16567
+ 429: {
16568
+ description: "Per-user rate limit exceeded.",
16569
+ schema: ApiErrorSchema
16570
+ }
16571
+ },
15436
16572
  codeSample: {
15437
16573
  authToken: "b4m_live_<key>",
15438
16574
  streaming: false,
15439
16575
  body: {}
15440
16576
  }
15441
16577
  });
16578
+ const CREATE_EXAMPLE = {
16579
+ name: "Market research",
16580
+ description: "Sources and notes for the Q3 market analysis.",
16581
+ session_ids: [EXAMPLE_SESSION_ID],
16582
+ file_ids: [EXAMPLE_FILE_ID]
16583
+ };
16584
+ defineEndpoint({
16585
+ method: "post",
16586
+ path: "/api/v1/projects",
16587
+ operationId: "createProject",
16588
+ summary: "Create a project",
16589
+ description: `Creates a project owned by the caller, optionally grouping existing sessions and files in it. Every listed session and file must be readable by the caller. ${MEMBERSHIP_NOTE} Sharing the project later shares them too. Unknown body fields are rejected.`,
16590
+ tags: ["Projects"],
16591
+ auth: "apiKeyOrJwt",
16592
+ scopes: ["projects:write"],
16593
+ request: CreateProjectRequestSchema,
16594
+ requestExample: CREATE_EXAMPLE,
16595
+ emitsRateLimitHeaders: true,
16596
+ responses: {
16597
+ 201: {
16598
+ description: "The created project.",
16599
+ schema: ProjectResourceSchema
16600
+ },
16601
+ 400: {
16602
+ description: "A listed session or file does not exist or is not readable by the caller, or the body is not valid JSON. Nothing is created.",
16603
+ schema: ApiErrorSchema
16604
+ },
16605
+ 403: {
16606
+ description: "The API key lacks `projects:write`.",
16607
+ schema: ScopeForbiddenErrorSchema
16608
+ },
16609
+ 422: {
16610
+ description: "Request body failed validation, or you already have a live project with this `name`.",
16611
+ schema: ApiErrorSchema
16612
+ },
16613
+ 429: {
16614
+ description: "Per-user rate limit exceeded.",
16615
+ schema: ApiErrorSchema
16616
+ }
16617
+ },
16618
+ codeSample: {
16619
+ authToken: "b4m_live_<key>",
16620
+ streaming: false,
16621
+ body: CREATE_EXAMPLE
16622
+ }
16623
+ });
15442
16624
  /**
15443
16625
  * The fence language the model writes to place an inline map of search-result places in a reply.
15444
16626
  *
@@ -16337,6 +17519,7 @@ z$1.object({
16337
17519
  createdAt: z$1.union([z$1.string(), z$1.date()]).optional(),
16338
17520
  updatedAt: z$1.union([z$1.string(), z$1.date()]).optional()
16339
17521
  });
17522
+ new RegExp(`[аеорсхуіјѕԁһӏαονικρτυ]`, "g");
16340
17523
  const OPENER_STICKY = /<artifact/iy;
16341
17524
  const WHITESPACE$1 = /\s/;
16342
17525
  function isSpace(ch) {
@@ -16594,6 +17777,34 @@ function stripDeliveredArtifactBlocks(text, deliveredMarkup) {
16594
17777
  }
16595
17778
  return out + text.slice(cursor);
16596
17779
  }
17780
+ /**
17781
+ * The local token estimate counts every non-OpenAI model with tiktoken's cl100k_base, because tiktoken
17782
+ * has no other vendor's encoding (see TiktokenTokenizer in @bike4mind/utils). Claude's tokenizers spend
17783
+ * more tokens on the same text, so the raw count under-states Claude input. These multipliers scale it
17784
+ * back up to the model's own tokenizer.
17785
+ *
17786
+ * Measured 2026-10-01 against Bedrock input-token usage on 8 KB prose, markdown, TypeScript and JSON
17787
+ * samples. Each multiplier is the smallest ratio across those samples, rounded down, so a scaled estimate
17788
+ * stays a lower bound: it cannot over-reserve credits or reject a turn that actually fits.
17789
+ */
17790
+ const CLAUDE_4_7_PLUS_MULTIPLIER = 1.5;
17791
+ const CLAUDE_4_5_MULTIPLIER = 1.09;
17792
+ const CLAUDE_FAMILY_VERSION = /(?:^|\.)claude-(?:opus|sonnet|haiku)-(\d+)(?:-(\d{1,2}))?(?=$|[-:])/;
17793
+ /**
17794
+ * Factor to scale a cl100k_base token count by for `modelId`. 1 for anything unmeasured: non-Claude
17795
+ * models, Claude before 4.5 (retired on Bedrock, could not be measured), and Fable (Bedrock rejects it
17796
+ * under the account's data-retention mode). Claude releases after 5.5 inherit the newest factor on the
17797
+ * assumption they keep its tokenizer; [BILLING_DRIFT] flags it if one does not.
17798
+ */
17799
+ function tokenEstimateMultiplier(modelId) {
17800
+ const match = modelId?.match(CLAUDE_FAMILY_VERSION);
17801
+ if (!match) return 1;
17802
+ const major = Number(match[1]);
17803
+ const minor = Number(match[2] ?? 0);
17804
+ if (major > 4 || major === 4 && minor >= 7) return CLAUDE_4_7_PLUS_MULTIPLIER;
17805
+ if (major === 4 && minor >= 5) return CLAUDE_4_5_MULTIPLIER;
17806
+ return 1;
17807
+ }
16597
17808
  [...IMAGE_SIZE_CONSTRAINTS.DALL_E_2.sizes, ...IMAGE_SIZE_CONSTRAINTS.DALL_E_3.sizes];
16598
17809
  /** Generation was cut off against the output-token ceiling. */
16599
17810
  const TRUNCATED_FINISH_REASON = "max_tokens";
@@ -16766,6 +17977,17 @@ z$1.array(triggerWordSchema).max(20, "Up to 20 trigger words allowed.").transfor
16766
17977
  }
16767
17978
  return out;
16768
17979
  });
17980
+ /**
17981
+ * Extension sets for files a quest tool drops onto `quest.images` (image_generation,
17982
+ * edit_image, music_generation, audio_generation, excel_generation, ...). Single source of
17983
+ * truth shared by the web renderer (PromptReplies.classifyGeneratedFiles) and the API file
17984
+ * classifier (server/utils/generatedFiles), so the two never drift on how a generated file
17985
+ * renders. Browser-safe (regex literals only) - importable from either build.
17986
+ */
17987
+ const GENERATED_IMAGE_EXTENSION_RE = /\.(png|jpe?g|webp|gif|svg|bmp|avif)$/i;
17988
+ const GENERATED_KEY_UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
17989
+ new RegExp(`^${GENERATED_KEY_UUID}\\.[a-z]+$`, "i");
17990
+ new RegExp(`^${GENERATED_KEY_UUID}\\.(?:png|jpe?g|webp|gif)$`);
16769
17991
  IMAGE_SIZE_CONSTRAINTS.BFL.minWidth, IMAGE_SIZE_CONSTRAINTS.BFL.maxWidth, IMAGE_SIZE_CONSTRAINTS.BFL.stepSize;
16770
17992
  const WHITESPACE = /\s/;
16771
17993
  const EQUALS_OPS = [
@@ -16864,7 +18086,7 @@ Array.from(new Set([
16864
18086
  {
16865
18087
  id: "opti.scheduling.problem",
16866
18088
  section: "opti",
16867
- label: "Problem Editor",
18089
+ label: "Scheduling Problem",
16868
18090
  description: "Define and preview scheduling problems — job-shop matrix encoding",
16869
18091
  navigationType: "action",
16870
18092
  target: "scheduling.problem",
@@ -16881,7 +18103,7 @@ Array.from(new Set([
16881
18103
  {
16882
18104
  id: "opti.scheduling.solvers",
16883
18105
  section: "opti",
16884
- label: "Solver Selection",
18106
+ label: "Scheduling Solvers",
16885
18107
  description: "Configure which solvers to race across a range of optimization strategies",
16886
18108
  navigationType: "action",
16887
18109
  target: "scheduling.solvers",
@@ -16957,6 +18179,37 @@ Array.from(new Set([
16957
18179
  "path"
16958
18180
  ]
16959
18181
  },
18182
+ {
18183
+ id: "opti.routing.problem",
18184
+ section: "opti",
18185
+ label: "Routing Problem",
18186
+ description: "Define and preview routing problems: stops, vehicles, distances",
18187
+ navigationType: "action",
18188
+ target: "routing.problem",
18189
+ keywords: [
18190
+ "routing problem",
18191
+ "stops",
18192
+ "depot",
18193
+ "vehicles",
18194
+ "distances",
18195
+ "route editor"
18196
+ ]
18197
+ },
18198
+ {
18199
+ id: "opti.routing.solvers",
18200
+ section: "opti",
18201
+ label: "Routing Solvers",
18202
+ description: "Choose which solvers to race on the routing problem",
18203
+ navigationType: "action",
18204
+ target: "routing.solvers",
18205
+ keywords: [
18206
+ "routing solvers",
18207
+ "TSP solvers",
18208
+ "route solvers",
18209
+ "configure solvers",
18210
+ "solver race"
18211
+ ]
18212
+ },
16960
18213
  {
16961
18214
  id: "opti.packing",
16962
18215
  section: "opti",
@@ -16973,6 +18226,37 @@ Array.from(new Set([
16973
18226
  "space"
16974
18227
  ]
16975
18228
  },
18229
+ {
18230
+ id: "opti.packing.problem",
18231
+ section: "opti",
18232
+ label: "Packing Problem",
18233
+ description: "Define and preview packing problems: items, bins, capacities",
18234
+ navigationType: "action",
18235
+ target: "packing.problem",
18236
+ keywords: [
18237
+ "packing problem",
18238
+ "items",
18239
+ "bins",
18240
+ "capacity",
18241
+ "knapsack editor",
18242
+ "container"
18243
+ ]
18244
+ },
18245
+ {
18246
+ id: "opti.packing.solvers",
18247
+ section: "opti",
18248
+ label: "Packing Solvers",
18249
+ description: "Choose which solvers to race on the packing problem",
18250
+ navigationType: "action",
18251
+ target: "packing.solvers",
18252
+ keywords: [
18253
+ "packing solvers",
18254
+ "bin packing solvers",
18255
+ "knapsack solvers",
18256
+ "configure solvers",
18257
+ "solver race"
18258
+ ]
18259
+ },
16976
18260
  {
16977
18261
  id: "opti.network",
16978
18262
  section: "opti",
@@ -16989,6 +18273,37 @@ Array.from(new Set([
16989
18273
  "graph"
16990
18274
  ]
16991
18275
  },
18276
+ {
18277
+ id: "opti.network.problem",
18278
+ section: "opti",
18279
+ label: "Network Problem",
18280
+ description: "Define and preview network design problems: nodes, edges, facilities",
18281
+ navigationType: "action",
18282
+ target: "network.problem",
18283
+ keywords: [
18284
+ "network problem",
18285
+ "nodes",
18286
+ "edges",
18287
+ "facilities",
18288
+ "supply chain editor",
18289
+ "graph"
18290
+ ]
18291
+ },
18292
+ {
18293
+ id: "opti.network.solvers",
18294
+ section: "opti",
18295
+ label: "Network Solvers",
18296
+ description: "Choose which solvers to race on the network design problem",
18297
+ navigationType: "action",
18298
+ target: "network.solvers",
18299
+ keywords: [
18300
+ "network solvers",
18301
+ "flow solvers",
18302
+ "facility solvers",
18303
+ "configure solvers",
18304
+ "solver race"
18305
+ ]
18306
+ },
16992
18307
  {
16993
18308
  id: "opti.selection",
16994
18309
  section: "opti",
@@ -17005,6 +18320,37 @@ Array.from(new Set([
17005
18320
  "choose"
17006
18321
  ]
17007
18322
  },
18323
+ {
18324
+ id: "opti.selection.problem",
18325
+ section: "opti",
18326
+ label: "Selection Problem",
18327
+ description: "Define and preview selection problems: candidates, values, constraints",
18328
+ navigationType: "action",
18329
+ target: "selection.problem",
18330
+ keywords: [
18331
+ "selection problem",
18332
+ "candidates",
18333
+ "portfolio editor",
18334
+ "subset",
18335
+ "values",
18336
+ "constraints"
18337
+ ]
18338
+ },
18339
+ {
18340
+ id: "opti.selection.solvers",
18341
+ section: "opti",
18342
+ label: "Selection Solvers",
18343
+ description: "Choose which solvers to race on the selection problem",
18344
+ navigationType: "action",
18345
+ target: "selection.solvers",
18346
+ keywords: [
18347
+ "selection solvers",
18348
+ "portfolio solvers",
18349
+ "subset solvers",
18350
+ "configure solvers",
18351
+ "solver race"
18352
+ ]
18353
+ },
17008
18354
  {
17009
18355
  id: "opti.economic",
17010
18356
  section: "opti",
@@ -17021,6 +18367,37 @@ Array.from(new Set([
17021
18367
  "cost"
17022
18368
  ]
17023
18369
  },
18370
+ {
18371
+ id: "opti.economic.problem",
18372
+ section: "opti",
18373
+ label: "Economic Problem",
18374
+ description: "Define and preview economic problems: resources, prices, budgets",
18375
+ navigationType: "action",
18376
+ target: "economic.problem",
18377
+ keywords: [
18378
+ "economic problem",
18379
+ "resources",
18380
+ "prices",
18381
+ "budget editor",
18382
+ "allocation",
18383
+ "costs"
18384
+ ]
18385
+ },
18386
+ {
18387
+ id: "opti.economic.solvers",
18388
+ section: "opti",
18389
+ label: "Economic Solvers",
18390
+ description: "Choose which solvers to race on the economic problem",
18391
+ navigationType: "action",
18392
+ target: "economic.solvers",
18393
+ keywords: [
18394
+ "economic solvers",
18395
+ "allocation solvers",
18396
+ "pricing solvers",
18397
+ "configure solvers",
18398
+ "solver race"
18399
+ ]
18400
+ },
17024
18401
  {
17025
18402
  id: "opti.assignment",
17026
18403
  section: "opti",
@@ -17037,6 +18414,37 @@ Array.from(new Set([
17037
18414
  "assign"
17038
18415
  ]
17039
18416
  },
18417
+ {
18418
+ id: "opti.assignment.problem",
18419
+ section: "opti",
18420
+ label: "Assignment Problem",
18421
+ description: "Define and preview assignment problems: agents, tasks, cost matrix",
18422
+ navigationType: "action",
18423
+ target: "assignment.problem",
18424
+ keywords: [
18425
+ "assignment problem",
18426
+ "agents",
18427
+ "tasks",
18428
+ "cost matrix",
18429
+ "matching editor",
18430
+ "workers"
18431
+ ]
18432
+ },
18433
+ {
18434
+ id: "opti.assignment.solvers",
18435
+ section: "opti",
18436
+ label: "Assignment Solvers",
18437
+ description: "Choose which solvers to race on the assignment problem",
18438
+ navigationType: "action",
18439
+ target: "assignment.solvers",
18440
+ keywords: [
18441
+ "assignment solvers",
18442
+ "matching solvers",
18443
+ "workforce solvers",
18444
+ "configure solvers",
18445
+ "solver race"
18446
+ ]
18447
+ },
17040
18448
  {
17041
18449
  id: "opti.partitioning",
17042
18450
  section: "opti",
@@ -17053,6 +18461,37 @@ Array.from(new Set([
17053
18461
  "split"
17054
18462
  ]
17055
18463
  },
18464
+ {
18465
+ id: "opti.partitioning.problem",
18466
+ section: "opti",
18467
+ label: "Partitioning Problem",
18468
+ description: "Define and preview partitioning problems: graph, parts, balance",
18469
+ navigationType: "action",
18470
+ target: "partitioning.problem",
18471
+ keywords: [
18472
+ "partitioning problem",
18473
+ "graph",
18474
+ "parts",
18475
+ "balance",
18476
+ "cluster editor",
18477
+ "partition editor"
18478
+ ]
18479
+ },
18480
+ {
18481
+ id: "opti.partitioning.solvers",
18482
+ section: "opti",
18483
+ label: "Partitioning Solvers",
18484
+ description: "Choose which solvers to race on the partitioning problem",
18485
+ navigationType: "action",
18486
+ target: "partitioning.solvers",
18487
+ keywords: [
18488
+ "partitioning solvers",
18489
+ "clustering solvers",
18490
+ "balancing solvers",
18491
+ "configure solvers",
18492
+ "solver race"
18493
+ ]
18494
+ },
17056
18495
  {
17057
18496
  id: "admin.users",
17058
18497
  section: "admin",
@@ -18736,6 +20175,7 @@ const CliConfigSchema = z$1.object({
18736
20175
  * editing the config file directly.
18737
20176
  */
18738
20177
  promptVariant: z$1.enum(["current", "minimal"]).optional().prefault("current"),
20178
+ showThoughts: z$1.boolean().optional(),
18739
20179
  subagentHistoryTtlMs: z$1.number().optional()
18740
20180
  }),
18741
20181
  tools: z$1.object({
@@ -19847,4 +21287,4 @@ var ConfigStore = class {
19847
21287
  }
19848
21288
  };
19849
21289
  //#endregion
19850
- export { hasDeliverablePinnedArtifact as $, DEGENERATE_FINISH_REASON as A, REVIEW_GATE_STATUS_VALUES as B, ARTIFACT_REMOVED_PLACEHOLDER as C, CREDIT_DEDUCT_TRANSACTION_TYPES as D, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as E, NotFoundError as F, WORK_ITEM_STATUSES as G, SUBQUEST_STATUS_VALUES as H, OllamaEmbeddingModel as I, filterToolArtifactMarkup as J, createThinkMarkerEscaper as K, OpenAIEmbeddingModel as L, MODEL_INFO_FIELD_GROUP_OF as M, McpServerName as N, ChatModels as O, ModelBackend as P, googleMapsSearchUrl as Q, PROMPT_TEXT_MAX as R, ARTIFACT_DELIVERED_PLACEHOLDER as S, BedrockEmbeddingModel as T, SupportedFabFileMimeTypes as U, SEARCH_RESULT_CARDS_LANGUAGE as V, VoyageAIEmbeddingModel as W, getMcpProviderMetadata as X, generatedAudioResponseSchema as Y, getQuestErrorCode as Z, requireApiUrl as _, actorKindMarker as _t, htmlErrorTitle as a, secureParameters as at, AGENT_QUEST_MANIFEST as b, APP_NAME as bt, loadContextFiles as c, stripDeliveredArtifactBlocks as ct, isReadOnlyTool as d, ttsBase64ResponseSchema as dt, isEarlyStop as et, ApiEndpointUnconfiguredError as f, ttsRequestSchema as ft, parseApiUrl as g, actorColorIndex as gt, getEnvironmentName as h, ACTOR_COLOR_SLOTS as ht, isValidSessionId as i, obfuscateApiKey as it, LOCATION_MAP_LANGUAGE as j, DEFAULT_TTS_PROVIDER as k, canTrustTool as l, stripToolArtifactMarkup as lt, getCreditsUrl as m, withRetry as mt, logger as n, isRetryableError as nt, htmlFirstH1 as o, signImageUrl as ot, LOCAL_DEV_URL as p, ttsResponseTooLargeSchema as pt, escapeThinkMarkers as q, SESSION_ID_PATTERN as r, isUserInitiatedAbort as rt, extractCompactInstructions as s, splitEquals as st, ConfigStore as t, isPlaceholderImageSigningSecret as tt, getToolCategory as u, supportedVoiceGenerationVendor as ut, resolveApiEndpoint as v, actorKindSchema as vt, ApiKeyType as w, AGENT_QUEST_MCP_URI as x, WEBSITE_URL as xt, AGENT_QUEST_ID as y, selfClaimedActorKindSchema as yt, PermissionDeniedError as z };
21290
+ export { escapeThinkMarkers as $, ChatModels as A, delegationPayloadSchema as At, NotFoundError as B, ARTIFACT_REMOVED_PLACEHOLDER as C, ttsResponseTooLargeSchema as Ct, BriefcasePromptIdSchema as D, actorColorIndex as Dt, BedrockEmbeddingModel as E, DELEGATION_PAYLOAD_SCHEMA_NAME as Et, ImagePromptResolutionSchema as F, REVIEW_GATE_STATUS_VALUES as G, OpenAIEmbeddingModel as H, LOCATION_MAP_LANGUAGE as I, SupportedFabFileMimeTypes as J, SEARCH_RESULT_CARDS_LANGUAGE as K, MODEL_INFO_FIELD_GROUP_OF as L, DEGENERATE_FINISH_REASON as M, APP_NAME as Mt, GENERATED_IMAGE_EXTENSION_RE as N, WEBSITE_URL as Nt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as O, actorKindMarker as Ot, ImageModels as P, createThinkMarkerEscaper as Q, McpServerName as R, ARTIFACT_DELIVERED_PLACEHOLDER as S, ttsRequestSchema as St, BRIEFCASE_CATALOG_QUERIES as T, ACTOR_COLOR_SLOTS as Tt, PROMPT_TEXT_MAX as U, OllamaEmbeddingModel as V, PermissionDeniedError as W, WORK_ITEM_STATUSES as X, VoyageAIEmbeddingModel as Y, buildPromptContext as Z, requireApiUrl as _, stripDeliveredArtifactBlocks as _t, htmlErrorTitle as a, hasDeliverablePinnedArtifact as at, AGENT_QUEST_MANIFEST as b, tokenEstimateMultiplier as bt, loadContextFiles as c, isRetryableError as ct, isReadOnlyTool as d, obfuscateApiKey as dt, filterToolArtifactMarkup as et, ApiEndpointUnconfiguredError as f, questReplyText as ft, parseApiUrl as g, splitEquals as gt, getEnvironmentName as h, signImageUrl as ht, isValidSessionId as i, googleMapsSearchUrl as it, DEFAULT_TTS_PROVIDER as j, selfClaimedActorKindSchema as jt, CREDIT_DEDUCT_TRANSACTION_TYPES as k, actorKindSchema as kt, canTrustTool as l, isUserInitiatedAbort as lt, getCreditsUrl as m, secureParameters as mt, logger as n, getMcpProviderMetadata as nt, htmlFirstH1 as o, isEarlyStop as ot, LOCAL_DEV_URL as p, replacePromptVariables as pt, SUBQUEST_STATUS_VALUES as q, SESSION_ID_PATTERN as r, getQuestErrorCode as rt, extractCompactInstructions as s, isPlaceholderImageSigningSecret as st, ConfigStore as t, generatedAudioResponseSchema as tt, getToolCategory as u, joinReplySlots as ut, resolveApiEndpoint as v, stripToolArtifactMarkup as vt, ApiKeyType as w, withRetry as wt, AGENT_QUEST_MCP_URI as x, ttsBase64ResponseSchema as xt, AGENT_QUEST_ID as y, supportedVoiceGenerationVendor as yt, ModelBackend as z };