@bike4mind/cli 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ import path from "path";
7
7
  import { v4 } from "uuid";
8
8
  import * as z$2 from "zod";
9
9
  import z, { ZodError, z as z$1 } from "zod";
10
- import { hearthEventKindSchema, hearthEventRefsSchema, hearthMachineBodySchema } from "@bike4mind/hearth";
10
+ import { actorKindSchema, hearthEventKindSchema, hearthEventRefsSchema, hearthMachineBodySchema } from "@bike4mind/hearth";
11
11
  import dayjs from "dayjs";
12
12
  import timezone from "dayjs/plugin/timezone.js";
13
13
  import utc from "dayjs/plugin/utc.js";
@@ -699,6 +699,12 @@ let KnowledgeType = /* @__PURE__ */ function(KnowledgeType) {
699
699
  * so the client can branch to a targeted error UI (see `IChatHistoryItem.errorCode`).
700
700
  * Single source of truth: the streamed-action Zod enum in `schemas/actions.ts`
701
701
  * derives its values from this tuple, so the two can never drift.
702
+ *
703
+ * SSE-frame scoped, and a NARROWING of the platform-wide `API_ERROR_CODES`: a
704
+ * quest fails for billing reasons, never for the provider-configuration reasons
705
+ * the HTTP surface reports. The `satisfies` is what keeps it a narrowing rather
706
+ * than a second vocabulary - a code added here that is not in `API_ERROR_CODES`
707
+ * fails the build.
702
708
  */
703
709
  const QUEST_ERROR_CODES = ["insufficient_credits", "spend_cap_exceeded"];
704
710
  z$1.union([
@@ -896,6 +902,12 @@ let ChatModels = /* @__PURE__ */ function(ChatModels) {
896
902
  const CHAT_MODELS = Object.values(ChatModels);
897
903
  const supportedChatModels = z$1.enum(ChatModels);
898
904
  /**
905
+ * Every `ChatModels` Gemini entry is named `gemini...` (see the GEMINI block above) - a prefix
906
+ * check tracks that naming convention automatically as new Gemini models are added, unlike an
907
+ * explicit id list that would need updating in lockstep and could silently miss one.
908
+ */
909
+ const isGeminiModelId = (model) => model.startsWith("gemini");
910
+ /**
899
911
  * Models that support the reasoning_effort parameter.
900
912
  * o1-preview and o1-mini do NOT support reasoning_effort.
901
913
  */
@@ -1369,6 +1381,12 @@ z$1.object({
1369
1381
  ownerId: z$1.string(),
1370
1382
  ownerType: z$1.enum(CreditHolderType),
1371
1383
  sessionId: z$1.string().optional(),
1384
+ /**
1385
+ * Data lake this call is 1:1 attributable to (ingestion embeds only - a query
1386
+ * embedding can span multiple lakes and is never attributed here). Unset for
1387
+ * every other feature/call.
1388
+ */
1389
+ dataLakeId: z$1.string().optional(),
1372
1390
  feature: z$1.enum([
1373
1391
  "chat",
1374
1392
  "image_generation",
@@ -2108,6 +2126,37 @@ z$1.object({
2108
2126
  createdAt: true,
2109
2127
  updatedAt: true
2110
2128
  });
2129
+ z$1.object({
2130
+ id: z$1.string().optional(),
2131
+ /** UTC month, YYYY-MM. */
2132
+ month: z$1.string().regex(/^\d{4}-\d{2}$/, "month must be YYYY-MM"),
2133
+ /** Matches UsageEvent.provider values (e.g. "anthropic", "openai"). */
2134
+ provider: z$1.string().min(1),
2135
+ /** Provider-reported spend in USD for the month. */
2136
+ providerUsd: z$1.number().finite().nonnegative(),
2137
+ /** Our internal COGS estimate from UsageEvent aggregation. */
2138
+ internalUsd: z$1.number().finite().nonnegative(),
2139
+ /** providerUsd - internalUsd; positive = we underestimate. */
2140
+ deltaUsd: z$1.number().finite(),
2141
+ /** Absolute delta as a percentage of max(providerUsd, internalUsd) (0-100). */
2142
+ deltaPct: z$1.number().finite().nonnegative(),
2143
+ /** How the provider figure was obtained. */
2144
+ source: z$1.enum([
2145
+ "anthropic_admin_api",
2146
+ "openai_usage_api",
2147
+ "manual"
2148
+ ]),
2149
+ /** Optional detail: per-key or per-model breakdown from the provider. */
2150
+ providerBreakdown: z$1.record(z$1.string(), z$1.number()).optional(),
2151
+ /** Human-readable note (error messages, partial data warnings, etc.). */
2152
+ note: z$1.string().optional(),
2153
+ createdAt: z$1.date(),
2154
+ updatedAt: z$1.date()
2155
+ }).omit({
2156
+ id: true,
2157
+ createdAt: true,
2158
+ updatedAt: true
2159
+ });
2111
2160
  z$1.object({
2112
2161
  id: z$1.string().optional(),
2113
2162
  ownerId: z$1.string(),
@@ -2184,6 +2233,76 @@ function isPlaceholderApiKey(value) {
2184
2233
  if (!normalized) return true;
2185
2234
  return PLACEHOLDER_API_KEY_REGEX.test(normalized);
2186
2235
  }
2236
+ z$1.object({
2237
+ /**
2238
+ * The granting lake's Mongo `_id`. ALWAYS a persisted DB lake: a hardcoded/fallback lake has no
2239
+ * backing document to hang a grant on (its id is a human slug, never an ObjectId), so the write
2240
+ * boundary refuses one via `assertLakeGrantable`. That is the explicit fallback carve-out issue
2241
+ * #1667 calls for - enforced at the service layer, where the static registry is known, not here.
2242
+ */
2243
+ dataLakeId: z$1.string(),
2244
+ principalType: z$1.enum(["user", "organization"]),
2245
+ /** The granted principal's id - a userId or an organizationId, per `principalType`. */
2246
+ principalId: z$1.string(),
2247
+ role: z$1.enum([
2248
+ "owner",
2249
+ "curator",
2250
+ "reader"
2251
+ ]),
2252
+ /** The actor (userId) who created the grant. */
2253
+ grantedByUserId: z$1.string(),
2254
+ /**
2255
+ * Optional expiry for a time-boxed grant (trials, temporary internal collaborators,
2256
+ * evaluations). A grant is expired once `expiresAt` is set and in the past. Expired rows are
2257
+ * filtered at READ time by grant resolution (#1673) and are deliberately NOT swept from the
2258
+ * collection, so an owner-facing membership view (#1672) and the audit trail (#1663) can still
2259
+ * render a lapsed grant. Absent/null = never expires.
2260
+ *
2261
+ * Only meaningful for a principal INSIDE the lake's organization: membership never crosses
2262
+ * organizations (epic decision 12), so this is not a cross-org mechanism.
2263
+ */
2264
+ expiresAt: z$1.date().nullish()
2265
+ });
2266
+ /**
2267
+ * Every `IDataLake` field, classified as audited or not. A TOTAL map keyed by `keyof IDataLake`,
2268
+ * exactly like `LAKE_FIELD_VISIBILITY` in redactLakeForActor.ts and for the same reason: a list of
2269
+ * strings cannot notice an ABSENCE, so a new config field would simply never be audited and nobody
2270
+ * would find out. Keyed this way, a field added to the entity and classified nowhere is a COMPILE
2271
+ * error here, which is the only forcing function that actually works.
2272
+ *
2273
+ * `excluded` is for fields no operator chooses and which steer no answer: the content stats, the
2274
+ * teardown bookkeeping stamps, the cost meter and the lake-memory lease. They change constantly and
2275
+ * a history full of them would bury the changes that matter.
2276
+ */
2277
+ const LAKE_CONFIG_FIELD_AUDIT = {
2278
+ name: "audited",
2279
+ slug: "audited",
2280
+ description: "audited",
2281
+ systemPrompt: "audited",
2282
+ preferredSystemPromptId: "audited",
2283
+ groundingMode: "audited",
2284
+ requiredPassageTokenTarget: "audited",
2285
+ fileTagPrefix: "audited",
2286
+ datalakeTag: "audited",
2287
+ requiredUserTag: "audited",
2288
+ requiredEntitlement: "audited",
2289
+ organizationId: "audited",
2290
+ isPublic: "audited",
2291
+ auditQueryTextEnabled: "audited",
2292
+ status: "audited",
2293
+ createdByUserId: "audited",
2294
+ lastUpdatedByUserId: "excluded",
2295
+ fileCount: "excluded",
2296
+ totalSizeBytes: "excluded",
2297
+ totalChunkedChars: "excluded",
2298
+ embeddingSpendMicroUsd: "excluded",
2299
+ lastSyncAt: "excluded",
2300
+ filesDeletedAt: "excluded",
2301
+ filesArchivedAt: "excluded",
2302
+ lakeMemoryExtractionAt: "excluded",
2303
+ lakeMemoryCursor: "excluded"
2304
+ };
2305
+ [...Object.keys(LAKE_CONFIG_FIELD_AUDIT).filter((field) => LAKE_CONFIG_FIELD_AUDIT[field] === "audited")];
2187
2306
  /**
2188
2307
  * SRE Agent Trio - Shared Types
2189
2308
  *
@@ -2982,6 +3101,66 @@ z.enum([
2982
3101
  "xai",
2983
3102
  "gemini"
2984
3103
  ]);
3104
+ z$1.object({
3105
+ sessionId: z$1.string().nullish(),
3106
+ message: z$1.string(),
3107
+ organizationId: z$1.string().optional(),
3108
+ model: z$1.string().optional(),
3109
+ temperature: z$1.number().min(0).max(2).optional(),
3110
+ max_tokens: z$1.number().positive().optional(),
3111
+ maxTokens: z$1.number().positive().optional(),
3112
+ maxOutputTokens: z$1.number().positive().optional(),
3113
+ stream: z$1.boolean().prefault(false),
3114
+ historyCount: z$1.number().positive().default(10),
3115
+ fileIds: z$1.array(z$1.string()).prefault([]),
3116
+ wait: z$1.boolean().prefault(false),
3117
+ enableTools: z$1.boolean().prefault(false),
3118
+ toolMode: z$1.enum(["fast", "smart"]).optional(),
3119
+ tools: z$1.array(z$1.string()).optional(),
3120
+ enableQuestMaster: z$1.boolean().optional(),
3121
+ enableMementos: z$1.boolean().optional(),
3122
+ enableAgents: z$1.boolean().optional(),
3123
+ promptMode: z$1.enum([
3124
+ "raw",
3125
+ "grounded",
3126
+ "surface"
3127
+ ]).optional(),
3128
+ includePromptDetails: z$1.boolean().optional(),
3129
+ includeSystemPrompt: z$1.boolean().optional()
3130
+ });
3131
+ z$1.object({
3132
+ id: z$1.string(),
3133
+ status: z$1.string(),
3134
+ message_received: z$1.boolean(),
3135
+ timestamp: z$1.string(),
3136
+ model: z$1.string(),
3137
+ message: z$1.string().optional(),
3138
+ tracking_info: z$1.object({
3139
+ quest_id: z$1.string(),
3140
+ check_status_url: z$1.string(),
3141
+ poll_url: z$1.string().optional()
3142
+ })
3143
+ });
3144
+ /**
3145
+ * Reusable JSON error envelope (plain; the OpenAPI layer annotates it).
3146
+ *
3147
+ * Must stay in sync with the published `ErrorResponse` component
3148
+ * (../openapi/schemas.ts) - `openapi/errorEnvelopeParity.test.ts` pins the two
3149
+ * together, and apps/client's errorHandler test uses this shape as the stand-in for
3150
+ * the component, which is generate-time only and cannot be imported at runtime.
3151
+ */
3152
+ const ApiErrorSchema = z$1.object({
3153
+ error: z$1.string(),
3154
+ request_id: z$1.string().optional(),
3155
+ /**
3156
+ * Deprecated, sunset 2026-12-01. The `name` of whatever was thrown - our own error
3157
+ * classes usually, a library/driver class name on an unhandled 500 - added to every
3158
+ * body by apps/client's errorHandler. Documented here so the runtime and the spec
3159
+ * agree while it is still served; do not build on it. See CONVENTIONS.md section 1.
3160
+ */
3161
+ name: z$1.string().optional()
3162
+ });
3163
+ ApiErrorSchema.extend({ errorCode: z$1.literal("insufficient_credits").optional() });
2985
3164
  const supportedVoiceGenerationVendor = z.enum(["openai", "elevenlabs"]);
2986
3165
  const voiceOutputFormatSchema = z.enum([
2987
3166
  "mp3",
@@ -3010,6 +3189,49 @@ z.object({
3010
3189
  languageCode: ttsLanguageCodeSchema.optional(),
3011
3190
  preview: z.boolean().optional()
3012
3191
  });
3192
+ /**
3193
+ * Why a browsable copy of generated audio was not kept. Saving is best-effort and
3194
+ * never fatal (the caller was already billed for the bytes it is being handed), so
3195
+ * this is reported alongside a successful response rather than as an error.
3196
+ *
3197
+ * Must stay in sync with `PersistGeneratedAudioResult` in
3198
+ * apps/client/server/utils/persistGeneratedAudio.ts, which derives its `reason`
3199
+ * from this schema.
3200
+ */
3201
+ const audioSaveSkippedReasonSchema = z.enum([
3202
+ "storage_limit",
3203
+ "file_too_large",
3204
+ "error"
3205
+ ]);
3206
+ z.object({
3207
+ /** Base64-encoded audio payload. */
3208
+ audio: z.string(),
3209
+ format: voiceOutputFormatSchema,
3210
+ contentType: z.string(),
3211
+ saved: z.boolean().optional(),
3212
+ fabFileId: z.string().optional(),
3213
+ fileUrl: z.string().optional(),
3214
+ saveSkippedReason: audioSaveSkippedReasonSchema.optional(),
3215
+ /** The provider that actually produced the audio, present only on a fallback. */
3216
+ provider: supportedVoiceGenerationVendor.optional(),
3217
+ /** The originally requested provider that could not serve the request. */
3218
+ fallbackFrom: supportedVoiceGenerationVendor.optional()
3219
+ });
3220
+ ApiErrorSchema.extend({
3221
+ provider: supportedVoiceGenerationVendor.optional(),
3222
+ errorCode: z.enum([
3223
+ "insufficient_credits",
3224
+ "provider_not_configured",
3225
+ "provider_rejected"
3226
+ ]).optional()
3227
+ });
3228
+ z.object({
3229
+ error: z.string(),
3230
+ provider: supportedVoiceGenerationVendor,
3231
+ saved: z.literal(true).optional(),
3232
+ fabFileId: z.string().optional(),
3233
+ fileUrl: z.string().optional()
3234
+ });
3013
3235
  z.enum(["openai"]);
3014
3236
  /**
3015
3237
  * Supported sound-effects generation vendors. Currently only ElevenLabs.
@@ -3939,6 +4161,7 @@ const HearthEventAction = z$1.object({
3939
4161
  seq: z$1.number(),
3940
4162
  actorId: z$1.string(),
3941
4163
  actorName: z$1.string().optional(),
4164
+ actorKind: actorKindSchema.optional(),
3942
4165
  kind: hearthEventKindSchema,
3943
4166
  human: z$1.object({
3944
4167
  text: z$1.string(),
@@ -4516,50 +4739,6 @@ z$1.discriminatedUnion("action", [
4516
4739
  PermissionRequestAction,
4517
4740
  ReconnectResultAction
4518
4741
  ]);
4519
- z$1.object({
4520
- sessionId: z$1.string().nullish(),
4521
- message: z$1.string(),
4522
- model: z$1.string().optional(),
4523
- temperature: z$1.number().min(0).max(2).optional(),
4524
- max_tokens: z$1.number().positive().optional(),
4525
- maxTokens: z$1.number().positive().optional(),
4526
- maxOutputTokens: z$1.number().positive().optional(),
4527
- stream: z$1.boolean().prefault(false),
4528
- historyCount: z$1.number().positive().default(10),
4529
- fileIds: z$1.array(z$1.string()).prefault([]),
4530
- wait: z$1.boolean().prefault(false),
4531
- enableTools: z$1.boolean().prefault(false),
4532
- toolMode: z$1.enum(["fast", "smart"]).optional(),
4533
- tools: z$1.array(z$1.string()).optional(),
4534
- enableQuestMaster: z$1.boolean().optional(),
4535
- enableMementos: z$1.boolean().optional(),
4536
- enableAgents: z$1.boolean().optional(),
4537
- promptMode: z$1.enum([
4538
- "raw",
4539
- "grounded",
4540
- "surface"
4541
- ]).optional(),
4542
- includePromptDetails: z$1.boolean().optional(),
4543
- includeSystemPrompt: z$1.boolean().optional()
4544
- });
4545
- z$1.object({
4546
- id: z$1.string(),
4547
- status: z$1.string(),
4548
- message_received: z$1.boolean(),
4549
- timestamp: z$1.string(),
4550
- model: z$1.string(),
4551
- message: z$1.string().optional(),
4552
- tracking_info: z$1.object({
4553
- quest_id: z$1.string(),
4554
- check_status_url: z$1.string(),
4555
- poll_url: z$1.string().optional()
4556
- })
4557
- });
4558
- /** Reusable JSON error envelope (plain; the OpenAPI layer annotates it). */
4559
- const ApiErrorSchema = z$1.object({
4560
- error: z$1.string(),
4561
- request_id: z$1.string().optional()
4562
- });
4563
4742
  /**
4564
4743
  * Tool schema matching ICompletionOptionTools.toolSchema. The Zod surface only
4565
4744
  * covers wire-format fields (toolFn is server-side). Replaces the historical
@@ -4653,6 +4832,7 @@ const CompletionContentEventSchema = z$1.object({
4653
4832
  "tool_use",
4654
4833
  "best-effort"
4655
4834
  ]).optional(),
4835
+ stopReason: z$1.string().optional(),
4656
4836
  thinking: z$1.array(z$1.any()).optional()
4657
4837
  });
4658
4838
  const CompletionSseErrorEventSchema = z$1.object({
@@ -5231,7 +5411,7 @@ const BFL_SAFETY_TOLERANCE = {
5231
5411
  */
5232
5412
  const BFLSafetyToleranceSchema = z$1.number().min(BFL_SAFETY_TOLERANCE.MIN).max(BFL_SAFETY_TOLERANCE.LEGACY_INPUT_MAX).optional().prefault(BFL_SAFETY_TOLERANCE.DEFAULT).transform((value) => Math.min(value, BFL_SAFETY_TOLERANCE.MAX));
5233
5413
  /**
5234
- * List of image models supported by BlackForest Labs
5414
+ * List of image models supported by Black Forest Labs
5235
5415
  */
5236
5416
  const BFL_IMAGE_MODELS = [
5237
5417
  "flux-pro-1.1",
@@ -5532,6 +5712,32 @@ z$1.enum([
5532
5712
  "complex"
5533
5713
  ]);
5534
5714
  z$1.preprocess((v) => Array.isArray(v) ? v.at(-1) : v, z$1.union([z$1.boolean(), z$1.string()])).prefault(false).transform((v) => ["true", "1"].includes(String(v).toLowerCase())).catch(false);
5715
+ const SessionTagSchema = z$1.object({
5716
+ name: z$1.string(),
5717
+ strength: z$1.number()
5718
+ });
5719
+ z$1.object({
5720
+ name: z$1.string().min(1).optional(),
5721
+ knowledgeIds: z$1.array(z$1.string()).optional(),
5722
+ artifactIds: z$1.array(z$1.string()).optional(),
5723
+ tags: z$1.array(SessionTagSchema).optional(),
5724
+ 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."),
5725
+ forceKnowledgeRetrieval: z$1.boolean().optional(),
5726
+ 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.")
5727
+ });
5728
+ z$1.object({ id: z$1.string().min(1) });
5729
+ z$1.object({
5730
+ id: z$1.string(),
5731
+ name: z$1.string(),
5732
+ userId: z$1.string(),
5733
+ knowledgeIds: z$1.array(z$1.string()).optional(),
5734
+ artifactIds: z$1.array(z$1.string()).optional(),
5735
+ tags: z$1.array(SessionTagSchema).optional(),
5736
+ forceKnowledgeRetrieval: z$1.boolean().optional(),
5737
+ lastUsedModel: z$1.string().nullish(),
5738
+ firstCreated: z$1.date(),
5739
+ lastUpdated: z$1.date()
5740
+ });
5535
5741
  z$2.enum([
5536
5742
  "",
5537
5743
  "TFG",
@@ -5598,6 +5804,130 @@ z$2.object({
5598
5804
  lastActiveAt: z$2.date().optional(),
5599
5805
  isOnline: z$2.boolean()
5600
5806
  });
5807
+ /**
5808
+ * A chunk larger than this (in tokens) marks a file whose chunking predates the passage-target
5809
+ * fix: a whole-document / whole-section blob rather than a ~512-token passage. Used to detect the
5810
+ * files a lake "Rebuild passages" pass should re-chunk. Deliberately well above
5811
+ * DEFAULT_PASSAGE_TOKEN_TARGET (512) so a correctly-chunked passage never trips it, and below the
5812
+ * ~6.5K model-window packing the old chunker produced, so every legacy blob does.
5813
+ */
5814
+ const OVERSIZED_PASSAGE_TOKEN_THRESHOLD = 1500;
5815
+ /**
5816
+ * `FabFile.notes` marker written when the data-lake convergence kill switch abandons a vectorize
5817
+ * (#1676). The file keeps its chunks but has no vectors, so it is unsearchable until re-indexed, and
5818
+ * it does NOT auto-resume.
5819
+ *
5820
+ * Lives here rather than beside its writer (apps/client fabFileVectorize) because it is a
5821
+ * cross-layer contract: the queue handler writes it and the lake-health evaluator
5822
+ * (constants/lakeHealth.ts) reads it to tell a permanently-stalled file from one still in flight.
5823
+ * b4m-core cannot import from apps/client, so a copy there would have to drift silently.
5824
+ */
5825
+ const CONVERGENCE_PAUSED_NOTE = "Indexing paused by the data-lake convergence kill switch - reprocess to complete.";
5826
+ /**
5827
+ * `FabFile.notes` marker for the OTHER half of the same kill switch: a re-chunk dropped before it
5828
+ * ran (#1676/#1681). Distinct from `CONVERGENCE_PAUSED_NOTE` because the damage is worse and the
5829
+ * wording has to say so - the producer resets a wave's chunk state BEFORE the messages are handled,
5830
+ * so a file halted here has NO chunks at all rather than chunks without vectors.
5831
+ *
5832
+ * Without a marker this state is invisible to every surface at once, which is the failure it exists
5833
+ * to prevent: `chunkCount: 0` with `error: null` reads as an image or a pending upload, so health
5834
+ * drops it from the denominator, convergence grades it `conformant` (its stale stamp still matches),
5835
+ * search does not withhold it because it is not "in flight", and the rescue sweep's own filter
5836
+ * passes over it. The file's passages are simply gone and nothing reports it.
5837
+ *
5838
+ * Same cross-layer reason as the constant above for living here: the queue handler writes it and
5839
+ * b4m-core's evaluators read it, and b4m-core cannot import from apps/client.
5840
+ */
5841
+ const CONVERGENCE_PAUSED_CHUNK_NOTE = "Re-chunking paused by the data-lake convergence kill switch - its passages were removed and are rebuilt when convergence resumes.";
5842
+ /**
5843
+ * `FabFile.chunkRebuildRequestedAt`: stamped by `resetChunkStateByIds` in the SAME write that
5844
+ * clears a file's chunk rollups, so "this file's passages are being rebuilt" can never be lost the
5845
+ * way the pair of steps that creates the state can be. The reset and the queue send are two
5846
+ * operations - kill the producer between them, or lose the consumer's marker write, and the file
5847
+ * sits at `chunkCount: 0` with `error: null` and `notes: ''`, a shape indistinguishable from an
5848
+ * image or a still-uploading row. It then drops out of lake health's denominator, out of the
5849
+ * convergence plan and out of the retrieval withhold at the same moment: every rollup says its
5850
+ * passages are gone, and nothing reports it.
5851
+ *
5852
+ * Deliberately NOT `CONVERGENCE_PAUSED_CHUNK_NOTE` pre-written by the producer, which is the obvious
5853
+ * fix and the wrong one: that marker means "halted, needs an administrator", so a file awaiting an
5854
+ * ORDINARY rebuild would read to every reader as permanently paused for the whole rebuild - search
5855
+ * would tell readers it does not return on its own, health would hard-fail P3, and "Rebuild
5856
+ * passages" would offer to repair a file that is already repairing. A flag that cries wolf on the
5857
+ * normal path is worse than the rare window it closes.
5858
+ *
5859
+ * So the two facts are distinct states, and the consumer UPGRADES one to the other: pending means
5860
+ * "in flight, returns on its own", the paused note means "halted, needs intervention". A LOST
5861
+ * upgrade therefore degrades to mislabelled-but-visible rather than invisible, which is the trade
5862
+ * this field exists to make - invisibility is the real harm, labelling is secondary.
5863
+ *
5864
+ * A dedicated field rather than a third `notes` string on purpose: `notes` already carries two
5865
+ * unrelated facts (the user's own note / NO_EXTRACTABLE_TEXT, and the kill-switch markers), so every
5866
+ * writer of it clobbers the others.
5867
+ *
5868
+ * Cleared by `commitFabFileChunks` (the rebuild landed) and by the chunk handler's pause write (the
5869
+ * rebuild was halted instead). A file carrying `error` is settled regardless - see
5870
+ * `isMemberIndexingInFlight`, which is where the precedence between these three lives.
5871
+ */
5872
+ function isChunkRebuildPending(requestedAt) {
5873
+ return requestedAt !== null && requestedAt !== void 0 && requestedAt !== "";
5874
+ }
5875
+ /**
5876
+ * Whether a file's `notes` marks it as stalled by the convergence kill switch, by either arm.
5877
+ * THE predicate every reader uses, so adding a third stall marker reaches health, convergence and
5878
+ * retrieval without three separate string comparisons drifting apart.
5879
+ */
5880
+ function isConvergencePausedNote(notes) {
5881
+ return CONVERGENCE_PAUSED_NOTES.includes(notes);
5882
+ }
5883
+ /**
5884
+ * Datastore mirror of `isConvergencePausedNote`, for a Mongo `notes: { $in: [...] }`. Exported so a
5885
+ * query and the in-memory predicate cannot drift: adding a third stall marker to this array reaches
5886
+ * both. Declared after the two constants it names so the function above can close over it.
5887
+ */
5888
+ const CONVERGENCE_PAUSED_NOTES = [CONVERGENCE_PAUSED_NOTE, CONVERGENCE_PAUSED_CHUNK_NOTE];
5889
+ /** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
5890
+ const LAKE_ACCESS_AUDIT_RETENTION_MAX_DAYS = 2555;
5891
+ /**
5892
+ * Lake CONFIG-change audit retention and value caps.
5893
+ *
5894
+ * Twin of `constants/lakeAccessAudit.ts` and here for the same reason: the values are needed by
5895
+ * both the admin-settings schema in this package and by the repository that applies them
5896
+ * (`packages/database`, which cannot import an app-server layer).
5897
+ *
5898
+ * DELIBERATELY NOT the same numbers as the read audit, and deliberately not the same lever. A
5899
+ * retrieval is frequent, low-value and cheap to lose; a config change is rare, high-value and
5900
+ * alters every future answer the lake gives, so the two want opposite retention. Folding config
5901
+ * changes into the read collection would force one of them onto the other's clock - either a
5902
+ * config change expiring on a read-volume schedule, or a high-volume collection's storage
5903
+ * multiplied to serve a low-volume need.
5904
+ *
5905
+ * Same one-way ratchet as the read side (#1658's levers rule): `resolveLakeConfigAuditRetentionDays`
5906
+ * can only raise a configured value to the floor, never lower it, so an org cannot configure its
5907
+ * own retention down to nothing and defeat the control. Platform-level, not per-org: the setting
5908
+ * carries no `scope.settableAt`, so #1660's resolver returns the platform value at every scope.
5909
+ */
5910
+ /** 3 years: long enough to outlive a typical contract and audit cycle, on a collection whose
5911
+ * volume is a few rows per lake per year. Longer than the read audit's 450 days on purpose - the
5912
+ * rarer and more consequential the event, the longer it is worth keeping. */
5913
+ const LAKE_CONFIG_AUDIT_RETENTION_FLOOR_DAYS = 1095;
5914
+ /** A separate constant from the floor on purpose, even though they agree today - a future change
5915
+ * to one must not silently move the other. */
5916
+ const LAKE_CONFIG_AUDIT_RETENTION_DEFAULT_DAYS = 1095;
5917
+ /** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
5918
+ const LAKE_CONFIG_AUDIT_RETENTION_MAX_DAYS = 3650;
5919
+ /**
5920
+ * Forced-retrieval budget defaults, shared between the admin-settings schema in this package and
5921
+ * `ChatCompletionFeatures.ts` (which cannot import from `common`'s settings schema without a
5922
+ * dependency cycle, so the constant lives here instead).
5923
+ *
5924
+ * Only `FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT` is a lever (see the `forcedRetrievalCharBudget`
5925
+ * setting) - the char budget is the measured binding constraint on how much of a corpus reaches
5926
+ * the model on every Data-Lake-mode turn. The relevance floor is exported alongside it so the two
5927
+ * stay next to each other, not because it is tunable today.
5928
+ */
5929
+ /** Total characters of retrieved chunk text injected into a forced-retrieval prompt. */
5930
+ const FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT = 12e3;
5601
5931
  let OpenAIEmbeddingModel = /* @__PURE__ */ function(OpenAIEmbeddingModel) {
5602
5932
  OpenAIEmbeddingModel["TEXT_EMBEDDING_3_SMALL"] = "text-embedding-3-small";
5603
5933
  OpenAIEmbeddingModel["TEXT_EMBEDDING_3_LARGE"] = "text-embedding-3-large";
@@ -5742,11 +6072,15 @@ const HELP_CENTER_PROMPT = `HELP CENTER: Bike4Mind has a built-in Help Center th
5742
6072
  *
5743
6073
  * Counterweight to the completeness pressure the rest of the system prompt applies: without an
5744
6074
  * explicit licence to abstain, the model treats "answer fully" as unconditional and fills gaps with
5745
- * invented specifics about the user or their data. Measured as the single largest quality gain on
5746
- * questions whose correct answer is a refusal, so it ships on every completion rather than only on
5747
- * the grounded surfaces. Kept short on purpose - it must be cheap and behaviorally light.
6075
+ * invented specifics - including a named customer, competitor, deal or dollar figure a leading
6076
+ * question implied but no source supports, volunteered with citation-like framing so it reads as
6077
+ * sourced. In internal evaluation (a harness kept outside this repo) this was among the largest
6078
+ * quality gains on questions whose correct answer is a refusal, so it ships on every completion
6079
+ * rather than only on the grounded surfaces (it is also the only surface covering a turn that
6080
+ * answers WITHOUT searching the knowledge base). Kept short
6081
+ * on purpose - it must be cheap and behaviorally light.
5748
6082
  */
5749
- const ABSTENTION_PROMPT = `When a request is underspecified or your sources do not cover it, say so and name what is missing. "I do not have enough to answer that" is a correct, high-value answer. Never invent facts about the user, their business, or their data.`;
6083
+ const ABSTENTION_PROMPT = `When a request is underspecified or your sources do not cover it, say so and name what is missing. "I do not have enough to answer that" is a correct, high-value answer. Never invent facts about the user, their business, or their data, and never state a specific customer, competitor, deal, or figure as fact - or cite a source for it - unless your sources support it, even when the question assumes it.`;
5750
6084
  /**
5751
6085
  * Default text for the formatting system message. Runtime fallback used by
5752
6086
  * `includeHardcodedSystemMessage` (b4m-core/utils/src/llm/utils.ts) when the `FormatPromptTemplate`
@@ -5800,8 +6134,14 @@ z$1.enum([
5800
6134
  "EnableDataLakes",
5801
6135
  "EnableDataLakesDefault",
5802
6136
  "EnableDataLakeSlackAdd",
6137
+ "EnableDataLakeGroundingMode",
5803
6138
  "EnableLakeMemory",
5804
6139
  "EnableDataLakeVectorSearch",
6140
+ "PauseLakeConvergence",
6141
+ "LakeConvergenceBulkChangeSharePct",
6142
+ "EnforceLakeReadGrants",
6143
+ "EnableDataLakeDrivePoll",
6144
+ "EnforceLakeAdmission",
5805
6145
  "EnableBriefcase",
5806
6146
  "EnableBriefcaseDefault",
5807
6147
  "EnableImageTemplates",
@@ -5827,6 +6167,7 @@ z$1.enum([
5827
6167
  "ReferralCreditsAmount",
5828
6168
  "registrationLink",
5829
6169
  "FeedbackReceiveEmail",
6170
+ "FeedbackReceiveEmailNonProd",
5830
6171
  "FeedbackKyle",
5831
6172
  "EnableFeedBackToEmail",
5832
6173
  "EnableFeedBackToSlack",
@@ -5892,6 +6233,22 @@ z$1.enum([
5892
6233
  "defaultEmbeddingModel",
5893
6234
  "dataLakeSearchMaxFiles",
5894
6235
  "dataLakeSearchMaxChunks",
6236
+ "forcedRetrievalCharBudget",
6237
+ "kbSearchDefaultResults",
6238
+ "kbSearchResultTokenBudget",
6239
+ "kbSearchMinRelevancePct",
6240
+ "dataLakeEmbeddingSpendEnabled",
6241
+ "dataLakeEmbeddingBudgetPerRunUsd",
6242
+ "dataLakeEmbeddingBudgetPerLakeUsd",
6243
+ "dataLakeEmbeddingBudgetPerPeriodUsd",
6244
+ "dataLakeEmbeddingBudgetPeriodHours",
6245
+ "dataLakeEmbeddingMaxCallsPerMinute",
6246
+ "dataLakeVectorizeChunkBatchSize",
6247
+ "dataLakeEmbeddingTierMultiplierIndividual",
6248
+ "dataLakeEmbeddingTierMultiplierOrganization",
6249
+ "LakeAccessAuditRetentionDays",
6250
+ "LakeAccessQueryTextRetentionDays",
6251
+ "LakeConfigAuditRetentionDays",
5895
6252
  "MaxContentLength",
5896
6253
  "enableAutoChunk",
5897
6254
  "SlackDefaultWebhookUrl",
@@ -5899,6 +6256,7 @@ z$1.enum([
5899
6256
  "SlackLiveopsWebhookUrl",
5900
6257
  "SlackUserActivityWebhookUrl",
5901
6258
  "SlackFeedbackWebhookUrl",
6259
+ "SlackNonProdFeedbackWebhookUrl",
5902
6260
  "SlackEmailAuditWebhookUrl",
5903
6261
  "slackSigningSecret",
5904
6262
  "slackBotToken",
@@ -5960,7 +6318,11 @@ z$1.enum([
5960
6318
  "modelDiscoveryAutoEnable",
5961
6319
  "modelDiscoveryAllowEgress",
5962
6320
  "modelDiscoveryPriceBandPct",
5963
- "modelDiscoveryAutoRemap"
6321
+ "modelDiscoveryAutoRemap",
6322
+ "prReportRepo",
6323
+ "prReportIdentityMap",
6324
+ "prReportWebhookUrl",
6325
+ "prReportEgressAllowlist"
5964
6326
  ]);
5965
6327
  /**
5966
6328
  * Intent-classifier sub-config. Drives the LLM-based silent
@@ -6077,6 +6439,9 @@ function makeStringSetting(config) {
6077
6439
  */
6078
6440
  const DATA_LAKE_SEARCH_MAX_FILES_DEFAULT = 5e3;
6079
6441
  const DATA_LAKE_SEARCH_MAX_CHUNKS_DEFAULT = 1e5;
6442
+ const DATA_LAKE_EMBEDDING_BUDGET_PER_LAKE_USD_MAX = 1e4;
6443
+ const DATA_LAKE_EMBEDDING_BUDGET_PER_PERIOD_USD_MAX = 5e3;
6444
+ const DATA_LAKE_EMBEDDING_MAX_CALLS_PER_MINUTE_MAX = 1e4;
6080
6445
  function makeNumberSetting(config) {
6081
6446
  let numberSchema = z$1.coerce.number();
6082
6447
  if (config.min !== void 0) numberSchema = numberSchema.min(config.min);
@@ -6665,6 +7030,66 @@ const API_SERVICE_GROUPS = {
6665
7030
  {
6666
7031
  key: "dataLakeSearchMaxChunks",
6667
7032
  order: 3
7033
+ },
7034
+ {
7035
+ key: "forcedRetrievalCharBudget",
7036
+ order: 4
7037
+ },
7038
+ {
7039
+ key: "kbSearchDefaultResults",
7040
+ order: 5
7041
+ },
7042
+ {
7043
+ key: "kbSearchResultTokenBudget",
7044
+ order: 6
7045
+ },
7046
+ {
7047
+ key: "kbSearchMinRelevancePct",
7048
+ order: 7
7049
+ }
7050
+ ]
7051
+ },
7052
+ DATA_LAKE_COST: {
7053
+ id: "dataLakeCostGovernance",
7054
+ name: "Data Lake Cost Governance",
7055
+ description: "Spend levers for data-lake embedding work (ingestion, reprocessing, convergence). Budgets are USD; 0 means stop spending, not \"use the default\". The two tier multipliers scale the per-run and per-lake budgets by whether a lake is individual- or organization-owned.",
7056
+ icon: "Savings",
7057
+ settings: [
7058
+ {
7059
+ key: "dataLakeEmbeddingSpendEnabled",
7060
+ order: 1
7061
+ },
7062
+ {
7063
+ key: "dataLakeEmbeddingBudgetPerRunUsd",
7064
+ order: 2
7065
+ },
7066
+ {
7067
+ key: "dataLakeEmbeddingBudgetPerLakeUsd",
7068
+ order: 3
7069
+ },
7070
+ {
7071
+ key: "dataLakeEmbeddingBudgetPerPeriodUsd",
7072
+ order: 4
7073
+ },
7074
+ {
7075
+ key: "dataLakeEmbeddingBudgetPeriodHours",
7076
+ order: 5
7077
+ },
7078
+ {
7079
+ key: "dataLakeEmbeddingMaxCallsPerMinute",
7080
+ order: 6
7081
+ },
7082
+ {
7083
+ key: "dataLakeVectorizeChunkBatchSize",
7084
+ order: 7
7085
+ },
7086
+ {
7087
+ key: "dataLakeEmbeddingTierMultiplierIndividual",
7088
+ order: 8
7089
+ },
7090
+ {
7091
+ key: "dataLakeEmbeddingTierMultiplierOrganization",
7092
+ order: 9
6668
7093
  }
6669
7094
  ]
6670
7095
  },
@@ -6913,6 +7338,14 @@ const API_SERVICE_GROUPS = {
6913
7338
  key: "SlackEmailAuditWebhookUrl",
6914
7339
  order: 6.5
6915
7340
  },
7341
+ {
7342
+ key: "SlackFeedbackWebhookUrl",
7343
+ order: 6.75
7344
+ },
7345
+ {
7346
+ key: "SlackNonProdFeedbackWebhookUrl",
7347
+ order: 6.8
7348
+ },
6916
7349
  {
6917
7350
  key: "FeedbackSendEmailUsername",
6918
7351
  order: 7
@@ -6925,6 +7358,10 @@ const API_SERVICE_GROUPS = {
6925
7358
  key: "FeedbackReceiveEmail",
6926
7359
  order: 9
6927
7360
  },
7361
+ {
7362
+ key: "FeedbackReceiveEmailNonProd",
7363
+ order: 9.5
7364
+ },
6928
7365
  {
6929
7366
  key: "liveFeedbackEmail",
6930
7367
  order: 10
@@ -7437,6 +7874,26 @@ const API_SERVICE_GROUPS = {
7437
7874
  order: 3
7438
7875
  }
7439
7876
  ]
7877
+ },
7878
+ DATA_LAKE_AUDIT: {
7879
+ id: "dataLakeAuditService",
7880
+ name: "Data Lake Audit",
7881
+ description: "Retention for the lake audit trail: who READ a lake (plus the opt-in query-text log) and who CHANGED its configuration",
7882
+ icon: "Security",
7883
+ settings: [
7884
+ {
7885
+ key: "LakeAccessAuditRetentionDays",
7886
+ order: 1
7887
+ },
7888
+ {
7889
+ key: "LakeAccessQueryTextRetentionDays",
7890
+ order: 2
7891
+ },
7892
+ {
7893
+ key: "LakeConfigAuditRetentionDays",
7894
+ order: 3
7895
+ }
7896
+ ]
7440
7897
  }
7441
7898
  };
7442
7899
  const settingsMap = {
@@ -7541,13 +7998,23 @@ const settingsMap = {
7541
7998
  EnableDataLakeSlackAdd: makeBooleanSetting({
7542
7999
  key: "EnableDataLakeSlackAdd",
7543
8000
  name: "Data Lakes: Slack \"@datalake add\" path",
7544
- defaultValue: false,
7545
- description: "Server-side gate for adding content to a Data Lake from Slack via \"@datalake add\". Off by default - the Slack command is intercepted deterministically but performs no ingest until this is turned on.",
8001
+ defaultValue: true,
8002
+ description: "Server-side gate for adding content to a Data Lake from Slack via \"@datalake add\". On by default. Turn OFF to make the Slack command inert - it is still intercepted deterministically, so the bot stays silent rather than falling through to the LLM.",
7546
8003
  category: "Experimental",
7547
8004
  group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
7548
8005
  order: 90,
7549
8006
  dependsOn: "EnableDataLakes"
7550
8007
  }),
8008
+ EnableDataLakeGroundingMode: makeBooleanSetting({
8009
+ key: "EnableDataLakeGroundingMode",
8010
+ name: "Data Lakes: Per-lake grounding mode",
8011
+ defaultValue: true,
8012
+ description: "Global rollback lever for the per-lake grounding mode (inline vs retrieve vs auto-by-size). On by default. Turn OFF to ignore every lake's configured mode and fall back to pure size-only corpus deferral (CorpusRetrievalMinInlineTokensPerDoc), reverting the retrieve-by-default behavior for all lakes at once without editing each lake.",
8013
+ category: "Experimental",
8014
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8015
+ order: 92,
8016
+ dependsOn: "EnableDataLakes"
8017
+ }),
7551
8018
  EnableLakeMemory: makeBooleanSetting({
7552
8019
  key: "EnableLakeMemory",
7553
8020
  name: "Data Lakes: Lake memory profile (extraction)",
@@ -7568,6 +8035,72 @@ const settingsMap = {
7568
8035
  order: 92,
7569
8036
  dependsOn: "EnableDataLakes"
7570
8037
  }),
8038
+ PauseLakeConvergence: makeBooleanSetting({
8039
+ key: "PauseLakeConvergence",
8040
+ name: "Data Lakes: Pause background convergence work",
8041
+ defaultValue: false,
8042
+ description: "Kill switch for background data-lake ingestion work (convergence sweeps, rescue re-chunking) - NOT real-time user uploads, which are always honored. Off by default. Turn ON to halt in-flight background chunk/vectorize messages the next time the handler picks them up (a re-check inside the shared handler, so it takes effect on work already queued, not just the next scheduling pass). The platform value pauses every lake at once; a per-lake (or per-org / per-owner) override pauses a subset while the rest keep running. A platform-level flip applies immediately to lake-wide work and within ~5 min to per-lake-scoped work (settings cache).",
8043
+ category: "Experimental",
8044
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8045
+ order: 93,
8046
+ dependsOn: "EnableDataLakes",
8047
+ scope: { settableAt: [
8048
+ "organization",
8049
+ "owner",
8050
+ "lake"
8051
+ ] }
8052
+ }),
8053
+ LakeConvergenceBulkChangeSharePct: makeNumberSetting({
8054
+ key: "LakeConvergenceBulkChangeSharePct",
8055
+ name: "Data Lakes: Convergence bulk-change confirmation threshold (%)",
8056
+ defaultValue: 25,
8057
+ min: 1,
8058
+ max: 100,
8059
+ description: "Share of a data lake, as a percentage of its gradable members, above which owner-triggered convergence (#1681) requires an explicit confirmation before it rewrites anything. A mass rewrite is the signature of a misconfigured chunk policy, and every individual change inside one looks locally reasonable, so the share is the only place the mistake is visible. The guard is suppressed on lakes with fewer gradable members than the plan needs for a percentage to mean anything. Lower it to make convergence ask more often; it never blocks a confirmed run.",
8060
+ category: "AI",
8061
+ order: 4,
8062
+ dependsOn: "EnableDataLakes",
8063
+ scope: { settableAt: [
8064
+ "organization",
8065
+ "owner",
8066
+ "lake"
8067
+ ] }
8068
+ }),
8069
+ EnforceLakeReadGrants: makeBooleanSetting({
8070
+ key: "EnforceLakeReadGrants",
8071
+ name: "Data Lakes: Enforce read-time grant resolution",
8072
+ defaultValue: false,
8073
+ description: "Read-time grant cutover (#1673). OFF by default = report-only: the read gate resolves a persisted READER/org grant into an ephemeral membership view and logs where it WOULD change access ([lakeReadGrantCutover] lines), but the enforced decision stays the legacy owner/org/tag/entitlement/public rule so no one gains or loses access. NOTE: turning this ON is currently a NO-OP guarded by a source-level interlock (READ_GRANT_ENFORCEMENT_READY) - enforcement will not activate until the follow-up code (member-management write path + retrieval arm) lands and flips it, and a premature toggle just logs a warning and stays report-only. This is deliberate so the setting cannot half-enable a half-wired gate. Platform altitude on purpose: a one-time install-wide migration cutover, not a per-lake lever. Tag and entitlement grants always resolve live and are never affected by this flag; only persisted reader/org rows are gated by it.",
8074
+ category: "Experimental",
8075
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8076
+ order: 94,
8077
+ dependsOn: "EnableDataLakes"
8078
+ }),
8079
+ EnableDataLakeDrivePoll: makeBooleanSetting({
8080
+ key: "EnableDataLakeDrivePoll",
8081
+ name: "Data Lakes: Google Drive auto re-sync poll",
8082
+ defaultValue: false,
8083
+ description: "Server-side gate for the scheduled poll that keeps connected Google Drive folders in sync with their data lakes (adds/edits/removals). Off by default - a connected folder still syncs on demand via the Re-sync button; turn this on to also reconcile it automatically on a schedule.",
8084
+ category: "Experimental",
8085
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8086
+ order: 95,
8087
+ dependsOn: "EnableDataLakes"
8088
+ }),
8089
+ EnforceLakeAdmission: makeBooleanSetting({
8090
+ key: "EnforceLakeAdmission",
8091
+ name: "Data Lakes: Enforce the admission contract",
8092
+ defaultValue: false,
8093
+ description: "Retrievability contract at admission (#1680). OFF by default = report-only: a file whose chunks cannot honor the chunk policy a lake REQUIRES is logged as quarantined ([admission] lines) but still joins the lake, exactly as today. ON refuses the membership write instead, so unretrievable content never becomes a member and no embedding spend is incurred for it; the caller gets an error naming the required and actual passage targets. Enforcement applies to NEW memberships only - files already in a lake are never evicted, and no query is ever blocked on lake health, which is advisory permanently. Turn this on only after the lake health report shows how many members would be refused. The lake rung is the one that matters (a lake enforces its own contract); the org and owner rungs enforce across every lake in that scope at once. A flip is not instantaneous: the settings cache is per-instance, so it applies immediately on the instance that served the change and within ~5 min (one cache TTL) everywhere else - an upload that still succeeds right after turning this on is stale cache, not a broken lever.",
8094
+ category: "Experimental",
8095
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
8096
+ order: 96,
8097
+ dependsOn: "EnableDataLakes",
8098
+ scope: { settableAt: [
8099
+ "organization",
8100
+ "owner",
8101
+ "lake"
8102
+ ] }
8103
+ }),
7571
8104
  EnableBriefcase: makeBooleanSetting({
7572
8105
  key: "EnableBriefcase",
7573
8106
  name: "Enable Briefcase",
@@ -7790,9 +8323,14 @@ const settingsMap = {
7790
8323
  name: "Default Chunk Size",
7791
8324
  defaultValue: 512,
7792
8325
  min: 64,
7793
- description: "Passage target in TOKENS for splitting large documents. The DEFAULT matches the chunker; a value stored here overrides it, and a stored value larger than the chunker default makes the UI reprocess path produce coarser chunks than /api/files/reprocess. Coarser chunks measurably worsen retrieval.",
8326
+ max: OVERSIZED_PASSAGE_TOKEN_THRESHOLD,
8327
+ description: "Passage target in TOKENS for splitting large documents. The DEFAULT matches the chunker; a value stored here overrides it, and a stored value larger than the chunker default makes the UI reprocess path produce coarser chunks than /api/files/reprocess. Coarser chunks measurably worsen retrieval, and values above the under-chunked detection threshold also stop \"Rebuild passages\" converging, so the accepted range is capped there. Resolves at file-OWNER altitude: an org/individual owner may pin their own default above the platform value; a data lake does NOT override it (epic decision 7) - a lake declares the policy it REQUIRES and a file that cannot satisfy every lake it belongs to is reported as a conflict rather than silently re-chunked.",
7794
8328
  category: "AI",
7795
- order: 3
8329
+ order: 3,
8330
+ scope: {
8331
+ settableAt: ["organization", "owner"],
8332
+ clamp: (value) => Math.min(Math.max(Math.floor(value), 64), OVERSIZED_PASSAGE_TOKEN_THRESHOLD)
8333
+ }
7796
8334
  }),
7797
8335
  ModerationEnabled: makeBooleanSetting({
7798
8336
  key: "ModerationEnabled",
@@ -7836,7 +8374,7 @@ const settingsMap = {
7836
8374
  key: "AbstentionPrompt",
7837
8375
  name: "Abstention Prompt",
7838
8376
  defaultValue: ABSTENTION_PROMPT,
7839
- description: "Short system prompt licensing the model to say \"I do not have enough to answer that\" and to name what is missing instead of inventing facts about the user or their data. Injected on every chat completion. Live-editable; clearing it reverts to the built-in default.",
8377
+ description: "Short system prompt licensing the model to say \"I do not have enough to answer that\" and to name what is missing instead of inventing facts about the user or their data. Injected on every chat completion. Live-editable; clearing it reverts to the built-in default. After an upgrade, diff a saved copy against that default: a saved copy pins the wording from whenever it was saved and will not pick up fixes made since.",
7840
8378
  category: "AI",
7841
8379
  order: 11
7842
8380
  }),
@@ -7962,6 +8500,15 @@ const settingsMap = {
7962
8500
  group: API_SERVICE_GROUPS.FEEDBACK.id,
7963
8501
  order: 9
7964
8502
  }),
8503
+ FeedbackReceiveEmailNonProd: makeStringSetting({
8504
+ key: "FeedbackReceiveEmailNonProd",
8505
+ name: "Non-Production Feedback Email",
8506
+ defaultValue: "",
8507
+ description: "Comma-separated recipient list for feedback submitted from every non-production stage (dev, staging, previews). Does not apply to a self-host install, which routes through FeedbackReceiveEmail like production. Leave empty to suppress non-production email entirely - it never falls back to the production recipient list.",
8508
+ category: "Feedback",
8509
+ group: API_SERVICE_GROUPS.FEEDBACK.id,
8510
+ order: 9.5
8511
+ }),
7965
8512
  FeedbackKyle: makeStringSetting({
7966
8513
  key: "FeedbackKyle",
7967
8514
  name: "Kyle Feedback Email",
@@ -8036,7 +8583,17 @@ const settingsMap = {
8036
8583
  description: "The webhook URL for sending feedback to the #bike4mind-feedback Slack channel.",
8037
8584
  category: "Feedback",
8038
8585
  group: API_SERVICE_GROUPS.FEEDBACK.id,
8039
- order: 7,
8586
+ order: 6.75,
8587
+ isSensitive: true
8588
+ }),
8589
+ SlackNonProdFeedbackWebhookUrl: makeStringSetting({
8590
+ key: "SlackNonProdFeedbackWebhookUrl",
8591
+ name: "Non-Production Feedback Channel Webhook URL",
8592
+ defaultValue: "",
8593
+ description: "Incoming-webhook URL that receives feedback submitted from every non-production stage (dev, staging, previews). Does not apply to a self-host install, which routes through SlackFeedbackWebhookUrl like production. Leave empty to suppress non-production feedback entirely - it never falls back to the production feedback channel.",
8594
+ category: "Feedback",
8595
+ group: API_SERVICE_GROUPS.FEEDBACK.id,
8596
+ order: 6.8,
8040
8597
  isSensitive: true
8041
8598
  }),
8042
8599
  SlackEmailAuditWebhookUrl: makeStringSetting({
@@ -8643,9 +9200,9 @@ const settingsMap = {
8643
9200
  }),
8644
9201
  bflApiKey: makeStringSetting({
8645
9202
  key: "bflApiKey",
8646
- name: "BlackForest Labs API Key",
9203
+ name: "Black Forest Labs API Key",
8647
9204
  defaultValue: "",
8648
- description: "The API Key for BlackForest Labs image generation service.",
9205
+ description: "The API Key for Black Forest Labs image generation service.",
8649
9206
  isSensitive: true,
8650
9207
  category: "AI",
8651
9208
  group: API_SERVICE_GROUPS.IMAGE_GENERATION.id,
@@ -8673,7 +9230,12 @@ const settingsMap = {
8673
9230
  description: "Most files one data-lake semantic search will scope. Beyond this the search reports itself as truncated rather than silently ignoring the rest. Raising it well past a few thousand also deepens the paging offset, so prefer reporting truncation over a very large value.",
8674
9231
  category: "AI",
8675
9232
  group: API_SERVICE_GROUPS.EMBEDDING.id,
8676
- order: 2
9233
+ order: 2,
9234
+ scope: { settableAt: [
9235
+ "organization",
9236
+ "owner",
9237
+ "lake"
9238
+ ] }
8677
9239
  }),
8678
9240
  dataLakeSearchMaxChunks: makeNumberSetting({
8679
9241
  key: "dataLakeSearchMaxChunks",
@@ -8683,8 +9245,190 @@ const settingsMap = {
8683
9245
  description: "Most chunk vectors one data-lake semantic search will score. Raising it trades query latency for coverage; lowering it makes truncation more likely (and reported).",
8684
9246
  category: "AI",
8685
9247
  group: API_SERVICE_GROUPS.EMBEDDING.id,
9248
+ order: 3,
9249
+ scope: { settableAt: [
9250
+ "organization",
9251
+ "owner",
9252
+ "lake"
9253
+ ] }
9254
+ }),
9255
+ forcedRetrievalCharBudget: makeNumberSetting({
9256
+ key: "forcedRetrievalCharBudget",
9257
+ name: "Forced Retrieval Char Budget",
9258
+ defaultValue: FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT,
9259
+ min: 1e3,
9260
+ max: 1e5,
9261
+ description: "Total characters of retrieved chunk text injected into a Data-Lake-mode turn. Measured saturating on every turn against a 47-document lake, so this is the binding constraint on how much of a corpus reaches the model - not the relevance floor. Raising it admits more passages at the cost of prompt tokens and latency on every Data-Lake turn; it is NOT automatically better, since more context can dilute ranking. Platform-only for now: this read does not go through the scoped-settings resolver, so a `settableAt` block here would be inert metadata at best and could arm the resolver's fail-loud owner check at worst.",
9262
+ category: "AI",
9263
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
9264
+ order: 4
9265
+ }),
9266
+ kbSearchDefaultResults: makeNumberSetting({
9267
+ key: "kbSearchDefaultResults",
9268
+ name: "Knowledge Base Search Default Results",
9269
+ defaultValue: 5,
9270
+ min: 1,
9271
+ max: 10,
9272
+ description: "Passages the search_knowledge_base tool returns when a model call omits max_results, which is most calls. This is the exact bound while kbSearchResultTokenBudget is unset (0). Once a token budget is set, it takes over as the primary bound for search results (this setting's own value is then unused there, though it still governs the keyword-search fallback, and the count served if token pricing itself fails). Does NOT raise the tool's hard ceiling of 10 passages per call - a model that reads max_results up to 10 from its own tool schema won't ask for more than that regardless of this setting.",
9273
+ category: "AI",
9274
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
9275
+ order: 5,
9276
+ scope: { settableAt: ["organization", "owner"] }
9277
+ }),
9278
+ kbSearchResultTokenBudget: makeNumberSetting({
9279
+ key: "kbSearchResultTokenBudget",
9280
+ name: "Knowledge Base Search Result Token Budget",
9281
+ defaultValue: 0,
9282
+ min: 0,
9283
+ max: 2e4,
9284
+ description: "Approximate tokens of served passage TEXT (post-trim, post-clip - what the model actually receives, not the raw stored chunk) the search_knowledge_base tool may return in one call. Counted with a fixed tokenizer as a proxy, not billed against any specific model. Replaces a passage count as the primary bound once set, since it is invariant to chunk size - a lake chunked smaller no longer silently returns less material for the same setting. 0 (default) disables it: search_knowledge_base then serves exactly kbSearchDefaultResults passages, unchanged from before this setting existed. The FIRST matching passage is always returned even if it alone exceeds the budget - a search that found something never returns nothing.",
9285
+ category: "AI",
9286
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
9287
+ order: 6,
9288
+ scope: { settableAt: ["organization", "owner"] }
9289
+ }),
9290
+ kbSearchMinRelevancePct: makeNumberSetting({
9291
+ key: "kbSearchMinRelevancePct",
9292
+ name: "Knowledge Base Search Minimum Relevance (%)",
9293
+ defaultValue: 0,
9294
+ min: 0,
9295
+ max: 100,
9296
+ description: "Minimum cosine relevance, as a percent, a passage must clear to be returned by search_knowledge_base. 0 (default) matches current behavior (no relevance floor beyond a non-negative cosine score). Raising it lets breadth adapt per query - a narrow question can return fewer, more relevant passages instead of always padding out to the configured count. Cosine similarity is not comparable across embedding models: a floor tuned for one model can filter out an entire alternate model, when a lake mixes embedding models, more aggressively than intended. Start low and raise gradually while watching the tool's own retrieval-skipped notices.",
9297
+ category: "AI",
9298
+ group: API_SERVICE_GROUPS.EMBEDDING.id,
9299
+ order: 7,
9300
+ scope: { settableAt: ["organization", "owner"] }
9301
+ }),
9302
+ LakeAccessAuditRetentionDays: makeNumberSetting({
9303
+ key: "LakeAccessAuditRetentionDays",
9304
+ name: "Lake Access Audit Retention (days)",
9305
+ defaultValue: 450,
9306
+ min: 450,
9307
+ max: LAKE_ACCESS_AUDIT_RETENTION_MAX_DAYS,
9308
+ description: "How long a lake access audit event (who read a lake, and when) is retained, in days. Has a floor of 450 days (12 months live plus a Type II observation tail) - this is a platform-wide value, not per-organization, until a scoped settings resolver exists. Applies only to events written after a change: expiresAt is computed once at write time and is immutable, so raising or lowering this value never affects rows already recorded.",
9309
+ category: "SecOps",
9310
+ group: API_SERVICE_GROUPS.DATA_LAKE_AUDIT.id,
9311
+ order: 1
9312
+ }),
9313
+ LakeAccessQueryTextRetentionDays: makeNumberSetting({
9314
+ key: "LakeAccessQueryTextRetentionDays",
9315
+ name: "Lake Access Query Text Retention (days)",
9316
+ defaultValue: 30,
9317
+ min: 1,
9318
+ max: 90,
9319
+ description: "How long the opt-in query-text log (the natural-language question behind a lake retrieval) is retained, in days. Always resolved shorter than the audit event retention itself, regardless of this value, since the query text is more sensitive than the event metadata. Applies only to events written after a change - already-recorded rows keep the expiry computed at write time and are not retroactively shortened or extended.",
9320
+ category: "SecOps",
9321
+ group: API_SERVICE_GROUPS.DATA_LAKE_AUDIT.id,
9322
+ order: 2
9323
+ }),
9324
+ LakeConfigAuditRetentionDays: makeNumberSetting({
9325
+ key: "LakeConfigAuditRetentionDays",
9326
+ name: "Lake Config Change Audit Retention (days)",
9327
+ defaultValue: LAKE_CONFIG_AUDIT_RETENTION_DEFAULT_DAYS,
9328
+ min: LAKE_CONFIG_AUDIT_RETENTION_FLOOR_DAYS,
9329
+ max: LAKE_CONFIG_AUDIT_RETENTION_MAX_DAYS,
9330
+ description: "How long a lake CONFIG-change event (who changed a lake, what they changed, and which manage rung authorized it) is retained, in days. Floored at 1095 days - deliberately longer than the access-audit retention above, because a config change is rare and alters every future answer the lake gives, where a read is one turn. Platform-wide, not per-organization, until a scoped settings resolver exists.",
9331
+ category: "SecOps",
9332
+ group: API_SERVICE_GROUPS.DATA_LAKE_AUDIT.id,
8686
9333
  order: 3
8687
9334
  }),
9335
+ dataLakeEmbeddingSpendEnabled: makeBooleanSetting({
9336
+ key: "dataLakeEmbeddingSpendEnabled",
9337
+ name: "Data Lake Embedding Spend Enabled",
9338
+ defaultValue: true,
9339
+ description: "Master switch for data-lake embedding spend (ingestion, reprocessing, convergence). Off halts all provider embedding calls on those paths; cached embeddings still apply.",
9340
+ category: "AI",
9341
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9342
+ order: 1
9343
+ }),
9344
+ dataLakeEmbeddingBudgetPerRunUsd: makeNumberSetting({
9345
+ key: "dataLakeEmbeddingBudgetPerRunUsd",
9346
+ name: "Embedding Budget Per Run (USD)",
9347
+ defaultValue: 5,
9348
+ min: 0,
9349
+ max: 500,
9350
+ description: "Most USD one ingestion/reprocess run (upload batch) may spend on embedding calls. 0 stops runs from spending at all.",
9351
+ category: "AI",
9352
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9353
+ order: 2
9354
+ }),
9355
+ dataLakeEmbeddingBudgetPerLakeUsd: makeNumberSetting({
9356
+ key: "dataLakeEmbeddingBudgetPerLakeUsd",
9357
+ name: "Embedding Budget Per Lake (USD)",
9358
+ defaultValue: 100,
9359
+ min: 0,
9360
+ max: DATA_LAKE_EMBEDDING_BUDGET_PER_LAKE_USD_MAX,
9361
+ description: "Most USD one data lake may spend on embedding calls over its lifetime. 0 stops all spend for every lake.",
9362
+ category: "AI",
9363
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9364
+ order: 3
9365
+ }),
9366
+ dataLakeEmbeddingBudgetPerPeriodUsd: makeNumberSetting({
9367
+ key: "dataLakeEmbeddingBudgetPerPeriodUsd",
9368
+ name: "Embedding Budget Per Period (USD)",
9369
+ defaultValue: 50,
9370
+ min: 0,
9371
+ max: DATA_LAKE_EMBEDDING_BUDGET_PER_PERIOD_USD_MAX,
9372
+ description: "Most USD the whole platform may spend on data-lake embedding calls per rolling period (see the period-hours setting). 0 stops all spend.",
9373
+ category: "AI",
9374
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9375
+ order: 4
9376
+ }),
9377
+ dataLakeEmbeddingBudgetPeriodHours: makeNumberSetting({
9378
+ key: "dataLakeEmbeddingBudgetPeriodHours",
9379
+ name: "Embedding Budget Period (hours)",
9380
+ defaultValue: 24,
9381
+ min: 1,
9382
+ max: 720,
9383
+ description: "Length of the per-period budget window in hours. Not a spend value itself, so 0 is not meaningful here (min 1).",
9384
+ category: "AI",
9385
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9386
+ order: 5
9387
+ }),
9388
+ dataLakeEmbeddingMaxCallsPerMinute: makeNumberSetting({
9389
+ key: "dataLakeEmbeddingMaxCallsPerMinute",
9390
+ name: "Embedding Max Calls Per Minute",
9391
+ defaultValue: 120,
9392
+ min: 0,
9393
+ max: DATA_LAKE_EMBEDDING_MAX_CALLS_PER_MINUTE_MAX,
9394
+ description: "Most provider embedding API calls per minute across all data-lake work. The real throttle in front of the embed call (the queue concurrency in infra is a deploy-time constant, not this lever). 0 stops all calls.",
9395
+ category: "AI",
9396
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9397
+ order: 6
9398
+ }),
9399
+ dataLakeVectorizeChunkBatchSize: makeNumberSetting({
9400
+ key: "dataLakeVectorizeChunkBatchSize",
9401
+ name: "Vectorize Chunk Batch Size",
9402
+ defaultValue: 50,
9403
+ min: 1,
9404
+ max: 500,
9405
+ description: "How many chunks the chunk handler packs into one vectorize-queue message. Smaller batches smooth the fan-out; not a spend value, so min 1.",
9406
+ category: "AI",
9407
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9408
+ order: 7
9409
+ }),
9410
+ dataLakeEmbeddingTierMultiplierIndividual: makeNumberSetting({
9411
+ key: "dataLakeEmbeddingTierMultiplierIndividual",
9412
+ name: "Cost Tier Multiplier - Individual-Owned Lakes",
9413
+ defaultValue: 1,
9414
+ min: 0,
9415
+ max: 100,
9416
+ description: "Scales the per-run and per-lake embedding budgets for lakes owned by an individual user. 1 means those lakes get exactly the configured budgets; 0 stops them spending at all. The effective budget is still capped by the same hard rail as the untiered value.",
9417
+ category: "AI",
9418
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9419
+ order: 8
9420
+ }),
9421
+ dataLakeEmbeddingTierMultiplierOrganization: makeNumberSetting({
9422
+ key: "dataLakeEmbeddingTierMultiplierOrganization",
9423
+ name: "Cost Tier Multiplier - Organization-Owned Lakes",
9424
+ defaultValue: 5,
9425
+ min: 0,
9426
+ max: 100,
9427
+ description: "Scales the per-run and per-lake embedding budgets for lakes owned by an organization, which serve a whole team rather than one person. 0 stops org-owned lakes spending at all. The effective budget is still capped by the same hard rail as the untiered value.",
9428
+ category: "AI",
9429
+ group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
9430
+ order: 9
9431
+ }),
8688
9432
  slackSigningSecret: makeStringSetting({
8689
9433
  key: "slackSigningSecret",
8690
9434
  name: "Slack Signing Secret",
@@ -9393,6 +10137,40 @@ const settingsMap = {
9393
10137
  category: "AI",
9394
10138
  group: API_SERVICE_GROUPS.MODEL_DISCOVERY.id,
9395
10139
  order: 6
10140
+ }),
10141
+ prReportRepo: makeStringSetting({
10142
+ key: "prReportRepo",
10143
+ name: "PR Report Repository",
10144
+ defaultValue: "",
10145
+ description: "The `owner/repo` whose open pull requests the PR status digest reports on. Validated against an anchored GitHub repo grammar before it is interpolated into any authenticated outbound URL (SSRF guard) - a value with an empty or `..` segment is rejected.",
10146
+ category: "Admin",
10147
+ order: 141
10148
+ }),
10149
+ prReportIdentityMap: makeStringSetting({
10150
+ key: "prReportIdentityMap",
10151
+ name: "PR Report Identity Map",
10152
+ defaultValue: "",
10153
+ description: "Maps GitHub logins and synthetic role keys (`qa_*`, `devops_*`, `reviewer_*`) to Slack member IDs, one mapping per line. Accepts `key value`, `key=value` or `key: value`; blank and `#` comment lines are ignored. Values must be real Slack member IDs - display names do not produce notification mentions.",
10154
+ category: "Admin",
10155
+ order: 142
10156
+ }),
10157
+ prReportWebhookUrl: makeStringSetting({
10158
+ key: "prReportWebhookUrl",
10159
+ name: "PR Report Slack Webhook URL",
10160
+ defaultValue: "",
10161
+ isSensitive: true,
10162
+ description: "Slack Incoming Webhook URL the PR status digest posts to (https://hooks.slack.com/services/...). It already encodes its channel and workspace, so no bot token or channel ID is needed to send. Bearer-equivalent: anyone holding it can post to the channel, so it is stored encrypted and never returned to the browser.",
10163
+ category: "Slack",
10164
+ order: 143
10165
+ }),
10166
+ prReportEgressAllowlist: makeObjectSetting({
10167
+ key: "prReportEgressAllowlist",
10168
+ name: "PR Report Egress Allowlist",
10169
+ defaultValue: { hosts: ["hooks.slack.com"] },
10170
+ 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.",
10171
+ category: "Slack",
10172
+ order: 144,
10173
+ schema: z$1.object({ hosts: z$1.array(z$1.string()).default([]) })
9396
10174
  })
9397
10175
  };
9398
10176
  /**
@@ -9781,6 +10559,7 @@ const PromptMetaTokenUsageSchema = z$1.object({
9781
10559
  actualOutputTokens: z$1.number().optional(),
9782
10560
  actualTotalTokens: z$1.number().optional(),
9783
10561
  cacheReadInputTokens: z$1.number().optional(),
10562
+ cacheCreationInputTokens: z$1.number().optional(),
9784
10563
  settledBasis: z$1.enum(["provider", "local"]).optional(),
9785
10564
  estimatedCost: z$1.number().optional(),
9786
10565
  creditsUsed: z$1.number().optional()
@@ -9980,10 +10759,67 @@ const CitableSourceSchema = z$1.object({
9980
10759
  fullContext: z$1.string().optional()
9981
10760
  }).optional()
9982
10761
  });
10762
+ /**
10763
+ * Per-turn retrieval outcome (#1867): whether retrieval was attempted this turn and what happened,
10764
+ * independent of whether the model then cited anything. Exists specifically to make the zero case
10765
+ * distinguishable from "never asked" - `context.lakeMemory` and `citables` both go silent on a
10766
+ * zero-result retrieval, so a turn that legitimately found nothing is indistinguishable from one
10767
+ * where retrieval never ran at all.
10768
+ *
10769
+ * Deliberately holds NO counts and NO chunk/document identifiers. Counts already exist and are
10770
+ * more precise: `citables.filter(c => c.type === 'document')` is deduped by id/url/title in
10771
+ * `applyQuestStatusChanges`, while this shape cannot dedupe (no identifiers to dedupe by) and
10772
+ * would have to sum - producing a second, disagreeing number for the same question. Similarity
10773
+ * scores live on `LakeAccessEvent`, not here.
10774
+ *
10775
+ * CAUTION, not a guarantee: the absence of chunk/document identifiers is what keeps this shape
10776
+ * OUT of `promptMetaRedaction.ts`'s scope (that helper is a functionCalls-only denylist and would
10777
+ * not catch a nested nonidentifier field like `dataLakeTags` regardless). It does NOT mean this
10778
+ * field never needs redaction consideration - `dataLakeTags` (which lakes were involved) already
10779
+ * reaches non-owner viewers the same way `lakeMemory.dataLakeTags` does (session shares, feedback
10780
+ * egress, admin logs, session clone - see redactedFeedback.ts, admin/model-logs.ts, clone.ts, none
10781
+ * of which touch this field). That exposure is not new in the general case, but it IS new
10782
+ * specifically on a zero-recall turn: `lakeMemory` was never written there before this field
10783
+ * existed, so a turn that previously carried no lake-identity signal at all now carries one.
10784
+ *
10785
+ * `attempted`/`outcome` on their own would still be ambiguous about WHICH lakes were searched on a
10786
+ * zero-recall turn (dataLakeTags otherwise lives only inside `lakeMemory`, written after the
10787
+ * zero-belief return), so this stamps the resolved tags at write time rather than making a reader
10788
+ * fall back to the session's current (possibly since-changed) `retrievalTags`.
10789
+ *
10790
+ * Absent-or-fully-present, matching `lakeMemory` above - see the Mongoose-side subSchema comment
10791
+ * in QuestModel.ts for why partial-write and default-array shapes are unsafe here.
10792
+ */
10793
+ const RetrievalSummarySchema = z$1.object({
10794
+ /** True once a retrieval-capable surface actually ran (not merely offered) this turn. */
10795
+ attempted: z$1.boolean(),
10796
+ /**
10797
+ * 'ok' - ran, whether or not anything came back (the zero case is a legitimate 'ok').
10798
+ * 'no_lakes' - ran but the user had no entitled/selected lake in scope.
10799
+ * 'failed' - threw; recall did not complete.
10800
+ * On multiple retrieval calls within one turn, merge priority is failed > ok > no_lakes (see
10801
+ * retrievalSummaryMerge.ts's mergeRetrievalSummary): a single failure is never masked by a later
10802
+ * success or abstain, and a real success on one surface is never masked by another surface's
10803
+ * "no lakes in scope" abstain in the same turn.
10804
+ */
10805
+ outcome: z$1.enum([
10806
+ "ok",
10807
+ "no_lakes",
10808
+ "failed"
10809
+ ]),
10810
+ /** Which retrieval-capable surface(s) ran this turn, e.g. 'lake-memory', 'knowledgeBaseSearch'. */
10811
+ surfaces: z$1.array(z$1.string()),
10812
+ /** Lakes resolved at the moment retrieval ran, stamped point-in-time (not read live from the session). */
10813
+ dataLakeTags: z$1.array(z$1.string())
10814
+ });
9983
10815
  z$1.object({
9984
10816
  model: PromptMetaModelSchema.optional(),
9985
10817
  tokenUsage: PromptMetaTokenUsageSchema.optional(),
9986
10818
  context: PromptMetaContextSchema.optional(),
10819
+ /** Per-turn retrieval outcome - see RetrievalSummarySchema. Top-level (not under `context`)
10820
+ * deliberately: applyQuestStatusChanges does a one-level spread merge, so a field nested under
10821
+ * `context` would be replaced wholesale by any tool-arm write instead of merging. */
10822
+ retrieval: RetrievalSummarySchema.optional(),
9987
10823
  functionCalls: z$1.array(PromptMetaFunctionCallSchema).optional(),
9988
10824
  /**
9989
10825
  * Names of the tools actually offered to the model this turn - the output of `buildTools`
@@ -10594,6 +11430,24 @@ z$1.object({
10594
11430
  */
10595
11431
  const DATALAKE_TAG_PREFIX = "datalake:";
10596
11432
  /**
11433
+ * How a lake's attached corpus is grounded into a chat turn, as a DELIBERATE per-lake product
11434
+ * choice rather than a side effect of who is asking (see IDataLake.groundingMode):
11435
+ * - `inline`: paste the corpus into the prompt (never defer to the search tool).
11436
+ * - `retrieve`: leave the corpus to the offered search_knowledge_base tool (always defer the
11437
+ * tool-retrievable subset), so an owner and an entitlement-only reader ground identically.
11438
+ * - `auto-by-size`: keep the size heuristic - defer only when the per-doc even-split inline depth
11439
+ * falls below the `CorpusRetrievalMinInlineTokensPerDoc` floor (see shouldDeferCorpusToRetrieval).
11440
+ *
11441
+ * The resolution seam is create-time (resolveLakeSessionDefaults -> session.corpusGroundingMode);
11442
+ * the enforcement seam is the completion-path defer plan. Keep this tuple and DataLakeGroundingMode
11443
+ * as the single source both the Zod schema and the Mongoose enum derive from.
11444
+ */
11445
+ const DATA_LAKE_GROUNDING_MODES = [
11446
+ "inline",
11447
+ "retrieve",
11448
+ "auto-by-size"
11449
+ ];
11450
+ /**
10597
11451
  * True when a would-be `fileTagPrefix` sits inside the `datalake:` namespace, which holds every
10598
11452
  * lake's membership meta-tag. Such a prefix would make one lake's content prefix match other
10599
11453
  * lakes' membership tags. Shared by the create schema and the wizard's client-side gate so the
@@ -10613,7 +11467,16 @@ const INVISIBLE_INK = /\u115F|\u1160|\u17B4|\u17B5|\u2800|\u3164|\uFFA0/g;
10613
11467
  const hasBlankTagPrefixSegment = (prefix) => {
10614
11468
  return (prefix.endsWith(":") ? prefix.slice(0, -1) : prefix).split(":").some((part) => !/[\p{L}\p{N}\p{P}\p{S}\p{M}]/u.test(part.replace(INVISIBLE_INK, "")));
10615
11469
  };
10616
- [...(() => {
11470
+ const DATA_LAKE_SLUG_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
11471
+ const DATA_LAKES = [{
11472
+ id: "opti-knowledge",
11473
+ slug: "opti-knowledge",
11474
+ name: "Optimization Knowledge Base",
11475
+ requiredUserTag: "Opti",
11476
+ requiredEntitlement: "optihashi:pro",
11477
+ fileTagPrefix: "opti:",
11478
+ datalakeTag: "datalake:opti-knowledge"
11479
+ }, ...(() => {
10617
11480
  const raw = process.env.NEXT_PUBLIC_PREMIUM_DATA_LAKES;
10618
11481
  if (!raw) return [];
10619
11482
  try {
@@ -10623,6 +11486,7 @@ const hasBlankTagPrefixSegment = (prefix) => {
10623
11486
  return [];
10624
11487
  }
10625
11488
  })()];
11489
+ new Set(DATA_LAKES.map((l) => l.id));
10626
11490
  /**
10627
11491
  * Canonical normalization for entitlement keys + `requiredEntitlement` values - the ONE
10628
11492
  * rule, applied at write time (create/update/stamp) and at match time. Mirrors the
@@ -10630,11 +11494,10 @@ const hasBlankTagPrefixSegment = (prefix) => {
10630
11494
  * casing matches the lowercase keys the resolver produces.
10631
11495
  */
10632
11496
  const normalizeEntitlementKey = (key) => key.trim().toLowerCase();
10633
- const slugRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
10634
11497
  const sha256Regex = /^[a-f0-9]{64}$/;
10635
11498
  z.object({
10636
11499
  name: z.string().min(1).max(200),
10637
- slug: z.string().min(2).max(60).regex(slugRegex, "Slug must be lowercase alphanumeric with hyphens (e.g. \"my-data-lake\")"),
11500
+ slug: z.string().min(2).max(60).regex(DATA_LAKE_SLUG_REGEX, "Slug must be lowercase alphanumeric with hyphens (e.g. \"my-data-lake\")"),
10638
11501
  description: z.string().max(2e3).optional(),
10639
11502
  fileTagPrefix: z.string().trim().min(2).max(30).refine((s) => s.endsWith(":"), "Tag prefix must end with \":\" (e.g. \"acme:\")").refine((s) => !hasBlankTagPrefixSegment(s), "Tag prefix segments must be non-empty (e.g. \"acme:\" or \"acme:legal:\")").refine((s) => !isReservedTagPrefix(s), `Tag prefix cannot use the reserved "${DATALAKE_TAG_PREFIX}" namespace`),
10640
11503
  requiredUserTag: z.string().min(1).max(100).optional(),
@@ -10646,8 +11509,16 @@ z.object({
10646
11509
  description: z.string().max(2e3).optional(),
10647
11510
  systemPrompt: z.string().optional(),
10648
11511
  preferredSystemPromptId: z.union([z.literal(""), z.string().min(1).max(200)]).optional(),
11512
+ groundingMode: z.enum(DATA_LAKE_GROUNDING_MODES).optional(),
10649
11513
  requiredUserTag: z.union([z.literal(""), z.string().min(1).max(100)]).optional(),
10650
- requiredEntitlement: z.union([z.literal(""), z.string().min(3).max(100).refine((s) => s.includes(":") && s.split(":").every((part) => part.length > 0), "Entitlement key must be namespaced with non-empty parts (e.g. \"product:pro\")")]).optional()
11514
+ requiredEntitlement: z.union([z.literal(""), z.string().min(3).max(100).refine((s) => s.includes(":") && s.split(":").every((part) => part.length > 0), "Entitlement key must be namespaced with non-empty parts (e.g. \"product:pro\")")]).optional(),
11515
+ auditQueryTextEnabled: z.boolean().optional(),
11516
+ requiredPassageTokenTarget: z.number().int().min(64).max(OVERSIZED_PASSAGE_TOKEN_THRESHOLD).nullable().optional()
11517
+ });
11518
+ z.object({
11519
+ groundingMode: z.enum(DATA_LAKE_GROUNDING_MODES).optional(),
11520
+ preferredSystemPromptId: z.union([z.literal(""), z.string().min(1).max(200)]).optional(),
11521
+ systemPrompt: z.string().optional()
10651
11522
  });
10652
11523
  z.object({
10653
11524
  organizationId: z.string().optional(),
@@ -10834,6 +11705,45 @@ z$1.object({
10834
11705
  }).omit({ model: true }).partial();
10835
11706
  z$1.union([ToolExecutionResponseSchema, ApiErrorSchema]);
10836
11707
  /**
11708
+ * Response details shared by the endpoints that return generated audio as raw
11709
+ * bytes (music, sound effects). Not a contract - just the pieces both of their
11710
+ * contracts declare, kept in one place so the published media types and headers
11711
+ * cannot describe one endpoint and not the other.
11712
+ */
11713
+ /**
11714
+ * Every Content-Type the ElevenLabs generators map an `output_format` token to
11715
+ * (`contentTypeForFormat` in ElevenLabsMusicGenerator / ElevenLabsSoundGenerator).
11716
+ * The first entry is the default (mp3); the rest are declared as alternates.
11717
+ * Must stay in sync with those two mappings.
11718
+ */
11719
+ const GENERATED_AUDIO_CONTENT_TYPES = [
11720
+ "audio/mpeg",
11721
+ "audio/opus",
11722
+ "audio/L16",
11723
+ "audio/basic",
11724
+ "application/octet-stream"
11725
+ ];
11726
+ /**
11727
+ * Where the browsable copy of the generated audio ended up. These are the ONLY
11728
+ * channel for that information on these endpoints: the body is raw audio, so a
11729
+ * caller that wants the saved file has nowhere else to read it from.
11730
+ */
11731
+ const GENERATED_AUDIO_SAVE_HEADERS = {
11732
+ "X-B4M-Audio-Saved": "Whether a browsable copy was saved to the file browser (\"true\"/\"false\").",
11733
+ "X-B4M-Audio-Fab-File-Id": "Id of the saved file. Present only when the copy was saved.",
11734
+ "X-B4M-Audio-File-Name": "File name of the saved copy. Present only when the copy was saved.",
11735
+ "X-B4M-Audio-File-Url": "Signed URL for the saved copy, minted at creation. Use this rather than re-resolving the file via GET /api/files/{id}, which fails closed until the async moderation scan completes."
11736
+ };
11737
+ /** The 200 response body of a raw-audio endpoint: default media type plus alternates. */
11738
+ const generatedAudioBody = () => ({
11739
+ contentType: GENERATED_AUDIO_CONTENT_TYPES[0],
11740
+ alsoReturns: GENERATED_AUDIO_CONTENT_TYPES.slice(1).map((contentType) => ({ contentType })),
11741
+ headers: GENERATED_AUDIO_SAVE_HEADERS
11742
+ });
11743
+ ({ ...generatedAudioBody() });
11744
+ ({ ...generatedAudioBody() });
11745
+ z$1.enum(["user", "convergence"]).optional().catch(void 0), z$1.string().optional();
11746
+ /**
10837
11747
  * Blessed, self-hosted artifact library script paths (root-relative).
10838
11748
  *
10839
11749
  * Single source of truth shared across the artifact pipeline:
@@ -10937,6 +11847,10 @@ OpenAIImageGenerationInput.extend({
10937
11847
  aspect_ratio: z$1.string().optional(),
10938
11848
  fabFileIds: z$1.array(z$1.string()).prefault([]),
10939
11849
  tools: z$1.array(z$1.union([b4mLLMTools, z$1.string()])).optional(),
11850
+ safety_tolerance: BFLSafetyToleranceSchema,
11851
+ prompt_upsampling: z$1.boolean().optional(),
11852
+ seed: z$1.number().nullable().optional(),
11853
+ output_format: z$1.enum(["jpeg", "png"]).nullable().optional(),
10940
11854
  /** Resolved by the API route's prompt resolver. Defaults to 'fresh' for first-turn or sessions with no prior image. */
10941
11855
  intent: PromptIntentSchema.optional(),
10942
11856
  promptEnhancement: z$1.object({
@@ -11130,6 +12044,12 @@ z$1.object({
11130
12044
  }).optional()
11131
12045
  });
11132
12046
  const MCP_PROVIDER_METADATA = {
12047
+ notion: { defaultToolDescriptions: {
12048
+ notion_search: "Search for pages and databases in the connected Notion workspace by text query. Returns matching page titles, IDs, and URLs.",
12049
+ notion_create_page: "Create a new page in the connected Notion workspace. Requires write access to be enabled. The page is created under the configured root page or a specified parent.",
12050
+ notion_read_page: "Read the content of a Notion page by its ID. Returns the child blocks (text, headings, lists, etc.) and a plain-text summary. Results are paginated; pass start_cursor with the returned next_cursor when has_more is true.",
12051
+ notion_append_blocks: "Append content blocks (paragraphs, headings, lists, code, etc.) to an existing Notion page or block. Requires write access."
12052
+ } },
11133
12053
  atlassian: { defaultToolDescriptions: {
11134
12054
  confluence_get_page: "Retrieve a Confluence page by ID or search by title within a space. Include page metadata and optional content.",
11135
12055
  confluence_create_page: "Create a new Confluence page. Automatically uses your personal space when spaceId is omitted - no need to call confluence_get_current_user first.",
@@ -11575,6 +12495,7 @@ z$1.object({
11575
12495
  reason: ReportReasonSchema,
11576
12496
  details: z$1.string().max(2e3).optional()
11577
12497
  });
12498
+ const PublishTagsSchema = z$1.array(z$1.string().max(60)).max(20);
11578
12499
  z$1.object({
11579
12500
  /** Short opaque id for short URLs (`/p/r/{publicId}`, `/p/f/{publicId}`) and lookups. */
11580
12501
  publicId: z$1.string(),
@@ -11583,6 +12504,11 @@ z$1.object({
11583
12504
  slug: SlugSchema,
11584
12505
  title: z$1.string().min(1).max(200),
11585
12506
  description: z$1.string().max(1e3).optional(),
12507
+ /** Freeform owner-supplied labels, normalized by normalizePublishTags. Shares a vocabulary
12508
+ * with AppFile tags (see GET /api/publish/tags) so one label means one thing across the app,
12509
+ * but stored per artifact rather than in a central tag table - there is no tag entity to keep
12510
+ * in sync, and a tag nobody uses simply stops appearing. */
12511
+ tags: PublishTagsSchema.prefault([]),
11586
12512
  visibility: VisibilitySchema.prefault("private"),
11587
12513
  /** Group id a viewer must belong to when gated cross-scope. */
11588
12514
  gatedToGroupId: z$1.string().optional(),
@@ -11668,6 +12594,9 @@ z$1.object({
11668
12594
  title: z$1.string().min(1).max(200),
11669
12595
  description: z$1.string().max(1e3).optional(),
11670
12596
  visibility: VisibilitySchema.optional(),
12597
+ /** Optional at publish time so a client that already knows its labels - the CLI publish
12598
+ * skill - can set them in the same call instead of a follow-up PATCH. */
12599
+ tags: PublishTagsSchema.optional(),
11671
12600
  gatedToGroupId: z$1.string().optional(),
11672
12601
  /** Who may annotate the published artifact. Defaults to `none` (read-only). */
11673
12602
  commentPolicy: CommentPolicySchema.optional(),
@@ -11908,6 +12837,32 @@ function isModelAccessible(model, userTags, isAdmin = false, entitlementKeys = [
11908
12837
  const normalizedAllowedEntitlements = (model.allowedEntitlements ?? []).map(normalizeEntitlementKey);
11909
12838
  return normalizedKeys.some((key) => normalizedAllowedEntitlements.includes(key));
11910
12839
  }
12840
+ [...OPENAI_IMAGE_MODELS, ...GEMINI_IMAGE_MODELS];
12841
+ /** Generation was cut off against the output-token ceiling. */
12842
+ const TRUNCATED_FINISH_REASON = "max_tokens";
12843
+ /**
12844
+ * We aborted the stream ourselves because it degenerated into repetition
12845
+ * (`DEGENERATE_STREAM_STOP_REASON` in `@bike4mind/llm-adapters`). Distinct from
12846
+ * `max_tokens` because the useful advice differs: telling a user to continue is actively
12847
+ * wrong here, since resuming from a degenerated tail tends to reproduce the loop.
12848
+ */
12849
+ const DEGENERATE_FINISH_REASON = "degenerate_repetition";
12850
+ /**
12851
+ * Every reason meaning "this reply stopped early". Membership rather than equality with a
12852
+ * single literal, so a newly-added early-stop reason surfaces a notice automatically.
12853
+ */
12854
+ const EARLY_STOP_FINISH_REASONS = /* @__PURE__ */ new Set([TRUNCATED_FINISH_REASON, DEGENERATE_FINISH_REASON]);
12855
+ /**
12856
+ * Whether a reply stopped early, given the reason reported for it.
12857
+ *
12858
+ * An ABSENT reason is NOT an early stop. Plenty of paths never report one (a backend whose
12859
+ * complete() leaves it unset, an older server, a non-terminal chunk), and treating silence
12860
+ * as truncation would cry wolf on every one of them. Only a reason we recognize as an
12861
+ * early stop counts - an unrecognized value is left alone rather than guessed at.
12862
+ */
12863
+ function isEarlyStop(stopReason) {
12864
+ return !!stopReason && EARLY_STOP_FINISH_REASONS.has(stopReason);
12865
+ }
11911
12866
  /**
11912
12867
  * Trigger-word validation, shared between client form and server agent
11913
12868
  * endpoints so the validation rules can't drift.
@@ -12517,9 +13472,9 @@ Array.from(new Set([
12517
13472
  requiresAdmin: true
12518
13473
  },
12519
13474
  {
12520
- id: "admin.feedbacks",
13475
+ id: "admin.feedback",
12521
13476
  section: "admin",
12522
- label: "Feedbacks",
13477
+ label: "Feedback",
12523
13478
  description: "View and manage user feedback, bug reports, and feature requests",
12524
13479
  navigationType: "tab",
12525
13480
  target: "2",
@@ -13540,7 +14495,7 @@ const CliConfigSchema = z$1.object({
13540
14495
  }).optional(),
13541
14496
  mcpServers: McpServersSchema,
13542
14497
  preferences: z$1.object({
13543
- maxTokens: z$1.number(),
14498
+ maxTokens: z$1.number().optional(),
13544
14499
  temperature: z$1.number(),
13545
14500
  autoSave: z$1.boolean(),
13546
14501
  autoCompact: z$1.boolean().optional().prefault(true),
@@ -13635,10 +14590,27 @@ const ProjectLocalConfigSchema = z$1.object({
13635
14590
  sandbox: PartialSandboxConfigSchema
13636
14591
  });
13637
14592
  /**
14593
+ * The output budget every pre-migration config was born with, back when
14594
+ * `preferences.maxTokens` was required and DEFAULT_CONFIG supplied this value. It is
14595
+ * indistinguishable from a user who deliberately typed 4096, and the migration reverts
14596
+ * it either way - acceptable only because the cleanup runs ONCE (see CONFIG_SCHEMA_VERSION):
14597
+ * a user who wanted 4096 sets it again and keeps it, while an install that never chose it
14598
+ * stops being capped by it. A value-keyed rule with no marker would instead make 4096
14599
+ * permanently unrepresentable, even though the /config select still offers it.
14600
+ */
14601
+ const LEGACY_PINNED_MAX_TOKENS = 4096;
14602
+ /**
14603
+ * Schema version of the on-disk config, and the marker that makes the migrations in
14604
+ * `load()` one-time. A file stamped with anything else gets the upgrade pass and is
14605
+ * rewritten at the current version; a file already at it is left alone. Bump this when
14606
+ * adding a migration, and gate the new step on the version it needs to run from.
14607
+ */
14608
+ const CONFIG_SCHEMA_VERSION = "0.2.0";
14609
+ /**
13638
14610
  * Default configuration
13639
14611
  */
13640
14612
  const DEFAULT_CONFIG = {
13641
- version: "0.1.0",
14613
+ version: CONFIG_SCHEMA_VERSION,
13642
14614
  userId: v4(),
13643
14615
  defaultModel: ChatModels.CLAUDE_4_5_SONNET,
13644
14616
  toolApiKeys: {
@@ -13647,7 +14619,6 @@ const DEFAULT_CONFIG = {
13647
14619
  },
13648
14620
  mcpServers: [],
13649
14621
  preferences: {
13650
- maxTokens: 4096,
13651
14622
  temperature: .7,
13652
14623
  autoSave: true,
13653
14624
  autoCompact: true,
@@ -13927,6 +14898,13 @@ var ConfigStore = class {
13927
14898
  if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
13928
14899
  else rawConfig.apiConfig = {};
13929
14900
  }
14901
+ if (rawConfig.version !== CONFIG_SCHEMA_VERSION) {
14902
+ if (rawConfig.preferences?.maxTokens === LEGACY_PINNED_MAX_TOKENS) delete rawConfig.preferences.maxTokens;
14903
+ rawConfig.version = CONFIG_SCHEMA_VERSION;
14904
+ try {
14905
+ await promises.writeFile(this.configPath, JSON.stringify(rawConfig, null, 2), "utf-8");
14906
+ } catch {}
14907
+ }
13930
14908
  const validated = CliConfigSchema.parse(rawConfig);
13931
14909
  const normalizedMcpServers = normalizeMcpServers(validated.mcpServers);
13932
14910
  globalConfig = {
@@ -14357,4 +15335,4 @@ var ConfigStore = class {
14357
15335
  }
14358
15336
  };
14359
15337
  //#endregion
14360
- export { WORK_ITEM_STATUSES as $, HttpStatus as A, usdToCredits as At, REASONING_EFFORT_INCOMPATIBLE_WITH_TOOLS_MODELS as B, DEFAULT_MUSIC_MODEL_ID as C, mapMimeTypeToArtifactType as Ct, FORMAT_PROMPT_TEMPLATE as D, settingsMap as Dt, FIXED_TEMPERATURE_MODELS as E, secureParameters as Et, NO_TEMPERATURE_MODELS as F, isNearLimit as Ft, SupportedFabFileMimeTypes as G, REFUSAL_FALLBACK_MODELS as H, NotFoundError as I, parseRateLimitHeaders as It, UnauthorizedError as J, TTS_MAX_INPUT_CHARS as K, OllamaEmbeddingModel as L, InternalServerError as M, withRetry as Mt, MODEL_INFO_FIELD_GROUP_OF as N, buildRateLimitLogEntry as Nt, ForbiddenError as O, toModelInfo as Ot, ModelBackend as P, extractSnippetMeta as Pt, VoyageAIEmbeddingModel as Q, OpenAIEmbeddingModel as R, CorruptedFileError as S, isZodError as St, FIELD_GROUP_OF as T, resolveHistoryFetchLimit as Tt, RESPONSES_API_TOOL_MODELS as U, REASONING_SUPPORTED_MODELS as V, SpeechToTextModels as W, VIDEO_SIZE_CONSTRAINTS as X, UnprocessableEntityError as Y, VideoModels as Z, BadRequestError as _, isRenderableModelType as _t, getCreditsUrl as a, getQuestErrorCode as at, CREDIT_DEDUCT_TRANSACTION_TYPES as b, isUnlimitedHistory as bt, requireApiUrl as c, isFieldGroup as ct, AGENT_QUEST_MANIFEST as d, isImageAttachment as dt, applyModelPriceCatalog as et, AGENT_QUEST_MCP_URI as f, isImageServeable as ft, BFL_SAFETY_TOLERANCE as g, isPlaceholderApiKey as gt, BEDROCK_NO_PROMPT_CACHING_MODELS as h, isModelDeprecated as ht, LOCAL_DEV_URL as i, getMcpProviderMetadata as it, ImageModels as j, usdToCreditsStochastic as jt, HTTPError as k, toModelRecord as kt, resolveApiEndpoint as l, isGPTImage2Model as lt, ApiKeyType as m, isModelAccessible as mt, logger as n, dayjsConfig_default as nt, getEnvironmentName as o, getRetryAfterMs as ot, ARTIFACT_ATTRS_PATTERN as p, isMediaModelType as pt, TooManyRequestsError as q, ApiEndpointUnconfiguredError as r, defaultEmbeddingModelForEnv as rt, parseApiUrl as s, isAudioMimeType as st, ConfigStore as t, calculateRetryDelay as tt, AGENT_QUEST_ID as u, isGPTImageModel as ut, BedrockEmbeddingModel as v, isRetryableError as vt, DEFAULT_UNKNOWN_CONTEXT_WINDOW as w, obfuscateApiKey as wt, ChatModels as x, isUserInitiatedAbort as xt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as y, isSupportedFabFileMimeType as yt, PermissionDeniedError as z };
15338
+ export { VoyageAIEmbeddingModel as $, HTTPError as A, resolveHistoryFetchLimit as At, PermissionDeniedError as B, isNearLimit as Bt, DEFAULT_MUSIC_MODEL_ID as C, isRetryableError as Ct, FIXED_TEMPERATURE_MODELS as D, isZodError as Dt, FIELD_GROUP_OF as E, isUserInitiatedAbort as Et, ModelBackend as F, usdToCredits as Ft, SpeechToTextModels as G, REASONING_SUPPORTED_MODELS as H, NO_TEMPERATURE_MODELS as I, usdToCreditsStochastic as It, TooManyRequestsError as J, SupportedFabFileMimeTypes as K, NotFoundError as L, withRetry as Lt, ImageModels as M, settingsMap as Mt, InternalServerError as N, toModelInfo as Nt, FORMAT_PROMPT_TEMPLATE as O, mapMimeTypeToArtifactType as Ot, MODEL_INFO_FIELD_GROUP_OF as P, toModelRecord as Pt, VideoModels as Q, OllamaEmbeddingModel as R, buildRateLimitLogEntry as Rt, CorruptedFileError as S, isRenderableModelType as St, DEGENERATE_FINISH_REASON as T, isUnlimitedHistory as Tt, REFUSAL_FALLBACK_MODELS as U, REASONING_EFFORT_INCOMPATIBLE_WITH_TOOLS_MODELS as V, parseRateLimitHeaders as Vt, RESPONSES_API_TOOL_MODELS as W, UnprocessableEntityError as X, UnauthorizedError as Y, VIDEO_SIZE_CONSTRAINTS as Z, BadRequestError as _, isImageServeable as _t, getCreditsUrl as a, getMcpProviderMetadata as at, CREDIT_DEDUCT_TRANSACTION_TYPES as b, isModelDeprecated as bt, requireApiUrl as c, isAudioMimeType as ct, AGENT_QUEST_MANIFEST as d, isEarlyStop as dt, WORK_ITEM_STATUSES as et, AGENT_QUEST_MCP_URI as f, isFieldGroup as ft, BFL_SAFETY_TOLERANCE as g, isImageAttachment as gt, BEDROCK_NO_PROMPT_CACHING_MODELS as h, isGeminiModelId as ht, LOCAL_DEV_URL as i, defaultEmbeddingModelForEnv as it, HttpStatus as j, secureParameters as jt, ForbiddenError as k, obfuscateApiKey as kt, resolveApiEndpoint as l, isChunkRebuildPending as lt, ApiKeyType as m, isGPTImageModel as mt, logger as n, calculateRetryDelay as nt, getEnvironmentName as o, getQuestErrorCode as ot, ARTIFACT_ATTRS_PATTERN as p, isGPTImage2Model as pt, TTS_MAX_INPUT_CHARS as q, ApiEndpointUnconfiguredError as r, dayjsConfig_default as rt, parseApiUrl as s, getRetryAfterMs as st, ConfigStore as t, applyModelPriceCatalog as tt, AGENT_QUEST_ID as u, isConvergencePausedNote as ut, BedrockEmbeddingModel as v, isMediaModelType as vt, DEFAULT_UNKNOWN_CONTEXT_WINDOW as w, isSupportedFabFileMimeType as wt, ChatModels as x, isPlaceholderApiKey as xt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as y, isModelAccessible as yt, OpenAIEmbeddingModel as z, extractSnippetMeta as zt };