@bike4mind/cli 0.20.2 → 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-T7Oh84Yn.mjs → AgentHistoryStore-ucv6xXVT.mjs} +1127 -193
- package/dist/{ApiClient-BopvMmQk.mjs → ApiClient-Ut1MXtOs.mjs} +2 -2
- package/dist/{ConfigStore-cIyF7hDg.mjs → ConfigStore-CoY0l0gr.mjs} +1393 -187
- package/dist/{buildAgent-P0tOMLt1.mjs → buildAgent-jZhBReAr.mjs} +2 -2
- 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 +3 -3
- 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 +13 -54
- package/dist/{package-CnVCHR3U.mjs → package-CfIETbXd.mjs} +1 -1
- package/dist/{serve-BocVOJ3W.mjs → serve-C9UDR5px.mjs} +5 -3
- package/package.json +13 -12
|
@@ -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",
|
|
@@ -785,6 +925,62 @@ let ImageModels = /* @__PURE__ */ function(ImageModels) {
|
|
|
785
925
|
Object.values(ImageModels);
|
|
786
926
|
z$1.enum(ImageModels);
|
|
787
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
|
+
/**
|
|
788
984
|
* Chat Models
|
|
789
985
|
*
|
|
790
986
|
* https://platform.openai.com/docs/models/continuous-model-upgrades
|
|
@@ -1122,6 +1318,14 @@ const SUBQUEST_STATUS_VALUES = [
|
|
|
1122
1318
|
"deleted"
|
|
1123
1319
|
];
|
|
1124
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
|
+
/**
|
|
1125
1329
|
* Valid complexity ratings for quests.
|
|
1126
1330
|
* Canonical vocabulary - matches what the planner generates and validates
|
|
1127
1331
|
* (Easy < 1 hour, Medium 1-4 hours, Hard > 4 hours).
|
|
@@ -2188,6 +2392,13 @@ z$1.object({
|
|
|
2188
2392
|
createdAt: z$1.date(),
|
|
2189
2393
|
updatedAt: z$1.date()
|
|
2190
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
|
+
}({});
|
|
2191
2402
|
/**
|
|
2192
2403
|
* Check if a value is a placeholder (not configured or SST default).
|
|
2193
2404
|
* Uses case-insensitive comparison and trims whitespace to prevent bypass attempts.
|
|
@@ -2233,6 +2444,42 @@ function isPlaceholderApiKey(value) {
|
|
|
2233
2444
|
if (!normalized) return true;
|
|
2234
2445
|
return PLACEHOLDER_API_KEY_REGEX.test(normalized);
|
|
2235
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));
|
|
2236
2483
|
z$1.object({
|
|
2237
2484
|
/**
|
|
2238
2485
|
* The granting lake's Mongo `_id`. ALWAYS a persisted DB lake: a hardcoded/fallback lake has no
|
|
@@ -2263,6 +2510,23 @@ z$1.object({
|
|
|
2263
2510
|
*/
|
|
2264
2511
|
expiresAt: z$1.date().nullish()
|
|
2265
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
|
+
});
|
|
2266
2530
|
/**
|
|
2267
2531
|
* Every `IDataLake` field, classified as audited or not. A TOTAL map keyed by `keyof IDataLake`,
|
|
2268
2532
|
* exactly like `LAKE_FIELD_VISIBILITY` in redactLakeForActor.ts and for the same reason: a list of
|
|
@@ -2289,6 +2553,7 @@ const LAKE_CONFIG_FIELD_AUDIT = {
|
|
|
2289
2553
|
organizationId: "audited",
|
|
2290
2554
|
isPublic: "audited",
|
|
2291
2555
|
auditQueryTextEnabled: "audited",
|
|
2556
|
+
lakeMemoryEnabled: "audited",
|
|
2292
2557
|
status: "audited",
|
|
2293
2558
|
createdByUserId: "audited",
|
|
2294
2559
|
lastUpdatedByUserId: "excluded",
|
|
@@ -2300,7 +2565,10 @@ const LAKE_CONFIG_FIELD_AUDIT = {
|
|
|
2300
2565
|
filesDeletedAt: "excluded",
|
|
2301
2566
|
filesArchivedAt: "excluded",
|
|
2302
2567
|
lakeMemoryExtractionAt: "excluded",
|
|
2303
|
-
lakeMemoryCursor: "excluded"
|
|
2568
|
+
lakeMemoryCursor: "excluded",
|
|
2569
|
+
lakeMemoryPurgedAt: "excluded",
|
|
2570
|
+
inconsistencyReport: "excluded",
|
|
2571
|
+
inconsistencyComputedAt: "excluded"
|
|
2304
2572
|
};
|
|
2305
2573
|
[...Object.keys(LAKE_CONFIG_FIELD_AUDIT).filter((field) => LAKE_CONFIG_FIELD_AUDIT[field] === "audited")];
|
|
2306
2574
|
/**
|
|
@@ -3094,6 +3362,71 @@ const usdToCreditsStochastic = (usd, rng = cryptoUniform, rate = CREDITS_PER_USD
|
|
|
3094
3362
|
const fraction = raw - base;
|
|
3095
3363
|
return base + (rng() < fraction ? 1 : 0);
|
|
3096
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);
|
|
3097
3430
|
z.enum([
|
|
3098
3431
|
"openai",
|
|
3099
3432
|
"test",
|
|
@@ -3101,6 +3434,58 @@ z.enum([
|
|
|
3101
3434
|
"xai",
|
|
3102
3435
|
"gemini"
|
|
3103
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" }) });
|
|
3104
3489
|
z$1.object({
|
|
3105
3490
|
sessionId: z$1.string().nullish(),
|
|
3106
3491
|
message: z$1.string(),
|
|
@@ -3116,7 +3501,7 @@ z$1.object({
|
|
|
3116
3501
|
wait: z$1.boolean().prefault(false),
|
|
3117
3502
|
enableTools: z$1.boolean().prefault(false),
|
|
3118
3503
|
toolMode: z$1.enum(["fast", "smart"]).optional(),
|
|
3119
|
-
tools: z$1.array(z$1.string()).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."),
|
|
3120
3505
|
enableQuestMaster: z$1.boolean().optional(),
|
|
3121
3506
|
enableMementos: z$1.boolean().optional(),
|
|
3122
3507
|
enableAgents: z$1.boolean().optional(),
|
|
@@ -3125,8 +3510,10 @@ z$1.object({
|
|
|
3125
3510
|
"grounded",
|
|
3126
3511
|
"surface"
|
|
3127
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."),
|
|
3128
3514
|
includePromptDetails: z$1.boolean().optional(),
|
|
3129
|
-
includeSystemPrompt: 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.")
|
|
3130
3517
|
});
|
|
3131
3518
|
z$1.object({
|
|
3132
3519
|
id: z$1.string(),
|
|
@@ -3135,6 +3522,12 @@ z$1.object({
|
|
|
3135
3522
|
timestamp: z$1.string(),
|
|
3136
3523
|
model: z$1.string(),
|
|
3137
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(),
|
|
3138
3531
|
tracking_info: z$1.object({
|
|
3139
3532
|
quest_id: z$1.string(),
|
|
3140
3533
|
check_status_url: z$1.string(),
|
|
@@ -3357,13 +3750,93 @@ const AGENT_EXECUTION_STATUSES = [
|
|
|
3357
3750
|
"failed",
|
|
3358
3751
|
"aborted"
|
|
3359
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
|
+
}
|
|
3360
3828
|
const DataSubscribeRequestAction = z$1.object({
|
|
3361
3829
|
action: z$1.literal("subscribe_query"),
|
|
3362
3830
|
accessToken: z$1.string().optional(),
|
|
3363
3831
|
subscriptionId: z$1.string(),
|
|
3364
3832
|
collectionName: z$1.string(),
|
|
3365
|
-
query: z$1.looseObject({}),
|
|
3366
|
-
|
|
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()])),
|
|
3367
3840
|
fetchInitialData: z$1.boolean().prefault(true).optional(),
|
|
3368
3841
|
clientId: z$1.string().optional()
|
|
3369
3842
|
});
|
|
@@ -3450,6 +3923,17 @@ const DataSubscriptionUpdateAction = z$1.object({
|
|
|
3450
3923
|
id: z$1.string()
|
|
3451
3924
|
})
|
|
3452
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
|
+
});
|
|
3453
3937
|
const LLMStatusUpdateAction = z$1.object({
|
|
3454
3938
|
action: z$1.literal("llm_status_update"),
|
|
3455
3939
|
status: z$1.string().nullable(),
|
|
@@ -4682,6 +5166,7 @@ const OptiHashiRunUpdatedAction = z$1.object({
|
|
|
4682
5166
|
});
|
|
4683
5167
|
z$1.discriminatedUnion("action", [
|
|
4684
5168
|
DataSubscriptionUpdateAction,
|
|
5169
|
+
DataSubscribeErrorAction,
|
|
4685
5170
|
InboxRefetchAction,
|
|
4686
5171
|
LLMStatusUpdateAction,
|
|
4687
5172
|
InvitesRefetchAction,
|
|
@@ -4739,6 +5224,126 @@ z$1.discriminatedUnion("action", [
|
|
|
4739
5224
|
PermissionRequestAction,
|
|
4740
5225
|
ReconnectResultAction
|
|
4741
5226
|
]);
|
|
5227
|
+
z$1.object({
|
|
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. */
|
|
5231
|
+
model: z$1.string().optional(),
|
|
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
|
+
*/
|
|
5251
|
+
tools: z$1.array(z$1.string()).optional(),
|
|
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()
|
|
5283
|
+
});
|
|
5284
|
+
z$1.object({
|
|
5285
|
+
id: z$1.string(),
|
|
5286
|
+
status: z$1.literal("pending"),
|
|
5287
|
+
session_id: z$1.string(),
|
|
5288
|
+
model: z$1.string(),
|
|
5289
|
+
timestamp: z$1.string(),
|
|
5290
|
+
tracking_info: z$1.object({
|
|
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()
|
|
5298
|
+
})
|
|
5299
|
+
});
|
|
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()
|
|
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) });
|
|
4742
5347
|
/**
|
|
4743
5348
|
* Tool schema matching ICompletionOptionTools.toolSchema. The Zod surface only
|
|
4744
5349
|
* covers wire-format fields (toolFn is server-side). Replaces the historical
|
|
@@ -5813,43 +6418,129 @@ z$2.object({
|
|
|
5813
6418
|
*/
|
|
5814
6419
|
const OVERSIZED_PASSAGE_TOKEN_THRESHOLD = 1500;
|
|
5815
6420
|
/**
|
|
5816
|
-
*
|
|
5817
|
-
* (#1676). The file keeps its chunks but has no vectors, so it is unsearchable until re-indexed, and
|
|
5818
|
-
* it does NOT auto-resume.
|
|
6421
|
+
* Why a file's chunk/vector pipeline is STALLED, stored in `FabFile.chunkStallReason`.
|
|
5819
6422
|
*
|
|
5820
|
-
*
|
|
5821
|
-
*
|
|
5822
|
-
*
|
|
5823
|
-
*
|
|
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.
|
|
5824
6488
|
*/
|
|
5825
|
-
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;
|
|
5826
6496
|
/**
|
|
5827
|
-
*
|
|
5828
|
-
*
|
|
5829
|
-
*
|
|
5830
|
-
*
|
|
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.
|
|
5831
6508
|
*
|
|
5832
|
-
*
|
|
5833
|
-
*
|
|
5834
|
-
*
|
|
5835
|
-
*
|
|
5836
|
-
*
|
|
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.
|
|
5837
6518
|
*
|
|
5838
|
-
*
|
|
5839
|
-
*
|
|
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.
|
|
5840
6528
|
*/
|
|
5841
|
-
const
|
|
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
|
+
}
|
|
5842
6533
|
/**
|
|
5843
6534
|
* `FabFile.chunkRebuildRequestedAt`: stamped by `resetChunkStateByIds` in the SAME write that
|
|
5844
6535
|
* clears a file's chunk rollups, so "this file's passages are being rebuilt" can never be lost the
|
|
5845
6536
|
* way the pair of steps that creates the state can be. The reset and the queue send are two
|
|
5846
6537
|
* operations - kill the producer between them, or lose the consumer's marker write, and the file
|
|
5847
|
-
* sits at `chunkCount: 0` with `error: null` and
|
|
6538
|
+
* sits at `chunkCount: 0` with `error: null` and no stall reason, a shape indistinguishable from an
|
|
5848
6539
|
* image or a still-uploading row. It then drops out of lake health's denominator, out of the
|
|
5849
6540
|
* convergence plan and out of the retrieval withhold at the same moment: every rollup says its
|
|
5850
6541
|
* passages are gone, and nothing reports it.
|
|
5851
6542
|
*
|
|
5852
|
-
* Deliberately NOT `
|
|
6543
|
+
* Deliberately NOT the `rechunkPaused` stall reason pre-written by the producer, which is the obvious
|
|
5853
6544
|
* fix and the wrong one: that marker means "halted, needs an administrator", so a file awaiting an
|
|
5854
6545
|
* ORDINARY rebuild would read to every reader as permanently paused for the whole rebuild - search
|
|
5855
6546
|
* would tell readers it does not return on its own, health would hard-fail P3, and "Rebuild
|
|
@@ -5861,9 +6552,9 @@ const CONVERGENCE_PAUSED_CHUNK_NOTE = "Re-chunking paused by the data-lake conve
|
|
|
5861
6552
|
* upgrade therefore degrades to mislabelled-but-visible rather than invisible, which is the trade
|
|
5862
6553
|
* this field exists to make - invisibility is the real harm, labelling is secondary.
|
|
5863
6554
|
*
|
|
5864
|
-
* A dedicated field
|
|
5865
|
-
*
|
|
5866
|
-
*
|
|
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.
|
|
5867
6558
|
*
|
|
5868
6559
|
* Cleared by `commitFabFileChunks` (the rebuild landed) and by the chunk handler's pause write (the
|
|
5869
6560
|
* rebuild was halted instead). A file carrying `error` is settled regardless - see
|
|
@@ -5872,20 +6563,6 @@ const CONVERGENCE_PAUSED_CHUNK_NOTE = "Re-chunking paused by the data-lake conve
|
|
|
5872
6563
|
function isChunkRebuildPending(requestedAt) {
|
|
5873
6564
|
return requestedAt !== null && requestedAt !== void 0 && requestedAt !== "";
|
|
5874
6565
|
}
|
|
5875
|
-
/**
|
|
5876
|
-
* Whether a file's `notes` marks it as stalled by the convergence kill switch, by either arm.
|
|
5877
|
-
* THE predicate every reader uses, so adding a third stall marker reaches health, convergence and
|
|
5878
|
-
* retrieval without three separate string comparisons drifting apart.
|
|
5879
|
-
*/
|
|
5880
|
-
function isConvergencePausedNote(notes) {
|
|
5881
|
-
return CONVERGENCE_PAUSED_NOTES.includes(notes);
|
|
5882
|
-
}
|
|
5883
|
-
/**
|
|
5884
|
-
* Datastore mirror of `isConvergencePausedNote`, for a Mongo `notes: { $in: [...] }`. Exported so a
|
|
5885
|
-
* query and the in-memory predicate cannot drift: adding a third stall marker to this array reaches
|
|
5886
|
-
* both. Declared after the two constants it names so the function above can close over it.
|
|
5887
|
-
*/
|
|
5888
|
-
const CONVERGENCE_PAUSED_NOTES = [CONVERGENCE_PAUSED_NOTE, CONVERGENCE_PAUSED_CHUNK_NOTE];
|
|
5889
6566
|
/** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
|
|
5890
6567
|
const LAKE_ACCESS_AUDIT_RETENTION_MAX_DAYS = 2555;
|
|
5891
6568
|
/**
|
|
@@ -5917,14 +6594,13 @@ const LAKE_CONFIG_AUDIT_RETENTION_DEFAULT_DAYS = 1095;
|
|
|
5917
6594
|
/** Ceiling so "adjustable" cannot mean "unbounded" in either direction. */
|
|
5918
6595
|
const LAKE_CONFIG_AUDIT_RETENTION_MAX_DAYS = 3650;
|
|
5919
6596
|
/**
|
|
5920
|
-
* Forced-retrieval budget defaults, shared between the admin-settings schema in this
|
|
5921
|
-
* `ChatCompletionFeatures.ts` (which cannot import from `common`'s settings schema
|
|
5922
|
-
* dependency cycle, so the
|
|
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).
|
|
5923
6600
|
*
|
|
5924
|
-
*
|
|
5925
|
-
*
|
|
5926
|
-
*
|
|
5927
|
-
* stay next to each other, not because it is tunable today.
|
|
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`).
|
|
5928
6604
|
*/
|
|
5929
6605
|
/** Total characters of retrieved chunk text injected into a forced-retrieval prompt. */
|
|
5930
6606
|
const FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT = 12e3;
|
|
@@ -6082,6 +6758,76 @@ const HELP_CENTER_PROMPT = `HELP CENTER: Bike4Mind has a built-in Help Center th
|
|
|
6082
6758
|
*/
|
|
6083
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.`;
|
|
6084
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
|
+
/**
|
|
6085
6831
|
* Default text for the formatting system message. Runtime fallback used by
|
|
6086
6832
|
* `includeHardcodedSystemMessage` (b4m-core/utils/src/llm/utils.ts) when the `FormatPromptTemplate`
|
|
6087
6833
|
* admin setting is blank; that setting's own default is intentionally '' - keep this the sole home.
|
|
@@ -6114,6 +6860,8 @@ z$1.enum([
|
|
|
6114
6860
|
"ArtifactEmissionPrompt",
|
|
6115
6861
|
"HelpCenterPrompt",
|
|
6116
6862
|
"AbstentionPrompt",
|
|
6863
|
+
"WebSearchFreshnessPrompt",
|
|
6864
|
+
"KnowledgeBaseRetrievalPrompt",
|
|
6117
6865
|
"UseFormatPrompt",
|
|
6118
6866
|
"EnableQuestMaster",
|
|
6119
6867
|
"EnableQuestMasterDefault",
|
|
@@ -6132,11 +6880,11 @@ z$1.enum([
|
|
|
6132
6880
|
"EnableLattice",
|
|
6133
6881
|
"EnableLatticeDefault",
|
|
6134
6882
|
"EnableDataLakes",
|
|
6135
|
-
"EnableDataLakesDefault",
|
|
6136
6883
|
"EnableDataLakeSlackAdd",
|
|
6137
6884
|
"EnableDataLakeGroundingMode",
|
|
6138
6885
|
"EnableLakeMemory",
|
|
6139
6886
|
"EnableDataLakeVectorSearch",
|
|
6887
|
+
"EnableRetrievalSupersessionCollapse",
|
|
6140
6888
|
"PauseLakeConvergence",
|
|
6141
6889
|
"LakeConvergenceBulkChangeSharePct",
|
|
6142
6890
|
"EnforceLakeReadGrants",
|
|
@@ -6234,15 +6982,19 @@ z$1.enum([
|
|
|
6234
6982
|
"dataLakeSearchMaxFiles",
|
|
6235
6983
|
"dataLakeSearchMaxChunks",
|
|
6236
6984
|
"forcedRetrievalCharBudget",
|
|
6985
|
+
"lakeMemoryRecallK",
|
|
6237
6986
|
"kbSearchDefaultResults",
|
|
6238
6987
|
"kbSearchResultTokenBudget",
|
|
6239
6988
|
"kbSearchMinRelevancePct",
|
|
6989
|
+
"forcedRetrievalRelativeFloorPct",
|
|
6990
|
+
"forcedRetrievalMinSimilarityPct",
|
|
6240
6991
|
"dataLakeEmbeddingSpendEnabled",
|
|
6241
6992
|
"dataLakeEmbeddingBudgetPerRunUsd",
|
|
6242
6993
|
"dataLakeEmbeddingBudgetPerLakeUsd",
|
|
6243
6994
|
"dataLakeEmbeddingBudgetPerPeriodUsd",
|
|
6244
6995
|
"dataLakeEmbeddingBudgetPeriodHours",
|
|
6245
6996
|
"dataLakeEmbeddingMaxCallsPerMinute",
|
|
6997
|
+
"dataLakeEmbeddingMaxTokensPerMinute",
|
|
6246
6998
|
"dataLakeVectorizeChunkBatchSize",
|
|
6247
6999
|
"dataLakeEmbeddingTierMultiplierIndividual",
|
|
6248
7000
|
"dataLakeEmbeddingTierMultiplierOrganization",
|
|
@@ -6291,7 +7043,6 @@ z$1.enum([
|
|
|
6291
7043
|
"EnableBmPiDefault",
|
|
6292
7044
|
"EnableBmPiJira",
|
|
6293
7045
|
"EnableOptiHashi",
|
|
6294
|
-
"EnableOptiHashiDefault",
|
|
6295
7046
|
"EnableComputeSubmission",
|
|
6296
7047
|
"EnableFamilyCompute",
|
|
6297
7048
|
"EnableHybridCompute",
|
|
@@ -6361,10 +7112,12 @@ const OrchestrationDefaultsSchema = z$1.object({
|
|
|
6361
7112
|
"mermaid_chart"
|
|
6362
7113
|
]),
|
|
6363
7114
|
/**
|
|
6364
|
-
* Tool names explicitly forbidden. Enforced as a final subtraction in
|
|
6365
|
-
* `pickEffectiveEnabledTools`
|
|
6366
|
-
*
|
|
6367
|
-
*
|
|
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.
|
|
6368
7121
|
*
|
|
6369
7122
|
* Seeded with every tool that mutates user data (the spec's
|
|
6370
7123
|
* "anything tagged `mutates_user_data`"): destructive/overwriting filesystem
|
|
@@ -6442,8 +7195,30 @@ const DATA_LAKE_SEARCH_MAX_CHUNKS_DEFAULT = 1e5;
|
|
|
6442
7195
|
const DATA_LAKE_EMBEDDING_BUDGET_PER_LAKE_USD_MAX = 1e4;
|
|
6443
7196
|
const DATA_LAKE_EMBEDDING_BUDGET_PER_PERIOD_USD_MAX = 5e3;
|
|
6444
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;
|
|
6445
7219
|
function makeNumberSetting(config) {
|
|
6446
7220
|
let numberSchema = z$1.coerce.number();
|
|
7221
|
+
if (config.int) numberSchema = numberSchema.int();
|
|
6447
7222
|
if (config.min !== void 0) numberSchema = numberSchema.min(config.min);
|
|
6448
7223
|
if (config.max !== void 0) numberSchema = numberSchema.max(config.max);
|
|
6449
7224
|
return {
|
|
@@ -7010,6 +7785,14 @@ const API_SERVICE_GROUPS = {
|
|
|
7010
7785
|
{
|
|
7011
7786
|
key: "AbstentionPrompt",
|
|
7012
7787
|
order: 11
|
|
7788
|
+
},
|
|
7789
|
+
{
|
|
7790
|
+
key: "WebSearchFreshnessPrompt",
|
|
7791
|
+
order: 12
|
|
7792
|
+
},
|
|
7793
|
+
{
|
|
7794
|
+
key: "KnowledgeBaseRetrievalPrompt",
|
|
7795
|
+
order: 13
|
|
7013
7796
|
}
|
|
7014
7797
|
]
|
|
7015
7798
|
},
|
|
@@ -7046,6 +7829,18 @@ const API_SERVICE_GROUPS = {
|
|
|
7046
7829
|
{
|
|
7047
7830
|
key: "kbSearchMinRelevancePct",
|
|
7048
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
|
|
7049
7844
|
}
|
|
7050
7845
|
]
|
|
7051
7846
|
},
|
|
@@ -7080,16 +7875,20 @@ const API_SERVICE_GROUPS = {
|
|
|
7080
7875
|
order: 6
|
|
7081
7876
|
},
|
|
7082
7877
|
{
|
|
7083
|
-
key: "
|
|
7878
|
+
key: "dataLakeEmbeddingMaxTokensPerMinute",
|
|
7084
7879
|
order: 7
|
|
7085
7880
|
},
|
|
7086
7881
|
{
|
|
7087
|
-
key: "
|
|
7882
|
+
key: "dataLakeVectorizeChunkBatchSize",
|
|
7088
7883
|
order: 8
|
|
7089
7884
|
},
|
|
7090
7885
|
{
|
|
7091
|
-
key: "
|
|
7886
|
+
key: "dataLakeEmbeddingTierMultiplierIndividual",
|
|
7092
7887
|
order: 9
|
|
7888
|
+
},
|
|
7889
|
+
{
|
|
7890
|
+
key: "dataLakeEmbeddingTierMultiplierOrganization",
|
|
7891
|
+
order: 10
|
|
7093
7892
|
}
|
|
7094
7893
|
]
|
|
7095
7894
|
},
|
|
@@ -7486,10 +8285,6 @@ const API_SERVICE_GROUPS = {
|
|
|
7486
8285
|
key: "EnableOptiHashi",
|
|
7487
8286
|
order: 80
|
|
7488
8287
|
},
|
|
7489
|
-
{
|
|
7490
|
-
key: "EnableOptiHashiDefault",
|
|
7491
|
-
order: 81
|
|
7492
|
-
},
|
|
7493
8288
|
{
|
|
7494
8289
|
key: "EnableComputeSubmission",
|
|
7495
8290
|
order: 82
|
|
@@ -7985,16 +8780,6 @@ const settingsMap = {
|
|
|
7985
8780
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7986
8781
|
order: 88
|
|
7987
8782
|
}),
|
|
7988
|
-
EnableDataLakesDefault: makeBooleanSetting({
|
|
7989
|
-
key: "EnableDataLakesDefault",
|
|
7990
|
-
name: "Data Lakes: On by default for users",
|
|
7991
|
-
defaultValue: false,
|
|
7992
|
-
description: "When enabled, Data Lakes is active for users who have never explicitly toggled it.",
|
|
7993
|
-
category: "Experimental",
|
|
7994
|
-
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
7995
|
-
order: 89,
|
|
7996
|
-
dependsOn: "EnableDataLakes"
|
|
7997
|
-
}),
|
|
7998
8783
|
EnableDataLakeSlackAdd: makeBooleanSetting({
|
|
7999
8784
|
key: "EnableDataLakeSlackAdd",
|
|
8000
8785
|
name: "Data Lakes: Slack \"@datalake add\" path",
|
|
@@ -8019,7 +8804,7 @@ const settingsMap = {
|
|
|
8019
8804
|
key: "EnableLakeMemory",
|
|
8020
8805
|
name: "Data Lakes: Lake memory profile (extraction)",
|
|
8021
8806
|
defaultValue: false,
|
|
8022
|
-
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.",
|
|
8023
8808
|
category: "Experimental",
|
|
8024
8809
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8025
8810
|
order: 91,
|
|
@@ -8035,11 +8820,21 @@ const settingsMap = {
|
|
|
8035
8820
|
order: 92,
|
|
8036
8821
|
dependsOn: "EnableDataLakes"
|
|
8037
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
|
+
}),
|
|
8038
8833
|
PauseLakeConvergence: makeBooleanSetting({
|
|
8039
8834
|
key: "PauseLakeConvergence",
|
|
8040
8835
|
name: "Data Lakes: Pause background convergence work",
|
|
8041
8836
|
defaultValue: false,
|
|
8042
|
-
description: "Kill switch for background data-lake ingestion work (convergence sweeps, rescue re-chunking) - NOT real-time user uploads, which are always honored. Off by default. Turn ON to halt in-flight background chunk/vectorize messages the next time the handler picks them up (a re-check inside the shared handler, so it takes effect on work already queued, not just the next scheduling pass). The platform value pauses every lake at once; a per-lake (or per-org / per-owner) override pauses a subset while the rest keep running. A platform-level flip applies immediately to lake-wide work and within ~5 min to per-lake-scoped work (settings cache).",
|
|
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).",
|
|
8043
8838
|
category: "Experimental",
|
|
8044
8839
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8045
8840
|
order: 93,
|
|
@@ -8069,8 +8864,8 @@ const settingsMap = {
|
|
|
8069
8864
|
EnforceLakeReadGrants: makeBooleanSetting({
|
|
8070
8865
|
key: "EnforceLakeReadGrants",
|
|
8071
8866
|
name: "Data Lakes: Enforce read-time grant resolution",
|
|
8072
|
-
defaultValue:
|
|
8073
|
-
description: "Read-time grant
|
|
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.",
|
|
8074
8869
|
category: "Experimental",
|
|
8075
8870
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
8076
8871
|
order: 94,
|
|
@@ -8320,6 +9115,7 @@ const settingsMap = {
|
|
|
8320
9115
|
}),
|
|
8321
9116
|
DefaultChunkSize: makeNumberSetting({
|
|
8322
9117
|
key: "DefaultChunkSize",
|
|
9118
|
+
userReadable: true,
|
|
8323
9119
|
name: "Default Chunk Size",
|
|
8324
9120
|
defaultValue: 512,
|
|
8325
9121
|
min: 64,
|
|
@@ -8378,6 +9174,22 @@ const settingsMap = {
|
|
|
8378
9174
|
category: "AI",
|
|
8379
9175
|
order: 11
|
|
8380
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
|
+
}),
|
|
8381
9193
|
UseFormatPrompt: makeBooleanSetting({
|
|
8382
9194
|
key: "UseFormatPrompt",
|
|
8383
9195
|
name: "Use Format Prompt",
|
|
@@ -8396,6 +9208,7 @@ const settingsMap = {
|
|
|
8396
9208
|
}),
|
|
8397
9209
|
pricePerCredit: makeNumberSetting({
|
|
8398
9210
|
key: "pricePerCredit",
|
|
9211
|
+
userReadable: true,
|
|
8399
9212
|
name: "Price Per Credit",
|
|
8400
9213
|
defaultValue: 50,
|
|
8401
9214
|
description: "The price per credit for purchasing credits.",
|
|
@@ -8473,7 +9286,8 @@ const settingsMap = {
|
|
|
8473
9286
|
name: "Referal Credits Amount",
|
|
8474
9287
|
defaultValue: 1e4,
|
|
8475
9288
|
description: "Credits to give to the referred user.",
|
|
8476
|
-
category: "Referrals"
|
|
9289
|
+
category: "Referrals",
|
|
9290
|
+
userReadable: true
|
|
8477
9291
|
}),
|
|
8478
9292
|
EnableReferralToEmail: makeBooleanSetting({
|
|
8479
9293
|
key: "EnableReferralToEmail",
|
|
@@ -8662,8 +9476,10 @@ const settingsMap = {
|
|
|
8662
9476
|
}),
|
|
8663
9477
|
MaxFileSize: makeNumberSetting({
|
|
8664
9478
|
key: "MaxFileSize",
|
|
9479
|
+
userReadable: true,
|
|
8665
9480
|
name: "Max File Size",
|
|
8666
9481
|
defaultValue: 30,
|
|
9482
|
+
min: 1,
|
|
8667
9483
|
description: "The maximum file size allowed for uploads in MB.",
|
|
8668
9484
|
category: "Knowledge",
|
|
8669
9485
|
group: API_SERVICE_GROUPS.KNOWLEDGE.id,
|
|
@@ -8770,6 +9586,7 @@ const settingsMap = {
|
|
|
8770
9586
|
}),
|
|
8771
9587
|
enforceCredits: makeBooleanSetting({
|
|
8772
9588
|
key: "enforceCredits",
|
|
9589
|
+
userReadable: true,
|
|
8773
9590
|
name: "Enforce Credits",
|
|
8774
9591
|
defaultValue: process.env.B4M_SELF_HOST === "true" ? false : true,
|
|
8775
9592
|
description: "Whether to enforce credits for users",
|
|
@@ -8786,6 +9603,7 @@ const settingsMap = {
|
|
|
8786
9603
|
}),
|
|
8787
9604
|
enableTeamPlan: makeBooleanSetting({
|
|
8788
9605
|
key: "enableTeamPlan",
|
|
9606
|
+
userReadable: true,
|
|
8789
9607
|
name: "Enable Team Plan",
|
|
8790
9608
|
defaultValue: false,
|
|
8791
9609
|
description: "Whether to enable team plans",
|
|
@@ -8891,7 +9709,8 @@ const settingsMap = {
|
|
|
8891
9709
|
description: "The global system prompt files to be used for AI model configuration.",
|
|
8892
9710
|
category: "AI",
|
|
8893
9711
|
group: API_SERVICE_GROUPS.OPENAI.id,
|
|
8894
|
-
order: 8
|
|
9712
|
+
order: 8,
|
|
9713
|
+
userReadable: true
|
|
8895
9714
|
}),
|
|
8896
9715
|
OpenWeatherKey: makeStringSetting({
|
|
8897
9716
|
key: "OpenWeatherKey",
|
|
@@ -9045,6 +9864,7 @@ const settingsMap = {
|
|
|
9045
9864
|
}),
|
|
9046
9865
|
MaxContentLength: makeNumberSetting({
|
|
9047
9866
|
key: "MaxContentLength",
|
|
9867
|
+
userReadable: true,
|
|
9048
9868
|
name: "Max Content Length",
|
|
9049
9869
|
defaultValue: 5e4,
|
|
9050
9870
|
description: "The maximum character length for file content displayed in workbench (truncated if larger).",
|
|
@@ -9210,6 +10030,7 @@ const settingsMap = {
|
|
|
9210
10030
|
}),
|
|
9211
10031
|
defaultEmbeddingModel: makeStringSetting({
|
|
9212
10032
|
key: "defaultEmbeddingModel",
|
|
10033
|
+
userReadable: true,
|
|
9213
10034
|
name: "Default Embedding Model",
|
|
9214
10035
|
defaultValue: defaultEmbeddingModelForEnv(),
|
|
9215
10036
|
description: "The default embedding model to use",
|
|
@@ -9231,11 +10052,7 @@ const settingsMap = {
|
|
|
9231
10052
|
category: "AI",
|
|
9232
10053
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
9233
10054
|
order: 2,
|
|
9234
|
-
scope: { settableAt: [
|
|
9235
|
-
"organization",
|
|
9236
|
-
"owner",
|
|
9237
|
-
"lake"
|
|
9238
|
-
] }
|
|
10055
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
9239
10056
|
}),
|
|
9240
10057
|
dataLakeSearchMaxChunks: makeNumberSetting({
|
|
9241
10058
|
key: "dataLakeSearchMaxChunks",
|
|
@@ -9246,11 +10063,7 @@ const settingsMap = {
|
|
|
9246
10063
|
category: "AI",
|
|
9247
10064
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
9248
10065
|
order: 3,
|
|
9249
|
-
scope: { settableAt: [
|
|
9250
|
-
"organization",
|
|
9251
|
-
"owner",
|
|
9252
|
-
"lake"
|
|
9253
|
-
] }
|
|
10066
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
9254
10067
|
}),
|
|
9255
10068
|
forcedRetrievalCharBudget: makeNumberSetting({
|
|
9256
10069
|
key: "forcedRetrievalCharBudget",
|
|
@@ -9258,10 +10071,11 @@ const settingsMap = {
|
|
|
9258
10071
|
defaultValue: FORCED_RETRIEVAL_CHAR_BUDGET_DEFAULT,
|
|
9259
10072
|
min: 1e3,
|
|
9260
10073
|
max: 1e5,
|
|
9261
|
-
description: "Total characters of retrieved chunk text injected into a Data-Lake-mode turn. Measured saturating on every turn against a 47-document lake, so this is the binding constraint on how much of a corpus reaches the model - not the relevance floor. Raising it admits more passages at the cost of prompt tokens and latency on every Data-Lake turn; it is NOT automatically better, since more context can dilute ranking.
|
|
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.",
|
|
9262
10075
|
category: "AI",
|
|
9263
10076
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
9264
|
-
order: 4
|
|
10077
|
+
order: 4,
|
|
10078
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
9265
10079
|
}),
|
|
9266
10080
|
kbSearchDefaultResults: makeNumberSetting({
|
|
9267
10081
|
key: "kbSearchDefaultResults",
|
|
@@ -9269,7 +10083,7 @@ const settingsMap = {
|
|
|
9269
10083
|
defaultValue: 5,
|
|
9270
10084
|
min: 1,
|
|
9271
10085
|
max: 10,
|
|
9272
|
-
description: "Passages the search_knowledge_base tool returns when a model call omits max_results, which is most calls. This is the exact bound while kbSearchResultTokenBudget is unset (0). Once a token budget is set, it takes over as the primary bound for search results (this setting's own value is then unused there, though it still governs the keyword-search fallback, and the count served if token pricing itself fails). Does NOT raise the tool's hard ceiling of 10 passages per call - a model that reads max_results up to 10 from its own tool schema won't ask for more than that regardless of this setting.",
|
|
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.",
|
|
9273
10087
|
category: "AI",
|
|
9274
10088
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
9275
10089
|
order: 5,
|
|
@@ -9281,7 +10095,7 @@ const settingsMap = {
|
|
|
9281
10095
|
defaultValue: 0,
|
|
9282
10096
|
min: 0,
|
|
9283
10097
|
max: 2e4,
|
|
9284
|
-
description: "Approximate tokens of served passage TEXT (post-trim, post-clip - what the model actually receives, not the raw stored chunk) the search_knowledge_base tool may return in one call. Counted with a fixed tokenizer as a proxy, not billed against any specific model. Replaces a passage count as the primary bound once set, since it is invariant to chunk size - a lake chunked smaller no longer silently returns less material for the same setting. 0 (default) disables it: search_knowledge_base then serves exactly kbSearchDefaultResults passages, unchanged from before this setting existed. The FIRST matching passage is always returned even if it alone exceeds the budget - a search that found something never returns nothing.",
|
|
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.",
|
|
9285
10099
|
category: "AI",
|
|
9286
10100
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
9287
10101
|
order: 6,
|
|
@@ -9293,12 +10107,50 @@ const settingsMap = {
|
|
|
9293
10107
|
defaultValue: 0,
|
|
9294
10108
|
min: 0,
|
|
9295
10109
|
max: 100,
|
|
9296
|
-
description: "Minimum cosine relevance, as a percent, a passage must clear to be returned by search_knowledge_base. 0 (default) matches current behavior (no relevance floor beyond a non-negative cosine score). Raising it lets breadth adapt per query - a narrow question can return fewer, more relevant passages instead of always padding out to the configured count. Cosine similarity is not comparable across embedding models: a floor tuned for one model can filter out an entire alternate model, when a lake mixes embedding models, more aggressively than intended. Start low and raise gradually while watching the tool's own retrieval-skipped notices.",
|
|
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.",
|
|
9297
10111
|
category: "AI",
|
|
9298
10112
|
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
9299
10113
|
order: 7,
|
|
9300
10114
|
scope: { settableAt: ["organization", "owner"] }
|
|
9301
10115
|
}),
|
|
10116
|
+
lakeMemoryRecallK: makeNumberSetting({
|
|
10117
|
+
key: "lakeMemoryRecallK",
|
|
10118
|
+
name: "Lake Memory Belief Budget",
|
|
10119
|
+
defaultValue: 24,
|
|
10120
|
+
min: 1,
|
|
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.",
|
|
10124
|
+
category: "AI",
|
|
10125
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10126
|
+
order: 8
|
|
10127
|
+
}),
|
|
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,
|
|
10145
|
+
min: 1,
|
|
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.",
|
|
10149
|
+
category: "AI",
|
|
10150
|
+
group: API_SERVICE_GROUPS.EMBEDDING.id,
|
|
10151
|
+
order: 10,
|
|
10152
|
+
scope: { settableAt: ["organization", "owner"] }
|
|
10153
|
+
}),
|
|
9302
10154
|
LakeAccessAuditRetentionDays: makeNumberSetting({
|
|
9303
10155
|
key: "LakeAccessAuditRetentionDays",
|
|
9304
10156
|
name: "Lake Access Audit Retention (days)",
|
|
@@ -9334,6 +10186,7 @@ const settingsMap = {
|
|
|
9334
10186
|
}),
|
|
9335
10187
|
dataLakeEmbeddingSpendEnabled: makeBooleanSetting({
|
|
9336
10188
|
key: "dataLakeEmbeddingSpendEnabled",
|
|
10189
|
+
userReadable: true,
|
|
9337
10190
|
name: "Data Lake Embedding Spend Enabled",
|
|
9338
10191
|
defaultValue: true,
|
|
9339
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.",
|
|
@@ -9343,6 +10196,7 @@ const settingsMap = {
|
|
|
9343
10196
|
}),
|
|
9344
10197
|
dataLakeEmbeddingBudgetPerRunUsd: makeNumberSetting({
|
|
9345
10198
|
key: "dataLakeEmbeddingBudgetPerRunUsd",
|
|
10199
|
+
userReadable: true,
|
|
9346
10200
|
name: "Embedding Budget Per Run (USD)",
|
|
9347
10201
|
defaultValue: 5,
|
|
9348
10202
|
min: 0,
|
|
@@ -9396,6 +10250,17 @@ const settingsMap = {
|
|
|
9396
10250
|
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
9397
10251
|
order: 6
|
|
9398
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
|
+
}),
|
|
9399
10264
|
dataLakeVectorizeChunkBatchSize: makeNumberSetting({
|
|
9400
10265
|
key: "dataLakeVectorizeChunkBatchSize",
|
|
9401
10266
|
name: "Vectorize Chunk Batch Size",
|
|
@@ -9405,7 +10270,7 @@ const settingsMap = {
|
|
|
9405
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.",
|
|
9406
10271
|
category: "AI",
|
|
9407
10272
|
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
9408
|
-
order:
|
|
10273
|
+
order: 8
|
|
9409
10274
|
}),
|
|
9410
10275
|
dataLakeEmbeddingTierMultiplierIndividual: makeNumberSetting({
|
|
9411
10276
|
key: "dataLakeEmbeddingTierMultiplierIndividual",
|
|
@@ -9416,7 +10281,7 @@ const settingsMap = {
|
|
|
9416
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.",
|
|
9417
10282
|
category: "AI",
|
|
9418
10283
|
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
9419
|
-
order:
|
|
10284
|
+
order: 9
|
|
9420
10285
|
}),
|
|
9421
10286
|
dataLakeEmbeddingTierMultiplierOrganization: makeNumberSetting({
|
|
9422
10287
|
key: "dataLakeEmbeddingTierMultiplierOrganization",
|
|
@@ -9427,7 +10292,7 @@ const settingsMap = {
|
|
|
9427
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.",
|
|
9428
10293
|
category: "AI",
|
|
9429
10294
|
group: API_SERVICE_GROUPS.DATA_LAKE_COST.id,
|
|
9430
|
-
order:
|
|
10295
|
+
order: 10
|
|
9431
10296
|
}),
|
|
9432
10297
|
slackSigningSecret: makeStringSetting({
|
|
9433
10298
|
key: "slackSigningSecret",
|
|
@@ -9529,6 +10394,7 @@ const settingsMap = {
|
|
|
9529
10394
|
}),
|
|
9530
10395
|
enableVoiceSession: makeBooleanSetting({
|
|
9531
10396
|
key: "enableVoiceSession",
|
|
10397
|
+
userReadable: true,
|
|
9532
10398
|
name: "Enable Voice Session",
|
|
9533
10399
|
defaultValue: false,
|
|
9534
10400
|
description: "Whether to enable the voice session.",
|
|
@@ -9538,6 +10404,7 @@ const settingsMap = {
|
|
|
9538
10404
|
}),
|
|
9539
10405
|
voiceV2Enabled: makeBooleanSetting({
|
|
9540
10406
|
key: "voiceV2Enabled",
|
|
10407
|
+
userReadable: true,
|
|
9541
10408
|
name: "Enable Voice v2 (Model-Agnostic)",
|
|
9542
10409
|
defaultValue: false,
|
|
9543
10410
|
description: "Gate for the Voice v2 feature (ElevenLabs Conversational AI + any B4M reasoning model). When disabled, /api/voice/v2/sessions returns 403.",
|
|
@@ -9557,6 +10424,7 @@ const settingsMap = {
|
|
|
9557
10424
|
}),
|
|
9558
10425
|
voiceSessionAiVoice: makeStringSetting({
|
|
9559
10426
|
key: "voiceSessionAiVoice",
|
|
10427
|
+
userReadable: true,
|
|
9560
10428
|
name: "Default Assistant Voice",
|
|
9561
10429
|
defaultValue: "alloy",
|
|
9562
10430
|
description: "The default voice for the assistant in the voice session.",
|
|
@@ -9858,16 +10726,6 @@ const settingsMap = {
|
|
|
9858
10726
|
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
9859
10727
|
order: 80
|
|
9860
10728
|
}),
|
|
9861
|
-
EnableOptiHashiDefault: makeBooleanSetting({
|
|
9862
|
-
key: "EnableOptiHashiDefault",
|
|
9863
|
-
name: "OptiHashi: On by default for users",
|
|
9864
|
-
defaultValue: false,
|
|
9865
|
-
description: "When enabled, OptiHashi is active for users who have never explicitly toggled it.",
|
|
9866
|
-
category: "Experimental",
|
|
9867
|
-
group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
|
|
9868
|
-
order: 81,
|
|
9869
|
-
dependsOn: "EnableOptiHashi"
|
|
9870
|
-
}),
|
|
9871
10729
|
EnableLibreOncology: makeBooleanSetting({
|
|
9872
10730
|
key: "EnableLibreOncology",
|
|
9873
10731
|
name: "Enable LibreOncology",
|
|
@@ -10068,6 +10926,7 @@ const settingsMap = {
|
|
|
10068
10926
|
}),
|
|
10069
10927
|
orchestrationDefaults: makeObjectSetting({
|
|
10070
10928
|
key: "orchestrationDefaults",
|
|
10929
|
+
userReadable: true,
|
|
10071
10930
|
name: "Agent Orchestration Defaults",
|
|
10072
10931
|
defaultValue: OrchestrationDefaultsSchema.parse({}),
|
|
10073
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).",
|
|
@@ -10247,7 +11106,8 @@ const SystemPromptDetailSchema = z$1.object({
|
|
|
10247
11106
|
"user",
|
|
10248
11107
|
"project",
|
|
10249
11108
|
"session",
|
|
10250
|
-
"org"
|
|
11109
|
+
"org",
|
|
11110
|
+
"caller"
|
|
10251
11111
|
]),
|
|
10252
11112
|
/** e.g., "date_context", "tool_guidance" */
|
|
10253
11113
|
name: z$1.string(),
|
|
@@ -10641,6 +11501,7 @@ const PromptMetaContextSchema = z$1.object({
|
|
|
10641
11501
|
}).optional(),
|
|
10642
11502
|
lakeMemory: z$1.object({
|
|
10643
11503
|
beliefCount: z$1.number(),
|
|
11504
|
+
beliefBudget: z$1.number().optional(),
|
|
10644
11505
|
dataLakeTags: z$1.array(z$1.string())
|
|
10645
11506
|
}).optional(),
|
|
10646
11507
|
contextWindowUsage: z$1.object({
|
|
@@ -10685,7 +11546,15 @@ const PromptMetaPerformanceSchema = z$1.object({
|
|
|
10685
11546
|
totalResponseTime: z$1.number().optional(),
|
|
10686
11547
|
contextRetrievalTime: z$1.number().optional(),
|
|
10687
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
|
+
*/
|
|
10688
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(),
|
|
10689
11558
|
clientFirstTokenTime: z$1.number().optional(),
|
|
10690
11559
|
streamingPerformance: z$1.object({
|
|
10691
11560
|
chunkCount: z$1.number().optional(),
|
|
@@ -10766,11 +11635,16 @@ const CitableSourceSchema = z$1.object({
|
|
|
10766
11635
|
* zero-result retrieval, so a turn that legitimately found nothing is indistinguishable from one
|
|
10767
11636
|
* where retrieval never ran at all.
|
|
10768
11637
|
*
|
|
10769
|
-
*
|
|
10770
|
-
* more precise: `citables.filter(c => c.type === 'document')` is deduped by id/url/title in
|
|
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
|
|
10771
11640
|
* `applyQuestStatusChanges`, while this shape cannot dedupe (no identifiers to dedupe by) and
|
|
10772
|
-
* would have to sum - producing a second, disagreeing number for the same question.
|
|
10773
|
-
*
|
|
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.
|
|
10774
11648
|
*
|
|
10775
11649
|
* CAUTION, not a guarantee: the absence of chunk/document identifiers is what keeps this shape
|
|
10776
11650
|
* OUT of `promptMetaRedaction.ts`'s scope (that helper is a functionCalls-only denylist and would
|
|
@@ -10789,28 +11663,301 @@ const CitableSourceSchema = z$1.object({
|
|
|
10789
11663
|
*
|
|
10790
11664
|
* Absent-or-fully-present, matching `lakeMemory` above - see the Mongoose-side subSchema comment
|
|
10791
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.
|
|
10792
11674
|
*/
|
|
10793
11675
|
const RetrievalSummarySchema = z$1.object({
|
|
10794
11676
|
/** True once a retrieval-capable surface actually ran (not merely offered) this turn. */
|
|
10795
11677
|
attempted: z$1.boolean(),
|
|
10796
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
|
+
*
|
|
10797
11683
|
* 'ok' - ran, whether or not anything came back (the zero case is a legitimate 'ok').
|
|
10798
11684
|
* 'no_lakes' - ran but the user had no entitled/selected lake in scope.
|
|
10799
|
-
* '
|
|
10800
|
-
*
|
|
10801
|
-
*
|
|
10802
|
-
*
|
|
10803
|
-
*
|
|
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.
|
|
10804
11714
|
*/
|
|
10805
11715
|
outcome: z$1.enum([
|
|
10806
11716
|
"ok",
|
|
10807
11717
|
"no_lakes",
|
|
11718
|
+
"not_indexed",
|
|
10808
11719
|
"failed"
|
|
10809
|
-
]),
|
|
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(),
|
|
10810
11767
|
/** Which retrieval-capable surface(s) ran this turn, e.g. 'lake-memory', 'knowledgeBaseSearch'. */
|
|
10811
11768
|
surfaces: z$1.array(z$1.string()),
|
|
10812
11769
|
/** Lakes resolved at the moment retrieval ran, stamped point-in-time (not read live from the session). */
|
|
10813
|
-
dataLakeTags: z$1.array(z$1.string())
|
|
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())
|
|
10814
11961
|
});
|
|
10815
11962
|
z$1.object({
|
|
10816
11963
|
model: PromptMetaModelSchema.optional(),
|
|
@@ -10820,6 +11967,9 @@ z$1.object({
|
|
|
10820
11967
|
* deliberately: applyQuestStatusChanges does a one-level spread merge, so a field nested under
|
|
10821
11968
|
* `context` would be replaced wholesale by any tool-arm write instead of merging. */
|
|
10822
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(),
|
|
10823
11973
|
functionCalls: z$1.array(PromptMetaFunctionCallSchema).optional(),
|
|
10824
11974
|
/**
|
|
10825
11975
|
* Names of the tools actually offered to the model this turn - the output of `buildTools`
|
|
@@ -11513,6 +12663,7 @@ z.object({
|
|
|
11513
12663
|
requiredUserTag: z.union([z.literal(""), z.string().min(1).max(100)]).optional(),
|
|
11514
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(),
|
|
11515
12665
|
auditQueryTextEnabled: z.boolean().optional(),
|
|
12666
|
+
lakeMemoryEnabled: z.boolean().optional(),
|
|
11516
12667
|
requiredPassageTokenTarget: z.number().int().min(64).max(OVERSIZED_PASSAGE_TOKEN_THRESHOLD).nullable().optional()
|
|
11517
12668
|
});
|
|
11518
12669
|
z.object({
|
|
@@ -11587,6 +12738,7 @@ tags: z.array(TaxonomyTagInput).max(100) });
|
|
|
11587
12738
|
z.object({
|
|
11588
12739
|
/** User description of the data (helps the AI) */
|
|
11589
12740
|
context: z.string().max(2e3).optional() });
|
|
12741
|
+
z.object({ tags: z.array(z.string().min(1).max(130).regex(/^[^\r\n]*$/)).max(100) });
|
|
11590
12742
|
z.object({ hashes: z.array(z.string().regex(sha256Regex)).min(1).max(500) });
|
|
11591
12743
|
const SyncDeltaFileEntry = z.object({
|
|
11592
12744
|
relativePath: z.string(),
|
|
@@ -11618,55 +12770,6 @@ z.object({
|
|
|
11618
12770
|
skip: z.array(z.string())
|
|
11619
12771
|
})
|
|
11620
12772
|
});
|
|
11621
|
-
z$1.enum([
|
|
11622
|
-
"inject",
|
|
11623
|
-
"auto-fire",
|
|
11624
|
-
"hidden"
|
|
11625
|
-
]);
|
|
11626
|
-
/**
|
|
11627
|
-
* Modes acceptable at AUTHORING time. 'hidden' is intentionally excluded until
|
|
11628
|
-
* the host has true hidden-send support - accepting it would persist a value
|
|
11629
|
-
* that silently behaves as 'auto-fire' (a surprising downgrade). It stays in
|
|
11630
|
-
* ExecutionModeSchema/the stored enum for forward-compat.
|
|
11631
|
-
*/
|
|
11632
|
-
const AuthorableExecutionModeSchema = z$1.enum(["inject", "auto-fire"]);
|
|
11633
|
-
/**
|
|
11634
|
-
* Tools a prompt may require - constrained to the host's closed tool set, MINUS
|
|
11635
|
-
* integration-gated tools that act on the caller's own credentials/account. A
|
|
11636
|
-
* shared system prompt must not be able to inject e.g. blog-publishing into a
|
|
11637
|
-
* non-author's session via requiredTools. (Per-user entitlement of the remaining
|
|
11638
|
-
* tools is still the chat pipeline's responsibility - see follow-up note in the
|
|
11639
|
-
* briefcase blueprint; this allowlist is the storage-layer floor.)
|
|
11640
|
-
*/
|
|
11641
|
-
const BRIEFCASE_DISALLOWED_TOOLS = [
|
|
11642
|
-
"blog_publish",
|
|
11643
|
-
"blog_edit",
|
|
11644
|
-
"blog_draft"
|
|
11645
|
-
];
|
|
11646
|
-
const BriefcaseRequiredToolsSchema = z$1.array(b4mLLMTools.refine((t) => !BRIEFCASE_DISALLOWED_TOOLS.includes(t), "This tool is not permitted in a briefcase prompt")).max(16);
|
|
11647
|
-
z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid prompt id");
|
|
11648
|
-
const PROMPT_TEXT_MAX = 16e3;
|
|
11649
|
-
const TAGS_MAX = 20;
|
|
11650
|
-
z$1.object({
|
|
11651
|
-
type: z$1.string().min(1).max(100),
|
|
11652
|
-
name: z$1.string().min(1).max(200),
|
|
11653
|
-
description: z$1.string().max(500).optional(),
|
|
11654
|
-
promptText: z$1.string().min(1).max(PROMPT_TEXT_MAX),
|
|
11655
|
-
tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
|
|
11656
|
-
executionMode: AuthorableExecutionModeSchema.optional(),
|
|
11657
|
-
requiredTools: BriefcaseRequiredToolsSchema.optional()
|
|
11658
|
-
}).partial();
|
|
11659
|
-
/**
|
|
11660
|
-
* One catalog sub-query. Exactly one selector is used, in precedence order:
|
|
11661
|
-
* `personal` (resolved to the caller server-side) > `tags` > `type`.
|
|
11662
|
-
*/
|
|
11663
|
-
const PromptBatchQuerySchema = z$1.object({
|
|
11664
|
-
key: z$1.string().min(1).max(100),
|
|
11665
|
-
tags: z$1.array(z$1.string().min(1).max(50)).max(TAGS_MAX).optional(),
|
|
11666
|
-
type: z$1.string().max(100).optional(),
|
|
11667
|
-
personal: z$1.boolean().optional()
|
|
11668
|
-
});
|
|
11669
|
-
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" }) });
|
|
11670
12773
|
z$1.string().regex(/^[a-f0-9]{24}$/i, "Invalid template id");
|
|
11671
12774
|
/**
|
|
11672
12775
|
* The bound model. Reuses the legacy-remap preprocess so a template saved under
|
|
@@ -11969,6 +13072,31 @@ z$1.object({
|
|
|
11969
13072
|
"grounded",
|
|
11970
13073
|
"surface"
|
|
11971
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(),
|
|
11972
13100
|
/** Whether Mementos is enabled */
|
|
11973
13101
|
enableMementos: z$1.boolean().optional(),
|
|
11974
13102
|
/** Whether Artifacts is enabled */
|
|
@@ -12942,7 +14070,7 @@ Array.from(new Set([
|
|
|
12942
14070
|
id: "opti.root",
|
|
12943
14071
|
section: "opti",
|
|
12944
14072
|
label: "OptiHashi Home",
|
|
12945
|
-
description: "The OptiHashi Optimizer landing page showing
|
|
14073
|
+
description: "The OptiHashi Optimizer landing page showing the pattern family cards",
|
|
12946
14074
|
navigationType: "route",
|
|
12947
14075
|
target: "/opti",
|
|
12948
14076
|
keywords: [
|
|
@@ -13843,6 +14971,66 @@ Array.from(new Set([
|
|
|
13843
14971
|
const [, top] = v.target.split("/");
|
|
13844
14972
|
return `/${top}`;
|
|
13845
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
|
+
}
|
|
13846
15034
|
dayjs.extend(utc);
|
|
13847
15035
|
dayjs.extend(timezone);
|
|
13848
15036
|
dayjs.extend(relativeTime);
|
|
@@ -13911,16 +15099,34 @@ function isUserInitiatedAbort(error, userSignal) {
|
|
|
13911
15099
|
return isAbortError && !userSignal;
|
|
13912
15100
|
}
|
|
13913
15101
|
/**
|
|
13914
|
-
*
|
|
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.
|
|
13915
15121
|
*/
|
|
13916
15122
|
function getRetryAfterMs(error) {
|
|
13917
15123
|
if (!isAxiosError(error)) return null;
|
|
13918
15124
|
const retryAfter = error.response?.headers?.["retry-after"];
|
|
13919
15125
|
if (!retryAfter) return null;
|
|
13920
15126
|
const seconds = parseInt(retryAfter, 10);
|
|
13921
|
-
if (!isNaN(seconds)) return seconds * 1e3;
|
|
15127
|
+
if (!isNaN(seconds)) return retryAfterHintOrNull(seconds * 1e3);
|
|
13922
15128
|
const date = Date.parse(retryAfter);
|
|
13923
|
-
if (!isNaN(date)) return
|
|
15129
|
+
if (!isNaN(date)) return retryAfterHintOrNull(date - Date.now());
|
|
13924
15130
|
return null;
|
|
13925
15131
|
}
|
|
13926
15132
|
/**
|
|
@@ -15335,4 +16541,4 @@ var ConfigStore = class {
|
|
|
15335
16541
|
}
|
|
15336
16542
|
};
|
|
15337
16543
|
//#endregion
|
|
15338
|
-
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 };
|