@bike4mind/cli 0.20.1 → 0.21.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.
- package/README.md +3 -1
- package/dist/{AgentHistoryStore-BQiATPsQ.mjs → AgentHistoryStore-ucv6xXVT.mjs} +2292 -497
- package/dist/{ApiClient-BPmlalut.mjs → ApiClient-Ut1MXtOs.mjs} +5 -5
- package/dist/{ConfigStore-CNfbeaJf.mjs → ConfigStore-CoY0l0gr.mjs} +2065 -216
- package/dist/ProxyManager-B1jFWL7b.mjs +3 -0
- package/dist/{buildAgent-DwPvcTpz.mjs → buildAgent-jZhBReAr.mjs} +3 -3
- package/dist/commands/acpCommand.mjs +4 -4
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +4 -4
- package/dist/commands/mcpCommand.mjs +3 -3
- package/dist/commands/pluginCommand.mjs +3 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/index.mjs +51 -61
- package/dist/{package-CxHSRXdp.mjs → package-CfIETbXd.mjs} +1 -1
- package/dist/{serve-Du3HiqAH.mjs → serve-C9UDR5px.mjs} +5 -3
- package/package.json +13 -12
- package/dist/ProxyManager-Bqr7Lmsd.mjs +0 -3
- package/dist/{ProxyManager-C5H0pUyK.mjs → ProxyManager-C1-lgzEU.mjs} +1 -1
|
@@ -7,7 +7,6 @@ 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 { actorKindSchema, hearthEventKindSchema, hearthEventRefsSchema, hearthMachineBodySchema } from "@bike4mind/hearth";
|
|
11
10
|
import dayjs from "dayjs";
|
|
12
11
|
import timezone from "dayjs/plugin/timezone.js";
|
|
13
12
|
import utc from "dayjs/plugin/utc.js";
|
|
@@ -52,7 +51,7 @@ const extractSnippetMeta = (content) => {
|
|
|
52
51
|
}
|
|
53
52
|
return { sections };
|
|
54
53
|
};
|
|
55
|
-
function getHeader(headers, name) {
|
|
54
|
+
function getHeader$1(headers, name) {
|
|
56
55
|
if (!headers || typeof headers !== "object") return null;
|
|
57
56
|
if (typeof headers.get === "function") {
|
|
58
57
|
const val = headers.get(name);
|
|
@@ -76,10 +75,10 @@ function parseNumber(value) {
|
|
|
76
75
|
* - `Retry-After` - seconds to wait (on 429 responses), or an HTTP-date
|
|
77
76
|
*/
|
|
78
77
|
function parseRateLimitHeaders(headers) {
|
|
79
|
-
const limitStr = getHeader(headers, "X-RateLimit-Limit") ?? getHeader(headers, "x-ratelimit-limit");
|
|
80
|
-
const remainingStr = getHeader(headers, "X-RateLimit-Remaining") ?? getHeader(headers, "x-ratelimit-remaining");
|
|
81
|
-
const resetStr = getHeader(headers, "X-RateLimit-Reset") ?? getHeader(headers, "x-ratelimit-reset");
|
|
82
|
-
const retryAfterStr = getHeader(headers, "Retry-After") ?? getHeader(headers, "retry-after");
|
|
78
|
+
const limitStr = getHeader$1(headers, "X-RateLimit-Limit") ?? getHeader$1(headers, "x-ratelimit-limit");
|
|
79
|
+
const remainingStr = getHeader$1(headers, "X-RateLimit-Remaining") ?? getHeader$1(headers, "x-ratelimit-remaining");
|
|
80
|
+
const resetStr = getHeader$1(headers, "X-RateLimit-Reset") ?? getHeader$1(headers, "x-ratelimit-reset");
|
|
81
|
+
const retryAfterStr = getHeader$1(headers, "Retry-After") ?? getHeader$1(headers, "retry-after");
|
|
83
82
|
const limit = parseNumber(limitStr);
|
|
84
83
|
const remaining = parseNumber(remainingStr);
|
|
85
84
|
let resetAt = null;
|
|
@@ -110,13 +109,6 @@ function parseRateLimitHeaders(headers) {
|
|
|
110
109
|
usagePercent
|
|
111
110
|
};
|
|
112
111
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Check whether the current rate limit usage is near the threshold.
|
|
115
|
-
*
|
|
116
|
-
* @param info - Parsed rate limit info
|
|
117
|
-
* @param thresholdPercent - Usage percentage threshold (default: 80)
|
|
118
|
-
* @returns true if usage is at or above the threshold
|
|
119
|
-
*/
|
|
120
112
|
function isNearLimit(info, thresholdPercent = 80) {
|
|
121
113
|
if (info.usagePercent === null) return false;
|
|
122
114
|
return info.usagePercent >= thresholdPercent;
|
|
@@ -140,6 +132,151 @@ function buildRateLimitLogEntry(integration, endpoint, info, wasThrottled = fals
|
|
|
140
132
|
};
|
|
141
133
|
}
|
|
142
134
|
//#endregion
|
|
135
|
+
//#region ../../b4m-core/hearth/dist/index.mjs
|
|
136
|
+
/**
|
|
137
|
+
* Zod validation for data crossing the Hearth boundary (API routes, CLI
|
|
138
|
+
* tools, gateways). Must stay in sync with the types in types.ts.
|
|
139
|
+
*/
|
|
140
|
+
const actorKindSchema = z$1.enum([
|
|
141
|
+
"human",
|
|
142
|
+
"agent",
|
|
143
|
+
"gateway",
|
|
144
|
+
"device",
|
|
145
|
+
"system"
|
|
146
|
+
]);
|
|
147
|
+
/**
|
|
148
|
+
* The kinds a caller may claim FOR ITSELF. 'human' and 'system' are reserved:
|
|
149
|
+
* the human actor is derived from the authenticated session, never from a
|
|
150
|
+
* request body, so no credential can post an event that renders as the account
|
|
151
|
+
* owner. Claiming one of these three is a downgrade in trust, not a spoof.
|
|
152
|
+
*
|
|
153
|
+
* Single source for both self-identification paths - the actor override and the
|
|
154
|
+
* per-session kind (see HearthActorParamSchema / HearthSessionParamSchema in
|
|
155
|
+
* the client's hearthWire) - so the reserved set cannot drift between them.
|
|
156
|
+
*/
|
|
157
|
+
const selfClaimedActorKindSchema = z$1.enum([
|
|
158
|
+
"agent",
|
|
159
|
+
"gateway",
|
|
160
|
+
"device"
|
|
161
|
+
]);
|
|
162
|
+
const hearthEventKindSchema = z$1.enum([
|
|
163
|
+
"message",
|
|
164
|
+
"edit",
|
|
165
|
+
"reaction",
|
|
166
|
+
"artifact",
|
|
167
|
+
"presence",
|
|
168
|
+
"delegation",
|
|
169
|
+
"quest.update",
|
|
170
|
+
"gate.request",
|
|
171
|
+
"gate.resolve",
|
|
172
|
+
"system"
|
|
173
|
+
]);
|
|
174
|
+
const hearthHumanBodySchema = z$1.object({
|
|
175
|
+
text: z$1.string().min(1),
|
|
176
|
+
format: z$1.enum(["md", "text"])
|
|
177
|
+
});
|
|
178
|
+
const hearthMachineBodySchema = z$1.object({
|
|
179
|
+
schema: z$1.string().min(1),
|
|
180
|
+
payload: z$1.unknown()
|
|
181
|
+
});
|
|
182
|
+
const hearthEventRefsSchema = z$1.object({
|
|
183
|
+
threadRootId: z$1.string().min(1).optional(),
|
|
184
|
+
replyToId: z$1.string().min(1).optional(),
|
|
185
|
+
questId: z$1.string().min(1).optional(),
|
|
186
|
+
externalId: z$1.string().min(1).optional()
|
|
187
|
+
});
|
|
188
|
+
z$1.object({
|
|
189
|
+
channelId: z$1.string().min(1),
|
|
190
|
+
actorId: z$1.string().min(1),
|
|
191
|
+
kind: hearthEventKindSchema,
|
|
192
|
+
human: hearthHumanBodySchema,
|
|
193
|
+
machine: hearthMachineBodySchema.optional(),
|
|
194
|
+
refs: hearthEventRefsSchema
|
|
195
|
+
});
|
|
196
|
+
/** djb2. Deterministic across processes and restarts, which is the whole point. */
|
|
197
|
+
function hashOf(value) {
|
|
198
|
+
let hash = 5381;
|
|
199
|
+
for (let i = 0; i < value.length; i++) hash = (hash << 5) + hash + value.charCodeAt(i) | 0;
|
|
200
|
+
return Math.abs(hash);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Stable palette slot for an actor. Hash-derived, never array index or arrival
|
|
204
|
+
* order: those repaint every actor whenever the tail changes or a reload
|
|
205
|
+
* reorders the buffer, and a shifting color is worse than no color for telling
|
|
206
|
+
* two agents apart.
|
|
207
|
+
*/
|
|
208
|
+
function actorColorIndex(actorId) {
|
|
209
|
+
if (!actorId) return 0;
|
|
210
|
+
return hashOf(actorId) % 4;
|
|
211
|
+
}
|
|
212
|
+
const ACTOR_COLOR_SLOTS = [
|
|
213
|
+
{
|
|
214
|
+
light: "#2a78d6",
|
|
215
|
+
dark: "#3987e5"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
light: "#eda100",
|
|
219
|
+
dark: "#c98500"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
light: "#e87ba4",
|
|
223
|
+
dark: "#d55181"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
light: "#008300",
|
|
227
|
+
dark: "#008300"
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
/** Single-character marker for text-only surfaces (the CLI /hearth listing). */
|
|
231
|
+
const ACTOR_KIND_MARKERS = {
|
|
232
|
+
human: "H",
|
|
233
|
+
agent: "A",
|
|
234
|
+
gateway: "G",
|
|
235
|
+
device: "D",
|
|
236
|
+
system: "S"
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Read through a Partial view on purpose. The type is closed within one build,
|
|
240
|
+
* but the SPA bare-casts the WS payload, so a client running stale code against
|
|
241
|
+
* a server that has added a kind receives one that is absent from these maps -
|
|
242
|
+
* and a blank badge reads as "no kind stated" rather than "kind unknown", which
|
|
243
|
+
* is the opposite of what the mitigation needs to say.
|
|
244
|
+
*/
|
|
245
|
+
function lookup(map, kind, fallback) {
|
|
246
|
+
if (!kind) return fallback;
|
|
247
|
+
return map[kind] ?? fallback;
|
|
248
|
+
}
|
|
249
|
+
function actorKindMarker(kind) {
|
|
250
|
+
return lookup(ACTOR_KIND_MARKERS, kind, "?");
|
|
251
|
+
}
|
|
252
|
+
z$1.object({
|
|
253
|
+
/** Claude Code lifecycle event name; the reason fallback for tiers 0 and 1. */
|
|
254
|
+
hook_event_name: z$1.string().nullish(),
|
|
255
|
+
session_id: z$1.string().nullish(),
|
|
256
|
+
slug: z$1.string().nullish(),
|
|
257
|
+
/** Workspace BASENAME. Never a full path - that is content. */
|
|
258
|
+
workspace: z$1.string().nullish(),
|
|
259
|
+
/**
|
|
260
|
+
* Which reporter wrote this. A LOOSE string, not an enum: a fourth surface
|
|
261
|
+
* posting an unrecognized value must land on the roster with its detail
|
|
262
|
+
* intact, and a strict enum would fail the whole parse and drop the row - the
|
|
263
|
+
* exact "a third reporter is expensive" cost this contract exists to remove.
|
|
264
|
+
*/
|
|
265
|
+
surface: z$1.string().nullish(),
|
|
266
|
+
/** Engine driving the session, where the reporter knows it. */
|
|
267
|
+
source: z$1.string().nullish(),
|
|
268
|
+
claude_version: z$1.string().nullish(),
|
|
269
|
+
activity: z$1.object({
|
|
270
|
+
reason: z$1.string().nullish(),
|
|
271
|
+
tool: z$1.string().nullish(),
|
|
272
|
+
permission_mode: z$1.string().nullish(),
|
|
273
|
+
effort: z$1.string().nullish(),
|
|
274
|
+
duration_ms: z$1.number().nullish(),
|
|
275
|
+
subagent: z$1.string().nullish(),
|
|
276
|
+
background_tasks: z$1.number().int().min(0).nullish()
|
|
277
|
+
}).nullish()
|
|
278
|
+
});
|
|
279
|
+
//#endregion
|
|
143
280
|
//#region ../../b4m-core/common/dist/index.mjs
|
|
144
281
|
let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
|
|
145
282
|
HttpStatus[HttpStatus["Ok"] = 200] = "Ok";
|
|
@@ -148,9 +285,11 @@ let HttpStatus = /* @__PURE__ */ function(HttpStatus) {
|
|
|
148
285
|
HttpStatus[HttpStatus["Unauthorized"] = 401] = "Unauthorized";
|
|
149
286
|
HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";
|
|
150
287
|
HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound";
|
|
288
|
+
HttpStatus[HttpStatus["Conflict"] = 409] = "Conflict";
|
|
151
289
|
HttpStatus[HttpStatus["UnprocessableEntity"] = 422] = "UnprocessableEntity";
|
|
152
290
|
HttpStatus[HttpStatus["TooManyRequests"] = 429] = "TooManyRequests";
|
|
153
291
|
HttpStatus[HttpStatus["InternalServerError"] = 500] = "InternalServerError";
|
|
292
|
+
HttpStatus[HttpStatus["BadGateway"] = 502] = "BadGateway";
|
|
154
293
|
return HttpStatus;
|
|
155
294
|
}({});
|
|
156
295
|
var HTTPError = class extends Error {
|
|
@@ -312,6 +451,7 @@ const b4mLLMTools = z$1.enum([
|
|
|
312
451
|
"chess_engine",
|
|
313
452
|
"retrieve_knowledge_content",
|
|
314
453
|
"count_knowledge_base",
|
|
454
|
+
"describe_knowledge_base",
|
|
315
455
|
"delegate_to_agent",
|
|
316
456
|
"optihashi_schedule",
|
|
317
457
|
"optihashi_formulate",
|
|
@@ -699,6 +839,12 @@ let KnowledgeType = /* @__PURE__ */ function(KnowledgeType) {
|
|
|
699
839
|
* so the client can branch to a targeted error UI (see `IChatHistoryItem.errorCode`).
|
|
700
840
|
* Single source of truth: the streamed-action Zod enum in `schemas/actions.ts`
|
|
701
841
|
* derives its values from this tuple, so the two can never drift.
|
|
842
|
+
*
|
|
843
|
+
* SSE-frame scoped, and a NARROWING of the platform-wide `API_ERROR_CODES`: a
|
|
844
|
+
* quest fails for billing reasons, never for the provider-configuration reasons
|
|
845
|
+
* the HTTP surface reports. The `satisfies` is what keeps it a narrowing rather
|
|
846
|
+
* than a second vocabulary - a code added here that is not in `API_ERROR_CODES`
|
|
847
|
+
* fails the build.
|
|
702
848
|
*/
|
|
703
849
|
const QUEST_ERROR_CODES = ["insufficient_credits", "spend_cap_exceeded"];
|
|
704
850
|
z$1.union([
|
|
@@ -779,6 +925,62 @@ let ImageModels = /* @__PURE__ */ function(ImageModels) {
|
|
|
779
925
|
Object.values(ImageModels);
|
|
780
926
|
z$1.enum(ImageModels);
|
|
781
927
|
/**
|
|
928
|
+
* Image size constraints and options
|
|
929
|
+
*/
|
|
930
|
+
const IMAGE_SIZE_CONSTRAINTS = {
|
|
931
|
+
BFL: {
|
|
932
|
+
minWidth: 256,
|
|
933
|
+
maxWidth: 1440,
|
|
934
|
+
minHeight: 256,
|
|
935
|
+
maxHeight: 1440,
|
|
936
|
+
stepSize: 32,
|
|
937
|
+
defaultSize: "1280x960",
|
|
938
|
+
sizes: [
|
|
939
|
+
"1280x960",
|
|
940
|
+
"1024x768",
|
|
941
|
+
"800x600",
|
|
942
|
+
"1280x720",
|
|
943
|
+
"1024x576",
|
|
944
|
+
"1440x810",
|
|
945
|
+
"1024x1024",
|
|
946
|
+
"768x768",
|
|
947
|
+
"512x512",
|
|
948
|
+
"960x1280",
|
|
949
|
+
"768x1024",
|
|
950
|
+
"600x800"
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
GPT_IMAGE_1: {
|
|
954
|
+
sizes: [
|
|
955
|
+
"1024x1024",
|
|
956
|
+
"1024x1536",
|
|
957
|
+
"1536x1024"
|
|
958
|
+
],
|
|
959
|
+
defaultSize: "1024x1024"
|
|
960
|
+
},
|
|
961
|
+
GPT_IMAGE_2: {
|
|
962
|
+
/** Popular preset sizes shown in the UI. The API accepts any resolution meeting the constraints. */
|
|
963
|
+
sizes: [
|
|
964
|
+
"1024x1024",
|
|
965
|
+
"1536x1024",
|
|
966
|
+
"1024x1536",
|
|
967
|
+
"2048x2048",
|
|
968
|
+
"2048x1152",
|
|
969
|
+
"3840x2160",
|
|
970
|
+
"2160x3840"
|
|
971
|
+
],
|
|
972
|
+
defaultSize: "1024x1024",
|
|
973
|
+
/** Constraints for custom/flexible sizes */
|
|
974
|
+
constraints: {
|
|
975
|
+
maxEdge: 3840,
|
|
976
|
+
minTotalPixels: 655360,
|
|
977
|
+
maxTotalPixels: 8294400,
|
|
978
|
+
edgeMultiple: 16,
|
|
979
|
+
maxAspectRatio: 3
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
/**
|
|
782
984
|
* Chat Models
|
|
783
985
|
*
|
|
784
986
|
* https://platform.openai.com/docs/models/continuous-model-upgrades
|
|
@@ -896,6 +1098,12 @@ let ChatModels = /* @__PURE__ */ function(ChatModels) {
|
|
|
896
1098
|
const CHAT_MODELS = Object.values(ChatModels);
|
|
897
1099
|
const supportedChatModels = z$1.enum(ChatModels);
|
|
898
1100
|
/**
|
|
1101
|
+
* Every `ChatModels` Gemini entry is named `gemini...` (see the GEMINI block above) - a prefix
|
|
1102
|
+
* check tracks that naming convention automatically as new Gemini models are added, unlike an
|
|
1103
|
+
* explicit id list that would need updating in lockstep and could silently miss one.
|
|
1104
|
+
*/
|
|
1105
|
+
const isGeminiModelId = (model) => model.startsWith("gemini");
|
|
1106
|
+
/**
|
|
899
1107
|
* Models that support the reasoning_effort parameter.
|
|
900
1108
|
* o1-preview and o1-mini do NOT support reasoning_effort.
|
|
901
1109
|
*/
|
|
@@ -1110,6 +1318,14 @@ const SUBQUEST_STATUS_VALUES = [
|
|
|
1110
1318
|
"deleted"
|
|
1111
1319
|
];
|
|
1112
1320
|
/**
|
|
1321
|
+
* Status of a review gate on a sub-quest
|
|
1322
|
+
*/
|
|
1323
|
+
const REVIEW_GATE_STATUS_VALUES = [
|
|
1324
|
+
"pending",
|
|
1325
|
+
"approved",
|
|
1326
|
+
"rejected"
|
|
1327
|
+
];
|
|
1328
|
+
/**
|
|
1113
1329
|
* Valid complexity ratings for quests.
|
|
1114
1330
|
* Canonical vocabulary - matches what the planner generates and validates
|
|
1115
1331
|
* (Easy < 1 hour, Medium 1-4 hours, Hard > 4 hours).
|
|
@@ -1369,6 +1585,12 @@ z$1.object({
|
|
|
1369
1585
|
ownerId: z$1.string(),
|
|
1370
1586
|
ownerType: z$1.enum(CreditHolderType),
|
|
1371
1587
|
sessionId: z$1.string().optional(),
|
|
1588
|
+
/**
|
|
1589
|
+
* Data lake this call is 1:1 attributable to (ingestion embeds only - a query
|
|
1590
|
+
* embedding can span multiple lakes and is never attributed here). Unset for
|
|
1591
|
+
* every other feature/call.
|
|
1592
|
+
*/
|
|
1593
|
+
dataLakeId: z$1.string().optional(),
|
|
1372
1594
|
feature: z$1.enum([
|
|
1373
1595
|
"chat",
|
|
1374
1596
|
"image_generation",
|
|
@@ -2170,6 +2392,13 @@ z$1.object({
|
|
|
2170
2392
|
createdAt: z$1.date(),
|
|
2171
2393
|
updatedAt: z$1.date()
|
|
2172
2394
|
});
|
|
2395
|
+
let McpServerName = /* @__PURE__ */ function(McpServerName) {
|
|
2396
|
+
McpServerName["LinkedIn"] = "linkedin";
|
|
2397
|
+
McpServerName["Github"] = "github";
|
|
2398
|
+
McpServerName["Atlassian"] = "atlassian";
|
|
2399
|
+
McpServerName["Notion"] = "notion";
|
|
2400
|
+
return McpServerName;
|
|
2401
|
+
}({});
|
|
2173
2402
|
/**
|
|
2174
2403
|
* Check if a value is a placeholder (not configured or SST default).
|
|
2175
2404
|
* Uses case-insensitive comparison and trims whitespace to prevent bypass attempts.
|
|
@@ -2215,6 +2444,42 @@ function isPlaceholderApiKey(value) {
|
|
|
2215
2444
|
if (!normalized) return true;
|
|
2216
2445
|
return PLACEHOLDER_API_KEY_REGEX.test(normalized);
|
|
2217
2446
|
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Lake lifecycle. Stable states (draft/active/archived/deleted) plus transitional
|
|
2449
|
+
* states (archiving/unarchiving/restoring/deleting/purging) that exist to drive UI and make a crashed
|
|
2450
|
+
* mid-operation observable. draft -> active is one-way. It happens implicitly once the lake
|
|
2451
|
+
* holds its first member file (see `activateIfDraft` below), and unconditionally when an
|
|
2452
|
+
* archived or deleted lake is restored, which is how an empty lake can end up active.
|
|
2453
|
+
*
|
|
2454
|
+
* `purging` is the one transitional state that is NOT recoverable by retrying the same action:
|
|
2455
|
+
* it is claimed the moment a phase-2 hard delete is ACCEPTED (#1744), before the background
|
|
2456
|
+
* sweep runs, so that `listDeletedDataLakes` stops offering Restore on a lake whose
|
|
2457
|
+
* destruction is already irreversible. Everything else that reads `status` must treat it as
|
|
2458
|
+
* "going away", never as a lake to act on.
|
|
2459
|
+
*/
|
|
2460
|
+
const DATA_LAKE_STATUSES = [
|
|
2461
|
+
"draft",
|
|
2462
|
+
"active",
|
|
2463
|
+
"archiving",
|
|
2464
|
+
"archived",
|
|
2465
|
+
"unarchiving",
|
|
2466
|
+
"restoring",
|
|
2467
|
+
"deleting",
|
|
2468
|
+
"deleted",
|
|
2469
|
+
"purging"
|
|
2470
|
+
];
|
|
2471
|
+
/**
|
|
2472
|
+
* Stable (non-transitional) lake statuses - a lake sitting in one of these is at rest, not
|
|
2473
|
+
* mid-operation. Load-bearing as the INPUT to `DATA_LAKE_TRANSITIONAL_STATUSES` below, which is
|
|
2474
|
+
* what drives the needs-attention list; it is not itself a filter any list path applies.
|
|
2475
|
+
*/
|
|
2476
|
+
const DATA_LAKE_STABLE_STATUSES = [
|
|
2477
|
+
"draft",
|
|
2478
|
+
"active",
|
|
2479
|
+
"archived",
|
|
2480
|
+
"deleted"
|
|
2481
|
+
];
|
|
2482
|
+
DATA_LAKE_STATUSES.filter((s) => !DATA_LAKE_STABLE_STATUSES.includes(s));
|
|
2218
2483
|
z$1.object({
|
|
2219
2484
|
/**
|
|
2220
2485
|
* The granting lake's Mongo `_id`. ALWAYS a persisted DB lake: a hardcoded/fallback lake has no
|
|
@@ -2245,6 +2510,67 @@ z$1.object({
|
|
|
2245
2510
|
*/
|
|
2246
2511
|
expiresAt: z$1.date().nullish()
|
|
2247
2512
|
});
|
|
2513
|
+
/** One prefix-arm content tag a lake held on a file, as captured at removal for later restore. */
|
|
2514
|
+
const LakeMembershipRemovalContentTag = z$1.object({
|
|
2515
|
+
name: z$1.string(),
|
|
2516
|
+
strength: z$1.number()
|
|
2517
|
+
});
|
|
2518
|
+
z$1.object({
|
|
2519
|
+
dataLakeId: z$1.string(),
|
|
2520
|
+
fabFileId: z$1.string(),
|
|
2521
|
+
/** The principal who performed the removal - audit only, never a restore authorization input. */
|
|
2522
|
+
actorUserId: z$1.string(),
|
|
2523
|
+
/** The lake's prefix-arm tags the file carried, captured by `lakeMembershipSignals`. May be
|
|
2524
|
+
* empty - a meta-tag-only member and a non-creator-owned file both removed with no content
|
|
2525
|
+
* tags to restore, which is a complete and legitimate answer, not "nothing to record". */
|
|
2526
|
+
contentTags: z$1.array(LakeMembershipRemovalContentTag),
|
|
2527
|
+
removedAt: z$1.date(),
|
|
2528
|
+
expiresAt: z$1.date()
|
|
2529
|
+
});
|
|
2530
|
+
/**
|
|
2531
|
+
* Every `IDataLake` field, classified as audited or not. A TOTAL map keyed by `keyof IDataLake`,
|
|
2532
|
+
* exactly like `LAKE_FIELD_VISIBILITY` in redactLakeForActor.ts and for the same reason: a list of
|
|
2533
|
+
* strings cannot notice an ABSENCE, so a new config field would simply never be audited and nobody
|
|
2534
|
+
* would find out. Keyed this way, a field added to the entity and classified nowhere is a COMPILE
|
|
2535
|
+
* error here, which is the only forcing function that actually works.
|
|
2536
|
+
*
|
|
2537
|
+
* `excluded` is for fields no operator chooses and which steer no answer: the content stats, the
|
|
2538
|
+
* teardown bookkeeping stamps, the cost meter and the lake-memory lease. They change constantly and
|
|
2539
|
+
* a history full of them would bury the changes that matter.
|
|
2540
|
+
*/
|
|
2541
|
+
const LAKE_CONFIG_FIELD_AUDIT = {
|
|
2542
|
+
name: "audited",
|
|
2543
|
+
slug: "audited",
|
|
2544
|
+
description: "audited",
|
|
2545
|
+
systemPrompt: "audited",
|
|
2546
|
+
preferredSystemPromptId: "audited",
|
|
2547
|
+
groundingMode: "audited",
|
|
2548
|
+
requiredPassageTokenTarget: "audited",
|
|
2549
|
+
fileTagPrefix: "audited",
|
|
2550
|
+
datalakeTag: "audited",
|
|
2551
|
+
requiredUserTag: "audited",
|
|
2552
|
+
requiredEntitlement: "audited",
|
|
2553
|
+
organizationId: "audited",
|
|
2554
|
+
isPublic: "audited",
|
|
2555
|
+
auditQueryTextEnabled: "audited",
|
|
2556
|
+
lakeMemoryEnabled: "audited",
|
|
2557
|
+
status: "audited",
|
|
2558
|
+
createdByUserId: "audited",
|
|
2559
|
+
lastUpdatedByUserId: "excluded",
|
|
2560
|
+
fileCount: "excluded",
|
|
2561
|
+
totalSizeBytes: "excluded",
|
|
2562
|
+
totalChunkedChars: "excluded",
|
|
2563
|
+
embeddingSpendMicroUsd: "excluded",
|
|
2564
|
+
lastSyncAt: "excluded",
|
|
2565
|
+
filesDeletedAt: "excluded",
|
|
2566
|
+
filesArchivedAt: "excluded",
|
|
2567
|
+
lakeMemoryExtractionAt: "excluded",
|
|
2568
|
+
lakeMemoryCursor: "excluded",
|
|
2569
|
+
lakeMemoryPurgedAt: "excluded",
|
|
2570
|
+
inconsistencyReport: "excluded",
|
|
2571
|
+
inconsistencyComputedAt: "excluded"
|
|
2572
|
+
};
|
|
2573
|
+
[...Object.keys(LAKE_CONFIG_FIELD_AUDIT).filter((field) => LAKE_CONFIG_FIELD_AUDIT[field] === "audited")];
|
|
2248
2574
|
/**
|
|
2249
2575
|
* SRE Agent Trio - Shared Types
|
|
2250
2576
|
*
|
|
@@ -3036,6 +3362,71 @@ const usdToCreditsStochastic = (usd, rng = cryptoUniform, rate = CREDITS_PER_USD
|
|
|
3036
3362
|
const fraction = raw - base;
|
|
3037
3363
|
return base + (rng() < fraction ? 1 : 0);
|
|
3038
3364
|
};
|
|
3365
|
+
/**
|
|
3366
|
+
* Output tokens the pre-flight credit hold prices when a request's max_tokens
|
|
3367
|
+
* ceiling exceeds it.
|
|
3368
|
+
*
|
|
3369
|
+
* max_tokens is a *ceiling*, not a prediction: adaptive reasoning models stop at
|
|
3370
|
+
* end_turn well short of it, so pricing the hold at the full window (128K on the
|
|
3371
|
+
* flagship models) reserved several thousand credits per turn regardless of answer
|
|
3372
|
+
* length. The excess was always refunded at settlement, but the hold IS the
|
|
3373
|
+
* insufficient-funds gate, so users whose balance sat between their real cost and
|
|
3374
|
+
* the worst case were falsely blocked.
|
|
3375
|
+
*
|
|
3376
|
+
* 16K covers the realistic long answer with headroom - the largest replies seen in
|
|
3377
|
+
* practice are HTML-artifact turns at roughly 10-11K output tokens (see
|
|
3378
|
+
* buildThinkingParams in llm-adapters/thinkingParams.ts, whose max_tokens floor was
|
|
3379
|
+
* sized off the same measurement).
|
|
3380
|
+
*
|
|
3381
|
+
* UNDER-RESERVATION IS ACCEPTED, NOT PREVENTED. A turn that emits more than this
|
|
3382
|
+
* settles as a shortfall debit at reconciliation, which is already a supported path
|
|
3383
|
+
* (provider-basis settlement could always exceed a hold priced on the local
|
|
3384
|
+
* estimate). The two sites clamp the shortfall differently: chat's
|
|
3385
|
+
* computeSettlementDelta (services/llm/ChatCompletionProcess.ts) floors the debit at
|
|
3386
|
+
* the balance snapshot taken at its OWN admission and reports the remainder as
|
|
3387
|
+
* writtenOffCredits; cliCompletions.ts does an unclamped $inc on success and only
|
|
3388
|
+
* logs an ALERT if it lands negative. Neither is a non-negativity guarantee: the
|
|
3389
|
+
* chat snapshot predates any sibling turn's spend (see that function's own "best
|
|
3390
|
+
* effort" note), and when the shortfall still fits the stale snapshot the debit
|
|
3391
|
+
* applies in full - writtenOffCredits 0, and no BILLING_SHORTFALL_CLAMP log - so a
|
|
3392
|
+
* concurrent holder can land negative there too. Either way the resulting balance
|
|
3393
|
+
* fails the *next* turn's own admission gate - but that bound is per turn, not per
|
|
3394
|
+
* holder: turns admitted concurrently are each checked against the balance at their
|
|
3395
|
+
* own admission and settle against a snapshot that predates their siblings' spend,
|
|
3396
|
+
* so a holder running turns in parallel can be shorted once per in-flight turn, not
|
|
3397
|
+
* once total.
|
|
3398
|
+
*/
|
|
3399
|
+
const PREFLIGHT_RESERVATION_OUTPUT_TOKENS = 16384;
|
|
3400
|
+
/**
|
|
3401
|
+
* Reservation ceiling for models that spend reasoning tokens inside their output
|
|
3402
|
+
* budget (see reasonsWithinOutputBudget in llm-adapters/thinkingParams.ts). Those
|
|
3403
|
+
* tokens bill as output on top of the visible answer, so the 16K figure above -
|
|
3404
|
+
* which was measured on visible artifact size alone - under-reserves them badly.
|
|
3405
|
+
*
|
|
3406
|
+
* Deliberately below ADAPTIVE_THINKING_MAX_TOKENS_FLOOR (64K), which sizes the
|
|
3407
|
+
* request's real ceiling and therefore has to cover the worst case: exceeding it
|
|
3408
|
+
* truncates a reply mid-tag, an unrecoverable failure. A hold has no such duty -
|
|
3409
|
+
* exceeding it settles as a shortfall debit - so it is sized for the long turn
|
|
3410
|
+
* (roughly 3x the largest observed visible answer, leaving the rest for the trace)
|
|
3411
|
+
* rather than the worst one, which is what keeps the gate off affordable requests.
|
|
3412
|
+
*/
|
|
3413
|
+
const PREFLIGHT_RESERVATION_REASONING_OUTPUT_TOKENS = 32768;
|
|
3414
|
+
/**
|
|
3415
|
+
* Output-token figure to price a pre-flight credit hold at, given the max_tokens
|
|
3416
|
+
* this request will actually send. Never raises the caller's ceiling: a request
|
|
3417
|
+
* that asks for less than the cap holds only what it can possibly spend.
|
|
3418
|
+
*
|
|
3419
|
+
* Reserving only, never gating: the per-member org credit cap is still priced on
|
|
3420
|
+
* the unshrunk ceiling at both call sites, since that check has no settlement
|
|
3421
|
+
* counterpart to correct an under-estimate. That is a strictly larger figure than
|
|
3422
|
+
* the hold, not a true upper bound on the turn - it prices one model round trip,
|
|
3423
|
+
* at the uncached input rate, on the primary model - so it still under-counts a
|
|
3424
|
+
* multi-round tool loop, a cache-write turn, or a fallback hop onto pricier pricing.
|
|
3425
|
+
*
|
|
3426
|
+
* @param reasonsWithinOutputBudget - reasonsWithinOutputBudget(modelInfo); passed as
|
|
3427
|
+
* a boolean because common cannot import llm-adapters.
|
|
3428
|
+
*/
|
|
3429
|
+
const reservationOutputTokens = (requestedMaxTokens, reasonsWithinOutputBudget = false) => Math.min(requestedMaxTokens, reasonsWithinOutputBudget ? PREFLIGHT_RESERVATION_REASONING_OUTPUT_TOKENS : PREFLIGHT_RESERVATION_OUTPUT_TOKENS);
|
|
3039
3430
|
z.enum([
|
|
3040
3431
|
"openai",
|
|
3041
3432
|
"test",
|
|
@@ -3043,6 +3434,126 @@ z.enum([
|
|
|
3043
3434
|
"xai",
|
|
3044
3435
|
"gemini"
|
|
3045
3436
|
]);
|
|
3437
|
+
z$1.enum([
|
|
3438
|
+
"inject",
|
|
3439
|
+
"auto-fire",
|
|
3440
|
+
"hidden"
|
|
3441
|
+
]);
|
|
3442
|
+
/**
|
|
3443
|
+
* Modes acceptable at AUTHORING time. 'hidden' is intentionally excluded until
|
|
3444
|
+
* the host has true hidden-send support - accepting it would persist a value
|
|
3445
|
+
* that silently behaves as 'auto-fire' (a surprising downgrade). It stays in
|
|
3446
|
+
* ExecutionModeSchema/the stored enum for forward-compat.
|
|
3447
|
+
*/
|
|
3448
|
+
const AuthorableExecutionModeSchema = z$1.enum(["inject", "auto-fire"]);
|
|
3449
|
+
/**
|
|
3450
|
+
* Tools a prompt may require - constrained to the host's closed tool set, MINUS
|
|
3451
|
+
* integration-gated tools that act on the caller's own credentials/account. A
|
|
3452
|
+
* shared system prompt must not be able to inject e.g. blog-publishing into a
|
|
3453
|
+
* non-author's session via requiredTools. (Per-user entitlement of the remaining
|
|
3454
|
+
* tools is still the chat pipeline's responsibility - see follow-up note in the
|
|
3455
|
+
* briefcase blueprint; this allowlist is the storage-layer floor.)
|
|
3456
|
+
*/
|
|
3457
|
+
const BRIEFCASE_DISALLOWED_TOOLS = [
|
|
3458
|
+
"blog_publish",
|
|
3459
|
+
"blog_edit",
|
|
3460
|
+
"blog_draft"
|
|
3461
|
+
];
|
|
3462
|
+
const BriefcaseRequiredToolsSchema = z$1.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
|
|
3463
|
+
z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
|
|
3464
|
+
/** Shared cap for every free-text prompt body a caller can supply. Exported so the
|
|
3465
|
+
* caller-prompt caps on the chat request, the invoke params and the CLI tool import it
|
|
3466
|
+
* rather than each restating the literal. */
|
|
3467
|
+
const PROMPT_TEXT_MAX = 16e3;
|
|
3468
|
+
const TAGS_MAX = 20;
|
|
3469
|
+
z$1.object({
|
|
3470
|
+
type: z$1.string().min(1).max(100),
|
|
3471
|
+
name: z$1.string().min(1).max(200),
|
|
3472
|
+
description: z$1.string().max(500).optional(),
|
|
3473
|
+
promptText: z$1.string().min(1).max(PROMPT_TEXT_MAX),
|
|
3474
|
+
tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
|
|
3475
|
+
executionMode: AuthorableExecutionModeSchema.optional(),
|
|
3476
|
+
requiredTools: BriefcaseRequiredToolsSchema.optional()
|
|
3477
|
+
}).partial();
|
|
3478
|
+
/**
|
|
3479
|
+
* One catalog sub-query. Exactly one selector is used, in precedence order:
|
|
3480
|
+
* `personal` (resolved to the caller server-side) > `tags` > `type`.
|
|
3481
|
+
*/
|
|
3482
|
+
const PromptBatchQuerySchema = z$1.object({
|
|
3483
|
+
key: z$1.string().min(1).max(100),
|
|
3484
|
+
tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
|
|
3485
|
+
type: z$1.string().max(100).optional(),
|
|
3486
|
+
personal: z$1.boolean().optional()
|
|
3487
|
+
});
|
|
3488
|
+
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" }) });
|
|
3489
|
+
z$1.object({
|
|
3490
|
+
sessionId: z$1.string().nullish(),
|
|
3491
|
+
message: z$1.string(),
|
|
3492
|
+
organizationId: z$1.string().optional(),
|
|
3493
|
+
model: z$1.string().optional(),
|
|
3494
|
+
temperature: z$1.number().min(0).max(2).optional(),
|
|
3495
|
+
max_tokens: z$1.number().positive().optional(),
|
|
3496
|
+
maxTokens: z$1.number().positive().optional(),
|
|
3497
|
+
maxOutputTokens: z$1.number().positive().optional(),
|
|
3498
|
+
stream: z$1.boolean().prefault(false),
|
|
3499
|
+
historyCount: z$1.number().positive().default(10),
|
|
3500
|
+
fileIds: z$1.array(z$1.string()).prefault([]),
|
|
3501
|
+
wait: z$1.boolean().prefault(false),
|
|
3502
|
+
enableTools: z$1.boolean().prefault(false),
|
|
3503
|
+
toolMode: z$1.enum(["fast", "smart"]).optional(),
|
|
3504
|
+
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."),
|
|
3505
|
+
enableQuestMaster: z$1.boolean().optional(),
|
|
3506
|
+
enableMementos: z$1.boolean().optional(),
|
|
3507
|
+
enableAgents: z$1.boolean().optional(),
|
|
3508
|
+
promptMode: z$1.enum([
|
|
3509
|
+
"raw",
|
|
3510
|
+
"grounded",
|
|
3511
|
+
"surface"
|
|
3512
|
+
]).optional(),
|
|
3513
|
+
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, and skill invocation). Tools you request explicitly are unaffected. 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."),
|
|
3514
|
+
includePromptDetails: z$1.boolean().optional(),
|
|
3515
|
+
includeSystemPrompt: z$1.boolean().optional(),
|
|
3516
|
+
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.")
|
|
3517
|
+
});
|
|
3518
|
+
z$1.object({
|
|
3519
|
+
id: z$1.string(),
|
|
3520
|
+
status: z$1.string(),
|
|
3521
|
+
message_received: z$1.boolean(),
|
|
3522
|
+
timestamp: z$1.string(),
|
|
3523
|
+
model: z$1.string(),
|
|
3524
|
+
message: z$1.string().optional(),
|
|
3525
|
+
tools: z$1.object({
|
|
3526
|
+
toolMode: z$1.enum(["fast", "smart"]).optional(),
|
|
3527
|
+
autoSelectedTools: z$1.array(z$1.string()).optional(),
|
|
3528
|
+
effectiveTools: z$1.array(z$1.string()),
|
|
3529
|
+
unrecognizedTools: z$1.array(z$1.string()).optional()
|
|
3530
|
+
}).optional(),
|
|
3531
|
+
tracking_info: z$1.object({
|
|
3532
|
+
quest_id: z$1.string(),
|
|
3533
|
+
check_status_url: z$1.string(),
|
|
3534
|
+
poll_url: z$1.string().optional()
|
|
3535
|
+
})
|
|
3536
|
+
});
|
|
3537
|
+
/**
|
|
3538
|
+
* Reusable JSON error envelope (plain; the OpenAPI layer annotates it).
|
|
3539
|
+
*
|
|
3540
|
+
* Must stay in sync with the published `ErrorResponse` component
|
|
3541
|
+
* (../openapi/schemas.ts) - `openapi/errorEnvelopeParity.test.ts` pins the two
|
|
3542
|
+
* together, and apps/client's errorHandler test uses this shape as the stand-in for
|
|
3543
|
+
* the component, which is generate-time only and cannot be imported at runtime.
|
|
3544
|
+
*/
|
|
3545
|
+
const ApiErrorSchema = z$1.object({
|
|
3546
|
+
error: z$1.string(),
|
|
3547
|
+
request_id: z$1.string().optional(),
|
|
3548
|
+
/**
|
|
3549
|
+
* Deprecated, sunset 2026-12-01. The `name` of whatever was thrown - our own error
|
|
3550
|
+
* classes usually, a library/driver class name on an unhandled 500 - added to every
|
|
3551
|
+
* body by apps/client's errorHandler. Documented here so the runtime and the spec
|
|
3552
|
+
* agree while it is still served; do not build on it. See CONVENTIONS.md section 1.
|
|
3553
|
+
*/
|
|
3554
|
+
name: z$1.string().optional()
|
|
3555
|
+
});
|
|
3556
|
+
ApiErrorSchema.extend({ errorCode: z$1.literal("insufficient_credits").optional() });
|
|
3046
3557
|
const supportedVoiceGenerationVendor = z.enum(["openai", "elevenlabs"]);
|
|
3047
3558
|
const voiceOutputFormatSchema = z.enum([
|
|
3048
3559
|
"mp3",
|
|
@@ -3071,6 +3582,49 @@ z.object({
|
|
|
3071
3582
|
languageCode: ttsLanguageCodeSchema.optional(),
|
|
3072
3583
|
preview: z.boolean().optional()
|
|
3073
3584
|
});
|
|
3585
|
+
/**
|
|
3586
|
+
* Why a browsable copy of generated audio was not kept. Saving is best-effort and
|
|
3587
|
+
* never fatal (the caller was already billed for the bytes it is being handed), so
|
|
3588
|
+
* this is reported alongside a successful response rather than as an error.
|
|
3589
|
+
*
|
|
3590
|
+
* Must stay in sync with `PersistGeneratedAudioResult` in
|
|
3591
|
+
* apps/client/server/utils/persistGeneratedAudio.ts, which derives its `reason`
|
|
3592
|
+
* from this schema.
|
|
3593
|
+
*/
|
|
3594
|
+
const audioSaveSkippedReasonSchema = z.enum([
|
|
3595
|
+
"storage_limit",
|
|
3596
|
+
"file_too_large",
|
|
3597
|
+
"error"
|
|
3598
|
+
]);
|
|
3599
|
+
z.object({
|
|
3600
|
+
/** Base64-encoded audio payload. */
|
|
3601
|
+
audio: z.string(),
|
|
3602
|
+
format: voiceOutputFormatSchema,
|
|
3603
|
+
contentType: z.string(),
|
|
3604
|
+
saved: z.boolean().optional(),
|
|
3605
|
+
fabFileId: z.string().optional(),
|
|
3606
|
+
fileUrl: z.string().optional(),
|
|
3607
|
+
saveSkippedReason: audioSaveSkippedReasonSchema.optional(),
|
|
3608
|
+
/** The provider that actually produced the audio, present only on a fallback. */
|
|
3609
|
+
provider: supportedVoiceGenerationVendor.optional(),
|
|
3610
|
+
/** The originally requested provider that could not serve the request. */
|
|
3611
|
+
fallbackFrom: supportedVoiceGenerationVendor.optional()
|
|
3612
|
+
});
|
|
3613
|
+
ApiErrorSchema.extend({
|
|
3614
|
+
provider: supportedVoiceGenerationVendor.optional(),
|
|
3615
|
+
errorCode: z.enum([
|
|
3616
|
+
"insufficient_credits",
|
|
3617
|
+
"provider_not_configured",
|
|
3618
|
+
"provider_rejected"
|
|
3619
|
+
]).optional()
|
|
3620
|
+
});
|
|
3621
|
+
z.object({
|
|
3622
|
+
error: z.string(),
|
|
3623
|
+
provider: supportedVoiceGenerationVendor,
|
|
3624
|
+
saved: z.literal(true).optional(),
|
|
3625
|
+
fabFileId: z.string().optional(),
|
|
3626
|
+
fileUrl: z.string().optional()
|
|
3627
|
+
});
|
|
3074
3628
|
z.enum(["openai"]);
|
|
3075
3629
|
/**
|
|
3076
3630
|
* Supported sound-effects generation vendors. Currently only ElevenLabs.
|
|
@@ -3196,13 +3750,93 @@ const AGENT_EXECUTION_STATUSES = [
|
|
|
3196
3750
|
"failed",
|
|
3197
3751
|
"aborted"
|
|
3198
3752
|
];
|
|
3753
|
+
/**
|
|
3754
|
+
* Operator allow-list for the client-authored Mongo filter carried on a `subscribe_query` frame.
|
|
3755
|
+
*
|
|
3756
|
+
* The WS data-subscribe handler forwards that filter to `Model.find` and persists it on the
|
|
3757
|
+
* QuerySubscription the separate subscriber-fanout service replays, so an unconstrained
|
|
3758
|
+
* `$`-prefixed key lets any authenticated socket ask the database to run server-side JavaScript
|
|
3759
|
+
* (`$where`, `$expr` + `$function`) or an unbounded `$regex` - either of which pins a pooled
|
|
3760
|
+
* connection for as long as it runs.
|
|
3761
|
+
*
|
|
3762
|
+
* Live subscriptions only ever need equality, membership and range matching, so anything outside
|
|
3763
|
+
* this list is REFUSED rather than stripped: silently dropping an operator would quietly widen the
|
|
3764
|
+
* document set the caller ends up subscribed to.
|
|
3765
|
+
*
|
|
3766
|
+
* The scan is deliberately structural rather than a model of Mongo's grammar - it flags any
|
|
3767
|
+
* `$`-prefixed key anywhere in the tree that isn't allow-listed. It can therefore accept an
|
|
3768
|
+
* allow-listed operator in a position Mongo would treat as a literal sub-document field name, but
|
|
3769
|
+
* that is inert; what matters is that no disallowed operator can reach the driver.
|
|
3770
|
+
*/
|
|
3771
|
+
/** Combinators whose operands are themselves filters. */
|
|
3772
|
+
const SUBSCRIPTION_FILTER_LOGICAL_OPERATORS = [
|
|
3773
|
+
"$and",
|
|
3774
|
+
"$or",
|
|
3775
|
+
"$nor",
|
|
3776
|
+
"$not"
|
|
3777
|
+
];
|
|
3778
|
+
/** Value-level operators a subscription filter may use. */
|
|
3779
|
+
const SUBSCRIPTION_FILTER_VALUE_OPERATORS = [
|
|
3780
|
+
"$eq",
|
|
3781
|
+
"$ne",
|
|
3782
|
+
"$gt",
|
|
3783
|
+
"$gte",
|
|
3784
|
+
"$lt",
|
|
3785
|
+
"$lte",
|
|
3786
|
+
"$in",
|
|
3787
|
+
"$nin",
|
|
3788
|
+
"$exists",
|
|
3789
|
+
"$type",
|
|
3790
|
+
"$size",
|
|
3791
|
+
"$all",
|
|
3792
|
+
"$elemMatch"
|
|
3793
|
+
];
|
|
3794
|
+
const ALLOWED_OPERATORS = /* @__PURE__ */ new Set([...SUBSCRIPTION_FILTER_LOGICAL_OPERATORS, ...SUBSCRIPTION_FILTER_VALUE_OPERATORS]);
|
|
3795
|
+
/**
|
|
3796
|
+
* Returns a dotted path for every part of `filter` a subscription may not send: a disallowed
|
|
3797
|
+
* `$`-prefixed key, a `RegExp` operand, or nesting past {@link SUBSCRIPTION_FILTER_MAX_DEPTH}.
|
|
3798
|
+
* An empty array means the filter is safe to forward to the database.
|
|
3799
|
+
*/
|
|
3800
|
+
function findDisallowedSubscriptionFilterKeys(filter) {
|
|
3801
|
+
const violations = [];
|
|
3802
|
+
const walk = (node, path, depth) => {
|
|
3803
|
+
if (depth > 12) {
|
|
3804
|
+
violations.push(`${path} (nested deeper than 12)`);
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
3807
|
+
if (node instanceof RegExp) {
|
|
3808
|
+
violations.push(`${path} (regular expression)`);
|
|
3809
|
+
return;
|
|
3810
|
+
}
|
|
3811
|
+
if (Array.isArray(node)) {
|
|
3812
|
+
node.forEach((entry, i) => walk(entry, `${path}[${i}]`, depth + 1));
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
if (node === null || typeof node !== "object") return;
|
|
3816
|
+
for (const [key, value] of Object.entries(node)) {
|
|
3817
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
3818
|
+
if (key.startsWith("$") && !ALLOWED_OPERATORS.has(key)) {
|
|
3819
|
+
violations.push(childPath);
|
|
3820
|
+
continue;
|
|
3821
|
+
}
|
|
3822
|
+
walk(value, childPath, depth + 1);
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3825
|
+
walk(filter, "", 0);
|
|
3826
|
+
return violations;
|
|
3827
|
+
}
|
|
3199
3828
|
const DataSubscribeRequestAction = z$1.object({
|
|
3200
3829
|
action: z$1.literal("subscribe_query"),
|
|
3201
3830
|
accessToken: z$1.string().optional(),
|
|
3202
3831
|
subscriptionId: z$1.string(),
|
|
3203
3832
|
collectionName: z$1.string(),
|
|
3204
|
-
query: z$1.looseObject({}),
|
|
3205
|
-
|
|
3833
|
+
query: z$1.looseObject({}).superRefine((filter, ctx) => {
|
|
3834
|
+
for (const key of findDisallowedSubscriptionFilterKeys(filter)) ctx.addIssue({
|
|
3835
|
+
code: "custom",
|
|
3836
|
+
message: `Disallowed subscription filter: ${key}`
|
|
3837
|
+
});
|
|
3838
|
+
}),
|
|
3839
|
+
fields: z$1.record(z$1.string(), z$1.union([z$1.boolean(), z$1.number()])),
|
|
3206
3840
|
fetchInitialData: z$1.boolean().prefault(true).optional(),
|
|
3207
3841
|
clientId: z$1.string().optional()
|
|
3208
3842
|
});
|
|
@@ -3289,6 +3923,17 @@ const DataSubscriptionUpdateAction = z$1.object({
|
|
|
3289
3923
|
id: z$1.string()
|
|
3290
3924
|
})
|
|
3291
3925
|
});
|
|
3926
|
+
/**
|
|
3927
|
+
* Server -> Client: a `subscribe_query` frame was refused (the operator allow-list, `fields`
|
|
3928
|
+
* validation) or its initial fetch was aborted (`maxTimeMS`). Neither failure throws an
|
|
3929
|
+
* UnauthorizedError/JsonWebTokenError, so withWebSocketContext's status code never reaches the
|
|
3930
|
+
* client as a frame - this is the only signal the caller gets that the subscription never took.
|
|
3931
|
+
*/
|
|
3932
|
+
const DataSubscribeErrorAction = z$1.object({
|
|
3933
|
+
action: z$1.literal("data_subscribe_error"),
|
|
3934
|
+
subscriptionId: z$1.string(),
|
|
3935
|
+
error: z$1.string()
|
|
3936
|
+
});
|
|
3292
3937
|
const LLMStatusUpdateAction = z$1.object({
|
|
3293
3938
|
action: z$1.literal("llm_status_update"),
|
|
3294
3939
|
status: z$1.string().nullable(),
|
|
@@ -4521,6 +5166,7 @@ const OptiHashiRunUpdatedAction = z$1.object({
|
|
|
4521
5166
|
});
|
|
4522
5167
|
z$1.discriminatedUnion("action", [
|
|
4523
5168
|
DataSubscriptionUpdateAction,
|
|
5169
|
+
DataSubscribeErrorAction,
|
|
4524
5170
|
InboxRefetchAction,
|
|
4525
5171
|
LLMStatusUpdateAction,
|
|
4526
5172
|
InvitesRefetchAction,
|
|
@@ -4579,50 +5225,125 @@ z$1.discriminatedUnion("action", [
|
|
|
4579
5225
|
ReconnectResultAction
|
|
4580
5226
|
]);
|
|
4581
5227
|
z$1.object({
|
|
4582
|
-
|
|
4583
|
-
message: z$1.string(),
|
|
4584
|
-
|
|
5228
|
+
session_id: z$1.string().min(1),
|
|
5229
|
+
message: z$1.string().min(1),
|
|
5230
|
+
/** Falls back to the deployment's default chat model when omitted. */
|
|
4585
5231
|
model: z$1.string().optional(),
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
5232
|
+
/**
|
|
5233
|
+
* Run as a specific persisted agent. Omit to let the executor pick the profile for
|
|
5234
|
+
* the session: a session on a dedicated surface gets that surface's own profile,
|
|
5235
|
+
* otherwise a synthetic one built from admin orchestration defaults. Omitting this
|
|
5236
|
+
* is what reproduces the product UI's Agent Mode toggle.
|
|
5237
|
+
*/
|
|
5238
|
+
agent_id: z$1.string().optional(),
|
|
5239
|
+
/**
|
|
5240
|
+
* Bill this run to an organization's credit pool. The caller must belong to it; a
|
|
5241
|
+
* non-member gets 404. Omit to bill the caller personally.
|
|
5242
|
+
*/
|
|
5243
|
+
organization_id: z$1.string().optional(),
|
|
5244
|
+
/**
|
|
5245
|
+
* Tool-id allowlist for the run. Omit to use the resolved profile's own list.
|
|
5246
|
+
*
|
|
5247
|
+
* Doubles as pre-approval: REST runs have no interactive client to answer a
|
|
5248
|
+
* permission prompt, so tools named here are treated as approved. A run that calls
|
|
5249
|
+
* an approval-gated tool NOT named here fails with that tool named in `error`.
|
|
5250
|
+
*/
|
|
4596
5251
|
tools: z$1.array(z$1.string()).optional(),
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
5252
|
+
/**
|
|
5253
|
+
* Hard ceiling on ReAct iterations. Each one is a full LLM round-trip, so the cap is
|
|
5254
|
+
* bounded at 100 regardless of what the profile would allow.
|
|
5255
|
+
*/
|
|
5256
|
+
max_iterations: z$1.number().int().positive().max(100).optional(),
|
|
5257
|
+
temperature: z$1.number().min(0).max(2).optional(),
|
|
5258
|
+
max_tokens: z$1.number().int().positive().optional(),
|
|
5259
|
+
thinking: z$1.object({
|
|
5260
|
+
enabled: z$1.boolean(),
|
|
5261
|
+
/** Bounded at 32000: Anthropic rejects rather than clamps an oversized budget. */
|
|
5262
|
+
budget_tokens: z$1.number().int().positive().max(32e3).optional()
|
|
5263
|
+
}).optional(),
|
|
5264
|
+
/** Per-message file attachments (fabFile ids), materialized into the first iteration. */
|
|
5265
|
+
file_ids: z$1.array(z$1.string()).optional(),
|
|
5266
|
+
/** Workbench-level file ids for the session, forwarded as a dispatch-time snapshot. */
|
|
5267
|
+
session_file_ids: z$1.array(z$1.string()).optional(),
|
|
5268
|
+
enable_mementos: z$1.boolean().optional(),
|
|
5269
|
+
enable_lattice: z$1.boolean().optional(),
|
|
5270
|
+
/**
|
|
5271
|
+
* Opt out of the artifact-emission prompt and artifact persistence for this run.
|
|
5272
|
+
*
|
|
5273
|
+
* ANDed with the deployment's admin `EnableArtifacts` setting, so this can only ever
|
|
5274
|
+
* withhold artifacts, never force them on. Omitting it means "no preference" and
|
|
5275
|
+
* leaves the admin setting as the only gate; only an explicit `false` opts out.
|
|
5276
|
+
* Inherited by any subagent this run dispatches, so a delegating agent cannot route
|
|
5277
|
+
* around the opt-out.
|
|
5278
|
+
*
|
|
5279
|
+
* Worth setting on a REST run: the emission prompt costs roughly 2.8k tokens per
|
|
5280
|
+
* iteration, and nothing on this transport renders an artifact back to a human.
|
|
5281
|
+
*/
|
|
5282
|
+
enable_artifacts: z$1.boolean().optional()
|
|
4607
5283
|
});
|
|
4608
5284
|
z$1.object({
|
|
4609
5285
|
id: z$1.string(),
|
|
4610
|
-
status: z$1.
|
|
4611
|
-
|
|
4612
|
-
timestamp: z$1.string(),
|
|
5286
|
+
status: z$1.literal("pending"),
|
|
5287
|
+
session_id: z$1.string(),
|
|
4613
5288
|
model: z$1.string(),
|
|
4614
|
-
|
|
5289
|
+
timestamp: z$1.string(),
|
|
4615
5290
|
tracking_info: z$1.object({
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
5291
|
+
execution_id: z$1.string(),
|
|
5292
|
+
/**
|
|
5293
|
+
* The chat-history Quest holding the prompt, which gains the reply when the run
|
|
5294
|
+
* completes. Absent when that best-effort write failed; the run still proceeds.
|
|
5295
|
+
*/
|
|
5296
|
+
quest_id: z$1.string().optional(),
|
|
5297
|
+
poll_url: z$1.string()
|
|
4619
5298
|
})
|
|
4620
5299
|
});
|
|
4621
|
-
/**
|
|
4622
|
-
const
|
|
4623
|
-
|
|
4624
|
-
|
|
5300
|
+
/** One step of a published reasoning trace - a public projection of `IAgentStep`. */
|
|
5301
|
+
const AgentExecutionStepSchema = z$1.object({
|
|
5302
|
+
type: z$1.enum([
|
|
5303
|
+
"thought",
|
|
5304
|
+
"action",
|
|
5305
|
+
"observation",
|
|
5306
|
+
"final_answer"
|
|
5307
|
+
]),
|
|
5308
|
+
content: z$1.string(),
|
|
5309
|
+
/** 0-indexed iteration. Absent on traces checkpointed before the field existed. */
|
|
5310
|
+
iteration: z$1.number().int().nonnegative().optional(),
|
|
5311
|
+
/** Set on `action` steps: the tool the agent invoked. */
|
|
5312
|
+
tool_name: z$1.string().optional()
|
|
4625
5313
|
});
|
|
5314
|
+
z$1.object({
|
|
5315
|
+
id: z$1.string(),
|
|
5316
|
+
status: z$1.enum([
|
|
5317
|
+
"pending",
|
|
5318
|
+
"running",
|
|
5319
|
+
"continuing",
|
|
5320
|
+
"awaiting_permission",
|
|
5321
|
+
"awaiting_subagent",
|
|
5322
|
+
"awaiting_dag_children",
|
|
5323
|
+
"paused",
|
|
5324
|
+
"completed",
|
|
5325
|
+
"failed",
|
|
5326
|
+
"aborted"
|
|
5327
|
+
]),
|
|
5328
|
+
session_id: z$1.string().nullable(),
|
|
5329
|
+
answer: z$1.string().nullable(),
|
|
5330
|
+
/**
|
|
5331
|
+
* Why the run ended without an answer. Set only on `failed`; null otherwise.
|
|
5332
|
+
* Without this a caller polling a terminal run sees `failed` + a null answer and
|
|
5333
|
+
* cannot tell an approval-gated tool from a model error from a timeout.
|
|
5334
|
+
*
|
|
5335
|
+
* Approval-gate failures name the offending tool, since that is what the caller acts
|
|
5336
|
+
* on. Everything else is reduced to a coarse category (billing, rate limit, timeout,
|
|
5337
|
+
* auth) or a generic message: the stored reason is a raw internal exception, and
|
|
5338
|
+
* those carry infrastructure identifiers. Full detail stays in the server logs.
|
|
5339
|
+
*/
|
|
5340
|
+
error: z$1.string().nullable(),
|
|
5341
|
+
steps: z$1.array(AgentExecutionStepSchema),
|
|
5342
|
+
total_iterations: z$1.number().nullable(),
|
|
5343
|
+
created_at: z$1.string(),
|
|
5344
|
+
updated_at: z$1.string()
|
|
5345
|
+
});
|
|
5346
|
+
z$1.object({ id: z$1.string().min(1) });
|
|
4626
5347
|
/**
|
|
4627
5348
|
* Tool schema matching ICompletionOptionTools.toolSchema. The Zod surface only
|
|
4628
5349
|
* covers wire-format fields (toolFn is server-side). Replaces the historical
|
|
@@ -4716,6 +5437,7 @@ const CompletionContentEventSchema = z$1.object({
|
|
|
4716
5437
|
"tool_use",
|
|
4717
5438
|
"best-effort"
|
|
4718
5439
|
]).optional(),
|
|
5440
|
+
stopReason: z$1.string().optional(),
|
|
4719
5441
|
thinking: z$1.array(z$1.any()).optional()
|
|
4720
5442
|
});
|
|
4721
5443
|
const CompletionSseErrorEventSchema = z$1.object({
|
|
@@ -5294,7 +6016,7 @@ const BFL_SAFETY_TOLERANCE = {
|
|
|
5294
6016
|
*/
|
|
5295
6017
|
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));
|
|
5296
6018
|
/**
|
|
5297
|
-
* List of image models supported by
|
|
6019
|
+
* List of image models supported by Black Forest Labs
|
|
5298
6020
|
*/
|
|
5299
6021
|
const BFL_IMAGE_MODELS = [
|
|
5300
6022
|
"flux-pro-1.1",
|
|
@@ -5595,6 +6317,32 @@ z$1.enum([
|
|
|
5595
6317
|
"complex"
|
|
5596
6318
|
]);
|
|
5597
6319
|
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);
|
|
6320
|
+
const SessionTagSchema = z$1.object({
|
|
6321
|
+
name: z$1.string(),
|
|
6322
|
+
strength: z$1.number()
|
|
6323
|
+
});
|
|
6324
|
+
z$1.object({
|
|
6325
|
+
name: z$1.string().min(1).optional(),
|
|
6326
|
+
knowledgeIds: z$1.array(z$1.string()).optional(),
|
|
6327
|
+
artifactIds: z$1.array(z$1.string()).optional(),
|
|
6328
|
+
tags: z$1.array(SessionTagSchema).optional(),
|
|
6329
|
+
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."),
|
|
6330
|
+
forceKnowledgeRetrieval: z$1.boolean().optional(),
|
|
6331
|
+
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.")
|
|
6332
|
+
});
|
|
6333
|
+
z$1.object({ id: z$1.string().min(1) });
|
|
6334
|
+
z$1.object({
|
|
6335
|
+
id: z$1.string(),
|
|
6336
|
+
name: z$1.string(),
|
|
6337
|
+
userId: z$1.string(),
|
|
6338
|
+
knowledgeIds: z$1.array(z$1.string()).optional(),
|
|
6339
|
+
artifactIds: z$1.array(z$1.string()).optional(),
|
|
6340
|
+
tags: z$1.array(SessionTagSchema).optional(),
|
|
6341
|
+
forceKnowledgeRetrieval: z$1.boolean().optional(),
|
|
6342
|
+
lastUsedModel: z$1.string().nullish(),
|
|
6343
|
+
firstCreated: z$1.date(),
|
|
6344
|
+
lastUpdated: z$1.date()
|
|
6345
|
+
});
|
|
5598
6346
|
z$2.enum([
|
|
5599
6347
|
"",
|
|
5600
6348
|
"TFG",
|
|
@@ -5662,16 +6410,200 @@ z$2.object({
|
|
|
5662
6410
|
isOnline: z$2.boolean()
|
|
5663
6411
|
});
|
|
5664
6412
|
/**
|
|
5665
|
-
*
|
|
5666
|
-
*
|
|
5667
|
-
*
|
|
5668
|
-
*
|
|
5669
|
-
*
|
|
5670
|
-
|
|
6413
|
+
* A chunk larger than this (in tokens) marks a file whose chunking predates the passage-target
|
|
6414
|
+
* fix: a whole-document / whole-section blob rather than a ~512-token passage. Used to detect the
|
|
6415
|
+
* files a lake "Rebuild passages" pass should re-chunk. Deliberately well above
|
|
6416
|
+
* DEFAULT_PASSAGE_TOKEN_TARGET (512) so a correctly-chunked passage never trips it, and below the
|
|
6417
|
+
* ~6.5K model-window packing the old chunker produced, so every legacy blob does.
|
|
6418
|
+
*/
|
|
6419
|
+
const OVERSIZED_PASSAGE_TOKEN_THRESHOLD = 1500;
|
|
6420
|
+
/**
|
|
6421
|
+
* Why a file's chunk/vector pipeline is STALLED, stored in `FabFile.chunkStallReason`.
|
|
6422
|
+
*
|
|
6423
|
+
* - `vectorizePaused`: the data-lake convergence kill switch abandoned a vectorize (#1676). The
|
|
6424
|
+
* file keeps its chunks but has no vectors, so it is unsearchable until re-indexed.
|
|
6425
|
+
* - `rechunkPaused`: the OTHER half of the same switch dropped a re-chunk before it ran
|
|
6426
|
+
* (#1676/#1681). The damage is worse - the producer resets a wave's chunk state BEFORE the
|
|
6427
|
+
* messages are handled, so a file halted here has NO chunks at all.
|
|
6428
|
+
* - `unchunkedPaused`: the same chunk half, on a file that never had passages to lose. The rescue
|
|
6429
|
+
* sweep selects on `chunkCount: 0` and enqueues without resetting anything, so a file it routes
|
|
6430
|
+
* into the halt branch arrives already empty. The halted STATE is identical to `rechunkPaused`
|
|
6431
|
+
* and every reader keys on both (CHUNKLESS_STALL_REASONS) - the split exists so the owner is not
|
|
6432
|
+
* told that passages were removed which never existed.
|
|
6433
|
+
*
|
|
6434
|
+
* None auto-resumes; each needs a reprocess or a lifted switch.
|
|
6435
|
+
*
|
|
6436
|
+
* Without a marker the state is misread by every surface at once, which is the failure the field
|
|
6437
|
+
* exists to prevent: `chunkCount: 0` with `error: null` reads as an image or a pending upload, so
|
|
6438
|
+
* health drops it from the denominator, convergence grades it `conformant` (its stale stamp still
|
|
6439
|
+
* matches), and search does not withhold it because it is not "in flight". The file's passages are
|
|
6440
|
+
* simply gone and nothing REPORTS it. The rescue sweep is the one exception and deliberately so: an
|
|
6441
|
+
* unmarked file matches its filter and gets re-chunked, which is repair rather than reporting. That
|
|
6442
|
+
* is why the sweep excludes a stalled file only while the switch is ON - see
|
|
6443
|
+
* buildFabFileChunkScanFilter.
|
|
6444
|
+
*
|
|
6445
|
+
* A dedicated field rather than prose in `FabFile.notes` (#2016): `notes` is the USER's note, and
|
|
6446
|
+
* while the markers lived there every writer of the field clobbered the others - a "Rebuild
|
|
6447
|
+
* passages" wave silently deleted whatever the owner had typed.
|
|
6448
|
+
*
|
|
6449
|
+
* Lives here rather than beside its writers (apps/client's chunk and vectorize handlers) because it
|
|
6450
|
+
* is a cross-layer contract: the queue handlers write it and b4m-core's evaluators
|
|
6451
|
+
* (constants/lakeHealth.ts, constants/lakeConvergence.ts, dataLakeService/retrievalUnavailable.ts)
|
|
6452
|
+
* read it to tell a permanently-stalled file from one still in flight. b4m-core cannot import from
|
|
6453
|
+
* apps/client, so a copy there would have to drift silently.
|
|
6454
|
+
*/
|
|
6455
|
+
const CHUNK_STALL_REASONS = [
|
|
6456
|
+
"vectorizePaused",
|
|
6457
|
+
"rechunkPaused",
|
|
6458
|
+
"unchunkedPaused"
|
|
6459
|
+
];
|
|
6460
|
+
/**
|
|
6461
|
+
* Whether a file is stalled by the convergence kill switch, by any arm. THE predicate every
|
|
6462
|
+
* reader uses, so adding a stall reason reaches health, convergence and retrieval without separate
|
|
6463
|
+
* comparisons drifting apart. Also the in-memory mirror of a Mongo
|
|
6464
|
+
* `chunkStallReason: { $in: [...CHUNK_STALL_REASONS] }`.
|
|
6465
|
+
*/
|
|
6466
|
+
function isChunkStalled(reason) {
|
|
6467
|
+
return CHUNK_STALL_REASONS.includes(reason);
|
|
6468
|
+
}
|
|
6469
|
+
/**
|
|
6470
|
+
* Which reasons leave the file with NO passages, as opposed to passages with no vectors. A `Record`
|
|
6471
|
+
* over every reason rather than a hand-written subset array: a new stall reason then cannot compile
|
|
6472
|
+
* until it is classified, where a member missing from a literal array would just make a health count
|
|
6473
|
+
* silently wrong.
|
|
6474
|
+
*/
|
|
6475
|
+
const STALL_LEAVES_NO_PASSAGES = {
|
|
6476
|
+
vectorizePaused: false,
|
|
6477
|
+
rechunkPaused: true,
|
|
6478
|
+
unchunkedPaused: true
|
|
6479
|
+
};
|
|
6480
|
+
CHUNK_STALL_REASONS.filter((reason) => STALL_LEAVES_NO_PASSAGES[reason]);
|
|
6481
|
+
/**
|
|
6482
|
+
* Owner-facing prose for a stall reason, and the ONLY place it is worded.
|
|
6483
|
+
*
|
|
6484
|
+
* `vectorizePaused` and `rechunkPaused` are the exact strings those markers used while they lived in
|
|
6485
|
+
* `notes`, which is also what the #2016 migration matches on to derive the field for existing rows -
|
|
6486
|
+
* do not reword either without updating it. `unchunkedPaused` postdates that migration and was never
|
|
6487
|
+
* written to `notes`, so its wording is free to change: nothing matches on it.
|
|
5671
6488
|
*/
|
|
5672
|
-
const
|
|
6489
|
+
const CHUNK_STALL_NOTICES = {
|
|
6490
|
+
vectorizePaused: "Indexing paused by the data-lake convergence kill switch - reprocess to complete.",
|
|
6491
|
+
rechunkPaused: "Re-chunking paused by the data-lake convergence kill switch - its passages were removed and are rebuilt when convergence resumes.",
|
|
6492
|
+
unchunkedPaused: "Chunking paused by the data-lake convergence kill switch - this file has no passages yet and they are built when convergence resumes."
|
|
6493
|
+
};
|
|
6494
|
+
CHUNK_STALL_NOTICES.vectorizePaused;
|
|
6495
|
+
CHUNK_STALL_NOTICES.rechunkPaused;
|
|
6496
|
+
/**
|
|
6497
|
+
* TRANSITIONAL, and the ONE stall predicate every RETRIEVAL path must use until #2016's migration
|
|
6498
|
+
* has run in every environment. Reads the new field, then falls back to the legacy prose that the
|
|
6499
|
+
* pre-migration rows still carry in `notes`.
|
|
6500
|
+
*
|
|
6501
|
+
* It exists for the FORWARD window only: `migratorInvocation` is a `dependsOn` of the web stack
|
|
6502
|
+
* only (infra/web.ts); the queue stack has none, so the executor can serve forced retrieval and
|
|
6503
|
+
* `knowledge_base_search` while rows still carry the marker in `notes` and no `chunkStallReason`. A
|
|
6504
|
+
* row stalled by the chunk arm then reads as a plain unindexed file: `isRetrievalExcluded` drops it
|
|
6505
|
+
* upstream of the withhold on a vectorizedOnly lake, and `partitionByIndexAvailability` calls it
|
|
6506
|
+
* servable everywhere else. The turn answers around a passage-less file and reports FULL coverage -
|
|
6507
|
+
* the silent degradation this whole path exists to prevent.
|
|
6508
|
+
*
|
|
6509
|
+
* A code ROLLBACK is the mirror image and this arm CANNOT cover it: the rows are already migrated
|
|
6510
|
+
* (`chunkStallReason` set, `notes` unset) and the code restored is pre-#2016, which does not contain
|
|
6511
|
+
* this function. Nothing reverts the data on its own either - `migratorInvocation` only ever runs
|
|
6512
|
+
* `up` and `migrate down` is a manual CLI step - so `migrate down` is a REQUIRED step of any
|
|
6513
|
+
* rollback past #2016, not an optional tidy-up. What this arm does buy is that `down()` is safe to
|
|
6514
|
+
* run FIRST: whichever stack is still new keeps honoring the prose it restores, so a staggered
|
|
6515
|
+
* rollback has no window where a restored marker is invisible. `down()` is a PARTIAL restore
|
|
6516
|
+
* though - it skips a row whose owner typed a note after `up()`, and that row grades as unstalled
|
|
6517
|
+
* on both stacks once the field is dropped. See its own comment.
|
|
6518
|
+
*
|
|
6519
|
+
* Deliberately NOT used by the grading/health/UI readers: they are gated behind the web stack, and
|
|
6520
|
+
* a legacy row there renders the notice line AND the identical text as the owner's note.
|
|
6521
|
+
*
|
|
6522
|
+
* Mirrored in Mongo by `buildFabFileSearchQuery`'s `vectorizedOnly` exemption. Delete the legacy arm
|
|
6523
|
+
* from both together, one release after the migration has landed everywhere.
|
|
6524
|
+
*
|
|
6525
|
+
* Pinned to the two reasons the migration backfilled rather than every notice: `unchunkedPaused`
|
|
6526
|
+
* postdates it, so no row carries its prose, and including it would read an owner who happens to type
|
|
6527
|
+
* that sentence into `notes` as stalled.
|
|
6528
|
+
*/
|
|
6529
|
+
const LEGACY_CHUNK_STALL_NOTES = [CHUNK_STALL_NOTICES.vectorizePaused, CHUNK_STALL_NOTICES.rechunkPaused];
|
|
6530
|
+
function isChunkStalledFile(file) {
|
|
6531
|
+
return isChunkStalled(file.chunkStallReason) || LEGACY_CHUNK_STALL_NOTES.includes(file.notes ?? "");
|
|
6532
|
+
}
|
|
6533
|
+
/**
|
|
6534
|
+
* `FabFile.chunkRebuildRequestedAt`: stamped by `resetChunkStateByIds` in the SAME write that
|
|
6535
|
+
* clears a file's chunk rollups, so "this file's passages are being rebuilt" can never be lost the
|
|
6536
|
+
* way the pair of steps that creates the state can be. The reset and the queue send are two
|
|
6537
|
+
* operations - kill the producer between them, or lose the consumer's marker write, and the file
|
|
6538
|
+
* sits at `chunkCount: 0` with `error: null` and no stall reason, a shape indistinguishable from an
|
|
6539
|
+
* image or a still-uploading row. It then drops out of lake health's denominator, out of the
|
|
6540
|
+
* convergence plan and out of the retrieval withhold at the same moment: every rollup says its
|
|
6541
|
+
* passages are gone, and nothing reports it.
|
|
6542
|
+
*
|
|
6543
|
+
* Deliberately NOT the `rechunkPaused` stall reason pre-written by the producer, which is the obvious
|
|
6544
|
+
* fix and the wrong one: that marker means "halted, needs an administrator", so a file awaiting an
|
|
6545
|
+
* ORDINARY rebuild would read to every reader as permanently paused for the whole rebuild - search
|
|
6546
|
+
* would tell readers it does not return on its own, health would hard-fail P3, and "Rebuild
|
|
6547
|
+
* passages" would offer to repair a file that is already repairing. A flag that cries wolf on the
|
|
6548
|
+
* normal path is worse than the rare window it closes.
|
|
6549
|
+
*
|
|
6550
|
+
* So the two facts are distinct states, and the consumer UPGRADES one to the other: pending means
|
|
6551
|
+
* "in flight, returns on its own", the paused note means "halted, needs intervention". A LOST
|
|
6552
|
+
* upgrade therefore degrades to mislabelled-but-visible rather than invisible, which is the trade
|
|
6553
|
+
* this field exists to make - invisibility is the real harm, labelling is secondary.
|
|
6554
|
+
*
|
|
6555
|
+
* A dedicated field on purpose, and the precedent #2016 followed for the other two machine-written
|
|
6556
|
+
* facts: while they all shared `notes` every writer of that field clobbered the others, including
|
|
6557
|
+
* the user's own note.
|
|
6558
|
+
*
|
|
6559
|
+
* Cleared by `commitFabFileChunks` (the rebuild landed) and by the chunk handler's pause write (the
|
|
6560
|
+
* rebuild was halted instead). A file carrying `error` is settled regardless - see
|
|
6561
|
+
* `isMemberIndexingInFlight`, which is where the precedence between these three lives.
|
|
6562
|
+
*/
|
|
6563
|
+
function isChunkRebuildPending(requestedAt) {
|
|
6564
|
+
return requestedAt !== null && requestedAt !== void 0 && requestedAt !== "";
|
|
6565
|
+
}
|
|
5673
6566
|
/** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
|
|
5674
6567
|
const LAKE_ACCESS_AUDIT_RETENTION_MAX_DAYS = 2555;
|
|
6568
|
+
/**
|
|
6569
|
+
* Lake CONFIG-change audit retention and value caps.
|
|
6570
|
+
*
|
|
6571
|
+
* Twin of `constants/lakeAccessAudit.ts` and here for the same reason: the values are needed by
|
|
6572
|
+
* both the admin-settings schema in this package and by the repository that applies them
|
|
6573
|
+
* (`packages/database`, which cannot import an app-server layer).
|
|
6574
|
+
*
|
|
6575
|
+
* DELIBERATELY NOT the same numbers as the read audit, and deliberately not the same lever. A
|
|
6576
|
+
* retrieval is frequent, low-value and cheap to lose; a config change is rare, high-value and
|
|
6577
|
+
* alters every future answer the lake gives, so the two want opposite retention. Folding config
|
|
6578
|
+
* changes into the read collection would force one of them onto the other's clock - either a
|
|
6579
|
+
* config change expiring on a read-volume schedule, or a high-volume collection's storage
|
|
6580
|
+
* multiplied to serve a low-volume need.
|
|
6581
|
+
*
|
|
6582
|
+
* Same one-way ratchet as the read side (#1658's levers rule): `resolveLakeConfigAuditRetentionDays`
|
|
6583
|
+
* can only raise a configured value to the floor, never lower it, so an org cannot configure its
|
|
6584
|
+
* own retention down to nothing and defeat the control. Platform-level, not per-org: the setting
|
|
6585
|
+
* carries no `scope.settableAt`, so #1660's resolver returns the platform value at every scope.
|
|
6586
|
+
*/
|
|
6587
|
+
/** 3 years: long enough to outlive a typical contract and audit cycle, on a collection whose
|
|
6588
|
+
* volume is a few rows per lake per year. Longer than the read audit's 450 days on purpose - the
|
|
6589
|
+
* rarer and more consequential the event, the longer it is worth keeping. */
|
|
6590
|
+
const LAKE_CONFIG_AUDIT_RETENTION_FLOOR_DAYS = 1095;
|
|
6591
|
+
/** A separate constant from the floor on purpose, even though they agree today - a future change
|
|
6592
|
+
* to one must not silently move the other. */
|
|
6593
|
+
const LAKE_CONFIG_AUDIT_RETENTION_DEFAULT_DAYS = 1095;
|
|
6594
|
+
/** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
|
|
6595
|
+
const LAKE_CONFIG_AUDIT_RETENTION_MAX_DAYS = 3650;
|
|
6596
|
+
/**
|
|
6597
|
+
* Forced-retrieval budget and relevance defaults, shared between the admin-settings schema in this
|
|
6598
|
+
* package and `ChatCompletionFeatures.ts` (which cannot import from `common`'s settings schema
|
|
6599
|
+
* without a dependency cycle, so the constants live here instead).
|
|
6600
|
+
*
|
|
6601
|
+
* All three are levers. The char budget is the measured binding constraint on how much of a corpus
|
|
6602
|
+
* reaches the model on every Data-Lake-mode turn; the two floors decide which passages are eligible
|
|
6603
|
+
* to spend it (see `forcedRetrievalRelativeFloorPct` and `forcedRetrievalMinSimilarityPct`).
|
|
6604
|
+
*/
|
|
6605
|
+
/** Total characters of retrieved chunk text injected into a forced-retrieval prompt. */
|
|
6606
|
+
const FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT = 12e3;
|
|
5675
6607
|
let OpenAIEmbeddingModel = /* @__PURE__ */ function(OpenAIEmbeddingModel) {
|
|
5676
6608
|
OpenAIEmbeddingModel["TEXT_EMBEDDING_3_SMALL"] = "text-embedding-3-small";
|
|
5677
6609
|
OpenAIEmbeddingModel["TEXT_EMBEDDING_3_LARGE"] = "text-embedding-3-large";
|
|
@@ -5826,6 +6758,76 @@ const HELP_CENTER_PROMPT = `HELP CENTER: Bike4Mind has a built-in Help Center th
|
|
|
5826
6758
|
*/
|
|
5827
6759
|
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.`;
|
|
5828
6760
|
/**
|
|
6761
|
+
* Default text for the web-search freshness nudge, and the `WebSearchFreshnessPrompt` admin
|
|
6762
|
+
* setting's default.
|
|
6763
|
+
*
|
|
6764
|
+
* Unlike ABSTENTION_PROMPT / ARTIFACT_EMISSION_PROMPT / HELP_CENTER_PROMPT, this setting
|
|
6765
|
+
* distinguishes an absent row from a cleared one. ChatCompletionProcess reads it 2-arg, so an
|
|
6766
|
+
* absent row still falls back to this constant as the setting's registered default, but a cleared
|
|
6767
|
+
* '' is returned verbatim and drops the section rather than reverting. The siblings are read 3-arg
|
|
6768
|
+
* and collapse both cases to the constant. That divergence is deliberate - this section has no
|
|
6769
|
+
* companion boolean, so clearing the field is the only off switch it has. Keep the setting's
|
|
6770
|
+
* description in sync with that if either changes.
|
|
6771
|
+
*
|
|
6772
|
+
* Names no tool but `web_search`: the section is gated on web_search being offered, and web_fetch
|
|
6773
|
+
* is an independent toggle that may well be off.
|
|
6774
|
+
*/
|
|
6775
|
+
const WEB_SEARCH_FRESHNESS_PROMPT = `# WEB SEARCH AND FRESHNESS
|
|
6776
|
+
|
|
6777
|
+
Your training data has a cutoff. The current date is supplied to you in this conversation's system context - treat it as authoritative, and assume anything time-sensitive may have changed since your training.
|
|
6778
|
+
|
|
6779
|
+
Call \`web_search\` BEFORE answering when the answer depends on a fact that changes over time: current prices or rates, product availability or roadmap status, funding, organizational or personnel changes, published benchmarks or performance figures, competitive positioning, or anything the user frames as "current", "latest", "now", or "as of today". When a stale answer would mislead, search instead of answering from memory. When a search surfaces a specific page that matters, or the user names one, read that page directly rather than answering from the snippet.
|
|
6780
|
+
|
|
6781
|
+
You do not need to search for stable knowledge (definitions, mathematics, established theory), or for questions answerable purely from this conversation or from documents already retrieved for you.
|
|
6782
|
+
|
|
6783
|
+
When you report a time-sensitive fact, state what it is as of - the date of the source you used - and say plainly when you could not verify something and are answering from training data instead. Never present an unverified recollection as a current fact.`;
|
|
6784
|
+
/**
|
|
6785
|
+
* Default text for the knowledge-base retrieval nudge, and the `KnowledgeBaseRetrievalPrompt`
|
|
6786
|
+
* admin setting's default.
|
|
6787
|
+
*
|
|
6788
|
+
* The gap this closes: the tool prompt has a when-to-use section for the clock, for web search,
|
|
6789
|
+
* for MCP and for agent delegation, and none for the user's own corpus. The
|
|
6790
|
+
* `search_knowledge_base` description is entirely HOW to search ("Make ONE good search per
|
|
6791
|
+
* distinct topic") and never WHEN, so on the optional path the model decides unaided - and over 30
|
|
6792
|
+
* days of production it reached for the corpus on 20.1% of the turns it was offered on.
|
|
6793
|
+
*
|
|
6794
|
+
* Read 2-arg by ChatCompletionProcess, exactly as WEB_SEARCH_FRESHNESS_PROMPT is and unlike the
|
|
6795
|
+
* 3-arg siblings: an absent row falls back to this constant as the registered default, but a
|
|
6796
|
+
* cleared '' is returned verbatim and drops the section instead of reverting. Deliberate - the
|
|
6797
|
+
* section has no companion boolean, so clearing the field is its only off switch, and that off
|
|
6798
|
+
* switch is what makes it A/B-able without a deploy. Keep the setting's description in sync.
|
|
6799
|
+
*
|
|
6800
|
+
* Names no tool but `search_knowledge_base`, for the same reason the web-search section names no
|
|
6801
|
+
* `web_fetch`: the companion `retrieve_knowledge_content` is paired in at build time but a session
|
|
6802
|
+
* denylist can still strip it (ChatCompletionProcess warns on exactly that case), and instructing
|
|
6803
|
+
* the model to call a tool it was not given makes it emit the call as leaked JSON text.
|
|
6804
|
+
*
|
|
6805
|
+
* The "do not search" paragraph is load-bearing, not padding. A when-to-retrieve nudge without a
|
|
6806
|
+
* don't-retrieve clause buys retrieval on turns that need none - the same failure mode global
|
|
6807
|
+
* forced retrieval already shows on out-of-corpus questions, reached by a different route. Three of
|
|
6808
|
+
* its clauses are load-bearing for a specific co-resident path, not general hedging:
|
|
6809
|
+
* - "from an attached document" - a small attached corpus is INLINED rather than deferred to
|
|
6810
|
+
* retrieval (`shouldDeferCorpusToRetrieval`), and forced retrieval deliberately steps aside on
|
|
6811
|
+
* an attached-files turn (`forcedRetrievalAbstention` emits nothing there). Without this clause
|
|
6812
|
+
* the section tells the model to go searching for content already sitting in its context.
|
|
6813
|
+
* - "already been searched on this turn" - on a forced turn that found nothing,
|
|
6814
|
+
* `forcedRetrievalNoContextPrompt` instructs the model to say the library does not cover the
|
|
6815
|
+
* question. A nudge to search then invites a second identical query - a billed query embedding,
|
|
6816
|
+
* and a chance to talk itself out of a correct abstention.
|
|
6817
|
+
* - the opening scope, "unless its content has been placed in this conversation" - the reason the
|
|
6818
|
+
* first paragraph does not simply claim the documents are invisible, which is false whenever a
|
|
6819
|
+
* corpus was inlined.
|
|
6820
|
+
*/
|
|
6821
|
+
const KNOWLEDGE_BASE_RETRIEVAL_PROMPT = `# KNOWLEDGE BASE
|
|
6822
|
+
|
|
6823
|
+
\`search_knowledge_base\` searches a library of documents the user has made available to you - their own uploads, and any shared or organization library they can reach. You cannot see what a document holds unless its content has been placed in this conversation or you search for it; file names and tags are labels, not content.
|
|
6824
|
+
|
|
6825
|
+
Call \`search_knowledge_base\` BEFORE answering when that library would settle the question: anything about their organization, projects, customers, products, processes or people; a term, name, acronym or identifier that is not general public knowledge; a policy, decision, figure or date specific to them; or a question that assumes context this conversation never gave you. If you are about to answer in general terms a question the user means specifically, search first. A general-knowledge answer that sounds right is the failure this library exists to prevent.
|
|
6826
|
+
|
|
6827
|
+
Do not search when the answer is already in front of you or out of scope: general knowledge (definitions, mathematics, established theory, public facts); anything answerable from this conversation, from an attached document, or from content already retrieved for you this turn; or a request to transform, summarize or reformat text the user has just supplied. If the library has already been searched on this turn, do not search it again for the same question - a repeat spends a round trip to return the same passages.
|
|
6828
|
+
|
|
6829
|
+
When a search does not turn up what was asked for, say so plainly rather than filling the gap from training data, and never imply an answer came from the user's documents when it did not.`;
|
|
6830
|
+
/**
|
|
5829
6831
|
* Default text for the formatting system message. Runtime fallback used by
|
|
5830
6832
|
* `includeHardcodedSystemMessage` (b4m-core/utils/src/llm/utils.ts) when the `FormatPromptTemplate`
|
|
5831
6833
|
* admin setting is blank; that setting's own default is intentionally '' - keep this the sole home.
|
|
@@ -5858,6 +6860,8 @@ z$1.enum([
|
|
|
5858
6860
|
"ArtifactEmissionPrompt",
|
|
5859
6861
|
"HelpCenterPrompt",
|
|
5860
6862
|
"AbstentionPrompt",
|
|
6863
|
+
"WebSearchFreshnessPrompt",
|
|
6864
|
+
"KnowledgeBaseRetrievalPrompt",
|
|
5861
6865
|
"UseFormatPrompt",
|
|
5862
6866
|
"EnableQuestMaster",
|
|
5863
6867
|
"EnableQuestMasterDefault",
|
|
@@ -5876,12 +6880,16 @@ z$1.enum([
|
|
|
5876
6880
|
"EnableLattice",
|
|
5877
6881
|
"EnableLatticeDefault",
|
|
5878
6882
|
"EnableDataLakes",
|
|
5879
|
-
"EnableDataLakesDefault",
|
|
5880
6883
|
"EnableDataLakeSlackAdd",
|
|
5881
6884
|
"EnableDataLakeGroundingMode",
|
|
5882
6885
|
"EnableLakeMemory",
|
|
5883
6886
|
"EnableDataLakeVectorSearch",
|
|
6887
|
+
"EnableRetrievalSupersessionCollapse",
|
|
5884
6888
|
"PauseLakeConvergence",
|
|
6889
|
+
"LakeConvergenceBulkChangeSharePct",
|
|
6890
|
+
"EnforceLakeReadGrants",
|
|
6891
|
+
"EnableDataLakeDrivePoll",
|
|
6892
|
+
"EnforceLakeAdmission",
|
|
5885
6893
|
"EnableBriefcase",
|
|
5886
6894
|
"EnableBriefcaseDefault",
|
|
5887
6895
|
"EnableImageTemplates",
|
|
@@ -5907,6 +6915,7 @@ z$1.enum([
|
|
|
5907
6915
|
"ReferralCreditsAmount",
|
|
5908
6916
|
"registrationLink",
|
|
5909
6917
|
"FeedbackReceiveEmail",
|
|
6918
|
+
"FeedbackReceiveEmailNonProd",
|
|
5910
6919
|
"FeedbackKyle",
|
|
5911
6920
|
"EnableFeedBackToEmail",
|
|
5912
6921
|
"EnableFeedBackToSlack",
|
|
@@ -5972,15 +6981,26 @@ z$1.enum([
|
|
|
5972
6981
|
"defaultEmbeddingModel",
|
|
5973
6982
|
"dataLakeSearchMaxFiles",
|
|
5974
6983
|
"dataLakeSearchMaxChunks",
|
|
6984
|
+
"forcedRetrievalCharBudget",
|
|
6985
|
+
"lakeMemoryRecallK",
|
|
6986
|
+
"kbSearchDefaultResults",
|
|
6987
|
+
"kbSearchResultTokenBudget",
|
|
6988
|
+
"kbSearchMinRelevancePct",
|
|
6989
|
+
"forcedRetrievalRelativeFloorPct",
|
|
6990
|
+
"forcedRetrievalMinSimilarityPct",
|
|
5975
6991
|
"dataLakeEmbeddingSpendEnabled",
|
|
5976
6992
|
"dataLakeEmbeddingBudgetPerRunUsd",
|
|
5977
6993
|
"dataLakeEmbeddingBudgetPerLakeUsd",
|
|
5978
6994
|
"dataLakeEmbeddingBudgetPerPeriodUsd",
|
|
5979
6995
|
"dataLakeEmbeddingBudgetPeriodHours",
|
|
5980
6996
|
"dataLakeEmbeddingMaxCallsPerMinute",
|
|
6997
|
+
"dataLakeEmbeddingMaxTokensPerMinute",
|
|
5981
6998
|
"dataLakeVectorizeChunkBatchSize",
|
|
6999
|
+
"dataLakeEmbeddingTierMultiplierIndividual",
|
|
7000
|
+
"dataLakeEmbeddingTierMultiplierOrganization",
|
|
5982
7001
|
"LakeAccessAuditRetentionDays",
|
|
5983
7002
|
"LakeAccessQueryTextRetentionDays",
|
|
7003
|
+
"LakeConfigAuditRetentionDays",
|
|
5984
7004
|
"MaxContentLength",
|
|
5985
7005
|
"enableAutoChunk",
|
|
5986
7006
|
"SlackDefaultWebhookUrl",
|
|
@@ -5988,6 +7008,7 @@ z$1.enum([
|
|
|
5988
7008
|
"SlackLiveopsWebhookUrl",
|
|
5989
7009
|
"SlackUserActivityWebhookUrl",
|
|
5990
7010
|
"SlackFeedbackWebhookUrl",
|
|
7011
|
+
"SlackNonProdFeedbackWebhookUrl",
|
|
5991
7012
|
"SlackEmailAuditWebhookUrl",
|
|
5992
7013
|
"slackSigningSecret",
|
|
5993
7014
|
"slackBotToken",
|
|
@@ -6022,7 +7043,6 @@ z$1.enum([
|
|
|
6022
7043
|
"EnableBmPiDefault",
|
|
6023
7044
|
"EnableBmPiJira",
|
|
6024
7045
|
"EnableOptiHashi",
|
|
6025
|
-
"EnableOptiHashiDefault",
|
|
6026
7046
|
"EnableComputeSubmission",
|
|
6027
7047
|
"EnableFamilyCompute",
|
|
6028
7048
|
"EnableHybridCompute",
|
|
@@ -6052,7 +7072,7 @@ z$1.enum([
|
|
|
6052
7072
|
"modelDiscoveryAutoRemap",
|
|
6053
7073
|
"prReportRepo",
|
|
6054
7074
|
"prReportIdentityMap",
|
|
6055
|
-
"
|
|
7075
|
+
"prReportWebhookUrl",
|
|
6056
7076
|
"prReportEgressAllowlist"
|
|
6057
7077
|
]);
|
|
6058
7078
|
/**
|
|
@@ -6092,10 +7112,12 @@ const OrchestrationDefaultsSchema = z$1.object({
|
|
|
6092
7112
|
"mermaid_chart"
|
|
6093
7113
|
]),
|
|
6094
7114
|
/**
|
|
6095
|
-
* Tool names explicitly forbidden. Enforced as a final subtraction in
|
|
6096
|
-
* `pickEffectiveEnabledTools`
|
|
6097
|
-
*
|
|
6098
|
-
*
|
|
7115
|
+
* Tool names explicitly forbidden. Enforced in two places: as a final subtraction in
|
|
7116
|
+
* `pickEffectiveEnabledTools` (wins even over payload-pinned tools), and - for the two
|
|
7117
|
+
* delegation tools, which are injected as objects and never registered by name - at the
|
|
7118
|
+
* dependency gate in agentExecutor (`delegationOffer` withholds `agentStore` /
|
|
7119
|
+
* `dagDispatcher`). The name subtraction alone cannot reach those two; see
|
|
7120
|
+
* agentExecutor.sessionToolPolicy.
|
|
6099
7121
|
*
|
|
6100
7122
|
* Seeded with every tool that mutates user data (the spec's
|
|
6101
7123
|
* "anything tagged `mutates_user_data`"): destructive/overwriting filesystem
|
|
@@ -6173,8 +7195,30 @@ const DATA_LAKE_SEARCH_MAX_CHUNKS_DEFAULT = 1e5;
|
|
|
6173
7195
|
const DATA_LAKE_EMBEDDING_BUDGET_PER_LAKE_USD_MAX = 1e4;
|
|
6174
7196
|
const DATA_LAKE_EMBEDDING_BUDGET_PER_PERIOD_USD_MAX = 5e3;
|
|
6175
7197
|
const DATA_LAKE_EMBEDDING_MAX_CALLS_PER_MINUTE_MAX = 1e4;
|
|
7198
|
+
/**
|
|
7199
|
+
* The TOKEN half of the throughput cap, and the one that maps to what providers actually meter.
|
|
7200
|
+
* A call cap alone does not bound tokens: one call carries up to
|
|
7201
|
+
* DATA_LAKE_VECTORIZE_CHUNK_BATCH_SIZE_DEFAULT passages of DEFAULT_PASSAGE_TOKEN_TARGET tokens, so
|
|
7202
|
+
* 120 calls/min permits ~3.1M tokens/min - several times the smallest paid embeddings tier. The two
|
|
7203
|
+
* levers are complementary: calls/min bounds RPM, this bounds TPM, and a call must fit both.
|
|
7204
|
+
*
|
|
7205
|
+
* The default is deliberately LOW - it has to be safe on the smallest tier any deployment might
|
|
7206
|
+
* be on, including self-hosts nobody here can see. It is not a claim about what any particular
|
|
7207
|
+
* account can do, and reading it as one is the mistake to avoid: a provider tier is a property of
|
|
7208
|
+
* the provider organization, so it cannot be derived from this codebase at all.
|
|
7209
|
+
*
|
|
7210
|
+
* The real number is measurable per deployment: Admin -> Settings -> AI -> Data Lake Cost
|
|
7211
|
+
* Governance reads the configured provider's live ceiling (GET /api/admin/embedding-limits) and
|
|
7212
|
+
* shows it beside this lever, so an operator sets this from their own measured quota rather than
|
|
7213
|
+
* from a guess baked in here. Leave headroom below the measured ceiling for QUERY-side embedding,
|
|
7214
|
+
* which is exempt from this gate (see enforceEmbeddingSpendGate) and shares the same per-model
|
|
7215
|
+
* pool - a retrieval query must not queue behind a backfill.
|
|
7216
|
+
*/
|
|
7217
|
+
const DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_DEFAULT = 6e5;
|
|
7218
|
+
const DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_MAX = 5e7;
|
|
6176
7219
|
function makeNumberSetting(config) {
|
|
6177
7220
|
let numberSchema = z$1.coerce.number();
|
|
7221
|
+
if (config.int) numberSchema = numberSchema.int();
|
|
6178
7222
|
if (config.min !== void 0) numberSchema = numberSchema.min(config.min);
|
|
6179
7223
|
if (config.max !== void 0) numberSchema = numberSchema.max(config.max);
|
|
6180
7224
|
return {
|
|
@@ -6741,6 +7785,14 @@ const API_SERVICE_GROUPS = {
|
|
|
6741
7785
|
{
|
|
6742
7786
|
key: "AbstentionPrompt",
|
|
6743
7787
|
order: 11
|
|
7788
|
+
},
|
|
7789
|
+
{
|
|
7790
|
+
key: "WebSearchFreshnessPrompt",
|
|
7791
|
+
order: 12
|
|
7792
|
+
},
|
|
7793
|
+
{
|
|
7794
|
+
key: "KnowledgeBaseRetrievalPrompt",
|
|
7795
|
+
order: 13
|
|
6744
7796
|
}
|
|
6745
7797
|
]
|
|
6746
7798
|
},
|
|
@@ -6761,13 +7813,41 @@ const API_SERVICE_GROUPS = {
|
|
|
6761
7813
|
{
|
|
6762
7814
|
key: "dataLakeSearchMaxChunks",
|
|
6763
7815
|
order: 3
|
|
7816
|
+
},
|
|
7817
|
+
{
|
|
7818
|
+
key: "forcedRetrievalCharBudget",
|
|
7819
|
+
order: 4
|
|
7820
|
+
},
|
|
7821
|
+
{
|
|
7822
|
+
key: "kbSearchDefaultResults",
|
|
7823
|
+
order: 5
|
|
7824
|
+
},
|
|
7825
|
+
{
|
|
7826
|
+
key: "kbSearchResultTokenBudget",
|
|
7827
|
+
order: 6
|
|
7828
|
+
},
|
|
7829
|
+
{
|
|
7830
|
+
key: "kbSearchMinRelevancePct",
|
|
7831
|
+
order: 7
|
|
7832
|
+
},
|
|
7833
|
+
{
|
|
7834
|
+
key: "lakeMemoryRecallK",
|
|
7835
|
+
order: 8
|
|
7836
|
+
},
|
|
7837
|
+
{
|
|
7838
|
+
key: "forcedRetrievalRelativeFloorPct",
|
|
7839
|
+
order: 9
|
|
7840
|
+
},
|
|
7841
|
+
{
|
|
7842
|
+
key: "forcedRetrievalMinSimilarityPct",
|
|
7843
|
+
order: 10
|
|
6764
7844
|
}
|
|
6765
7845
|
]
|
|
6766
7846
|
},
|
|
6767
7847
|
DATA_LAKE_COST: {
|
|
6768
7848
|
id: "dataLakeCostGovernance",
|
|
6769
7849
|
name: "Data Lake Cost Governance",
|
|
6770
|
-
description: "Spend levers for data-lake embedding work (ingestion, reprocessing, convergence). Budgets are USD; 0 means stop spending, not \"use the default\".",
|
|
7850
|
+
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.",
|
|
6771
7851
|
icon: "Savings",
|
|
6772
7852
|
settings: [
|
|
6773
7853
|
{
|
|
@@ -6795,8 +7875,20 @@ const API_SERVICE_GROUPS = {
|
|
|
6795
7875
|
order: 6
|
|
6796
7876
|
},
|
|
6797
7877
|
{
|
|
6798
|
-
key: "
|
|
7878
|
+
key: "dataLakeEmbeddingMaxTokensPerMinute",
|
|
6799
7879
|
order: 7
|
|
7880
|
+
},
|
|
7881
|
+
{
|
|
7882
|
+
key: "dataLakeVectorizeChunkBatchSize",
|
|
7883
|
+
order: 8
|
|
7884
|
+
},
|
|
7885
|
+
{
|
|
7886
|
+
key: "dataLakeEmbeddingTierMultiplierIndividual",
|
|
7887
|
+
order: 9
|
|
7888
|
+
},
|
|
7889
|
+
{
|
|
7890
|
+
key: "dataLakeEmbeddingTierMultiplierOrganization",
|
|
7891
|
+
order: 10
|
|
6800
7892
|
}
|
|
6801
7893
|
]
|
|
6802
7894
|
},
|
|
@@ -7045,6 +8137,14 @@ const API_SERVICE_GROUPS = {
|
|
|
7045
8137
|
key: "SlackEmailAuditWebhookUrl",
|
|
7046
8138
|
order: 6.5
|
|
7047
8139
|
},
|
|
8140
|
+
{
|
|
8141
|
+
key: "SlackFeedbackWebhookUrl",
|
|
8142
|
+
order: 6.75
|
|
8143
|
+
},
|
|
8144
|
+
{
|
|
8145
|
+
key: "SlackNonProdFeedbackWebhookUrl",
|
|
8146
|
+
order: 6.8
|
|
8147
|
+
},
|
|
7048
8148
|
{
|
|
7049
8149
|
key: "FeedbackSendEmailUsername",
|
|
7050
8150
|
order: 7
|
|
@@ -7057,6 +8157,10 @@ const API_SERVICE_GROUPS = {
|
|
|
7057
8157
|
key: "FeedbackReceiveEmail",
|
|
7058
8158
|
order: 9
|
|
7059
8159
|
},
|
|
8160
|
+
{
|
|
8161
|
+
key: "FeedbackReceiveEmailNonProd",
|
|
8162
|
+
order: 9.5
|
|
8163
|
+
},
|
|
7060
8164
|
{
|
|
7061
8165
|
key: "liveFeedbackEmail",
|
|
7062
8166
|
order: 10
|
|
@@ -7181,10 +8285,6 @@ const API_SERVICE_GROUPS = {
|
|
|
7181
8285
|
key: "EnableOptiHashi",
|
|
7182
8286
|
order: 80
|
|
7183
8287
|
},
|
|
7184
|
-
{
|
|
7185
|
-
key: "EnableOptiHashiDefault",
|
|
7186
|
-
order: 81
|
|
7187
|
-
},
|
|
7188
8288
|
{
|
|
7189
8289
|
key: "EnableComputeSubmission",
|
|
7190
8290
|
order: 82
|
|
@@ -7572,16 +8672,23 @@ const API_SERVICE_GROUPS = {
|
|
|
7572
8672
|
},
|
|
7573
8673
|
DATA_LAKE_AUDIT: {
|
|
7574
8674
|
id: "dataLakeAuditService",
|
|
7575
|
-
name: "Data Lake
|
|
7576
|
-
description: "Retention for the lake
|
|
8675
|
+
name: "Data Lake Audit",
|
|
8676
|
+
description: "Retention for the lake audit trail: who READ a lake (plus the opt-in query-text log) and who CHANGED its configuration",
|
|
7577
8677
|
icon: "Security",
|
|
7578
|
-
settings: [
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
8678
|
+
settings: [
|
|
8679
|
+
{
|
|
8680
|
+
key: "LakeAccessAuditRetentionDays",
|
|
8681
|
+
order: 1
|
|
8682
|
+
},
|
|
8683
|
+
{
|
|
8684
|
+
key: "LakeAccessQueryTextRetentionDays",
|
|
8685
|
+
order: 2
|
|
8686
|
+
},
|
|
8687
|
+
{
|
|
8688
|
+
key: "LakeConfigAuditRetentionDays",
|
|
8689
|
+
order: 3
|
|
8690
|
+
}
|
|
8691
|
+
]
|
|
7585
8692
|
}
|
|
7586
8693
|
};
|
|
7587
8694
|
const settingsMap = {
|
|
@@ -7673,21 +8780,11 @@ const settingsMap = {
|
|
|
7673
8780
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7674
8781
|
order: 88
|
|
7675
8782
|
}),
|
|
7676
|
-
EnableDataLakesDefault: makeBooleanSetting({
|
|
7677
|
-
key: "EnableDataLakesDefault",
|
|
7678
|
-
name: "Data Lakes: On by default for users",
|
|
7679
|
-
defaultValue: false,
|
|
7680
|
-
description: "When enabled, Data Lakes is active for users who have never explicitly toggled it.",
|
|
7681
|
-
category: "Experimental",
|
|
7682
|
-
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7683
|
-
order: 89,
|
|
7684
|
-
dependsOn: "EnableDataLakes"
|
|
7685
|
-
}),
|
|
7686
8783
|
EnableDataLakeSlackAdd: makeBooleanSetting({
|
|
7687
8784
|
key: "EnableDataLakeSlackAdd",
|
|
7688
8785
|
name: "Data Lakes: Slack \"@datalake add\" path",
|
|
7689
|
-
defaultValue:
|
|
7690
|
-
description: "Server-side gate for adding content to a Data Lake from Slack via \"@datalake add\".
|
|
8786
|
+
defaultValue: true,
|
|
8787
|
+
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.",
|
|
7691
8788
|
category: "Experimental",
|
|
7692
8789
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7693
8790
|
order: 90,
|
|
@@ -7707,7 +8804,7 @@ const settingsMap = {
|
|
|
7707
8804
|
key: "EnableLakeMemory",
|
|
7708
8805
|
name: "Data Lakes: Lake memory profile (extraction)",
|
|
7709
8806
|
defaultValue: false,
|
|
7710
|
-
description: "
|
|
8807
|
+
description: "Master gate for lake memory, on all three sides: LLM extraction of a data lake's documents into a durable memory profile, recall of that profile into chats grounded in the lake, and whether the per-lake opt-in is offered at all. Off by default (measurement rollout). Turning it off stops recall immediately and stops new extractions from being queued or picked up, though a run already in flight finishes its slice (bounded by the handler timeout). It is NOT destructive - each lake keeps its own opt-in and its built profile, so flipping this back on resumes where it left off. Erasing a profile is a separate, explicit per-lake action.",
|
|
7711
8808
|
category: "Experimental",
|
|
7712
8809
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7713
8810
|
order: 91,
|
|
@@ -7723,11 +8820,21 @@ const settingsMap = {
|
|
|
7723
8820
|
order: 92,
|
|
7724
8821
|
dependsOn: "EnableDataLakes"
|
|
7725
8822
|
}),
|
|
8823
|
+
EnableRetrievalSupersessionCollapse: makeBooleanSetting({
|
|
8824
|
+
key: "EnableRetrievalSupersessionCollapse",
|
|
8825
|
+
name: "Data Lakes: Collapse superseded members before ranking",
|
|
8826
|
+
defaultValue: false,
|
|
8827
|
+
description: "When a lake holds two generations of the same document (a re-upload, a Drive sync, a migration), rank only the newest and report the suppression. Off by default: the weakest identity tier is a bare file name, so two genuinely different documents sharing a name in one lake would collapse to one - turn this on only after checking the reported collapse counts on real lakes. Suppression is recoverable either way; a collapsed member is still reachable by id or name through retrieve_knowledge_content.",
|
|
8828
|
+
category: "Experimental",
|
|
8829
|
+
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8830
|
+
order: 97,
|
|
8831
|
+
dependsOn: "EnableDataLakes"
|
|
8832
|
+
}),
|
|
7726
8833
|
PauseLakeConvergence: makeBooleanSetting({
|
|
7727
8834
|
key: "PauseLakeConvergence",
|
|
7728
8835
|
name: "Data Lakes: Pause background convergence work",
|
|
7729
8836
|
defaultValue: false,
|
|
7730
|
-
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).",
|
|
8837
|
+
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 - including overriding a platform-wide pause back OFF for one lake. Every producer honors the override, the global chunk rescue sweep included: it resolves each candidate against the lake it belongs to (#2157), so a file in no lake at all follows the platform value, which is correct for it. A platform-level flip applies immediately to lake-wide work and within ~5 min to per-lake-scoped work (settings cache).",
|
|
7731
8838
|
category: "Experimental",
|
|
7732
8839
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7733
8840
|
order: 93,
|
|
@@ -7738,6 +8845,57 @@ const settingsMap = {
|
|
|
7738
8845
|
"lake"
|
|
7739
8846
|
] }
|
|
7740
8847
|
}),
|
|
8848
|
+
LakeConvergenceBulkChangeSharePct: makeNumberSetting({
|
|
8849
|
+
key: "LakeConvergenceBulkChangeSharePct",
|
|
8850
|
+
name: "Data Lakes: Convergence bulk-change confirmation threshold (%)",
|
|
8851
|
+
defaultValue: 25,
|
|
8852
|
+
min: 1,
|
|
8853
|
+
max: 100,
|
|
8854
|
+
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.",
|
|
8855
|
+
category: "AI",
|
|
8856
|
+
order: 4,
|
|
8857
|
+
dependsOn: "EnableDataLakes",
|
|
8858
|
+
scope: { settableAt: [
|
|
8859
|
+
"organization",
|
|
8860
|
+
"owner",
|
|
8861
|
+
"lake"
|
|
8862
|
+
] }
|
|
8863
|
+
}),
|
|
8864
|
+
EnforceLakeReadGrants: makeBooleanSetting({
|
|
8865
|
+
key: "EnforceLakeReadGrants",
|
|
8866
|
+
name: "Data Lakes: Enforce read-time grant resolution",
|
|
8867
|
+
defaultValue: true,
|
|
8868
|
+
description: "Read-time grant resolution (#1673). ON is the shipped default: a persisted READER or ORG grant is resolved into the read decision, so a principal a lake was shared with can browse it, open it and ground on it. Resolution is purely ADDITIVE (legacy OR grant), so it takes no access away; this arm contains an ORG grant to the granting org, and expired rows never resolve. This is the standing KILL SWITCH for that arm, not a migration phase: turning it OFF returns to report-only, where the gate still resolves grants and logs where they WOULD change access ([lakeReadGrantCutover] lines) but the enforced decision falls back to the legacy owner/org/tag/entitlement/public rule - so those log lines are the diagnostic for a lake someone can no longer reach while the switch is off. Platform altitude on purpose: install-wide, 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.",
|
|
8869
|
+
category: "Experimental",
|
|
8870
|
+
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8871
|
+
order: 94,
|
|
8872
|
+
dependsOn: "EnableDataLakes"
|
|
8873
|
+
}),
|
|
8874
|
+
EnableDataLakeDrivePoll: makeBooleanSetting({
|
|
8875
|
+
key: "EnableDataLakeDrivePoll",
|
|
8876
|
+
name: "Data Lakes: Google Drive auto re-sync poll",
|
|
8877
|
+
defaultValue: false,
|
|
8878
|
+
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.",
|
|
8879
|
+
category: "Experimental",
|
|
8880
|
+
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8881
|
+
order: 95,
|
|
8882
|
+
dependsOn: "EnableDataLakes"
|
|
8883
|
+
}),
|
|
8884
|
+
EnforceLakeAdmission: makeBooleanSetting({
|
|
8885
|
+
key: "EnforceLakeAdmission",
|
|
8886
|
+
name: "Data Lakes: Enforce the admission contract",
|
|
8887
|
+
defaultValue: false,
|
|
8888
|
+
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.",
|
|
8889
|
+
category: "Experimental",
|
|
8890
|
+
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8891
|
+
order: 96,
|
|
8892
|
+
dependsOn: "EnableDataLakes",
|
|
8893
|
+
scope: { settableAt: [
|
|
8894
|
+
"organization",
|
|
8895
|
+
"owner",
|
|
8896
|
+
"lake"
|
|
8897
|
+
] }
|
|
8898
|
+
}),
|
|
7741
8899
|
EnableBriefcase: makeBooleanSetting({
|
|
7742
8900
|
key: "EnableBriefcase",
|
|
7743
8901
|
name: "Enable Briefcase",
|
|
@@ -7957,15 +9115,17 @@ const settingsMap = {
|
|
|
7957
9115
|
}),
|
|
7958
9116
|
DefaultChunkSize: makeNumberSetting({
|
|
7959
9117
|
key: "DefaultChunkSize",
|
|
9118
|
+
userReadable: true,
|
|
7960
9119
|
name: "Default Chunk Size",
|
|
7961
9120
|
defaultValue: 512,
|
|
7962
9121
|
min: 64,
|
|
7963
|
-
|
|
9122
|
+
max: OVERSIZED_PASSAGE_TOKEN_THRESHOLD,
|
|
9123
|
+
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.",
|
|
7964
9124
|
category: "AI",
|
|
7965
9125
|
order: 3,
|
|
7966
9126
|
scope: {
|
|
7967
9127
|
settableAt: ["organization", "owner"],
|
|
7968
|
-
clamp: (value) => Math.min(Math.max(Math.floor(value), 64),
|
|
9128
|
+
clamp: (value) => Math.min(Math.max(Math.floor(value), 64), OVERSIZED_PASSAGE_TOKEN_THRESHOLD)
|
|
7969
9129
|
}
|
|
7970
9130
|
}),
|
|
7971
9131
|
ModerationEnabled: makeBooleanSetting({
|
|
@@ -8014,6 +9174,22 @@ const settingsMap = {
|
|
|
8014
9174
|
category: "AI",
|
|
8015
9175
|
order: 11
|
|
8016
9176
|
}),
|
|
9177
|
+
WebSearchFreshnessPrompt: makeStringSetting({
|
|
9178
|
+
key: "WebSearchFreshnessPrompt",
|
|
9179
|
+
name: "Web Search Freshness Prompt",
|
|
9180
|
+
defaultValue: WEB_SEARCH_FRESHNESS_PROMPT,
|
|
9181
|
+
description: "System prompt telling the model when to reach for web_search rather than answer from training data, and to state the as-of date of any time-sensitive fact. Injected only when the web_search tool is offered for the request - a model instructed to search without a search tool tends to claim it searched. Clearing this field turns the section OFF rather than restoring the built-in default, and it is the only off switch this section has; to get the stock wording back, paste it in. A change 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. After an upgrade, diff a saved copy against the built-in default: a saved copy pins the wording from whenever it was saved and will not pick up fixes made since.",
|
|
9182
|
+
category: "AI",
|
|
9183
|
+
order: 12
|
|
9184
|
+
}),
|
|
9185
|
+
KnowledgeBaseRetrievalPrompt: makeStringSetting({
|
|
9186
|
+
key: "KnowledgeBaseRetrievalPrompt",
|
|
9187
|
+
name: "Knowledge Base Retrieval Prompt",
|
|
9188
|
+
defaultValue: KNOWLEDGE_BASE_RETRIEVAL_PROMPT,
|
|
9189
|
+
description: "System prompt telling the model when to reach for search_knowledge_base rather than answer from training data, and when NOT to. Injected only when the search_knowledge_base tool is offered for the request - a model instructed to search a corpus it has no tool for tends to claim it searched. Clearing this field turns the section OFF rather than restoring the built-in default, and it is the only off switch this section has; to get the stock wording back, paste it in. A change 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. After an upgrade, diff a saved copy against the built-in default: a saved copy pins the wording from whenever it was saved and will not pick up fixes made since.",
|
|
9190
|
+
category: "AI",
|
|
9191
|
+
order: 13
|
|
9192
|
+
}),
|
|
8017
9193
|
UseFormatPrompt: makeBooleanSetting({
|
|
8018
9194
|
key: "UseFormatPrompt",
|
|
8019
9195
|
name: "Use Format Prompt",
|
|
@@ -8032,6 +9208,7 @@ const settingsMap = {
|
|
|
8032
9208
|
}),
|
|
8033
9209
|
pricePerCredit: makeNumberSetting({
|
|
8034
9210
|
key: "pricePerCredit",
|
|
9211
|
+
userReadable: true,
|
|
8035
9212
|
name: "Price Per Credit",
|
|
8036
9213
|
defaultValue: 50,
|
|
8037
9214
|
description: "The price per credit for purchasing credits.",
|
|
@@ -8109,7 +9286,8 @@ const settingsMap = {
|
|
|
8109
9286
|
name: "Referal Credits Amount",
|
|
8110
9287
|
defaultValue: 1e4,
|
|
8111
9288
|
description: "Credits to give to the referred user.",
|
|
8112
|
-
category: "Referrals"
|
|
9289
|
+
category: "Referrals",
|
|
9290
|
+
userReadable: true
|
|
8113
9291
|
}),
|
|
8114
9292
|
EnableReferralToEmail: makeBooleanSetting({
|
|
8115
9293
|
key: "EnableReferralToEmail",
|
|
@@ -8136,6 +9314,15 @@ const settingsMap = {
|
|
|
8136
9314
|
group: API_SERVICE_GROUPS.FEEDBACK.id,
|
|
8137
9315
|
order: 9
|
|
8138
9316
|
}),
|
|
9317
|
+
FeedbackReceiveEmailNonProd: makeStringSetting({
|
|
9318
|
+
key: "FeedbackReceiveEmailNonProd",
|
|
9319
|
+
name: "Non-Production Feedback Email",
|
|
9320
|
+
defaultValue: "",
|
|
9321
|
+
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.",
|
|
9322
|
+
category: "Feedback",
|
|
9323
|
+
group: API_SERVICE_GROUPS.FEEDBACK.id,
|
|
9324
|
+
order: 9.5
|
|
9325
|
+
}),
|
|
8139
9326
|
FeedbackKyle: makeStringSetting({
|
|
8140
9327
|
key: "FeedbackKyle",
|
|
8141
9328
|
name: "Kyle Feedback Email",
|
|
@@ -8210,7 +9397,17 @@ const settingsMap = {
|
|
|
8210
9397
|
description: "The webhook URL for sending feedback to the #bike4mind-feedback Slack channel.",
|
|
8211
9398
|
category: "Feedback",
|
|
8212
9399
|
group: API_SERVICE_GROUPS.FEEDBACK.id,
|
|
8213
|
-
order:
|
|
9400
|
+
order: 6.75,
|
|
9401
|
+
isSensitive: true
|
|
9402
|
+
}),
|
|
9403
|
+
SlackNonProdFeedbackWebhookUrl: makeStringSetting({
|
|
9404
|
+
key: "SlackNonProdFeedbackWebhookUrl",
|
|
9405
|
+
name: "Non-Production Feedback Channel Webhook URL",
|
|
9406
|
+
defaultValue: "",
|
|
9407
|
+
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.",
|
|
9408
|
+
category: "Feedback",
|
|
9409
|
+
group: API_SERVICE_GROUPS.FEEDBACK.id,
|
|
9410
|
+
order: 6.8,
|
|
8214
9411
|
isSensitive: true
|
|
8215
9412
|
}),
|
|
8216
9413
|
SlackEmailAuditWebhookUrl: makeStringSetting({
|
|
@@ -8279,8 +9476,10 @@ const settingsMap = {
|
|
|
8279
9476
|
}),
|
|
8280
9477
|
MaxFileSize: makeNumberSetting({
|
|
8281
9478
|
key: "MaxFileSize",
|
|
9479
|
+
userReadable: true,
|
|
8282
9480
|
name: "Max File Size",
|
|
8283
9481
|
defaultValue: 30,
|
|
9482
|
+
min: 1,
|
|
8284
9483
|
description: "The maximum file size allowed for uploads in MB.",
|
|
8285
9484
|
category: "Knowledge",
|
|
8286
9485
|
group: API_SERVICE_GROUPS.KNOWLEDGE.id,
|
|
@@ -8387,6 +9586,7 @@ const settingsMap = {
|
|
|
8387
9586
|
}),
|
|
8388
9587
|
enforceCredits: makeBooleanSetting({
|
|
8389
9588
|
key: "enforceCredits",
|
|
9589
|
+
userReadable: true,
|
|
8390
9590
|
name: "Enforce Credits",
|
|
8391
9591
|
defaultValue: process.env.B4M_SELF_HOST === "true" ? false : true,
|
|
8392
9592
|
description: "Whether to enforce credits for users",
|
|
@@ -8403,6 +9603,7 @@ const settingsMap = {
|
|
|
8403
9603
|
}),
|
|
8404
9604
|
enableTeamPlan: makeBooleanSetting({
|
|
8405
9605
|
key: "enableTeamPlan",
|
|
9606
|
+
userReadable: true,
|
|
8406
9607
|
name: "Enable Team Plan",
|
|
8407
9608
|
defaultValue: false,
|
|
8408
9609
|
description: "Whether to enable team plans",
|
|
@@ -8508,7 +9709,8 @@ const settingsMap = {
|
|
|
8508
9709
|
description: "The global system prompt files to be used for AI model configuration.",
|
|
8509
9710
|
category: "AI",
|
|
8510
9711
|
group: API_SERVICE_GROUPS.OPENAI.id,
|
|
8511
|
-
order: 8
|
|
9712
|
+
order: 8,
|
|
9713
|
+
userReadable: true
|
|
8512
9714
|
}),
|
|
8513
9715
|
OpenWeatherKey: makeStringSetting({
|
|
8514
9716
|
key: "OpenWeatherKey",
|
|
@@ -8662,6 +9864,7 @@ const settingsMap = {
|
|
|
8662
9864
|
}),
|
|
8663
9865
|
MaxContentLength: makeNumberSetting({
|
|
8664
9866
|
key: "MaxContentLength",
|
|
9867
|
+
userReadable: true,
|
|
8665
9868
|
name: "Max Content Length",
|
|
8666
9869
|
defaultValue: 5e4,
|
|
8667
9870
|
description: "The maximum character length for file content displayed in workbench (truncated if larger).",
|
|
@@ -8817,57 +10020,136 @@ const settingsMap = {
|
|
|
8817
10020
|
}),
|
|
8818
10021
|
bflApiKey: makeStringSetting({
|
|
8819
10022
|
key: "bflApiKey",
|
|
8820
|
-
name: "
|
|
10023
|
+
name: "Black Forest Labs API Key",
|
|
8821
10024
|
defaultValue: "",
|
|
8822
|
-
description: "The API Key for
|
|
10025
|
+
description: "The API Key for Black Forest Labs image generation service.",
|
|
8823
10026
|
isSensitive: true,
|
|
8824
10027
|
category: "AI",
|
|
8825
10028
|
group: API_SERVICE_GROUPS.IMAGE_GENERATION.id,
|
|
8826
10029
|
order: 1
|
|
8827
10030
|
}),
|
|
8828
|
-
defaultEmbeddingModel: makeStringSetting({
|
|
8829
|
-
key: "defaultEmbeddingModel",
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
10031
|
+
defaultEmbeddingModel: makeStringSetting({
|
|
10032
|
+
key: "defaultEmbeddingModel",
|
|
10033
|
+
userReadable: true,
|
|
10034
|
+
name: "Default Embedding Model",
|
|
10035
|
+
defaultValue: defaultEmbeddingModelForEnv(),
|
|
10036
|
+
description: "The default embedding model to use",
|
|
10037
|
+
category: "AI",
|
|
10038
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10039
|
+
options: [
|
|
10040
|
+
...Object.values(OpenAIEmbeddingModel),
|
|
10041
|
+
...Object.values(VoyageAIEmbeddingModel),
|
|
10042
|
+
...Object.values(BedrockEmbeddingModel),
|
|
10043
|
+
...process.env.B4M_SELF_HOST === "true" ? Object.values(OllamaEmbeddingModel) : []
|
|
10044
|
+
]
|
|
10045
|
+
}),
|
|
10046
|
+
dataLakeSearchMaxFiles: makeNumberSetting({
|
|
10047
|
+
key: "dataLakeSearchMaxFiles",
|
|
10048
|
+
name: "Data Lake Search Max Files",
|
|
10049
|
+
defaultValue: DATA_LAKE_SEARCH_MAX_FILES_DEFAULT,
|
|
10050
|
+
min: 1,
|
|
10051
|
+
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.",
|
|
10052
|
+
category: "AI",
|
|
10053
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10054
|
+
order: 2,
|
|
10055
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10056
|
+
}),
|
|
10057
|
+
dataLakeSearchMaxChunks: makeNumberSetting({
|
|
10058
|
+
key: "dataLakeSearchMaxChunks",
|
|
10059
|
+
name: "Data Lake Search Max Chunks",
|
|
10060
|
+
defaultValue: DATA_LAKE_SEARCH_MAX_CHUNKS_DEFAULT,
|
|
10061
|
+
min: 1,
|
|
10062
|
+
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).",
|
|
10063
|
+
category: "AI",
|
|
10064
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10065
|
+
order: 3,
|
|
10066
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10067
|
+
}),
|
|
10068
|
+
forcedRetrievalCharBudget: makeNumberSetting({
|
|
10069
|
+
key: "forcedRetrievalCharBudget",
|
|
10070
|
+
name: "Forced Retrieval Char Budget",
|
|
10071
|
+
defaultValue: FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT,
|
|
10072
|
+
min: 1e3,
|
|
10073
|
+
max: 1e5,
|
|
10074
|
+
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. Overridable per organization and per owner, the same altitude as the two relevance floors resolved alongside it on the same turn.",
|
|
10075
|
+
category: "AI",
|
|
10076
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10077
|
+
order: 4,
|
|
10078
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10079
|
+
}),
|
|
10080
|
+
kbSearchDefaultResults: makeNumberSetting({
|
|
10081
|
+
key: "kbSearchDefaultResults",
|
|
10082
|
+
name: "Knowledge Base Search Default Results",
|
|
10083
|
+
defaultValue: 5,
|
|
10084
|
+
min: 1,
|
|
10085
|
+
max: 10,
|
|
10086
|
+
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. A change 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.",
|
|
10087
|
+
category: "AI",
|
|
10088
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10089
|
+
order: 5,
|
|
10090
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10091
|
+
}),
|
|
10092
|
+
kbSearchResultTokenBudget: makeNumberSetting({
|
|
10093
|
+
key: "kbSearchResultTokenBudget",
|
|
10094
|
+
name: "Knowledge Base Search Result Token Budget",
|
|
10095
|
+
defaultValue: 0,
|
|
10096
|
+
min: 0,
|
|
10097
|
+
max: 2e4,
|
|
10098
|
+
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. A change 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.",
|
|
10099
|
+
category: "AI",
|
|
10100
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10101
|
+
order: 6,
|
|
10102
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10103
|
+
}),
|
|
10104
|
+
kbSearchMinRelevancePct: makeNumberSetting({
|
|
10105
|
+
key: "kbSearchMinRelevancePct",
|
|
10106
|
+
name: "Knowledge Base Search Minimum Relevance (%)",
|
|
10107
|
+
defaultValue: 0,
|
|
10108
|
+
min: 0,
|
|
10109
|
+
max: 100,
|
|
10110
|
+
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. A change 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.",
|
|
8833
10111
|
category: "AI",
|
|
8834
10112
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
...Object.values(VoyageAIEmbeddingModel),
|
|
8838
|
-
...Object.values(BedrockEmbeddingModel),
|
|
8839
|
-
...process.env.B4M_SELF_HOST === "true" ? Object.values(OllamaEmbeddingModel) : []
|
|
8840
|
-
]
|
|
10113
|
+
order: 7,
|
|
10114
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
8841
10115
|
}),
|
|
8842
|
-
|
|
8843
|
-
key: "
|
|
8844
|
-
name: "
|
|
8845
|
-
defaultValue:
|
|
10116
|
+
lakeMemoryRecallK: makeNumberSetting({
|
|
10117
|
+
key: "lakeMemoryRecallK",
|
|
10118
|
+
name: "Lake Memory Belief Budget",
|
|
10119
|
+
defaultValue: 24,
|
|
8846
10120
|
min: 1,
|
|
8847
|
-
|
|
10121
|
+
max: 200,
|
|
10122
|
+
int: true,
|
|
10123
|
+
description: "Most beliefs the lake memory hot-card injects on a Data-Lake-mode turn, shared across every lake in scope. Recall still applies its cosine floor and the source-reachability gate first, so raising this does not admit low-quality beliefs - it raises the ceiling on how many QUALIFYING beliefs can actually be used, which was pinned at 8 (inherited from personal-memento recall) on no evidence beyond that inheritance. The sibling lever on the same turn is Forced Retrieval Char Budget, which governs raw chunk text rather than extracted beliefs. Platform-only for now, unlike that sibling: this read goes through plain getSettingsValue, which ignores settableAt, so a scope block here would be silently inert - every override written against it would resolve to nothing. Pointing the read at the scoped resolver is the prerequisite, not extra metadata.",
|
|
8848
10124
|
category: "AI",
|
|
8849
10125
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
8850
|
-
order:
|
|
8851
|
-
scope: { settableAt: [
|
|
8852
|
-
"organization",
|
|
8853
|
-
"owner",
|
|
8854
|
-
"lake"
|
|
8855
|
-
] }
|
|
10126
|
+
order: 8
|
|
8856
10127
|
}),
|
|
8857
|
-
|
|
8858
|
-
key: "
|
|
8859
|
-
name: "
|
|
8860
|
-
defaultValue:
|
|
10128
|
+
forcedRetrievalRelativeFloorPct: makeNumberSetting({
|
|
10129
|
+
key: "forcedRetrievalRelativeFloorPct",
|
|
10130
|
+
name: "Forced Retrieval Relative Floor (%)",
|
|
10131
|
+
defaultValue: 85,
|
|
10132
|
+
min: 0,
|
|
10133
|
+
max: 100,
|
|
10134
|
+
int: true,
|
|
10135
|
+
description: "How close to the best-scoring passage of the SAME turn a chunk must score to be injected on a Data-Lake-mode turn, as a percent of that top score. This is the floor that ranks; the absolute floor below only rejects. Unlike an absolute cosine line, it moves with the turn, so it keeps working when a corpus or an embedding model puts the whole score band somewhere else. Raising it injects fewer, more sharply-ranked passages and leaves char budget unspent; lowering it admits more of the tail. 0 disables the relative floor and leaves the absolute one as the only gate (the pre-#2497 behavior). The default is behavior-preserving rather than tuned: it admits everything the absolute floor admitted on the measured band, so it changes nothing until raised. Tune it AFTER an embedding-model change, never before - a migration shifts the band any value fitted to today would have been chosen against.",
|
|
10136
|
+
category: "AI",
|
|
10137
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10138
|
+
order: 9,
|
|
10139
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10140
|
+
}),
|
|
10141
|
+
forcedRetrievalMinSimilarityPct: makeNumberSetting({
|
|
10142
|
+
key: "forcedRetrievalMinSimilarityPct",
|
|
10143
|
+
name: "Forced Retrieval Absolute Floor (%)",
|
|
10144
|
+
defaultValue: 75,
|
|
8861
10145
|
min: 1,
|
|
8862
|
-
|
|
10146
|
+
max: 100,
|
|
10147
|
+
int: true,
|
|
10148
|
+
description: "Absolute minimum cosine similarity, as a percent, a chunk must clear to be injected on a Data-Lake-mode turn. This is a sanity floor for genuinely unrelated content, NOT the ranking gate - the relative floor above does the ranking. Measured over 166 injected chunks on a production lake the 75 default never once bound (the whole band sat between 80 and 91), so it currently reads like a quality gate while providing no protection. Lowering it toward 30-40 is the intended companion to raising the relative floor: it lets the relative rule govern a corpus whose band sits low, which a 75 line would otherwise reject wholesale. Cosine similarity is not comparable across embedding models, so a value tuned for one model does not transfer to another.",
|
|
8863
10149
|
category: "AI",
|
|
8864
10150
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
8865
|
-
order:
|
|
8866
|
-
scope: { settableAt: [
|
|
8867
|
-
"organization",
|
|
8868
|
-
"owner",
|
|
8869
|
-
"lake"
|
|
8870
|
-
] }
|
|
10151
|
+
order: 10,
|
|
10152
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
8871
10153
|
}),
|
|
8872
10154
|
LakeAccessAuditRetentionDays: makeNumberSetting({
|
|
8873
10155
|
key: "LakeAccessAuditRetentionDays",
|
|
@@ -8875,7 +10157,7 @@ const settingsMap = {
|
|
|
8875
10157
|
defaultValue: 450,
|
|
8876
10158
|
min: 450,
|
|
8877
10159
|
max: LAKE_ACCESS_AUDIT_RETENTION_MAX_DAYS,
|
|
8878
|
-
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.",
|
|
10160
|
+
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.",
|
|
8879
10161
|
category: "SecOps",
|
|
8880
10162
|
group: API_SERVICE_GROUPS.DATA_LAKE_AUDIT.id,
|
|
8881
10163
|
order: 1
|
|
@@ -8886,13 +10168,25 @@ const settingsMap = {
|
|
|
8886
10168
|
defaultValue: 30,
|
|
8887
10169
|
min: 1,
|
|
8888
10170
|
max: 90,
|
|
8889
|
-
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.",
|
|
10171
|
+
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.",
|
|
8890
10172
|
category: "SecOps",
|
|
8891
10173
|
group: API_SERVICE_GROUPS.DATA_LAKE_AUDIT.id,
|
|
8892
10174
|
order: 2
|
|
8893
10175
|
}),
|
|
10176
|
+
LakeConfigAuditRetentionDays: makeNumberSetting({
|
|
10177
|
+
key: "LakeConfigAuditRetentionDays",
|
|
10178
|
+
name: "Lake Config Change Audit Retention (days)",
|
|
10179
|
+
defaultValue: LAKE_CONFIG_AUDIT_RETENTION_DEFAULT_DAYS,
|
|
10180
|
+
min: LAKE_CONFIG_AUDIT_RETENTION_FLOOR_DAYS,
|
|
10181
|
+
max: LAKE_CONFIG_AUDIT_RETENTION_MAX_DAYS,
|
|
10182
|
+
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.",
|
|
10183
|
+
category: "SecOps",
|
|
10184
|
+
group: API_SERVICE_GROUPS.DATA_LAKE_AUDIT.id,
|
|
10185
|
+
order: 3
|
|
10186
|
+
}),
|
|
8894
10187
|
dataLakeEmbeddingSpendEnabled: makeBooleanSetting({
|
|
8895
10188
|
key: "dataLakeEmbeddingSpendEnabled",
|
|
10189
|
+
userReadable: true,
|
|
8896
10190
|
name: "Data Lake Embedding Spend Enabled",
|
|
8897
10191
|
defaultValue: true,
|
|
8898
10192
|
description: "Master switch for data-lake embedding spend (ingestion, reprocessing, convergence). Off halts all provider embedding calls on those paths; cached embeddings still apply.",
|
|
@@ -8902,6 +10196,7 @@ const settingsMap = {
|
|
|
8902
10196
|
}),
|
|
8903
10197
|
dataLakeEmbeddingBudgetPerRunUsd: makeNumberSetting({
|
|
8904
10198
|
key: "dataLakeEmbeddingBudgetPerRunUsd",
|
|
10199
|
+
userReadable: true,
|
|
8905
10200
|
name: "Embedding Budget Per Run (USD)",
|
|
8906
10201
|
defaultValue: 5,
|
|
8907
10202
|
min: 0,
|
|
@@ -8955,6 +10250,17 @@ const settingsMap = {
|
|
|
8955
10250
|
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
8956
10251
|
order: 6
|
|
8957
10252
|
}),
|
|
10253
|
+
dataLakeEmbeddingMaxTokensPerMinute: makeNumberSetting({
|
|
10254
|
+
key: "dataLakeEmbeddingMaxTokensPerMinute",
|
|
10255
|
+
name: "Embedding Max Tokens Per Minute",
|
|
10256
|
+
defaultValue: DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_DEFAULT,
|
|
10257
|
+
min: 0,
|
|
10258
|
+
max: DATA_LAKE_EMBEDDING_MAX_TOKENS_PER_MINUTE_MAX,
|
|
10259
|
+
description: "Most provider embedding TOKENS per minute across all data-lake work, which is the quantity providers actually meter. The calls-per-minute lever alone does not bound this: one call carries a whole batch of passages. Set it from your provider dashboard TPM, leaving headroom for query-side embedding (exempt, so a search never queues behind a backfill). 0 stops all calls.",
|
|
10260
|
+
category: "AI",
|
|
10261
|
+
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
10262
|
+
order: 7
|
|
10263
|
+
}),
|
|
8958
10264
|
dataLakeVectorizeChunkBatchSize: makeNumberSetting({
|
|
8959
10265
|
key: "dataLakeVectorizeChunkBatchSize",
|
|
8960
10266
|
name: "Vectorize Chunk Batch Size",
|
|
@@ -8964,7 +10270,29 @@ const settingsMap = {
|
|
|
8964
10270
|
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.",
|
|
8965
10271
|
category: "AI",
|
|
8966
10272
|
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
8967
|
-
order:
|
|
10273
|
+
order: 8
|
|
10274
|
+
}),
|
|
10275
|
+
dataLakeEmbeddingTierMultiplierIndividual: makeNumberSetting({
|
|
10276
|
+
key: "dataLakeEmbeddingTierMultiplierIndividual",
|
|
10277
|
+
name: "Cost Tier Multiplier - Individual-Owned Lakes",
|
|
10278
|
+
defaultValue: 1,
|
|
10279
|
+
min: 0,
|
|
10280
|
+
max: 100,
|
|
10281
|
+
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.",
|
|
10282
|
+
category: "AI",
|
|
10283
|
+
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
10284
|
+
order: 9
|
|
10285
|
+
}),
|
|
10286
|
+
dataLakeEmbeddingTierMultiplierOrganization: makeNumberSetting({
|
|
10287
|
+
key: "dataLakeEmbeddingTierMultiplierOrganization",
|
|
10288
|
+
name: "Cost Tier Multiplier - Organization-Owned Lakes",
|
|
10289
|
+
defaultValue: 5,
|
|
10290
|
+
min: 0,
|
|
10291
|
+
max: 100,
|
|
10292
|
+
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.",
|
|
10293
|
+
category: "AI",
|
|
10294
|
+
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
10295
|
+
order: 10
|
|
8968
10296
|
}),
|
|
8969
10297
|
slackSigningSecret: makeStringSetting({
|
|
8970
10298
|
key: "slackSigningSecret",
|
|
@@ -9066,6 +10394,7 @@ const settingsMap = {
|
|
|
9066
10394
|
}),
|
|
9067
10395
|
enableVoiceSession: makeBooleanSetting({
|
|
9068
10396
|
key: "enableVoiceSession",
|
|
10397
|
+
userReadable: true,
|
|
9069
10398
|
name: "Enable Voice Session",
|
|
9070
10399
|
defaultValue: false,
|
|
9071
10400
|
description: "Whether to enable the voice session.",
|
|
@@ -9075,6 +10404,7 @@ const settingsMap = {
|
|
|
9075
10404
|
}),
|
|
9076
10405
|
voiceV2Enabled: makeBooleanSetting({
|
|
9077
10406
|
key: "voiceV2Enabled",
|
|
10407
|
+
userReadable: true,
|
|
9078
10408
|
name: "Enable Voice v2 (Model-Agnostic)",
|
|
9079
10409
|
defaultValue: false,
|
|
9080
10410
|
description: "Gate for the Voice v2 feature (ElevenLabs Conversational AI + any B4M reasoning model). When disabled, /api/voice/v2/sessions returns 403.",
|
|
@@ -9094,6 +10424,7 @@ const settingsMap = {
|
|
|
9094
10424
|
}),
|
|
9095
10425
|
voiceSessionAiVoice: makeStringSetting({
|
|
9096
10426
|
key: "voiceSessionAiVoice",
|
|
10427
|
+
userReadable: true,
|
|
9097
10428
|
name: "Default Assistant Voice",
|
|
9098
10429
|
defaultValue: "alloy",
|
|
9099
10430
|
description: "The default voice for the assistant in the voice session.",
|
|
@@ -9395,16 +10726,6 @@ const settingsMap = {
|
|
|
9395
10726
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
9396
10727
|
order: 80
|
|
9397
10728
|
}),
|
|
9398
|
-
EnableOptiHashiDefault: makeBooleanSetting({
|
|
9399
|
-
key: "EnableOptiHashiDefault",
|
|
9400
|
-
name: "OptiHashi: On by default for users",
|
|
9401
|
-
defaultValue: false,
|
|
9402
|
-
description: "When enabled, OptiHashi is active for users who have never explicitly toggled it.",
|
|
9403
|
-
category: "Experimental",
|
|
9404
|
-
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
9405
|
-
order: 81,
|
|
9406
|
-
dependsOn: "EnableOptiHashi"
|
|
9407
|
-
}),
|
|
9408
10729
|
EnableLibreOncology: makeBooleanSetting({
|
|
9409
10730
|
key: "EnableLibreOncology",
|
|
9410
10731
|
name: "Enable LibreOncology",
|
|
@@ -9605,6 +10926,7 @@ const settingsMap = {
|
|
|
9605
10926
|
}),
|
|
9606
10927
|
orchestrationDefaults: makeObjectSetting({
|
|
9607
10928
|
key: "orchestrationDefaults",
|
|
10929
|
+
userReadable: true,
|
|
9608
10930
|
name: "Agent Orchestration Defaults",
|
|
9609
10931
|
defaultValue: OrchestrationDefaultsSchema.parse({}),
|
|
9610
10932
|
description: "Default ReAct profile for agentless executions (#8922). Drives allowed/denied tools, iteration ceilings, default thoroughness, and fallback models when the agent_executor is invoked without a persisted IAgent (e.g. the upcoming Agent-mode toggle).",
|
|
@@ -9691,19 +11013,20 @@ const settingsMap = {
|
|
|
9691
11013
|
category: "Admin",
|
|
9692
11014
|
order: 142
|
|
9693
11015
|
}),
|
|
9694
|
-
|
|
9695
|
-
key: "
|
|
9696
|
-
name: "PR Report Slack
|
|
11016
|
+
prReportWebhookUrl: makeStringSetting({
|
|
11017
|
+
key: "prReportWebhookUrl",
|
|
11018
|
+
name: "PR Report Slack Webhook URL",
|
|
9697
11019
|
defaultValue: "",
|
|
9698
|
-
|
|
11020
|
+
isSensitive: true,
|
|
11021
|
+
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.",
|
|
9699
11022
|
category: "Slack",
|
|
9700
11023
|
order: 143
|
|
9701
11024
|
}),
|
|
9702
11025
|
prReportEgressAllowlist: makeObjectSetting({
|
|
9703
11026
|
key: "prReportEgressAllowlist",
|
|
9704
11027
|
name: "PR Report Egress Allowlist",
|
|
9705
|
-
defaultValue: { hosts: ["
|
|
9706
|
-
description: "Hosts the PR digest may post to. 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.
|
|
11028
|
+
defaultValue: { hosts: ["hooks.slack.com"] },
|
|
11029
|
+
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.",
|
|
9707
11030
|
category: "Slack",
|
|
9708
11031
|
order: 144,
|
|
9709
11032
|
schema: z$1.object({ hosts: z$1.array(z$1.string()).default([]) })
|
|
@@ -9783,7 +11106,8 @@ const SystemPromptDetailSchema = z$1.object({
|
|
|
9783
11106
|
"user",
|
|
9784
11107
|
"project",
|
|
9785
11108
|
"session",
|
|
9786
|
-
"org"
|
|
11109
|
+
"org",
|
|
11110
|
+
"caller"
|
|
9787
11111
|
]),
|
|
9788
11112
|
/** e.g., "date_context", "tool_guidance" */
|
|
9789
11113
|
name: z$1.string(),
|
|
@@ -10095,6 +11419,7 @@ const PromptMetaTokenUsageSchema = z$1.object({
|
|
|
10095
11419
|
actualOutputTokens: z$1.number().optional(),
|
|
10096
11420
|
actualTotalTokens: z$1.number().optional(),
|
|
10097
11421
|
cacheReadInputTokens: z$1.number().optional(),
|
|
11422
|
+
cacheCreationInputTokens: z$1.number().optional(),
|
|
10098
11423
|
settledBasis: z$1.enum(["provider", "local"]).optional(),
|
|
10099
11424
|
estimatedCost: z$1.number().optional(),
|
|
10100
11425
|
creditsUsed: z$1.number().optional()
|
|
@@ -10176,6 +11501,7 @@ const PromptMetaContextSchema = z$1.object({
|
|
|
10176
11501
|
}).optional(),
|
|
10177
11502
|
lakeMemory: z$1.object({
|
|
10178
11503
|
beliefCount: z$1.number(),
|
|
11504
|
+
beliefBudget: z$1.number().optional(),
|
|
10179
11505
|
dataLakeTags: z$1.array(z$1.string())
|
|
10180
11506
|
}).optional(),
|
|
10181
11507
|
contextWindowUsage: z$1.object({
|
|
@@ -10220,7 +11546,15 @@ const PromptMetaPerformanceSchema = z$1.object({
|
|
|
10220
11546
|
totalResponseTime: z$1.number().optional(),
|
|
10221
11547
|
contextRetrievalTime: z$1.number().optional(),
|
|
10222
11548
|
modelInferenceTime: z$1.number().optional(),
|
|
11549
|
+
/**
|
|
11550
|
+
* Time to First Visible Token: elapsed ms until the first chunk the user can actually
|
|
11551
|
+
* see. Left unset when a turn streamed nothing visible (thinking-only, or a turn that
|
|
11552
|
+
* errored before answering), so absence reads as "never rendered" rather than as fast.
|
|
11553
|
+
* Pair with firstChunkTime to tell a slow model from a long hidden-reasoning window.
|
|
11554
|
+
*/
|
|
10223
11555
|
firstTokenTime: z$1.number().optional(),
|
|
11556
|
+
/** Elapsed ms until the first chunk of any kind, including a hidden thinking block. */
|
|
11557
|
+
firstChunkTime: z$1.number().optional(),
|
|
10224
11558
|
clientFirstTokenTime: z$1.number().optional(),
|
|
10225
11559
|
streamingPerformance: z$1.object({
|
|
10226
11560
|
chunkCount: z$1.number().optional(),
|
|
@@ -10294,10 +11628,348 @@ const CitableSourceSchema = z$1.object({
|
|
|
10294
11628
|
fullContext: z$1.string().optional()
|
|
10295
11629
|
}).optional()
|
|
10296
11630
|
});
|
|
11631
|
+
/**
|
|
11632
|
+
* Per-turn retrieval outcome (#1867): whether retrieval was attempted this turn and what happened,
|
|
11633
|
+
* independent of whether the model then cited anything. Exists specifically to make the zero case
|
|
11634
|
+
* distinguishable from "never asked" - `context.lakeMemory` and `citables` both go silent on a
|
|
11635
|
+
* zero-result retrieval, so a turn that legitimately found nothing is indistinguishable from one
|
|
11636
|
+
* where retrieval never ran at all.
|
|
11637
|
+
*
|
|
11638
|
+
* Holds NO chunk/document identifiers, and no DOCUMENT count. A document count already exists and
|
|
11639
|
+
* is more precise: `citables.filter(c => c.type === 'document')` is deduped by id/url/title in
|
|
11640
|
+
* `applyQuestStatusChanges`, while this shape cannot dedupe (no identifiers to dedupe by) and
|
|
11641
|
+
* would have to sum - producing a second, disagreeing number for the same question.
|
|
11642
|
+
*
|
|
11643
|
+
* `injected` below is NOT that number and does not reopen it: it counts PASSAGES and characters,
|
|
11644
|
+
* neither of which `citables` can express - one document contributes many passages, and a turn
|
|
11645
|
+
* that injected nothing emits no citable to count at all. Similarity scores otherwise live on
|
|
11646
|
+
* `LakeAccessEvent`; the single `injected.topScore` is here because that row is written only on a
|
|
11647
|
+
* turn that grounded, so it cannot carry the near-miss score of a turn that grounded on nothing.
|
|
11648
|
+
*
|
|
11649
|
+
* CAUTION, not a guarantee: the absence of chunk/document identifiers is what keeps this shape
|
|
11650
|
+
* OUT of `promptMetaRedaction.ts`'s scope (that helper is a functionCalls-only denylist and would
|
|
11651
|
+
* not catch a nested nonidentifier field like `dataLakeTags` regardless). It does NOT mean this
|
|
11652
|
+
* field never needs redaction consideration - `dataLakeTags` (which lakes were involved) already
|
|
11653
|
+
* reaches non-owner viewers the same way `lakeMemory.dataLakeTags` does (session shares, feedback
|
|
11654
|
+
* egress, admin logs, session clone - see redactedFeedback.ts, admin/model-logs.ts, clone.ts, none
|
|
11655
|
+
* of which touch this field). That exposure is not new in the general case, but it IS new
|
|
11656
|
+
* specifically on a zero-recall turn: `lakeMemory` was never written there before this field
|
|
11657
|
+
* existed, so a turn that previously carried no lake-identity signal at all now carries one.
|
|
11658
|
+
*
|
|
11659
|
+
* `attempted`/`outcome` on their own would still be ambiguous about WHICH lakes were searched on a
|
|
11660
|
+
* zero-recall turn (dataLakeTags otherwise lives only inside `lakeMemory`, written after the
|
|
11661
|
+
* zero-belief return), so this stamps the resolved tags at write time rather than making a reader
|
|
11662
|
+
* fall back to the session's current (possibly since-changed) `retrievalTags`.
|
|
11663
|
+
*
|
|
11664
|
+
* Absent-or-fully-present, matching `lakeMemory` above - see the Mongoose-side subSchema comment
|
|
11665
|
+
* in QuestModel.ts for why partial-write and default-array shapes are unsafe here.
|
|
11666
|
+
*
|
|
11667
|
+
* WIDER THAN ITS NAME SUGGESTS as of `mode` (#1394). The field is no longer written only when
|
|
11668
|
+
* retrieval ran: it is now seeded on every turn that could have retrieved (forced retrieval
|
|
11669
|
+
* enabled, or the knowledge tool offered), so `attempted: false` is a recorded fact rather than
|
|
11670
|
+
* an absence to be inferred. Presence therefore means "this turn was in a position to retrieve",
|
|
11671
|
+
* and turns with no knowledge in scope still carry no field at all. The distinction matters to a
|
|
11672
|
+
* rollup: absence is now ambiguous between "not a retrieval turn" and "written before this
|
|
11673
|
+
* existed", which is why `mode` documents its own date-bounding requirement.
|
|
11674
|
+
*/
|
|
11675
|
+
const RetrievalSummarySchema = z$1.object({
|
|
11676
|
+
/** True once a retrieval-capable surface actually ran (not merely offered) this turn. */
|
|
11677
|
+
attempted: z$1.boolean(),
|
|
11678
|
+
/**
|
|
11679
|
+
* Present if and only if `attempted` is true - an outcome describes a run, and a turn that
|
|
11680
|
+
* never ran retrieval has none. A reader testing for a specific value is unaffected (absence
|
|
11681
|
+
* is not any of them); a reader switching exhaustively must handle undefined.
|
|
11682
|
+
*
|
|
11683
|
+
* 'ok' - ran, whether or not anything came back (the zero case is a legitimate 'ok').
|
|
11684
|
+
* 'no_lakes' - ran but the user had no entitled/selected lake in scope.
|
|
11685
|
+
* 'not_indexed' - ran to completion having compared nothing: the corpus in scope carries no
|
|
11686
|
+
* usable vector (never indexed, or embedded with a foreign model), so no passage was ever
|
|
11687
|
+
* scored against the query. Distinct from 'ok' because the library was not searched at all,
|
|
11688
|
+
* and reporting that as a topical zero ("your documents do not cover this") is exactly the
|
|
11689
|
+
* confident-wrong-answer this field exists to catch. Distinct from 'failed' because nothing
|
|
11690
|
+
* broke: the remedy is re-vectorizing, which the corpus owner can do themselves, and a retry
|
|
11691
|
+
* never helps.
|
|
11692
|
+
* COVERAGE: recorded by forced retrieval (KnowledgeRetrievalFeature's `scoredCount === 0`
|
|
11693
|
+
* exit) and by knowledgeBaseSearch, whose semantic arms carry the same verdict through to the
|
|
11694
|
+
* keyword arm's write - the two agree on "not one passage was compared against the query",
|
|
11695
|
+
* not on any withholding flag, so a relevance floor that emptied a real search and a partial
|
|
11696
|
+
* withholding alongside a real search both stay 'ok' on both surfaces.
|
|
11697
|
+
* knowledgeBaseRetrieve cannot reach this state: it fetches named files rather than ranking
|
|
11698
|
+
* against a query embedding, so it has no comparison to come up empty.
|
|
11699
|
+
* 'failed' - recall did not complete: it threw, OR the retrieval repository is not wired on
|
|
11700
|
+
* this host (the guards in ChatCompletionFeatures / knowledgeBaseSearch / knowledgeBaseRetrieve
|
|
11701
|
+
* record it without anything throwing). What separates it from 'not_indexed' is the remedy,
|
|
11702
|
+
* not the tempo: fix the outage or the host wiring, never re-index content. An unwired host
|
|
11703
|
+
* reports continuously too, so "chronic" alone does not pick out 'not_indexed'.
|
|
11704
|
+
* NOT this: a model-supplied argument that is not a well-formed id. knowledgeBaseRetrieve
|
|
11705
|
+
* shape-checks `file_id` and answers a malformed one as a single-file miss ('ok'), because the
|
|
11706
|
+
* remedy is for the model to search for the right id - there is nothing for an operator to
|
|
11707
|
+
* fix. It is logged rather than counted here, so the rate stays observable without this field
|
|
11708
|
+
* reporting an outage that is not happening.
|
|
11709
|
+
* On multiple retrieval calls within one turn, merge priority is failed > not_indexed > ok >
|
|
11710
|
+
* no_lakes (see retrievalSummaryMerge.ts's mergeRetrievalSummary): a single failure is never
|
|
11711
|
+
* masked by a later success or abstain, an unsearchable corpus outranks a legitimate zero so a
|
|
11712
|
+
* success on another surface cannot erase it, and a real success is never masked by another
|
|
11713
|
+
* surface's "no lakes in scope" abstain in the same turn.
|
|
11714
|
+
*/
|
|
11715
|
+
outcome: z$1.enum([
|
|
11716
|
+
"ok",
|
|
11717
|
+
"no_lakes",
|
|
11718
|
+
"not_indexed",
|
|
11719
|
+
"failed"
|
|
11720
|
+
]).optional(),
|
|
11721
|
+
/**
|
|
11722
|
+
* Whether forced retrieval was ENABLED for this turn, independent of whether it then ran.
|
|
11723
|
+
*
|
|
11724
|
+
* This is what makes the optional path measurable. `attempted` says retrieval happened;
|
|
11725
|
+
* without `mode` there is no way to ask the complementary question - of the turns where the
|
|
11726
|
+
* model was merely OFFERED the knowledge tools, how often did it choose to retrieve - because
|
|
11727
|
+
* a forced turn and an optional turn both land as `attempted: true`.
|
|
11728
|
+
*
|
|
11729
|
+
* Optional on the schema, and absence NEVER means 'optional' - it means unclassified. Two
|
|
11730
|
+
* sources, one historical and one ongoing: turns recorded before this field landed, and
|
|
11731
|
+
* agent-mode runs, which write a retrieval summary through `persistRunAsQuest` but never pass
|
|
11732
|
+
* the seed site at the `offeredTools` write in ChatCompletionProcess. So date-bounding a rollup
|
|
11733
|
+
* removes the first source but not the second; count the unclassified bucket rather than
|
|
11734
|
+
* assuming it empties.
|
|
11735
|
+
*/
|
|
11736
|
+
mode: z$1.enum(["forced", "optional"]).optional(),
|
|
11737
|
+
/**
|
|
11738
|
+
* Whether the knowledge-base when-to-retrieve guidance section actually shipped in this turn's
|
|
11739
|
+
* tool prompt. Written only on turns that were OFFERED the knowledge tool, so absence means
|
|
11740
|
+
* "not an offered turn" or "recorded before this field landed" - it never means "cleared".
|
|
11741
|
+
*
|
|
11742
|
+
* `false` is the load-bearing value here, not filler. Clearing the KnowledgeBaseRetrievalPrompt
|
|
11743
|
+
* setting is the section's only off switch, so a turn recording `false` is the CONTROL arm of
|
|
11744
|
+
* the A/B this field exists to make readable. Anything merging or folding this must preserve an
|
|
11745
|
+
* explicit `false` rather than collapse it into absent - see mergeRetrievalSummary, which uses
|
|
11746
|
+
* `??` and deliberately not `||` for that reason.
|
|
11747
|
+
*
|
|
11748
|
+
* MUST STAY IN SYNC with TWO gates, not one. ToolBuilder.buildToolPrompt emits the section iff
|
|
11749
|
+
* the tool is offered AND the guidance string is non-empty; filterByPromptMode then drops the
|
|
11750
|
+
* whole `toolPrompt` source, which no promptMode admits, so an offered tool is not sufficient.
|
|
11751
|
+
* The ChatCompletionProcess seed site conjoins all three, and hands the first two to
|
|
11752
|
+
* buildToolPrompt as the same consts, so the flag and the actual emission cannot drift.
|
|
11753
|
+
*/
|
|
11754
|
+
knowledgeBaseGuidanceInjected: z$1.boolean().optional(),
|
|
11755
|
+
/**
|
|
11756
|
+
* Why the forced arm did not run on a turn that had it enabled. Only ever set with
|
|
11757
|
+
* `mode: 'forced'`, and only for the deliberate suppressions in
|
|
11758
|
+
* ChatCompletionFeatures.getContextMessages - a forced turn that ran and failed reports that
|
|
11759
|
+
* through `outcome`, not here.
|
|
11760
|
+
*
|
|
11761
|
+
* These turns are the reason this field exists: forced retrieval is configured, a rule
|
|
11762
|
+
* suppresses it, and the model falls back to the offered tool. That is exactly the population
|
|
11763
|
+
* the per-turn routing question is about, and before this it was indistinguishable from a turn
|
|
11764
|
+
* where forced retrieval was never configured at all.
|
|
11765
|
+
*/
|
|
11766
|
+
forcedSkipReason: z$1.enum(["attached_files", "personal_corpus"]).optional(),
|
|
11767
|
+
/** Which retrieval-capable surface(s) ran this turn, e.g. 'lake-memory', 'knowledgeBaseSearch'. */
|
|
11768
|
+
surfaces: z$1.array(z$1.string()),
|
|
11769
|
+
/** Lakes resolved at the moment retrieval ran, stamped point-in-time (not read live from the session). */
|
|
11770
|
+
dataLakeTags: z$1.array(z$1.string()),
|
|
11771
|
+
/**
|
|
11772
|
+
* Ids of the lakes whose `systemPrompt` was injected this turn (getAccessibleDataLakePrompts),
|
|
11773
|
+
* across every injection site (forced retrieval and the model-driven knowledge tools). NOT the
|
|
11774
|
+
* prompt text itself - that already reaches the model in the completion, and copying it here
|
|
11775
|
+
* widens exposure for nothing. Absent means no injection site ran; present-and-empty means one
|
|
11776
|
+
* ran but nothing qualified (untrusted, or an empty systemPrompt).
|
|
11777
|
+
*/
|
|
11778
|
+
injectedLakePromptIds: z$1.array(z$1.string()).optional(),
|
|
11779
|
+
/** mementoCount/mementoIds precedent: mirrors injectedLakePromptIds.length. */
|
|
11780
|
+
injectedLakePromptCount: z$1.number().optional(),
|
|
11781
|
+
/**
|
|
11782
|
+
* How much retrieved content actually reached the model this turn: `chunks` passages totalling
|
|
11783
|
+
* `chars` characters of retrieved CONTENT (headings and framing excluded, so the number means
|
|
11784
|
+
* the same thing on every surface), plus `topScore`, the best similarity among the compared
|
|
11785
|
+
* passages the reporting surface can SEE - which is not the same population on every surface.
|
|
11786
|
+
* Forced retrieval scores every chunk itself and so reports true near-misses; knowledgeBaseSearch's
|
|
11787
|
+
* semantic arm only ever sees `minScore` survivors, and reports no `topScore` at all on a starve,
|
|
11788
|
+
* so a sub-floor near-miss there is invisible rather than recorded.
|
|
11789
|
+
*
|
|
11790
|
+
* PRESENCE CONTRACT: present if and only if at least one surface COMPLETED a search this turn.
|
|
11791
|
+
* `chunks: 0` is a RECORDED STARVE - the library was searched and nothing was injected, which is
|
|
11792
|
+
* the case this field exists to make visible: without it, a forced-retrieval turn that injected
|
|
11793
|
+
* nothing is byte-identical to one that injected its whole character budget (both `outcome:
|
|
11794
|
+
* 'ok'`). Absence means the volume is UNKNOWN, which is what a turn carries when no surface
|
|
11795
|
+
* completed a search: retrieval was never attempted, nothing was in scope to search
|
|
11796
|
+
* ('no_lakes'), or the one surface that ran broke mid-flight, where a zero would be a lie.
|
|
11797
|
+
* A surface that completed but CANNOT know the turn's passage volume also stays silent rather
|
|
11798
|
+
* than claiming a zero - knowledgeBaseSearch's keyword arm on a hit is the case: it injects
|
|
11799
|
+
* file metadata and hands the model retrieve_knowledge_content, which injects the text and
|
|
11800
|
+
* reports no volume, so its zero would survive the merge as a starve that did not happen.
|
|
11801
|
+
*
|
|
11802
|
+
* KNOWN HOLE in that rule, while retrieve_knowledge_content stays uninstrumented: a recorded zero
|
|
11803
|
+
* is not PROOF of a starve. Forced retrieval and the knowledge tools are not mutually exclusive
|
|
11804
|
+
* (ChatCompletionProcess seeds on `forcedRetrievalEnabled || knowledgeToolOffered`), so the forced
|
|
11805
|
+
* arm can complete empty, write its honest zero, and the model can then ground the same turn
|
|
11806
|
+
* through retrieve_knowledge_content, which contributes no volume to oppose it. The zero is
|
|
11807
|
+
* per-surface-truthful and turn-level-misleading. Any rollup counting starves should treat a zero
|
|
11808
|
+
* as "nothing was injected by a surface that reports volume" and, until that tool reports its own,
|
|
11809
|
+
* cross-check `functionCalls` before calling the turn ungrounded.
|
|
11810
|
+
*
|
|
11811
|
+
* Per SURFACE, not per turn: a surface that breaks contributes nothing while a surface that
|
|
11812
|
+
* completed alongside it still reports its own volume, so a turn CAN read 'failed' next to a
|
|
11813
|
+
* recorded zero. That pairing means "one surface broke, and everything that did finish injected
|
|
11814
|
+
* nothing" - which is exactly what a reader needs, and strictly more than the outcome alone.
|
|
11815
|
+
*
|
|
11816
|
+
* SUMMED across surfaces, so this field and `outcome` can legitimately disagree in tone on a
|
|
11817
|
+
* multi-surface turn: forced retrieval grounding on 12 passages while knowledgeBaseSearch throws
|
|
11818
|
+
* gives `outcome: 'failed'` alongside `chunks: 12`. That is correct - `outcome` is worst-of,
|
|
11819
|
+
* `injected` is sum-of-completions.
|
|
11820
|
+
*
|
|
11821
|
+
* `topScore` is optional because only cosine-similarity surfaces have one to report. Lake
|
|
11822
|
+
* memory's belief `relevance` is a different scale and forced retrieval's pre-scan value is a
|
|
11823
|
+
* -1 sentinel; neither is ever written here, because a `max` across mixed scales, or against a
|
|
11824
|
+
* sentinel, is a number that reads as a similarity and is not one.
|
|
11825
|
+
*
|
|
11826
|
+
* Date-bound any rollup, the same caveat `mode` documents on itself: turns recorded before this
|
|
11827
|
+
* landed carry no volume, and no backfill is possible - the volume of a past turn is gone.
|
|
11828
|
+
*
|
|
11829
|
+
* `preRelativeFloorCandidates` and `postRelativeFloorCandidates` are the ONE pair here that is
|
|
11830
|
+
* not "what reached the model": `ranked.length` and `scored.length` in KnowledgeRetrievalFeature
|
|
11831
|
+
* - the candidates left after the absolute similarity floor, and after the relative floor
|
|
11832
|
+
* trims them. `chunks` is what survived the char budget on top of that, so the three
|
|
11833
|
+
* numbers bracket two independent trimmers:
|
|
11834
|
+
*
|
|
11835
|
+
* pre -> [relative floor] -> post -> [char budget] -> chunks
|
|
11836
|
+
*
|
|
11837
|
+
* They exist so a low `chunks` is diagnosable - a small corpus and a floor that trimmed a large
|
|
11838
|
+
* pool end in the same `chunks`. `pre - post` is the floor's own effect and nothing else;
|
|
11839
|
+
* `pre - chunks` is NOT, because the budget trims the same walk. Both optional: only forced
|
|
11840
|
+
* retrieval computes a ranked pool, a surface without one (lake memory, the knowledge tools)
|
|
11841
|
+
* never writes either, and absence must not read as zero candidates. SUMMED like `chunks`, with
|
|
11842
|
+
* the same absent-is-not-zero handling as `topScore`.
|
|
11843
|
+
*
|
|
11844
|
+
* COMPARE THE PAIR ONLY TO ITSELF, never to `chunks`, unless `surfaces` is forced retrieval
|
|
11845
|
+
* alone. `chunks` and `chars` sum across ALL surfaces while this pair is forced-only, so a mixed
|
|
11846
|
+
* turn can store `chunks` above `pre` - inverting the relationship the pair exposes. Lake memory
|
|
11847
|
+
* is the common case, not the exotic one: it is enabled inside the same forced-retrieval gate,
|
|
11848
|
+
* so on a lake-memory lake it writes on nearly every forced turn. Its chunks can be backed out
|
|
11849
|
+
* via `context.lakeMemory.beliefCount` (approximately - that count is pre-sanitization); its
|
|
11850
|
+
* CHARS land only inside the shared sum, with no per-surface field to subtract them back out, so
|
|
11851
|
+
* `chars` cannot be decontaminated at all. `pre - post` needs neither, which is the point of
|
|
11852
|
+
* storing both.
|
|
11853
|
+
*
|
|
11854
|
+
* BOTH SATURATE, so `pre` counts what the SCAN REACHED, not what the corpus holds: `pool` is
|
|
11855
|
+
* truncated in-scan at FORCED_RETRIEVAL_MAX_SCORED_CHUNKS (256), over a scan itself bounded by
|
|
11856
|
+
* FORCED_RETRIEVAL_MAX_SCANNED_CHUNKS (4000) across FORCED_RETRIEVAL_MAX_CANDIDATE_FILES (100).
|
|
11857
|
+
* 2000 qualifying chunks and 300 both record 256; above the cap a rollup is a plateau.
|
|
11858
|
+
*/
|
|
11859
|
+
injected: z$1.object({
|
|
11860
|
+
chunks: z$1.number(),
|
|
11861
|
+
chars: z$1.number(),
|
|
11862
|
+
topScore: z$1.number().optional(),
|
|
11863
|
+
preRelativeFloorCandidates: z$1.number().optional(),
|
|
11864
|
+
postRelativeFloorCandidates: z$1.number().optional()
|
|
11865
|
+
}).optional(),
|
|
11866
|
+
/**
|
|
11867
|
+
* Could the corpus in scope have answered this turn, whether or not the model went looking?
|
|
11868
|
+
*
|
|
11869
|
+
* The denominator the optional-path retrieval rate has always been missing (#1394). A rate of
|
|
11870
|
+
* "the model retrieved on 20% of offered turns" cannot say whether the other 80% were misses or
|
|
11871
|
+
* turns with nothing to find, and the two argue for opposite things: the first for routing work,
|
|
11872
|
+
* the second for leaving the optional path alone. Crossing this field with the rate separates
|
|
11873
|
+
* them.
|
|
11874
|
+
*
|
|
11875
|
+
* THE ONLY FIELD IN THIS BLOCK NOT WRITTEN BY THE TURN. Every sibling is stamped point-in-time
|
|
11876
|
+
* while the turn runs; this one is written afterwards by an offline replay
|
|
11877
|
+
* (packages/scripts/retrieval/answerability-replay.ts) that re-scores the recorded prompt against
|
|
11878
|
+
* the corpus. That is deliberate - the population it exists to measure is the turns where
|
|
11879
|
+
* retrieval did NOT run, so computing it live would mean adding a full brute-force chunk scan
|
|
11880
|
+
* (ChatCompletionFeatures' forced path, which has no ANN index) to exactly the turns that pay
|
|
11881
|
+
* nothing for retrieval today. The measurement is not worth that latency on live traffic.
|
|
11882
|
+
*
|
|
11883
|
+
* BEING A RECONSTRUCTION, IT CARRIES TWO DRIFTS THE OTHER FIELDS DO NOT:
|
|
11884
|
+
* 1. Corpus CONTENT moves. A document added or reindexed between the turn and the replay is
|
|
11885
|
+
* scored as though it had been there. `probedAt` discloses the gap; a replay run long after
|
|
11886
|
+
* the window is weak evidence, not strong.
|
|
11887
|
+
* 2. Corpus SCOPE is inferred, not recorded. The seed writes `dataLakeTags: []` on a turn where
|
|
11888
|
+
* retrieval never ran (ChatCompletionProcess), so the replay reconstructs scope from the
|
|
11889
|
+
* session's lakes as they stand at replay time. A session whose lake selection changed is
|
|
11890
|
+
* replayed against a corpus the turn never had, and NOTHING here flags that. Recording real
|
|
11891
|
+
* scope at seed time would fix it for future turns and is not done yet.
|
|
11892
|
+
* 3. The QUESTION can move out from under it. The probe is keyed to the quest, not to the
|
|
11893
|
+
* prompt text it scored, so a turn whose prompt is later rewritten in place keeps a probe
|
|
11894
|
+
* describing the question it used to ask. mergeRetrievalSummary preserves the probe across
|
|
11895
|
+
* a runtime write deliberately - dropping it would erase the backfill - so nothing
|
|
11896
|
+
* invalidates a stale one. Re-run the replay with --force over a window whose turns were
|
|
11897
|
+
* edited.
|
|
11898
|
+
*
|
|
11899
|
+
* RAW SCORE, NOT A VERDICT, so the cutoff lives in the reader. summarizeOptionalPathRetrieval
|
|
11900
|
+
* applies it at fold time, which lets the same replay be re-thresholded without re-running -
|
|
11901
|
+
* the point of storing the number, given the two live floors disagree by construction (forced
|
|
11902
|
+
* retrieval's absolute default is 0.75, the knowledge tool's is 0).
|
|
11903
|
+
*
|
|
11904
|
+
* `topScore` is the same raw cosine scale as `injected.topScore` and comparable to it. It is NOT
|
|
11905
|
+
* comparable to lake memory's belief relevance, for the reason `injected` documents at length.
|
|
11906
|
+
*
|
|
11907
|
+
* `scanTruncated` inherits forced retrieval's saturation: the replay bounds its scan the same
|
|
11908
|
+
* way, so a low `topScore` on a truncated scan is not proof the corpus lacked an answer - it is
|
|
11909
|
+
* proof the part that was scanned did. Treat those turns as unknown rather than as negatives.
|
|
11910
|
+
*
|
|
11911
|
+
* Absence means NOT PROBED - never "not answerable". Every turn predating the replay, and every
|
|
11912
|
+
* turn the replay skipped or failed on, is absent, so a fold must keep it as its own arm rather
|
|
11913
|
+
* than letting it fall in with the negatives.
|
|
11914
|
+
*/
|
|
11915
|
+
answerability: z$1.object({
|
|
11916
|
+
/** Best cosine the replay found across the reconstructed corpus. */
|
|
11917
|
+
topScore: z$1.number(),
|
|
11918
|
+
/** Chunks at or above `floor`. Separates "one lucky match" from "a rich seam". */
|
|
11919
|
+
candidatesAboveFloor: z$1.number(),
|
|
11920
|
+
/** The absolute floor the replay counted `candidatesAboveFloor` against, as a fraction. */
|
|
11921
|
+
floor: z$1.number(),
|
|
11922
|
+
/** The scan hit its chunk ceiling, so `topScore` is a floor on the true best, not the best. */
|
|
11923
|
+
scanTruncated: z$1.boolean(),
|
|
11924
|
+
/** When the replay ran, NOT when the turn ran - the disclosure for content drift above. */
|
|
11925
|
+
probedAt: JsonSafeDate
|
|
11926
|
+
}).optional(),
|
|
11927
|
+
/**
|
|
11928
|
+
* Which of this turn's injected lake prompt ids were BOTH in the session's pre-authorized (manage-
|
|
11929
|
+
* but-not-member admission) set AND injected on this turn - see unionPreauthorizedLakeAccess and
|
|
11930
|
+
* pages/api/sessions/create.ts. A subset of injectedLakePromptIds, never a superset. Narrows the
|
|
11931
|
+
* session's static `preauthorizedLakeIds` (what was ADMITTED) to what a given turn actually used.
|
|
11932
|
+
*
|
|
11933
|
+
* MEMBERSHIP, NOT CAUSATION. An admitted lake the caller could already reach - its creator, or a
|
|
11934
|
+
* member of its org - injects through the ordinary trust arm and is listed here all the same, so a
|
|
11935
|
+
* non-empty value does not prove the admission is what made the injection possible. Absent means no
|
|
11936
|
+
* admitted id was among this turn's injections, including every turn on a session with none.
|
|
11937
|
+
*/
|
|
11938
|
+
preauthorizedLakeIdsUsed: z$1.array(z$1.string()).optional()
|
|
11939
|
+
});
|
|
11940
|
+
/**
|
|
11941
|
+
* Why a grounded turn's library scan stopped short of the whole library.
|
|
11942
|
+
*
|
|
11943
|
+
* Written ONLY on a partially-covered turn (reportCoverage returns early otherwise), so presence
|
|
11944
|
+
* means "partial" and `partial` is always true - the flag is explicit anyway because a reader
|
|
11945
|
+
* checking `retrievalCoverage.partial` should not have to know that absence is the other half of
|
|
11946
|
+
* the contract.
|
|
11947
|
+
*
|
|
11948
|
+
* Single producer (ChatCompletionFeatures.reportCoverage), which is why - unlike `warnings`,
|
|
11949
|
+
* `citables` and `retrieval` - this field needs no merge case in applyQuestStatusChanges: a
|
|
11950
|
+
* later tool-arm write that omits it is preserved by the one-level spread.
|
|
11951
|
+
*
|
|
11952
|
+
* `reasons` is the same diagnostic prose the warnings entry interpolates. It is shown to the
|
|
11953
|
+
* reader behind a disclosure rather than in the banner body, because only some reasons are
|
|
11954
|
+
* actionable (a document mid-reindex returns on its own; a per-turn chunk budget does not).
|
|
11955
|
+
*/
|
|
11956
|
+
const RetrievalCoverageSchema = z$1.object({
|
|
11957
|
+
/** Always true - see the presence contract above. */
|
|
11958
|
+
partial: z$1.boolean(),
|
|
11959
|
+
/** One entry per distinct cause, e.g. a candidate cap, a scan budget, an embedding mismatch. */
|
|
11960
|
+
reasons: z$1.array(z$1.string())
|
|
11961
|
+
});
|
|
10297
11962
|
z$1.object({
|
|
10298
11963
|
model: PromptMetaModelSchema.optional(),
|
|
10299
11964
|
tokenUsage: PromptMetaTokenUsageSchema.optional(),
|
|
10300
11965
|
context: PromptMetaContextSchema.optional(),
|
|
11966
|
+
/** Per-turn retrieval outcome - see RetrievalSummarySchema. Top-level (not under `context`)
|
|
11967
|
+
* deliberately: applyQuestStatusChanges does a one-level spread merge, so a field nested under
|
|
11968
|
+
* `context` would be replaced wholesale by any tool-arm write instead of merging. */
|
|
11969
|
+
retrieval: RetrievalSummarySchema.optional(),
|
|
11970
|
+
/** Partial-grounding-coverage detail - see RetrievalCoverageSchema. Top-level for the same
|
|
11971
|
+
* one-level-spread-merge reason as `retrieval` above. */
|
|
11972
|
+
retrievalCoverage: RetrievalCoverageSchema.optional(),
|
|
10301
11973
|
functionCalls: z$1.array(PromptMetaFunctionCallSchema).optional(),
|
|
10302
11974
|
/**
|
|
10303
11975
|
* Names of the tools actually offered to the model this turn - the output of `buildTools`
|
|
@@ -10945,7 +12617,16 @@ const INVISIBLE_INK = /\u115F|\u1160|\u17B4|\u17B5|\u2800|\u3164|\uFFA0/g;
|
|
|
10945
12617
|
const hasBlankTagPrefixSegment = (prefix) => {
|
|
10946
12618
|
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, "")));
|
|
10947
12619
|
};
|
|
10948
|
-
|
|
12620
|
+
const DATA_LAKE_SLUG_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
12621
|
+
const DATA_LAKES = [{
|
|
12622
|
+
id: "opti-knowledge",
|
|
12623
|
+
slug: "opti-knowledge",
|
|
12624
|
+
name: "Optimization Knowledge Base",
|
|
12625
|
+
requiredUserTag: "Opti",
|
|
12626
|
+
requiredEntitlement: "optihashi:pro",
|
|
12627
|
+
fileTagPrefix: "opti:",
|
|
12628
|
+
datalakeTag: "datalake:opti-knowledge"
|
|
12629
|
+
}, ...(() => {
|
|
10949
12630
|
const raw = process.env.NEXT_PUBLIC_PREMIUM_DATA_LAKES;
|
|
10950
12631
|
if (!raw) return [];
|
|
10951
12632
|
try {
|
|
@@ -10955,6 +12636,7 @@ const hasBlankTagPrefixSegment = (prefix) => {
|
|
|
10955
12636
|
return [];
|
|
10956
12637
|
}
|
|
10957
12638
|
})()];
|
|
12639
|
+
new Set(DATA_LAKES.map((l) => l.id));
|
|
10958
12640
|
/**
|
|
10959
12641
|
* Canonical normalization for entitlement keys + `requiredEntitlement` values - the ONE
|
|
10960
12642
|
* rule, applied at write time (create/update/stamp) and at match time. Mirrors the
|
|
@@ -10962,11 +12644,10 @@ const hasBlankTagPrefixSegment = (prefix) => {
|
|
|
10962
12644
|
* casing matches the lowercase keys the resolver produces.
|
|
10963
12645
|
*/
|
|
10964
12646
|
const normalizeEntitlementKey = (key) => key.trim().toLowerCase();
|
|
10965
|
-
const slugRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
10966
12647
|
const sha256Regex = /^[a-f0-9]{64}$/;
|
|
10967
12648
|
z.object({
|
|
10968
12649
|
name: z.string().min(1).max(200),
|
|
10969
|
-
slug: z.string().min(2).max(60).regex(
|
|
12650
|
+
slug: z.string().min(2).max(60).regex(DATA_LAKE_SLUG_REGEX, "Slug must be lowercase alphanumeric with hyphens (e.g. \"my-data-lake\")"),
|
|
10970
12651
|
description: z.string().max(2e3).optional(),
|
|
10971
12652
|
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`),
|
|
10972
12653
|
requiredUserTag: z.string().min(1).max(100).optional(),
|
|
@@ -10982,7 +12663,13 @@ z.object({
|
|
|
10982
12663
|
requiredUserTag: z.union([z.literal(""), z.string().min(1).max(100)]).optional(),
|
|
10983
12664
|
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(),
|
|
10984
12665
|
auditQueryTextEnabled: z.boolean().optional(),
|
|
10985
|
-
|
|
12666
|
+
lakeMemoryEnabled: z.boolean().optional(),
|
|
12667
|
+
requiredPassageTokenTarget: z.number().int().min(64).max(OVERSIZED_PASSAGE_TOKEN_THRESHOLD).nullable().optional()
|
|
12668
|
+
});
|
|
12669
|
+
z.object({
|
|
12670
|
+
groundingMode: z.enum(DATA_LAKE_GROUNDING_MODES).optional(),
|
|
12671
|
+
preferredSystemPromptId: z.union([z.literal(""), z.string().min(1).max(200)]).optional(),
|
|
12672
|
+
systemPrompt: z.string().optional()
|
|
10986
12673
|
});
|
|
10987
12674
|
z.object({
|
|
10988
12675
|
organizationId: z.string().optional(),
|
|
@@ -11051,6 +12738,7 @@ tags: z.array(TaxonomyTagInput).max(100) });
|
|
|
11051
12738
|
z.object({
|
|
11052
12739
|
/** User description of the data (helps the AI) */
|
|
11053
12740
|
context: z.string().max(2e3).optional() });
|
|
12741
|
+
z.object({ tags: z.array(z.string().min(1).max(130).regex(/^[^\r\n]*$/)).max(100) });
|
|
11054
12742
|
z.object({ hashes: z.array(z.string().regex(sha256Regex)).min(1).max(500) });
|
|
11055
12743
|
const SyncDeltaFileEntry = z.object({
|
|
11056
12744
|
relativePath: z.string(),
|
|
@@ -11082,55 +12770,6 @@ z.object({
|
|
|
11082
12770
|
skip: z.array(z.string())
|
|
11083
12771
|
})
|
|
11084
12772
|
});
|
|
11085
|
-
z$1.enum([
|
|
11086
|
-
"inject",
|
|
11087
|
-
"auto-fire",
|
|
11088
|
-
"hidden"
|
|
11089
|
-
]);
|
|
11090
|
-
/**
|
|
11091
|
-
* Modes acceptable at AUTHORING time. 'hidden' is intentionally excluded until
|
|
11092
|
-
* the host has true hidden-send support - accepting it would persist a value
|
|
11093
|
-
* that silently behaves as 'auto-fire' (a surprising downgrade). It stays in
|
|
11094
|
-
* ExecutionModeSchema/the stored enum for forward-compat.
|
|
11095
|
-
*/
|
|
11096
|
-
const AuthorableExecutionModeSchema = z$1.enum(["inject", "auto-fire"]);
|
|
11097
|
-
/**
|
|
11098
|
-
* Tools a prompt may require - constrained to the host's closed tool set, MINUS
|
|
11099
|
-
* integration-gated tools that act on the caller's own credentials/account. A
|
|
11100
|
-
* shared system prompt must not be able to inject e.g. blog-publishing into a
|
|
11101
|
-
* non-author's session via requiredTools. (Per-user entitlement of the remaining
|
|
11102
|
-
* tools is still the chat pipeline's responsibility - see follow-up note in the
|
|
11103
|
-
* briefcase blueprint; this allowlist is the storage-layer floor.)
|
|
11104
|
-
*/
|
|
11105
|
-
const BRIEFCASE_DISALLOWED_TOOLS = [
|
|
11106
|
-
"blog_publish",
|
|
11107
|
-
"blog_edit",
|
|
11108
|
-
"blog_draft"
|
|
11109
|
-
];
|
|
11110
|
-
const BriefcaseRequiredToolsSchema = z$1.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
|
|
11111
|
-
z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
|
|
11112
|
-
const PROMPT_TEXT_MAX = 16e3;
|
|
11113
|
-
const TAGS_MAX = 20;
|
|
11114
|
-
z$1.object({
|
|
11115
|
-
type: z$1.string().min(1).max(100),
|
|
11116
|
-
name: z$1.string().min(1).max(200),
|
|
11117
|
-
description: z$1.string().max(500).optional(),
|
|
11118
|
-
promptText: z$1.string().min(1).max(PROMPT_TEXT_MAX),
|
|
11119
|
-
tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
|
|
11120
|
-
executionMode: AuthorableExecutionModeSchema.optional(),
|
|
11121
|
-
requiredTools: BriefcaseRequiredToolsSchema.optional()
|
|
11122
|
-
}).partial();
|
|
11123
|
-
/**
|
|
11124
|
-
* One catalog sub-query. Exactly one selector is used, in precedence order:
|
|
11125
|
-
* `personal` (resolved to the caller server-side) > `tags` > `type`.
|
|
11126
|
-
*/
|
|
11127
|
-
const PromptBatchQuerySchema = z$1.object({
|
|
11128
|
-
key: z$1.string().min(1).max(100),
|
|
11129
|
-
tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
|
|
11130
|
-
type: z$1.string().max(100).optional(),
|
|
11131
|
-
personal: z$1.boolean().optional()
|
|
11132
|
-
});
|
|
11133
|
-
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" }) });
|
|
11134
12773
|
z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid template id");
|
|
11135
12774
|
/**
|
|
11136
12775
|
* The bound model. Reuses the legacy-remap preprocess so a template saved under
|
|
@@ -11169,6 +12808,45 @@ z$1.object({
|
|
|
11169
12808
|
}).omit({ model: true }).partial();
|
|
11170
12809
|
z$1.union([ToolExecutionResponseSchema, ApiErrorSchema]);
|
|
11171
12810
|
/**
|
|
12811
|
+
* Response details shared by the endpoints that return generated audio as raw
|
|
12812
|
+
* bytes (music, sound effects). Not a contract - just the pieces both of their
|
|
12813
|
+
* contracts declare, kept in one place so the published media types and headers
|
|
12814
|
+
* cannot describe one endpoint and not the other.
|
|
12815
|
+
*/
|
|
12816
|
+
/**
|
|
12817
|
+
* Every Content-Type the ElevenLabs generators map an `output_format` token to
|
|
12818
|
+
* (`contentTypeForFormat` in ElevenLabsMusicGenerator / ElevenLabsSoundGenerator).
|
|
12819
|
+
* The first entry is the default (mp3); the rest are declared as alternates.
|
|
12820
|
+
* Must stay in sync with those two mappings.
|
|
12821
|
+
*/
|
|
12822
|
+
const GENERATED_AUDIO_CONTENT_TYPES = [
|
|
12823
|
+
"audio/mpeg",
|
|
12824
|
+
"audio/opus",
|
|
12825
|
+
"audio/L16",
|
|
12826
|
+
"audio/basic",
|
|
12827
|
+
"application/octet-stream"
|
|
12828
|
+
];
|
|
12829
|
+
/**
|
|
12830
|
+
* Where the browsable copy of the generated audio ended up. These are the ONLY
|
|
12831
|
+
* channel for that information on these endpoints: the body is raw audio, so a
|
|
12832
|
+
* caller that wants the saved file has nowhere else to read it from.
|
|
12833
|
+
*/
|
|
12834
|
+
const GENERATED_AUDIO_SAVE_HEADERS = {
|
|
12835
|
+
"X-B4M-Audio-Saved": "Whether a browsable copy was saved to the file browser (\"true\"/\"false\").",
|
|
12836
|
+
"X-B4M-Audio-Fab-File-Id": "Id of the saved file. Present only when the copy was saved.",
|
|
12837
|
+
"X-B4M-Audio-File-Name": "File name of the saved copy. Present only when the copy was saved.",
|
|
12838
|
+
"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."
|
|
12839
|
+
};
|
|
12840
|
+
/** The 200 response body of a raw-audio endpoint: default media type plus alternates. */
|
|
12841
|
+
const generatedAudioBody = () => ({
|
|
12842
|
+
contentType: GENERATED_AUDIO_CONTENT_TYPES[0],
|
|
12843
|
+
alsoReturns: GENERATED_AUDIO_CONTENT_TYPES.slice(1).map((contentType) => ({ contentType })),
|
|
12844
|
+
headers: GENERATED_AUDIO_SAVE_HEADERS
|
|
12845
|
+
});
|
|
12846
|
+
({ ...generatedAudioBody() });
|
|
12847
|
+
({ ...generatedAudioBody() });
|
|
12848
|
+
z$1.enum(["user", "convergence"]).optional().catch(void 0), z$1.string().optional();
|
|
12849
|
+
/**
|
|
11172
12850
|
* Blessed, self-hosted artifact library script paths (root-relative).
|
|
11173
12851
|
*
|
|
11174
12852
|
* Single source of truth shared across the artifact pipeline:
|
|
@@ -11272,6 +12950,10 @@ OpenAIImageGenerationInput.extend({
|
|
|
11272
12950
|
aspect_ratio: z$1.string().optional(),
|
|
11273
12951
|
fabFileIds: z$1.array(z$1.string()).prefault([]),
|
|
11274
12952
|
tools: z$1.array(z$1.union([b4mLLMTools, z$1.string()])).optional(),
|
|
12953
|
+
safety_tolerance: BFLSafetyToleranceSchema,
|
|
12954
|
+
prompt_upsampling: z$1.boolean().optional(),
|
|
12955
|
+
seed: z$1.number().nullable().optional(),
|
|
12956
|
+
output_format: z$1.enum(["jpeg", "png"]).nullable().optional(),
|
|
11275
12957
|
/** Resolved by the API route's prompt resolver. Defaults to 'fresh' for first-turn or sessions with no prior image. */
|
|
11276
12958
|
intent: PromptIntentSchema.optional(),
|
|
11277
12959
|
promptEnhancement: z$1.object({
|
|
@@ -11390,6 +13072,31 @@ z$1.object({
|
|
|
11390
13072
|
"grounded",
|
|
11391
13073
|
"surface"
|
|
11392
13074
|
]).optional(),
|
|
13075
|
+
/**
|
|
13076
|
+
* Suppress OUR server-side auto-offers without entering a promptMode. Exists because promptMode
|
|
13077
|
+
* was the only switch for the offer and it also strips every authored prompt, so no caller could
|
|
13078
|
+
* have an arm that went unoffered AND kept the abstention licence.
|
|
13079
|
+
*
|
|
13080
|
+
* Gates the three auto-add sites (the knowledge offer in resolveEnabledTools, the navigate_view
|
|
13081
|
+
* auto-add, the blog/skill gate), unioned with `Boolean(promptMode)` by
|
|
13082
|
+
* resolveSkipAutoOffers. A force-on, not an override: `false` under a promptMode still suppresses.
|
|
13083
|
+
* Withholding navigate_view also drops the viewRegistry system block, which only describes it.
|
|
13084
|
+
*
|
|
13085
|
+
* Withholds the OFFER, not knowledge: `session.forceKnowledgeRetrieval` is untouched, and an
|
|
13086
|
+
* already-attached corpus is inlined rather than deferred to the tool. An arm that must see no
|
|
13087
|
+
* knowledge at all also needs a session with no attachments and forced retrieval off.
|
|
13088
|
+
*/
|
|
13089
|
+
skipAutoOffers: z$1.boolean().optional(),
|
|
13090
|
+
/**
|
|
13091
|
+
* Caller-supplied system-prompt text. Rendered as a defended, deference-postured block
|
|
13092
|
+
* appended last in the system-prompt stack. Reached by both POST /api/chat and /api/ai/llm.
|
|
13093
|
+
*
|
|
13094
|
+
* This cap is the universal backstop, not a duplicate of a route check: the parse that opens
|
|
13095
|
+
* invoke() runs outside any try and before a quest row is written, so it holds for every caller
|
|
13096
|
+
* including ones that pass through no route schema. Do not drop it on the assumption that
|
|
13097
|
+
* whoever called validated first.
|
|
13098
|
+
*/
|
|
13099
|
+
systemPrompt: z$1.string().max(PROMPT_TEXT_MAX).optional(),
|
|
11393
13100
|
/** Whether Mementos is enabled */
|
|
11394
13101
|
enableMementos: z$1.boolean().optional(),
|
|
11395
13102
|
/** Whether Artifacts is enabled */
|
|
@@ -11465,6 +13172,12 @@ z$1.object({
|
|
|
11465
13172
|
}).optional()
|
|
11466
13173
|
});
|
|
11467
13174
|
const MCP_PROVIDER_METADATA = {
|
|
13175
|
+
notion: { defaultToolDescriptions: {
|
|
13176
|
+
notion_search: "Search for pages and databases in the connected Notion workspace by text query. Returns matching page titles, IDs, and URLs.",
|
|
13177
|
+
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.",
|
|
13178
|
+
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.",
|
|
13179
|
+
notion_append_blocks: "Append content blocks (paragraphs, headings, lists, code, etc.) to an existing Notion page or block. Requires write access."
|
|
13180
|
+
} },
|
|
11468
13181
|
atlassian: { defaultToolDescriptions: {
|
|
11469
13182
|
confluence_get_page: "Retrieve a Confluence page by ID or search by title within a space. Include page metadata and optional content.",
|
|
11470
13183
|
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.",
|
|
@@ -11910,6 +13623,7 @@ z$1.object({
|
|
|
11910
13623
|
reason: ReportReasonSchema,
|
|
11911
13624
|
details: z$1.string().max(2e3).optional()
|
|
11912
13625
|
});
|
|
13626
|
+
const PublishTagsSchema = z$1.array(z$1.string().max(60)).max(20);
|
|
11913
13627
|
z$1.object({
|
|
11914
13628
|
/** Short opaque id for short URLs (`/p/r/{publicId}`, `/p/f/{publicId}`) and lookups. */
|
|
11915
13629
|
publicId: z$1.string(),
|
|
@@ -11918,6 +13632,11 @@ z$1.object({
|
|
|
11918
13632
|
slug: SlugSchema,
|
|
11919
13633
|
title: z$1.string().min(1).max(200),
|
|
11920
13634
|
description: z$1.string().max(1e3).optional(),
|
|
13635
|
+
/** Freeform owner-supplied labels, normalized by normalizePublishTags. Shares a vocabulary
|
|
13636
|
+
* with AppFile tags (see GET /api/publish/tags) so one label means one thing across the app,
|
|
13637
|
+
* but stored per artifact rather than in a central tag table - there is no tag entity to keep
|
|
13638
|
+
* in sync, and a tag nobody uses simply stops appearing. */
|
|
13639
|
+
tags: PublishTagsSchema.prefault([]),
|
|
11921
13640
|
visibility: VisibilitySchema.prefault("private"),
|
|
11922
13641
|
/** Group id a viewer must belong to when gated cross-scope. */
|
|
11923
13642
|
gatedToGroupId: z$1.string().optional(),
|
|
@@ -12003,6 +13722,9 @@ z$1.object({
|
|
|
12003
13722
|
title: z$1.string().min(1).max(200),
|
|
12004
13723
|
description: z$1.string().max(1e3).optional(),
|
|
12005
13724
|
visibility: VisibilitySchema.optional(),
|
|
13725
|
+
/** Optional at publish time so a client that already knows its labels - the CLI publish
|
|
13726
|
+
* skill - can set them in the same call instead of a follow-up PATCH. */
|
|
13727
|
+
tags: PublishTagsSchema.optional(),
|
|
12006
13728
|
gatedToGroupId: z$1.string().optional(),
|
|
12007
13729
|
/** Who may annotate the published artifact. Defaults to `none` (read-only). */
|
|
12008
13730
|
commentPolicy: CommentPolicySchema.optional(),
|
|
@@ -12243,6 +13965,32 @@ function isModelAccessible(model, userTags, isAdmin = false, entitlementKeys = [
|
|
|
12243
13965
|
const normalizedAllowedEntitlements = (model.allowedEntitlements ?? []).map(normalizeEntitlementKey);
|
|
12244
13966
|
return normalizedKeys.some((key) => normalizedAllowedEntitlements.includes(key));
|
|
12245
13967
|
}
|
|
13968
|
+
[...OPENAI_IMAGE_MODELS, ...GEMINI_IMAGE_MODELS];
|
|
13969
|
+
/** Generation was cut off against the output-token ceiling. */
|
|
13970
|
+
const TRUNCATED_FINISH_REASON = "max_tokens";
|
|
13971
|
+
/**
|
|
13972
|
+
* We aborted the stream ourselves because it degenerated into repetition
|
|
13973
|
+
* (`DEGENERATE_STREAM_STOP_REASON` in `@bike4mind/llm-adapters`). Distinct from
|
|
13974
|
+
* `max_tokens` because the useful advice differs: telling a user to continue is actively
|
|
13975
|
+
* wrong here, since resuming from a degenerated tail tends to reproduce the loop.
|
|
13976
|
+
*/
|
|
13977
|
+
const DEGENERATE_FINISH_REASON = "degenerate_repetition";
|
|
13978
|
+
/**
|
|
13979
|
+
* Every reason meaning "this reply stopped early". Membership rather than equality with a
|
|
13980
|
+
* single literal, so a newly-added early-stop reason surfaces a notice automatically.
|
|
13981
|
+
*/
|
|
13982
|
+
const EARLY_STOP_FINISH_REASONS = /* @__PURE__ */ new Set([TRUNCATED_FINISH_REASON, DEGENERATE_FINISH_REASON]);
|
|
13983
|
+
/**
|
|
13984
|
+
* Whether a reply stopped early, given the reason reported for it.
|
|
13985
|
+
*
|
|
13986
|
+
* An ABSENT reason is NOT an early stop. Plenty of paths never report one (a backend whose
|
|
13987
|
+
* complete() leaves it unset, an older server, a non-terminal chunk), and treating silence
|
|
13988
|
+
* as truncation would cry wolf on every one of them. Only a reason we recognize as an
|
|
13989
|
+
* early stop counts - an unrecognized value is left alone rather than guessed at.
|
|
13990
|
+
*/
|
|
13991
|
+
function isEarlyStop(stopReason) {
|
|
13992
|
+
return !!stopReason && EARLY_STOP_FINISH_REASONS.has(stopReason);
|
|
13993
|
+
}
|
|
12246
13994
|
/**
|
|
12247
13995
|
* Trigger-word validation, shared between client form and server agent
|
|
12248
13996
|
* endpoints so the validation rules can't drift.
|
|
@@ -12322,7 +14070,7 @@ Array.from(new Set([
|
|
|
12322
14070
|
id: "opti.root",
|
|
12323
14071
|
section: "opti",
|
|
12324
14072
|
label: "OptiHashi Home",
|
|
12325
|
-
description: "The OptiHashi Optimizer landing page showing
|
|
14073
|
+
description: "The OptiHashi Optimizer landing page showing the pattern family cards",
|
|
12326
14074
|
navigationType: "route",
|
|
12327
14075
|
target: "/opti",
|
|
12328
14076
|
keywords: [
|
|
@@ -12852,9 +14600,9 @@ Array.from(new Set([
|
|
|
12852
14600
|
requiresAdmin: true
|
|
12853
14601
|
},
|
|
12854
14602
|
{
|
|
12855
|
-
id: "admin.
|
|
14603
|
+
id: "admin.feedback",
|
|
12856
14604
|
section: "admin",
|
|
12857
|
-
label: "
|
|
14605
|
+
label: "Feedback",
|
|
12858
14606
|
description: "View and manage user feedback, bug reports, and feature requests",
|
|
12859
14607
|
navigationType: "tab",
|
|
12860
14608
|
target: "2",
|
|
@@ -13223,6 +14971,66 @@ Array.from(new Set([
|
|
|
13223
14971
|
const [, top] = v.target.split("/");
|
|
13224
14972
|
return `/${top}`;
|
|
13225
14973
|
})));
|
|
14974
|
+
function getHeader(headers, name) {
|
|
14975
|
+
if (!headers || typeof headers !== "object") return null;
|
|
14976
|
+
if (typeof headers.get === "function") {
|
|
14977
|
+
const value = headers.get(name);
|
|
14978
|
+
return typeof value === "string" ? value : null;
|
|
14979
|
+
}
|
|
14980
|
+
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
14981
|
+
return typeof value === "string" ? value : null;
|
|
14982
|
+
}
|
|
14983
|
+
function parseCount(value) {
|
|
14984
|
+
if (value === null) return null;
|
|
14985
|
+
const trimmed = value.trim();
|
|
14986
|
+
if (!trimmed) return null;
|
|
14987
|
+
const parsed = Number(trimmed);
|
|
14988
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
14989
|
+
}
|
|
14990
|
+
const UNIT_MS = {
|
|
14991
|
+
ms: 1,
|
|
14992
|
+
s: 1e3,
|
|
14993
|
+
m: 6e4,
|
|
14994
|
+
h: 36e5
|
|
14995
|
+
};
|
|
14996
|
+
const DURATION_PART = /(\d+(?:\.\d+)?)(ms|h|m|s)/g;
|
|
14997
|
+
/**
|
|
14998
|
+
* Parse a Go-style duration ("6ms", "0s", "1m30s", "1h2m3s") to milliseconds.
|
|
14999
|
+
*
|
|
15000
|
+
* Exported for its own tests: it is the part of this module that can be wrong in a way the
|
|
15001
|
+
* numbers still look plausible.
|
|
15002
|
+
*/
|
|
15003
|
+
function parseDurationMs(value) {
|
|
15004
|
+
if (typeof value !== "string") return null;
|
|
15005
|
+
const trimmed = value.trim();
|
|
15006
|
+
if (!trimmed) return null;
|
|
15007
|
+
DURATION_PART.lastIndex = 0;
|
|
15008
|
+
let total = 0;
|
|
15009
|
+
let matched = 0;
|
|
15010
|
+
let consumed = 0;
|
|
15011
|
+
for (const part of trimmed.matchAll(DURATION_PART)) {
|
|
15012
|
+
total += Number(part[1]) * UNIT_MS[part[2]];
|
|
15013
|
+
consumed += part[0].length;
|
|
15014
|
+
matched += 1;
|
|
15015
|
+
}
|
|
15016
|
+
if (matched === 0 || consumed !== trimmed.length) return null;
|
|
15017
|
+
return total;
|
|
15018
|
+
}
|
|
15019
|
+
/** Read both rate-limit dimensions off a provider response. */
|
|
15020
|
+
function parseEmbeddingRateLimitHeaders(headers) {
|
|
15021
|
+
return {
|
|
15022
|
+
limitTokens: parseCount(getHeader(headers, "x-ratelimit-limit-tokens")),
|
|
15023
|
+
limitRequests: parseCount(getHeader(headers, "x-ratelimit-limit-requests")),
|
|
15024
|
+
remainingTokens: parseCount(getHeader(headers, "x-ratelimit-remaining-tokens")),
|
|
15025
|
+
remainingRequests: parseCount(getHeader(headers, "x-ratelimit-remaining-requests")),
|
|
15026
|
+
resetTokensMs: parseDurationMs(getHeader(headers, "x-ratelimit-reset-tokens")),
|
|
15027
|
+
resetRequestsMs: parseDurationMs(getHeader(headers, "x-ratelimit-reset-requests"))
|
|
15028
|
+
};
|
|
15029
|
+
}
|
|
15030
|
+
/** True when the provider reported at least one usable ceiling. */
|
|
15031
|
+
function hasUsableLimits(snapshot) {
|
|
15032
|
+
return snapshot.limitTokens !== null || snapshot.limitRequests !== null;
|
|
15033
|
+
}
|
|
13226
15034
|
dayjs.extend(utc);
|
|
13227
15035
|
dayjs.extend(timezone);
|
|
13228
15036
|
dayjs.extend(relativeTime);
|
|
@@ -13291,16 +15099,34 @@ function isUserInitiatedAbort(error, userSignal) {
|
|
|
13291
15099
|
return isAbortError && !userSignal;
|
|
13292
15100
|
}
|
|
13293
15101
|
/**
|
|
13294
|
-
*
|
|
15102
|
+
* A Retry-After hint is only useful if it asks us to wait. `Retry-After: 0`, a negative value, or an
|
|
15103
|
+
* HTTP date that has already passed all carry no timing information - and every `withRetry` in this
|
|
15104
|
+
* repo treats a non-null hint as authoritative *over* its exponential backoff, so returning 0 does
|
|
15105
|
+
* not mean "wait a moment", it means "abandon the backoff entirely and retry immediately".
|
|
15106
|
+
*
|
|
15107
|
+
* That inverts the retry budget exactly when it matters: a server sends Retry-After when it is
|
|
15108
|
+
* already struggling, so honouring a zero turns the remaining attempts into an instant burst against
|
|
15109
|
+
* a service asking for room. Null instead, so the caller falls through to its own backoff.
|
|
15110
|
+
*
|
|
15111
|
+
* Exported because the rule, not the code, is the thing worth sharing: `Retry-After` is parsed in
|
|
15112
|
+
* more than one package (fab-pipeline's `getOpenSearchRetryAfterMs`), and a four-token predicate
|
|
15113
|
+
* copied around is a rule that drifts. Feed it a delay already converted to ms.
|
|
15114
|
+
*/
|
|
15115
|
+
function retryAfterHintOrNull(ms) {
|
|
15116
|
+
return ms > 0 ? ms : null;
|
|
15117
|
+
}
|
|
15118
|
+
/**
|
|
15119
|
+
* Extract retry delay from error response (e.g., Retry-After header). Returns null when the header is
|
|
15120
|
+
* absent, unparseable, or does not ask us to wait - see retryAfterHintOrNull.
|
|
13295
15121
|
*/
|
|
13296
15122
|
function getRetryAfterMs(error) {
|
|
13297
15123
|
if (!isAxiosError(error)) return null;
|
|
13298
15124
|
const retryAfter = error.response?.headers?.["retry-after"];
|
|
13299
15125
|
if (!retryAfter) return null;
|
|
13300
15126
|
const seconds = parseInt(retryAfter, 10);
|
|
13301
|
-
if (!isNaN(seconds)) return seconds * 1e3;
|
|
15127
|
+
if (!isNaN(seconds)) return retryAfterHintOrNull(seconds * 1e3);
|
|
13302
15128
|
const date = Date.parse(retryAfter);
|
|
13303
|
-
if (!isNaN(date)) return
|
|
15129
|
+
if (!isNaN(date)) return retryAfterHintOrNull(date - Date.now());
|
|
13304
15130
|
return null;
|
|
13305
15131
|
}
|
|
13306
15132
|
/**
|
|
@@ -13875,7 +15701,7 @@ const CliConfigSchema = z$1.object({
|
|
|
13875
15701
|
}).optional(),
|
|
13876
15702
|
mcpServers: McpServersSchema,
|
|
13877
15703
|
preferences: z$1.object({
|
|
13878
|
-
maxTokens: z$1.number(),
|
|
15704
|
+
maxTokens: z$1.number().optional(),
|
|
13879
15705
|
temperature: z$1.number(),
|
|
13880
15706
|
autoSave: z$1.boolean(),
|
|
13881
15707
|
autoCompact: z$1.boolean().optional().prefault(true),
|
|
@@ -13970,10 +15796,27 @@ const ProjectLocalConfigSchema = z$1.object({
|
|
|
13970
15796
|
sandbox: PartialSandboxConfigSchema
|
|
13971
15797
|
});
|
|
13972
15798
|
/**
|
|
15799
|
+
* The output budget every pre-migration config was born with, back when
|
|
15800
|
+
* `preferences.maxTokens` was required and DEFAULT_CONFIG supplied this value. It is
|
|
15801
|
+
* indistinguishable from a user who deliberately typed 4096, and the migration reverts
|
|
15802
|
+
* it either way - acceptable only because the cleanup runs ONCE (see CONFIG_SCHEMA_VERSION):
|
|
15803
|
+
* a user who wanted 4096 sets it again and keeps it, while an install that never chose it
|
|
15804
|
+
* stops being capped by it. A value-keyed rule with no marker would instead make 4096
|
|
15805
|
+
* permanently unrepresentable, even though the /config select still offers it.
|
|
15806
|
+
*/
|
|
15807
|
+
const LEGACY_PINNED_MAX_TOKENS = 4096;
|
|
15808
|
+
/**
|
|
15809
|
+
* Schema version of the on-disk config, and the marker that makes the migrations in
|
|
15810
|
+
* `load()` one-time. A file stamped with anything else gets the upgrade pass and is
|
|
15811
|
+
* rewritten at the current version; a file already at it is left alone. Bump this when
|
|
15812
|
+
* adding a migration, and gate the new step on the version it needs to run from.
|
|
15813
|
+
*/
|
|
15814
|
+
const CONFIG_SCHEMA_VERSION = "0.2.0";
|
|
15815
|
+
/**
|
|
13973
15816
|
* Default configuration
|
|
13974
15817
|
*/
|
|
13975
15818
|
const DEFAULT_CONFIG = {
|
|
13976
|
-
version:
|
|
15819
|
+
version: CONFIG_SCHEMA_VERSION,
|
|
13977
15820
|
userId: v4(),
|
|
13978
15821
|
defaultModel: ChatModels.CLAUDE_4_5_SONNET,
|
|
13979
15822
|
toolApiKeys: {
|
|
@@ -13982,7 +15825,6 @@ const DEFAULT_CONFIG = {
|
|
|
13982
15825
|
},
|
|
13983
15826
|
mcpServers: [],
|
|
13984
15827
|
preferences: {
|
|
13985
|
-
maxTokens: 4096,
|
|
13986
15828
|
temperature: .7,
|
|
13987
15829
|
autoSave: true,
|
|
13988
15830
|
autoCompact: true,
|
|
@@ -14262,6 +16104,13 @@ var ConfigStore = class {
|
|
|
14262
16104
|
if (oldApiConfig.environment === "custom" && oldApiConfig.customUrl) rawConfig.apiConfig = { customUrl: oldApiConfig.customUrl };
|
|
14263
16105
|
else rawConfig.apiConfig = {};
|
|
14264
16106
|
}
|
|
16107
|
+
if (rawConfig.version !== CONFIG_SCHEMA_VERSION) {
|
|
16108
|
+
if (rawConfig.preferences?.maxTokens === LEGACY_PINNED_MAX_TOKENS) delete rawConfig.preferences.maxTokens;
|
|
16109
|
+
rawConfig.version = CONFIG_SCHEMA_VERSION;
|
|
16110
|
+
try {
|
|
16111
|
+
await promises.writeFile(this.configPath, JSON.stringify(rawConfig, null, 2), "utf-8");
|
|
16112
|
+
} catch {}
|
|
16113
|
+
}
|
|
14265
16114
|
const validated = CliConfigSchema.parse(rawConfig);
|
|
14266
16115
|
const normalizedMcpServers = normalizeMcpServers(validated.mcpServers);
|
|
14267
16116
|
globalConfig = {
|
|
@@ -14692,4 +16541,4 @@ var ConfigStore = class {
|
|
|
14692
16541
|
}
|
|
14693
16542
|
};
|
|
14694
16543
|
//#endregion
|
|
14695
|
-
export {
|
|
16544
|
+
export { SupportedFabFileMimeTypes as $, buildRateLimitLogEntry as $t, HTTPError as A, isRenderableModelType as At, OPENAI_GPT_IMAGE_1_IMAGE_SIZES as B, resolveHistoryFetchLimit as Bt, DEFAULT_MUSIC_MODEL_ID as C, isGeminiModelId as Ct, FIXED_TEMPERATURE_MODELS as D, isModelAccessible as Dt, FIELD_GROUP_OF as E, isMediaModelType as Et, MODEL_INFO_FIELD_GROUP_OF as F, isZodError as Ft, PermissionDeniedError as G, usdToCredits as Gt, OllamaEmbeddingModel as H, settingsMap as Ht, McpServerName as I, mapMimeTypeToArtifactType as It, REFUSAL_FALLBACK_MODELS as J, ACTOR_COLOR_SLOTS as Jt, REASONING_EFFORT_INCOMPATIBLE_WITH_TOOLS_MODELS as K, usdToCreditsStochastic as Kt, ModelBackend as L, obfuscateApiKey as Lt, IMAGE_SIZE_CONSTRAINTS as M, isSupportedFabFileMimeType as Mt, ImageModels as N, isUnlimitedHistory as Nt, FORMAT_PROMPT_TEMPLATE as O, isModelDeprecated as Ot, InternalServerError as P, isUserInitiatedAbort as Pt, SpeechToTextModels as Q, selfClaimedActorKindSchema as Qt, NO_TEMPERATURE_MODELS as R, parseEmbeddingRateLimitHeaders as Rt, CorruptedFileError as S, isGPTImageModel as St, DEGENERATE_FINISH_REASON as T, isImageServeable as Tt, OpenAIEmbeddingModel as U, toModelInfo as Ut, OPENAI_GPT_IMAGE_2_IMAGE_SIZES as V, secureParameters as Vt, PROMPT_TEXT_MAX as W, toModelRecord as Wt, REVIEW_GATE_STATUS_VALUES as X, actorKindMarker as Xt, RESPONSES_API_TOOL_MODELS as Y, actorColorIndex as Yt, SUBQUEST_STATUS_VALUES as Z, actorKindSchema as Zt, BadRequestError as _, isChunkRebuildPending as _t, getCreditsUrl as a, VideoModels as at, CREDIT_DEDUCT_TRANSACTION_TYPES as b, isFieldGroup as bt, requireApiUrl as c, applyModelPriceCatalog as ct, AGENT_QUEST_MANIFEST as d, defaultEmbeddingModelForEnv as dt, extractSnippetMeta as en, TTS_MAX_INPUT_CHARS as et, AGENT_QUEST_MCP_URI as f, getMcpProviderMetadata as ft, BFL_SAFETY_TOLERANCE as g, isAudioMimeType as gt, BEDROCK_NO_PROMPT_CACHING_MODELS as h, hasUsableLimits as ht, LOCAL_DEV_URL as i, VIDEO_SIZE_CONSTRAINTS as it, HttpStatus as j, isRetryableError as jt, ForbiddenError as k, isPlaceholderApiKey as kt, resolveApiEndpoint as l, calculateRetryDelay as lt, ApiKeyType as m, getRetryAfterMs as mt, logger as n, parseRateLimitHeaders as nn, UnauthorizedError as nt, getEnvironmentName as o, VoyageAIEmbeddingModel as ot, ARTIFACT_ATTRS_PATTERN as p, getQuestErrorCode as pt, REASONING_SUPPORTED_MODELS as q, withRetry as qt, ApiEndpointUnconfiguredError as r, UnprocessableEntityError as rt, parseApiUrl as s, WORK_ITEM_STATUSES as st, ConfigStore as t, isNearLimit as tn, TooManyRequestsError as tt, AGENT_QUEST_ID as u, dayjsConfig_default as ut, BedrockEmbeddingModel as v, isChunkStalledFile as vt, DEFAULT_UNKNOWN_CONTEXT_WINDOW as w, isImageAttachment as wt, ChatModels as x, isGPTImage2Model as xt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as y, isEarlyStop as yt, NotFoundError as z, reservationOutputTokens as zt };
|