@bike4mind/cli 3.3.0 → 4.0.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.
@@ -22,6 +22,49 @@ import { homedir as homedir$1 } from "node:os";
22
22
  const APP_NAME = process.env.APP_NAME || "";
23
23
  const WEBSITE_URL = process.env.WEBSITE_URL || "";
24
24
  //#endregion
25
+ //#region ../../b4m-core/common/dist/skillMdSource-CNWRf6Lw.mjs
26
+ /**
27
+ * The marker an exported SKILL.md carries so Bike4Mind can recognize its own exports. The B4M
28
+ * CLI/desktop loader (packages/cli CustomCommandStore) skips stamped files, and a future CLI sync
29
+ * keys off it to find files it owns - keep the `# Bike4Mind-Source: <url>` line format stable.
30
+ *
31
+ * Deliberately import-free so the stamp check never needs a YAML parser. skillMd.ts, which does need
32
+ * one, sits behind `@bike4mind/common/utils/skillMd` rather than the barrel the CLI imports.
33
+ */
34
+ const SKILL_MD_SOURCE_MARKER = "Bike4Mind-Source:";
35
+ /** Opening `---<lang>` fence, the frontmatter text, then the closing `---` line. */
36
+ const FRONTMATTER = /^---([^\r\n]*)\r?\n(?:([\s\S]*?)\r?\n)?---[ \t]*(?:\r?\n|$)/;
37
+ const SOURCE_LINE = new RegExp(`^[ \\t]*#[ \\t]*${SKILL_MD_SOURCE_MARKER}[ \\t]*(\\S+)`);
38
+ /** Splits a SKILL.md into its raw frontmatter and the rest; null when there is no closed fence. */
39
+ function splitSkillMdFrontmatter(text) {
40
+ if (text.charCodeAt(0) === 65279) text = text.slice(1);
41
+ const match = text.match(FRONTMATTER);
42
+ if (!match) return null;
43
+ return {
44
+ lang: match[1].trim().toLowerCase(),
45
+ frontmatter: match[2] ?? "",
46
+ rest: text.slice(match[0].length)
47
+ };
48
+ }
49
+ /** The source URL in the leading comment header, before any YAML data or scalar content. */
50
+ function readSkillMdSourceUrl(frontmatter) {
51
+ for (const line of frontmatter.split(/\r?\n/)) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed) continue;
54
+ if (!trimmed.startsWith("#")) return void 0;
55
+ const match = line.match(SOURCE_LINE);
56
+ if (match) return match[1];
57
+ }
58
+ }
59
+ /**
60
+ * True when the file's frontmatter carries the source stamp. Reads the raw fence text because the
61
+ * stamp is a YAML comment, which every YAML parser (gray-matter included) discards.
62
+ */
63
+ function frontmatterHasSourceStamp(text) {
64
+ const split = splitSkillMdFrontmatter(text);
65
+ return !!split && readSkillMdSourceUrl(split.frontmatter) !== void 0;
66
+ }
67
+ //#endregion
25
68
  //#region ../../b4m-core/hearth/dist/index.mjs
26
69
  /**
27
70
  * Zod validation for data crossing the Hearth boundary (API routes, CLI
@@ -354,6 +397,7 @@ const b4mLLMTools = z$1.enum([
354
397
  "retrieve_knowledge_content",
355
398
  "count_knowledge_base",
356
399
  "describe_knowledge_base",
400
+ "decide",
357
401
  "list_my_data_lakes",
358
402
  "create_data_lake",
359
403
  "save_content_to_data_lake",
@@ -747,6 +791,22 @@ const CHAT_HISTORY_ITEM_TYPES = [
747
791
  "system",
748
792
  "voice_transcript"
749
793
  ];
794
+ /**
795
+ * Why a turn's LLM call was retried. Timeouts we raise ourselves are classified ahead of
796
+ * overload so they never read as a provider fault (older backends worded them "may be
797
+ * overloaded"). Must stay in sync with classifyTurnRetryReason in @bike4mind/services.
798
+ */
799
+ const TURN_RETRY_REASONS = [
800
+ "stream_idle_timeout",
801
+ "stream_truncated",
802
+ "request_timeout",
803
+ "rate_limit",
804
+ "overloaded",
805
+ "server_error",
806
+ "transport",
807
+ "tool_pairing",
808
+ "other"
809
+ ];
750
810
  /** Where a session was created. Absent on sessions that predate the field; render those as 'web'. */
751
811
  const SESSION_ORIGIN_CHANNELS = [
752
812
  "web",
@@ -805,6 +865,12 @@ function parseClaudeModelId(modelId) {
805
865
  minor: minor === void 0 ? null : Number(minor)
806
866
  };
807
867
  }
868
+ /** `opus-4-5` for `claude-opus-4-5-20251101` or `global.anthropic.claude-opus-4-5-...`; null otherwise. */
869
+ function claudeFamilyVersion(modelId) {
870
+ const parsed = parseClaudeModelId(modelId);
871
+ if (!parsed) return null;
872
+ return parsed.minor === null ? `${parsed.family}-${parsed.major}` : `${parsed.family}-${parsed.major}-${parsed.minor}`;
873
+ }
808
874
  /**
809
875
  * Model backends
810
876
  */
@@ -978,6 +1044,7 @@ let ChatModels = /* @__PURE__ */ function(ChatModels) {
978
1044
  ChatModels["TINYLLAMA"] = "tinyllama";
979
1045
  ChatModels["DEEPSEEK_R1_BEDROCK"] = "us.deepseek.r1-v1:0";
980
1046
  ChatModels["DEEPSEEK_V3_1"] = "deepseek.v3-v1:0";
1047
+ ChatModels["NOVA_PRO_BEDROCK"] = "us.amazon.nova-pro-v1:0";
981
1048
  ChatModels["TITAN_TEXT_G1_LITE"] = "amazon.titan-text-lite-v1";
982
1049
  ChatModels["TITAN_TEXT_G1_EXPRESS"] = "amazon.titan-text-express-v1";
983
1050
  ChatModels["CLAUDE_3_HAIKU_BEDROCK"] = "anthropic.claude-3-haiku-20240307-v1:0";
@@ -1105,6 +1172,173 @@ z$1.enum({
1105
1172
  ...ImageModels,
1106
1173
  ...SpeechToTextModels
1107
1174
  });
1175
+ let ApiKeyScope = /* @__PURE__ */ function(ApiKeyScope) {
1176
+ ApiKeyScope["READ_NOTEBOOKS"] = "notebooks:read";
1177
+ ApiKeyScope["WRITE_NOTEBOOKS"] = "notebooks:write";
1178
+ ApiKeyScope["READ_FILES"] = "files:read";
1179
+ ApiKeyScope["WRITE_FILES"] = "files:write";
1180
+ ApiKeyScope["AI_GENERATE"] = "ai:generate";
1181
+ /**
1182
+ * Only the Decisions API (`POST /api/v1/decisions`, `GET /api/v1/decision-models`): typed questions answered
1183
+ * with probabilities. `ai:generate` also opens these routes. Not confined, so like `me:read` it also passes
1184
+ * scope-less routes; an image sent by `file_id` additionally needs `files:read`.
1185
+ */
1186
+ ApiKeyScope["AI_DECIDE"] = "ai:decide";
1187
+ ApiKeyScope["AI_CHAT"] = "ai:chat";
1188
+ ApiKeyScope["READ_PROJECTS"] = "projects:read";
1189
+ ApiKeyScope["WRITE_PROJECTS"] = "projects:write";
1190
+ /** List and read the key owner's agents (and agents shared with them). */
1191
+ ApiKeyScope["READ_AGENTS"] = "agents:read";
1192
+ /**
1193
+ * Create, update, delete and fund agents, manage their embed keys, and run the
1194
+ * agent-authoring assistants (description/avatar/system-prompt/field generation),
1195
+ * which spend the owner's credits on the agent's behalf.
1196
+ */
1197
+ ApiKeyScope["WRITE_AGENTS"] = "agents:write";
1198
+ /** Authorizes only the cc-bridge WS actions (cc_agent_register /
1199
+ * cc_agent_event / cc_agent_disconnect). Keys with this scope CANNOT
1200
+ * call chat/completions - a leaked bridge key has the narrow blast
1201
+ * radius of a sprite-spawning credential, not a billable AI key. */
1202
+ ApiKeyScope["CC_BRIDGE"] = "cc-bridge:connect";
1203
+ /**
1204
+ * Read the key owner's OWN commercial state - tier, credit balance, entitlement
1205
+ * keys - via `GET /api/v1/me`. Split from the AI scopes on purpose: a key minted
1206
+ * to generate text has no business enumerating what its owner has paid for. It
1207
+ * reaches only `GET /api/v1/me` and its balance-only subset `GET /api/v1/credits`
1208
+ * (which the AI scopes also open, since spend needs a pre-flight check), so it
1209
+ * carries the `:read` suffix that puts it in the New-Key modal's read-only preset.
1210
+ */
1211
+ ApiKeyScope["ME_READ"] = "me:read";
1212
+ ApiKeyScope["ADMIN"] = "admin:*";
1213
+ ApiKeyScope["MARKETING_REPORTS_READ"] = "marketing-reports:read";
1214
+ ApiKeyScope["MARKETING_REPORTS_WRITE"] = "marketing-reports:write";
1215
+ /** Server-to-server ingest scope for Overwatch analytics. Admin-provisioned only - never shown in user-facing key creation UI. */
1216
+ ApiKeyScope["OVERWATCH_INGEST_WRITE"] = "overwatch-ingest:write";
1217
+ /** Authorizes only embedded-widget chat against the single agent the key is
1218
+ * bound to (`agentId`). Like CC_BRIDGE, a leaked embed key has a narrow blast
1219
+ * radius: it can talk to one agent from allow-listed origins, nothing else. */
1220
+ ApiKeyScope["EMBED_CHAT"] = "embed:chat";
1221
+ /** Read a Hearth channel list and replay its events. Read-only: it cannot
1222
+ * advance an actor cursor, because consuming events out from under a
1223
+ * legitimate agent reader is a denial of context, not a read. */
1224
+ ApiKeyScope["HEARTH_READ"] = "hearth:read";
1225
+ /** Append Hearth events, create channels, and advance actor cursors. */
1226
+ ApiKeyScope["HEARTH_WRITE"] = "hearth:write";
1227
+ /**
1228
+ * Read OptiHashi problems, runs, and run artifacts. Deliberately split from
1229
+ * {@link OPTIHASHI_COMPUTE}: a key that only inspects results is structurally
1230
+ * unable to start a billable solve, so an agent-held key can be handed the
1231
+ * read half alone. Like every scope, it authorizes but never entitles - the
1232
+ * OptiHashi entitlement check still runs and can refuse on its own.
1233
+ */
1234
+ ApiKeyScope["OPTIHASHI_READ"] = "optihashi:read";
1235
+ /**
1236
+ * Submit OptiHashi compute (solve/decompose/schedule) and cancel a run. This
1237
+ * is the spend half of the OptiHashi pair: it commissions external compute
1238
+ * that costs real money, so it is never implied by {@link OPTIHASHI_READ}.
1239
+ */
1240
+ ApiKeyScope["OPTIHASHI_COMPUTE"] = "optihashi:compute";
1241
+ /**
1242
+ * Read data lakes and their contents: list/browse lakes (including keyword search over
1243
+ * articles), read a lake's health, spend, batches, and access view. Split from
1244
+ * {@link DATALAKE_WRITE} so a key handed to an agent can look at a lake without being able to
1245
+ * change one. A retrieval query that spends LLM/search budget (semantic search, the RLM answer
1246
+ * endpoint) needs {@link DATALAKE_QUERY} instead: this scope does not imply it, though a
1247
+ * {@link DATALAKE_QUERY} key does gain this one (it has to, to reach the read-gated routes its
1248
+ * own retrieval tools call back into).
1249
+ */
1250
+ ApiKeyScope["DATALAKE_READ"] = "datalake:read";
1251
+ /**
1252
+ * Change what is IN a lake or how it behaves: create/update/archive a lake,
1253
+ * attach, detach, retag, or purge its files, run converge/rechunk/research,
1254
+ * and manage upload batches. Deliberately does NOT carry
1255
+ * {@link DATALAKE_SHARE}: adding files to a lake is a different privilege
1256
+ * from handing the lake to someone else.
1257
+ */
1258
+ ApiKeyScope["DATALAKE_WRITE"] = "datalake:write";
1259
+ /**
1260
+ * Change WHO can reach a lake: its visibility and its ownership (and the
1261
+ * grant/revoke door). Never implied by {@link DATALAKE_WRITE} - re-sharing a
1262
+ * lake widens the blast radius of every document already in it, which is not
1263
+ * what a key minted to keep a lake's files current asked for.
1264
+ */
1265
+ ApiKeyScope["DATALAKE_SHARE"] = "datalake:share";
1266
+ /**
1267
+ * Run a retrieval query that spends LLM/search budget against a lake: semantic search and the
1268
+ * RLM answer endpoint. Deliberately NOT suffixed `:read` - {@link DATALAKE_READ} feeds the
1269
+ * New-Key modal's "Read-only" preset (`s.value.endsWith(':read')`), and a key an operator mints
1270
+ * expecting that preset to be free must not auto-join a scope that commissions billable work.
1271
+ * Mirrors {@link OPTIHASHI_COMPUTE}'s split from {@link OPTIHASHI_READ}. Never implied by
1272
+ * {@link DATALAKE_READ} or {@link DATALAKE_WRITE} - spend on a lake is opt-in on its own. The
1273
+ * implication runs the other way for gating purposes: a key holding only this scope also passes
1274
+ * {@link DATALAKE_READ}'s gate, because the RLM answer endpoint's in-REPL tools call back into
1275
+ * read-gated routes (e.g. GET /api/data-lakes/articles) with the caller's own credential.
1276
+ */
1277
+ ApiKeyScope["DATALAKE_QUERY"] = "datalake:query";
1278
+ /**
1279
+ * Read the Overwatch analytics surface - the cross-product overview, product
1280
+ * inventory and config, first-party metrics, product-stat history, funnel,
1281
+ * and pipeline freshness. Exists so an agent can be handed a credential that
1282
+ * can only *look*: it is deliberately NOT the read half of
1283
+ * {@link OVERWATCH_INGEST_WRITE}, which is a per-product ingest credential
1284
+ * bound to one `productId` and able to write that product's numbers. Handing
1285
+ * an explorer the ingest key would let it fabricate the very stats it reports
1286
+ * on, so the two are separate scopes rather than a read/write pair.
1287
+ *
1288
+ * Like every scope, it authorizes but never entitles - the Overwatch access
1289
+ * check (`requestHasOverwatchAccess`: admin OR developer OR `overwatch:pro`)
1290
+ * still runs against the key's owner and can refuse on its own. A key minted
1291
+ * with this scope by a user who does not hold Overwatch access opens nothing.
1292
+ * That check and the routes it guards live in the Overwatch overlay package;
1293
+ * neither has an implementation in this repository, so nothing here verifies
1294
+ * the claim - it is a property of the consumer, recorded for the reader.
1295
+ */
1296
+ ApiKeyScope["OVERWATCH_READ"] = "overwatch:read";
1297
+ /**
1298
+ * CI-to-server ingest of Playwright runs for the admin /status page
1299
+ * (`POST /api/qa/uploads`, `POST /api/qa/runs`). Admin-provisioned only,
1300
+ * and confined like {@link OVERWATCH_INGEST_WRITE}: a leaked CI key reaches the
1301
+ * two ingest routes and nothing else. The routes also require the key's owner
1302
+ * to carry the `qa-ingest` user tag (QA_INGEST_USER_TAG in schemas/qa.ts).
1303
+ */
1304
+ ApiKeyScope["QA_INGEST"] = "qa:ingest";
1305
+ /**
1306
+ * List and download the Claude Code plugin feed of an org's approved Skill Market listings
1307
+ * (`/api/v1/skills/market/*`). Confined: the key ends up in plaintext in `~/.claude/settings.json`,
1308
+ * so it must reach nothing else. Minted from the Skill Market's "Use in Claude Code" dialog.
1309
+ */
1310
+ ApiKeyScope["SKILLS_READ"] = "skills:read";
1311
+ /**
1312
+ * Release-note automation: find a release note and set its video, and upload the What's New
1313
+ * media for it. Admin-provisioned and confined so the job needs no `admin:*`
1314
+ * key: it reaches only the routes that name it, and those still require a platform-admin owner.
1315
+ */
1316
+ ApiKeyScope["RELEASE_NOTES_WRITE"] = "release-notes:write";
1317
+ /**
1318
+ * Read the key owner's own Bob results: single-site reads, two-site comparisons and
1319
+ * re-checks, the recent-reports list, and how many runs are left. It starts nothing and
1320
+ * spends nothing, so the `:read` suffix that puts it in the New-Key modal's Read-only
1321
+ * preset is deliberate, and safe there because every read is scoped to the key's owner.
1322
+ * Split from {@link BOB_RUN} so an agent can be handed the read half alone.
1323
+ *
1324
+ * Like every scope, it authorizes but never entitles. The Bob routes, and the gate that
1325
+ * decides which of the two scopes each one accepts, live in the Bob overlay package and
1326
+ * have no implementation in this repository, so nothing here verifies either claim.
1327
+ */
1328
+ ApiKeyScope["BOB_READ"] = "bob:read";
1329
+ /**
1330
+ * Start a Bob site read, a two-site comparison or a re-check. This is the spend half of
1331
+ * the Bob pair: it spends the key owner's credits and publishes a report as them, so it
1332
+ * is never implied by {@link BOB_READ}, and it is deliberately unsuffixed so it stays out
1333
+ * of the Read-only and Read & write presets, as {@link OPTIHASHI_COMPUTE} does. Nor does
1334
+ * it imply {@link BOB_READ} at this repository's scope gate.
1335
+ *
1336
+ * Authorizes without entitling, like {@link BOB_READ}: the gate that enforces it lives in
1337
+ * the Bob overlay package, with no implementation here.
1338
+ */
1339
+ ApiKeyScope["BOB_RUN"] = "bob:run";
1340
+ return ApiKeyScope;
1341
+ }({});
1108
1342
  Object.freeze({
1109
1343
  requestsPerMinute: 60,
1110
1344
  requestsPerDay: 1e3
@@ -1236,6 +1470,42 @@ const GenericCreditAddTransaction = BaseCreditTransaction.extend({
1236
1470
  transactionId: z$1.string().optional()
1237
1471
  });
1238
1472
  /**
1473
+ * Built-in reasons for generic credit deductions; they seed the registry below.
1474
+ * A package that needs a new reason registers it with registerGenericDeductReason
1475
+ * rather than extending this list.
1476
+ */
1477
+ const GenericDeductReasons = [
1478
+ "dispute_clawback",
1479
+ "refund_clawback",
1480
+ "payment_failed_clawback",
1481
+ "notebook_curation",
1482
+ "manual",
1483
+ "admin_adjustment",
1484
+ "refund_adjustment",
1485
+ "optihashi_reservation",
1486
+ "optihashi_cost_chargeup",
1487
+ "credit_expiry"
1488
+ ];
1489
+ const GENERIC_DEDUCT_REASON_PATTERN = /^[a-z][a-z0-9_]*$/;
1490
+ const GENERIC_DEDUCT_REASON_REGISTRY_KEY = Symbol.for("b4m.credit.genericDeductReasons");
1491
+ let seededRegistry;
1492
+ function genericDeductReasonRegistry() {
1493
+ const holder = globalThis;
1494
+ let registry = holder[GENERIC_DEDUCT_REASON_REGISTRY_KEY];
1495
+ if (!registry) {
1496
+ registry = /* @__PURE__ */ new Set();
1497
+ holder[GENERIC_DEDUCT_REASON_REGISTRY_KEY] = registry;
1498
+ }
1499
+ if (seededRegistry !== registry) {
1500
+ for (const reason of GenericDeductReasons) registry.add(reason);
1501
+ seededRegistry = registry;
1502
+ }
1503
+ return registry;
1504
+ }
1505
+ function isGenericDeductReason(value) {
1506
+ return typeof value === "string" && GENERIC_DEDUCT_REASON_PATTERN.test(value) && genericDeductReasonRegistry().has(value);
1507
+ }
1508
+ /**
1239
1509
  * Generic credit deduction transaction for legacy data and flexible use cases
1240
1510
  * Represents any credit deduction that doesn't fit specific categories
1241
1511
  * (e.g., admin adjustments, legacy usage, manual deductions)
@@ -1247,20 +1517,10 @@ const GenericCreditDeductTransaction = BaseCreditTransaction.extend({
1247
1517
  */
1248
1518
  userId: z$1.string().optional(),
1249
1519
  /**
1250
- * Reason/source for the transaction. Use GenericDeductReasons for new callers.
1520
+ * Reason/source for the transaction: a built-in GenericDeductReasons value or one added with
1521
+ * registerGenericDeductReason.
1251
1522
  */
1252
- reason: z$1.enum([
1253
- "dispute_clawback",
1254
- "refund_clawback",
1255
- "payment_failed_clawback",
1256
- "notebook_curation",
1257
- "manual",
1258
- "admin_adjustment",
1259
- "refund_adjustment",
1260
- "optihashi_reservation",
1261
- "optihashi_cost_chargeup",
1262
- "credit_expiry"
1263
- ]).optional(),
1523
+ reason: z$1.custom(isGenericDeductReason, { error: (issue) => typeof issue.input === "string" ? "Unregistered generic deduct reason" : `Expected a generic deduct reason string, received ${issue.input === null ? "null" : typeof issue.input}` }).optional(),
1264
1524
  /**
1265
1525
  * Stripe dispute ID - set for dispute clawback transactions.
1266
1526
  * Used for idempotency (unique sparse index prevents duplicate clawbacks).
@@ -1428,6 +1688,25 @@ const USAGE_EVENT_STATUSES = [
1428
1688
  "refusal",
1429
1689
  "degenerate"
1430
1690
  ];
1691
+ /** Where a UsageEvent's rates came from: a ModelPrice catalog row, or the adapter literal fallback. */
1692
+ const USAGE_PRICE_SOURCES = ["catalog", "literal"];
1693
+ /** USD per token for the pricing tier that priced a call (see AppliedRates in models.ts). */
1694
+ const UsageEventRates = z$1.object({
1695
+ /** The pricing-map key (input-token threshold) of the tier applied. */
1696
+ tierKey: z$1.number(),
1697
+ input: z$1.number(),
1698
+ output: z$1.number(),
1699
+ cacheRead: z$1.number().optional(),
1700
+ cacheWrite: z$1.number().optional()
1701
+ });
1702
+ /** One provider request of a multi-turn completion, with the tier it was priced on. */
1703
+ const UsageEventTurn = z$1.object({
1704
+ inputTokens: z$1.number(),
1705
+ outputTokens: z$1.number(),
1706
+ cacheReadTokens: z$1.number().optional(),
1707
+ cacheWriteTokens: z$1.number().optional(),
1708
+ tierKey: z$1.number()
1709
+ });
1431
1710
  z$1.object({
1432
1711
  id: z$1.string().optional(),
1433
1712
  /** App-level correlation id: questId for chat/image/video, session/run id otherwise. */
@@ -1473,6 +1752,30 @@ z$1.object({
1473
1752
  source: z$1.enum(COMPLETION_SOURCES).optional(),
1474
1753
  /** API key that authed the call; present only for API-key-authed usage. */
1475
1754
  apiKeyId: z$1.string().optional(),
1755
+ /** 'catalog' when a ModelPrice row priced the call, 'literal' when the adapter literal did. */
1756
+ priceSource: z$1.enum(USAGE_PRICE_SOURCES).optional(),
1757
+ /** The ModelPrice row in force; set only when priceSource is 'catalog'. */
1758
+ priceRowId: z$1.string().optional(),
1759
+ /**
1760
+ * Rates of the tier applied. A multi-turn row whose turns hit different tiers carries the
1761
+ * tier of its largest turn; turnUsage has each turn's own tierKey, but not that tier's rates,
1762
+ * so only a single-tier text charge is recomputable from its own fields. Provider-basis chat
1763
+ * rows use providerInputTokens/providerOutputTokens plus the cache buckets. Local-estimate chat
1764
+ * rows use input * (inputTokens - cachedInputTokens) + cacheRead * cachedInputTokens
1765
+ * + output * outputTokens. Operational and CLI rows use disjoint input/cache buckets instead:
1766
+ * input * inputTokens + cacheRead * cachedInputTokens + cacheWrite * cacheWriteTokens
1767
+ * + output * outputTokens, even when settledBasis is 'local' or unset.
1768
+ * Local-estimate chat rows are inexact when a fallback uses a different tokenEstimateMultiplier:
1769
+ * inputTokens remains in the requested model's units. Tool spend on other models is not covered.
1770
+ */
1771
+ rates: UsageEventRates.optional(),
1772
+ /**
1773
+ * Per-turn usage, written only when the completion settled over more than one provider
1774
+ * request (tool loops bill turn by turn). Capped at USAGE_EVENT_TURN_USAGE_CAP entries.
1775
+ */
1776
+ turnUsage: z$1.array(UsageEventTurn).optional(),
1777
+ /** True when turnUsage was capped: its last entry is the sum of every turn from there on. */
1778
+ turnUsageTruncated: z$1.boolean().optional(),
1476
1779
  createdAt: z$1.date(),
1477
1780
  updatedAt: z$1.date()
1478
1781
  });
@@ -1545,6 +1848,7 @@ const ADAPTER_FAMILIES = [
1545
1848
  "bedrock-moonshot",
1546
1849
  "bedrock-jurassic",
1547
1850
  "bedrock-titan",
1851
+ "bedrock-converse",
1548
1852
  "gemini",
1549
1853
  "xai",
1550
1854
  "kimi",
@@ -1616,6 +1920,8 @@ const LIFECYCLE_STATUSES = [...[
1616
1920
  "retired"
1617
1921
  ], "unlisted"];
1618
1922
  const LifecycleWrite = z$1.strictObject({
1923
+ /** Absent on v1 rows means current; true folds the model without disabling it. */
1924
+ isSuperseded: z$1.boolean().optional(),
1619
1925
  status: z$1.enum(LIFECYCLE_STATUSES),
1620
1926
  deprecationDate: CALENDAR_DATE.optional(),
1621
1927
  retirementDate: CALENDAR_DATE.optional(),
@@ -1799,6 +2105,7 @@ const MODEL_INFO_FIELD_GROUP_OF = {
1799
2105
  supportsTools: "modalities",
1800
2106
  supportsImageVariation: "modalities",
1801
2107
  supportsSafetyTolerance: "modalities",
2108
+ isSuperseded: "lifecycle",
1802
2109
  deprecationDate: "lifecycle",
1803
2110
  replacedBy: "lifecycle",
1804
2111
  adapterFamily: "dispatch",
@@ -2324,6 +2631,59 @@ z$1.object({
2324
2631
  summary: z$1.string(),
2325
2632
  counts: orgFeedbackSummaryCountsSchema
2326
2633
  });
2634
+ const MODAL_MEDIA_HOST_SUFFIXES = [".amazonaws.com", ".cloudfront.net"];
2635
+ const MODAL_MEDIA_URL_MAX_LENGTH = 2048;
2636
+ const MODAL_MEDIA_URL_HINT = "Upload the file, or paste an https URL on amazonaws.com or cloudfront.net";
2637
+ function isAllowedModalMediaUrl(url, policy = {}) {
2638
+ if (url.length > MODAL_MEDIA_URL_MAX_LENGTH) return false;
2639
+ if (policy.allowedUrls?.has(url)) return true;
2640
+ try {
2641
+ const { protocol, hostname, origin, port } = new URL(url);
2642
+ if (policy.extraOrigins?.includes(origin)) return true;
2643
+ return protocol === "https:" && port === "" && MODAL_MEDIA_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix));
2644
+ } catch {
2645
+ return false;
2646
+ }
2647
+ }
2648
+ /** Per-field problems with a modal video, empty when it is valid. Shared by the admin form and the API. */
2649
+ function getModalVideoErrors(video, policy = {}) {
2650
+ const errors = {};
2651
+ if (!video) return errors;
2652
+ for (const field of [
2653
+ "landscapeUrl",
2654
+ "landscapePosterUrl",
2655
+ "portraitUrl",
2656
+ "portraitPosterUrl"
2657
+ ]) {
2658
+ const url = video[field];
2659
+ if (url && !isAllowedModalMediaUrl(url, policy)) errors[field] = MODAL_MEDIA_URL_HINT;
2660
+ }
2661
+ if (video.landscapePosterUrl && !video.landscapeUrl) errors.landscapePosterUrl ??= "A 16:9 poster needs its 16:9 video";
2662
+ if (video.portraitPosterUrl && !video.portraitUrl) errors.portraitPosterUrl ??= "A 9:16 poster needs its 9:16 video";
2663
+ return errors;
2664
+ }
2665
+ /** The video's fields with no host check, for reading stored or upstream data written under another policy. */
2666
+ const ModalVideoShapeSchema = z$1.object({
2667
+ landscapeUrl: z$1.string().nullable().optional(),
2668
+ landscapePosterUrl: z$1.string().nullable().optional(),
2669
+ portraitUrl: z$1.string().nullable().optional(),
2670
+ portraitPosterUrl: z$1.string().nullable().optional()
2671
+ });
2672
+ /**
2673
+ * Product video for a modal slide. Landscape is the 16:9 cut shown on desktop, portrait the 9:16 cut
2674
+ * shown on mobile; the slider falls back to the other orientation when one is missing. The API passes
2675
+ * its deployment's extra origins; the bare `ModalVideoSchema` allows only the static host list.
2676
+ */
2677
+ function createModalVideoSchema(policy = {}) {
2678
+ return ModalVideoShapeSchema.superRefine((video, ctx) => {
2679
+ for (const [field, message] of Object.entries(getModalVideoErrors(video, policy))) ctx.addIssue({
2680
+ code: "custom",
2681
+ path: [field],
2682
+ message
2683
+ });
2684
+ });
2685
+ }
2686
+ createModalVideoSchema();
2327
2687
  /**
2328
2688
  * Check if a value is a placeholder (not configured or SST default).
2329
2689
  * Uses case-insensitive comparison and trims whitespace to prevent bypass attempts.
@@ -2940,18 +3300,21 @@ const AuditChangeSchema = z$1.object({
2940
3300
  before: z$1.unknown(),
2941
3301
  after: z$1.unknown()
2942
3302
  });
3303
+ /** Sources a stored AdminAuditLog row can carry; the write input and the filters stay inside this set. */
3304
+ const StoredAuditContextSourceSchema = z$1.enum([
3305
+ "admin_ui",
3306
+ "api_key",
3307
+ "script",
3308
+ "worker"
3309
+ ]);
3310
+ /** Read-side context: 'user' is the account holder's own session, which only the security-store adapters emit. */
2943
3311
  const AuditContextSchema = z$1.object({
2944
3312
  ip: z$1.string().optional(),
2945
3313
  userAgent: z$1.string().optional(),
2946
3314
  requestId: z$1.string().optional(),
2947
- source: z$1.enum([
2948
- "admin_ui",
2949
- "api_key",
2950
- "script",
2951
- "worker"
2952
- ]),
3315
+ source: StoredAuditContextSourceSchema,
2953
3316
  route: z$1.string().optional()
2954
- });
3317
+ }).extend({ source: z$1.union([StoredAuditContextSourceSchema, z$1.literal("user")]) });
2955
3318
  const AdminAuditCategorySchema = z$1.enum([
2956
3319
  "credits",
2957
3320
  "access",
@@ -2992,6 +3355,268 @@ z$1.object({
2992
3355
  }).optional(),
2993
3356
  backfilled: z$1.boolean().optional()
2994
3357
  });
3358
+ z$1.string({ error: "A reason is required" }).trim().min(5, `A reason of at least 5 characters is required`).max(500);
3359
+ /** App defaults for the AdminSettings grant thresholds (AdminAuditCriticalGrantCredits / AdminAuditDailyGrantCredits). */
3360
+ const ADMIN_AUDIT_DEFAULT_CRITICAL_GRANT_CREDITS = 5e4;
3361
+ const ADMIN_AUDIT_DEFAULT_DAILY_GRANT_CREDITS = 2e5;
3362
+ const ADMIN_AUDIT_CATALOGUE = {
3363
+ "org.credits.granted": {
3364
+ category: "credits",
3365
+ severity: "notice",
3366
+ write: "strict",
3367
+ escalatesAboveGrantThreshold: true
3368
+ },
3369
+ "org.credits.topped_up": {
3370
+ category: "credits",
3371
+ severity: "notice",
3372
+ write: "strict",
3373
+ escalatesAboveGrantThreshold: true
3374
+ },
3375
+ "org.credits.adjusted": {
3376
+ category: "credits",
3377
+ severity: "notice",
3378
+ write: "strict",
3379
+ escalatesAboveGrantThreshold: true
3380
+ },
3381
+ "org.credits.refunded": {
3382
+ category: "credits",
3383
+ severity: "notice",
3384
+ write: "strict",
3385
+ escalatesAboveGrantThreshold: true
3386
+ },
3387
+ "user.subscription.granted": {
3388
+ category: "credits",
3389
+ severity: "notice",
3390
+ write: "strict"
3391
+ },
3392
+ "user.credits.adjusted": {
3393
+ category: "credits",
3394
+ severity: "notice",
3395
+ write: "strict",
3396
+ escalatesAboveGrantThreshold: true
3397
+ },
3398
+ "user.credits.denied": {
3399
+ category: "credits",
3400
+ severity: "critical",
3401
+ write: "bestEffort"
3402
+ },
3403
+ "user.subscription.denied": {
3404
+ category: "credits",
3405
+ severity: "critical",
3406
+ write: "bestEffort"
3407
+ },
3408
+ "user.role.admin_granted": {
3409
+ category: "access",
3410
+ severity: "critical",
3411
+ write: "strict"
3412
+ },
3413
+ "user.role.admin_revoked": {
3414
+ category: "access",
3415
+ severity: "critical",
3416
+ write: "strict"
3417
+ },
3418
+ "user.role.denied": {
3419
+ category: "access",
3420
+ severity: "critical",
3421
+ write: "bestEffort"
3422
+ },
3423
+ "user.impersonation.started": {
3424
+ category: "access",
3425
+ severity: "critical",
3426
+ write: "strict"
3427
+ },
3428
+ "user.impersonation.ended": {
3429
+ category: "access",
3430
+ severity: "info",
3431
+ write: "bestEffort"
3432
+ },
3433
+ "user.impersonation.denied": {
3434
+ category: "access",
3435
+ severity: "critical",
3436
+ write: "bestEffort"
3437
+ },
3438
+ "user.deleted": {
3439
+ category: "account",
3440
+ severity: "critical",
3441
+ write: "strict"
3442
+ },
3443
+ "user.deletion.denied": {
3444
+ category: "account",
3445
+ severity: "critical",
3446
+ write: "bestEffort"
3447
+ },
3448
+ "user.profile.updated": {
3449
+ category: "account",
3450
+ severity: "notice",
3451
+ write: "bestEffort"
3452
+ },
3453
+ "org.seats.changed": {
3454
+ category: "org",
3455
+ severity: "notice",
3456
+ write: "bestEffort"
3457
+ },
3458
+ "org.seats.ceiling_raised": {
3459
+ category: "org",
3460
+ severity: "info",
3461
+ write: "bestEffort"
3462
+ },
3463
+ "org.conversion.initiated": {
3464
+ category: "org",
3465
+ severity: "notice",
3466
+ write: "bestEffort"
3467
+ },
3468
+ "org.revoked": {
3469
+ category: "org",
3470
+ severity: "notice",
3471
+ write: "strict"
3472
+ },
3473
+ "org.group_types.updated": {
3474
+ category: "org",
3475
+ severity: "info",
3476
+ write: "bestEffort"
3477
+ },
3478
+ "org.admins.updated": {
3479
+ category: "org",
3480
+ severity: "notice",
3481
+ write: "bestEffort"
3482
+ },
3483
+ "org.group_member.assigned": {
3484
+ category: "org",
3485
+ severity: "info",
3486
+ write: "bestEffort"
3487
+ },
3488
+ "org.group_member.unassigned": {
3489
+ category: "org",
3490
+ severity: "info",
3491
+ write: "bestEffort"
3492
+ },
3493
+ "org.invite.created": {
3494
+ category: "org",
3495
+ severity: "info",
3496
+ write: "bestEffort"
3497
+ },
3498
+ "org.member_budget.updated": {
3499
+ category: "org",
3500
+ severity: "notice",
3501
+ write: "bestEffort"
3502
+ },
3503
+ "settings.admin_settings.updated": {
3504
+ category: "settings",
3505
+ severity: "notice",
3506
+ write: "strict"
3507
+ },
3508
+ "settings.whats_new.updated": {
3509
+ category: "settings",
3510
+ severity: "info",
3511
+ write: "bestEffort"
3512
+ },
3513
+ "settings.whats_new.synced": {
3514
+ category: "settings",
3515
+ severity: "info",
3516
+ write: "bestEffort"
3517
+ },
3518
+ "settings.whats_new.generation_triggered": {
3519
+ category: "settings",
3520
+ severity: "info",
3521
+ write: "bestEffort"
3522
+ },
3523
+ "settings.security_scan.enabled": {
3524
+ category: "settings",
3525
+ severity: "notice",
3526
+ write: "bestEffort"
3527
+ },
3528
+ "settings.security_scan.disabled": {
3529
+ category: "settings",
3530
+ severity: "notice",
3531
+ write: "bestEffort"
3532
+ },
3533
+ "settings.security_scan.triggered": {
3534
+ category: "settings",
3535
+ severity: "notice",
3536
+ write: "bestEffort"
3537
+ },
3538
+ "security.attack_simulation.run": {
3539
+ category: "settings",
3540
+ severity: "notice",
3541
+ write: "bestEffort"
3542
+ },
3543
+ "oauth_client.created": {
3544
+ category: "settings",
3545
+ severity: "notice",
3546
+ write: "bestEffort"
3547
+ },
3548
+ "oauth_client.secret_rotated": {
3549
+ category: "settings",
3550
+ severity: "notice",
3551
+ write: "bestEffort"
3552
+ },
3553
+ "oauth_client.updated": {
3554
+ category: "settings",
3555
+ severity: "notice",
3556
+ write: "bestEffort"
3557
+ },
3558
+ "oauth_client.activated": {
3559
+ category: "settings",
3560
+ severity: "notice",
3561
+ write: "bestEffort"
3562
+ },
3563
+ "oauth_client.deactivated": {
3564
+ category: "settings",
3565
+ severity: "notice",
3566
+ write: "bestEffort"
3567
+ },
3568
+ "system_health.test_run": {
3569
+ category: "settings",
3570
+ severity: "info",
3571
+ write: "bestEffort"
3572
+ },
3573
+ "user.email.verified": {
3574
+ category: "identity",
3575
+ severity: "info",
3576
+ write: "bestEffort"
3577
+ },
3578
+ "user.email.unverified": {
3579
+ category: "identity",
3580
+ severity: "info",
3581
+ write: "bestEffort"
3582
+ },
3583
+ "user.email.verification_resent": {
3584
+ category: "identity",
3585
+ severity: "info",
3586
+ write: "bestEffort"
3587
+ },
3588
+ "user.email.change_resent": {
3589
+ category: "identity",
3590
+ severity: "info",
3591
+ write: "bestEffort"
3592
+ },
3593
+ "user.email.change_cancelled": {
3594
+ category: "identity",
3595
+ severity: "info",
3596
+ write: "bestEffort"
3597
+ },
3598
+ "user.sessions.revoked": {
3599
+ category: "identity",
3600
+ severity: "notice",
3601
+ write: "bestEffort"
3602
+ },
3603
+ "user.mfa.reset": {
3604
+ category: "identity",
3605
+ severity: "notice",
3606
+ write: "strict"
3607
+ },
3608
+ "user.mfa.denied": {
3609
+ category: "identity",
3610
+ severity: "critical",
3611
+ write: "bestEffort"
3612
+ },
3613
+ "audit.exported": {
3614
+ category: "support",
3615
+ severity: "info",
3616
+ write: "bestEffort"
3617
+ }
3618
+ };
3619
+ Object.keys(ADMIN_AUDIT_CATALOGUE).filter((action) => "escalatesAboveGrantThreshold" in ADMIN_AUDIT_CATALOGUE[action]);
2995
3620
  z$1.object({
2996
3621
  category: AdminAuditCategorySchema.optional(),
2997
3622
  severity: AdminAuditSeveritySchema.optional(),
@@ -3000,13 +3625,14 @@ z$1.object({
3000
3625
  actorUserId: z$1.string().min(1).optional(),
3001
3626
  targetKind: AuditTargetSchema.shape.kind.optional(),
3002
3627
  targetId: z$1.string().min(1).optional(),
3003
- source: AuditContextSchema.shape.source.optional(),
3628
+ source: StoredAuditContextSourceSchema.optional(),
3004
3629
  actorScope: AuditActorSchema.shape.scope.optional(),
3005
3630
  from: z$1.iso.datetime().optional(),
3006
3631
  to: z$1.iso.datetime().optional(),
3007
3632
  requestId: z$1.string().min(1).optional(),
3008
3633
  impersonationSessionId: z$1.string().min(1).optional()
3009
3634
  });
3635
+ z$1.enum(["target", "actor"]);
3010
3636
  let SupportedFabFileMimeTypes = /* @__PURE__ */ function(SupportedFabFileMimeTypes) {
3011
3637
  SupportedFabFileMimeTypes["PDF"] = "application/pdf";
3012
3638
  SupportedFabFileMimeTypes["DOCX"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
@@ -3052,6 +3678,26 @@ let SupportedFabFileMimeTypes = /* @__PURE__ */ function(SupportedFabFileMimeTyp
3052
3678
  return SupportedFabFileMimeTypes;
3053
3679
  }({});
3054
3680
  new Set(Object.values(SupportedFabFileMimeTypes));
3681
+ /**
3682
+ * Known MIME types emitted by the generated-audio endpoints (TTS + sound
3683
+ * effects). Deliberately SEPARATE from SupportedFabFileMimeTypes: that set is
3684
+ * the ingest pipeline's chunk+vectorize allowlist, and audio is intentionally
3685
+ * NOT vectorizable and NOT attachable to a chat completion (no model accepts
3686
+ * audio input). Kept only for extension mapping / documentation; the runtime
3687
+ * guard below matches any `audio/*` so unusual provider subtypes are still
3688
+ * treated as audio (i.e. still excluded from every LLM path).
3689
+ */
3690
+ let AudioMimeType = /* @__PURE__ */ function(AudioMimeType) {
3691
+ AudioMimeType["MP3"] = "audio/mpeg";
3692
+ AudioMimeType["WAV"] = "audio/wav";
3693
+ AudioMimeType["OPUS"] = "audio/opus";
3694
+ AudioMimeType["AAC"] = "audio/aac";
3695
+ AudioMimeType["FLAC"] = "audio/flac";
3696
+ AudioMimeType["PCM"] = "audio/pcm";
3697
+ AudioMimeType["OGG"] = "audio/ogg";
3698
+ AudioMimeType["WEBM"] = "audio/webm";
3699
+ return AudioMimeType;
3700
+ }({});
3055
3701
  /** Reads back the classifier set by the tagged-error helpers above; `undefined` for untagged errors. */
3056
3702
  function getQuestErrorCode(error) {
3057
3703
  const code = error?.additionalInfo?.errorCode;
@@ -3188,6 +3834,49 @@ const PROMPT_META_MODEL_TYPES = [
3188
3834
  "image",
3189
3835
  "video"
3190
3836
  ];
3837
+ /**
3838
+ * Native Anthropic tool capabilities, by Claude family and version. Allow-lists rather than a
3839
+ * version comparison, so an older or not-yet-reviewed id (a preview, a new family) reads false.
3840
+ * Sources: the tool search tool and mid-conversation system messages pages of the Claude docs.
3841
+ */
3842
+ const TOOL_SEARCH_VERSIONS = /* @__PURE__ */ new Set([
3843
+ "opus-4-5",
3844
+ "opus-4-6",
3845
+ "opus-4-7",
3846
+ "opus-4-8",
3847
+ "opus-5",
3848
+ "opus-5-5",
3849
+ "sonnet-4-5",
3850
+ "sonnet-4-6",
3851
+ "sonnet-5",
3852
+ "sonnet-5-5",
3853
+ "haiku-4-5",
3854
+ "haiku-5-5",
3855
+ "fable-5",
3856
+ "fable-5-1",
3857
+ "mythos-5",
3858
+ "mythos-5-1"
3859
+ ]);
3860
+ function servedNatively(model, versions) {
3861
+ if (model.backend !== "anthropic" && model.backend !== "bedrock") return false;
3862
+ const version = claudeFamilyVersion(model.id);
3863
+ return version !== null && versions.has(version);
3864
+ }
3865
+ /** Whether the model accepts the tool search server tool and `defer_loading` on tool definitions. */
3866
+ function supportsToolSearch(model) {
3867
+ return servedNatively(model, TOOL_SEARCH_VERSIONS);
3868
+ }
3869
+ function servedByDirectAnthropic(model) {
3870
+ return model.adapterFamily ? model.adapterFamily === "anthropic-messages" : model.backend === "anthropic";
3871
+ }
3872
+ function servedByClaudeAdapter(model) {
3873
+ if (servedByDirectAnthropic(model)) return true;
3874
+ return model.adapterFamily ? model.adapterFamily === "bedrock-anthropic" : model.backend === "bedrock";
3875
+ }
3876
+ /** supportsToolSearch, narrowed to models whose `/api/ai/v1/completions` adapter actually forwards it. */
3877
+ function completionsSupportsToolSearch(model) {
3878
+ return servedByClaudeAdapter(model) && supportsToolSearch(model);
3879
+ }
3191
3880
  const DEFAULT_PRICE_MARGIN = 1.2;
3192
3881
  const DEFAULT_USD_TO_CREDITS_RATE = 6e-4;
3193
3882
  /**
@@ -3803,7 +4492,7 @@ const CompletionCustomToolSchema = z$1.object({
3803
4492
  required: z$1.array(z$1.string()).optional()
3804
4493
  }).passthrough(),
3805
4494
  strict: z$1.boolean().optional(),
3806
- defer_loading: z$1.boolean().optional().describe("Keep this definition out of the model context until the tool search server tool (or a `tool_reference`) loads it. Honoured only on models served by the direct Anthropic API whose `supports_tool_search` is true; on every other model (including Bedrock-served Claude, for now) it is ignored and the tool is sent as usual. At least one tool, or a `serverTool` entry, must stay non-deferred (`errorCode: \"all_tools_deferred\"`)."),
4495
+ defer_loading: z$1.boolean().optional().describe("Keep this definition out of the model context until the tool search server tool (or a `tool_reference`) loads it. Honoured only on Claude models (direct Anthropic or Bedrock) whose `supports_tool_search` is true; on every other model it is ignored and the tool is sent as usual. At least one tool, or a `serverTool` entry, must stay non-deferred (`errorCode: \"all_tools_deferred\"`)."),
3807
4496
  cache_control: z$1.unknown().optional().describe("Accepted for validation only and not forwarded. Rejected on a deferred tool (`errorCode: \"deferred_tool_cache_control\"`).")
3808
4497
  }),
3809
4498
  _isMcpTool: z$1.boolean().optional()
@@ -3816,7 +4505,7 @@ const ToolSearchServerToolSchema = z$1.discriminatedUnion("type", [z$1.object({
3816
4505
  type: z$1.literal("tool_search_tool_bm25_20251119"),
3817
4506
  name: z$1.literal("tool_search_tool_bm25")
3818
4507
  })]);
3819
- const CompletionServerToolSchema = z$1.object({ serverTool: ToolSearchServerToolSchema.describe("A provider-run tool, sent to the provider as `{ type, name }`. Only the Anthropic tool search tools exist today, and only on models served by the direct Anthropic API whose `supports_tool_search` is true; any other model, Bedrock-served Claude included, is refused with `errorCode: \"tool_search_unsupported\"` rather than run without it.") });
4508
+ const CompletionServerToolSchema = z$1.object({ serverTool: ToolSearchServerToolSchema.describe("A provider-run tool, sent to the provider as `{ type, name }`. Only the Anthropic tool search tools exist today, and only on Claude models (direct Anthropic or Bedrock) whose `supports_tool_search` is true; any other model is refused with `errorCode: \"tool_search_unsupported\"` rather than run without it.") });
3820
4509
  /** One `tools[]` entry: a caller-defined tool, or a provider-run server tool. */
3821
4510
  const CompletionToolSchema = z$1.union([CompletionCustomToolSchema, CompletionServerToolSchema]);
3822
4511
  /**
@@ -3885,7 +4574,7 @@ const CompletionMessageSchema = z$1.object({
3885
4574
  */
3886
4575
  const CompletionRequestSchema = z$1.object({
3887
4576
  model: z$1.string().max(256),
3888
- messages: z$1.array(CompletionMessageSchema),
4577
+ messages: z$1.array(CompletionMessageSchema).min(1),
3889
4578
  response_format: ResponseFormatSchema.optional(),
3890
4579
  stream: z$1.boolean().optional(),
3891
4580
  tools: z$1.array(CompletionToolSchema).optional(),
@@ -3946,6 +4635,7 @@ const CompletionContentEventSchema = z$1.object({
3946
4635
  */
3947
4636
  const COMPLETION_SSE_ERROR_CODES = [
3948
4637
  ...QUEST_ERROR_CODES,
4638
+ "invalid_request",
3949
4639
  "provider_error",
3950
4640
  "provider_rejected",
3951
4641
  "content_blocked"
@@ -3956,9 +4646,9 @@ const COMPLETION_SSE_ERROR_CODES = [
3956
4646
  * real status instead; see completions.contract.ts.
3957
4647
  *
3958
4648
  * `code` is the machine-readable classifier: a billing code when settlement runs out mid-generation,
3959
- * `provider_rejected` / `provider_error` when the model provider fails, `content_blocked` when the
3960
- * model's safety classifier declined the request. Optional; callers must never match on the prose
3961
- * `message`.
4649
+ * `invalid_request` when the provider rejects caller-controlled input, `provider_rejected` /
4650
+ * `provider_error` when the model provider fails, `content_blocked` when the model's safety classifier
4651
+ * declined the request. Optional; callers must never match on the prose `message`.
3962
4652
  *
3963
4653
  * The field is `code` rather than the conventions' `errorCode` because this frame shipped that way
3964
4654
  * and is a published wire shape.
@@ -5083,9 +5773,9 @@ const RetrievalSummarySchema = z$1.object({
5083
5773
  * `citables` and `retrieval` - this field needs no merge case in applyQuestStatusChanges: a
5084
5774
  * later tool-arm write that omits it is preserved by the one-level spread.
5085
5775
  *
5086
- * `reasons` is the same diagnostic prose the warnings entry interpolates. It is shown to the
5087
- * reader behind a disclosure rather than in the banner body, because only some reasons are
5088
- * actionable (a document mid-reindex returns on its own; a per-turn chunk budget does not).
5776
+ * `reasons` is reader-facing prose with no document ids or internal limits, shown behind a
5777
+ * disclosure in the chat notice. The diagnostic version (ids, caps, counts) lives only in the
5778
+ * `warnings` entry. A supersession collapse alone does not produce this field at all.
5089
5779
  */
5090
5780
  const RetrievalCoverageSchema = z$1.object({
5091
5781
  /** Always true - see the presence contract above. */
@@ -5415,10 +6105,14 @@ const StorageLimitErrorSchema = ApiErrorSchema.extend(StorageLimitErrorInfoSchem
5415
6105
  /** 503 when this deployment has no usable key for a provider the request needs (data-lake search, voice). */
5416
6106
  const ProviderNotConfiguredErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.literal("provider_not_configured").optional() });
5417
6107
  /**
5418
- * 400 for a `model` the caller cannot use, or a refused tool search request; `errorCode` is absent on the
5419
- * other 400s (e.g. malformed JSON).
6108
+ * 400 for provider-rejected caller input, a `model` the caller cannot use, or a refused tool search
6109
+ * request; `errorCode` is absent on the other 400s (e.g. malformed JSON).
5420
6110
  */
5421
- const CompletionsBadRequestErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.enum([UNKNOWN_MODEL_ERROR_CODE, ...COMPLETION_TOOL_ERROR_CODES]).optional() });
6111
+ const CompletionsBadRequestErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.enum([
6112
+ "invalid_request",
6113
+ UNKNOWN_MODEL_ERROR_CODE,
6114
+ ...COMPLETION_TOOL_ERROR_CODES
6115
+ ]).optional() });
5422
6116
  /**
5423
6117
  * Error envelope for the 403 apiKeyAuth (apps/client/server/middlewares/apiKeyAuth.ts)
5424
6118
  * throws when an API key lacks a route's scopes. It names what the ROUTE requires,
@@ -5431,8 +6125,9 @@ const ScopeForbiddenErrorSchema = ApiErrorSchema.extend({
5431
6125
  also_required_scopes: z$1.array(z$1.string()).optional().describe("API key scopes this route also requires. Every one must be held, on top of `required_scopes`.")
5432
6126
  });
5433
6127
  /**
5434
- * 403 on the v1 media create routes: the scope refusal above, or an organization-billed API key sent with
5435
- * another billing organization (apps/client/server/billing/resolveMediaBillingOrgId.ts).
6128
+ * 403 on the v1 media create routes and POST /api/v1/agent-executions: the scope refusal above, or an
6129
+ * organization-billed API key sent with another billing organization
6130
+ * (apps/client/server/billing/resolveMediaBillingOrgId.ts).
5436
6131
  */
5437
6132
  const BillingOrganizationForbiddenErrorSchema = ScopeForbiddenErrorSchema.extend({ errorCode: z$1.literal(BILLING_ORGANIZATION_MISMATCH_ERROR_CODE).optional() });
5438
6133
  const supportedVoiceGenerationVendor = z.enum(["openai", "elevenlabs"]);
@@ -5445,10 +6140,11 @@ const voiceOutputFormatSchema = z.enum([
5445
6140
  "flac",
5446
6141
  "pcm"
5447
6142
  ]);
5448
- const TTS_ABSOLUTE_MAX_INPUT_CHARS = Math.max(...Object.values({
6143
+ const TTS_MAX_INPUT_CHARS = {
5449
6144
  openai: 4096,
5450
6145
  elevenlabs: 1e4
5451
- }));
6146
+ };
6147
+ const TTS_ABSOLUTE_MAX_INPUT_CHARS = Math.max(...Object.values(TTS_MAX_INPUT_CHARS));
5452
6148
  const ttsLanguageCodeSchema = z.string().regex(/^[a-z]{2}$/, "languageCode must be a lowercase ISO 639-1 code, e.g. \"en\" or \"ja\"");
5453
6149
  const ttsRequestSchema = z.object({
5454
6150
  text: z.string().min(1).max(TTS_ABSOLUTE_MAX_INPUT_CHARS),
@@ -5540,12 +6236,30 @@ const ttsResponseTooLargeSchema = generatedAudioTooLargeSchema.extend({
5540
6236
  fabFileId: z.string().optional(),
5541
6237
  fileUrl: z.string().optional()
5542
6238
  });
6239
+ /**
6240
+ * The model each vendor's service synthesizes and bills with when the caller
6241
+ * names none. Read by OpenAIVoiceService / ElevenLabsVoiceService in
6242
+ * @bike4mind/utils, so a pre-flight estimate prices the model the charge will use
6243
+ * when the requested vendor serves the call. The TTS route reserves the highest
6244
+ * estimate across its candidate vendors, using these defaults for alternates,
6245
+ * then settles the actual vendor's cost and refunds the difference.
6246
+ */
6247
+ const TTS_DEFAULT_MODEL = {
6248
+ openai: "tts-1",
6249
+ elevenlabs: "eleven_multilingual_v2"
6250
+ };
5543
6251
  const GENERATION_OUTPUT_AVAILABILITIES = [
5544
6252
  "ready",
5545
6253
  "pending_scan",
5546
6254
  "unavailable"
5547
6255
  ];
5548
6256
  const VIDEO_MODES = ["text_to_video", "image_to_video"];
6257
+ const VIDEO_INPUT_ROLES = [
6258
+ "first_frame",
6259
+ "last_frame",
6260
+ "reference",
6261
+ "style"
6262
+ ];
5549
6263
  const ASPECT_RATIOS = [
5550
6264
  "16:9",
5551
6265
  "9:16",
@@ -5569,6 +6283,13 @@ const VideoModelIdSchema = z$1.enum([
5569
6283
  "grok-imagine-video-1.5",
5570
6284
  "veo-3.1-fast-generate-preview"
5571
6285
  ]);
6286
+ const VideoInputSchema = z$1.union([z$1.strictObject({
6287
+ role: z$1.enum(VIDEO_INPUT_ROLES),
6288
+ fileId: z$1.string().min(1)
6289
+ }), z$1.strictObject({
6290
+ role: z$1.enum(VIDEO_INPUT_ROLES),
6291
+ generatedImageKey: z$1.string().min(1)
6292
+ })]);
5572
6293
  z$1.object({
5573
6294
  model: VideoModelIdSchema,
5574
6295
  mode: z$1.enum(VIDEO_MODES),
@@ -5576,6 +6297,7 @@ z$1.object({
5576
6297
  durationSeconds: z$1.number().positive(),
5577
6298
  aspectRatio: z$1.enum(ASPECT_RATIOS),
5578
6299
  resolution: z$1.enum(RESOLUTION_TIERS),
6300
+ inputs: z$1.array(VideoInputSchema).max(8).optional(),
5579
6301
  inputImageFileId: z$1.string().min(1).optional(),
5580
6302
  inputGeneratedImageKey: z$1.string().min(1).optional(),
5581
6303
  audio: z$1.boolean().optional()
@@ -5587,6 +6309,7 @@ const VIDEO_VALIDATION_ERROR_CODES = [
5587
6309
  "unsupported_mode",
5588
6310
  "missing_input_image",
5589
6311
  "unexpected_input_image",
6312
+ "unsupported_input_role",
5590
6313
  "unsupported_audio_option"
5591
6314
  ];
5592
6315
  const MAX_MUSIC_LYRICS_CHARS = 1e4;
@@ -5808,6 +6531,7 @@ z$2.object({
5808
6531
  ]),
5809
6532
  slackSettings: redactedSubdocument([
5810
6533
  "slackUserId",
6534
+ "slackTeamId",
5811
6535
  "defaultNotebookId",
5812
6536
  "autoCreateNotebook",
5813
6537
  "notebookNamePrefix",
@@ -5828,7 +6552,10 @@ z$2.object({
5828
6552
  "connectedAt"
5829
6553
  ])
5830
6554
  }).extend({
5831
- securityQuestions: passthroughField,
6555
+ securityQuestions: z$2.array(z$2.object({
6556
+ question: z$2.string(),
6557
+ answer: z$2.literal("")
6558
+ })).nullable().optional(),
5832
6559
  userNotes: passthroughField
5833
6560
  });
5834
6561
  /**
@@ -5848,7 +6575,6 @@ const INTEGRATION_IDS = [
5848
6575
  "mcp-cli",
5849
6576
  "atlassian",
5850
6577
  "notion",
5851
- "google-calendar",
5852
6578
  "slack",
5853
6579
  "email",
5854
6580
  "linkedin",
@@ -6021,20 +6747,6 @@ const INTEGRATION_CATALOG = {
6021
6747
  mcpServer: "notion"
6022
6748
  }]
6023
6749
  },
6024
- "google-calendar": {
6025
- name: "Google Calendar",
6026
- description: "Book briefing calls into open slots on a shared Bike4Mind calendar, not your own.",
6027
- category: "productivity",
6028
- logo: "google-calendar",
6029
- stage: "beta",
6030
- flag: "enableGoogleCalendar",
6031
- capabilities: [{
6032
- id: "calendar-booking",
6033
- label: "Book briefings on a shared calendar",
6034
- kind: "notifications",
6035
- stage: "beta"
6036
- }]
6037
- },
6038
6750
  slack: {
6039
6751
  name: "Slack",
6040
6752
  description: "Chat with Bike4Mind in Slack and send threads and files to a data lake.",
@@ -6609,7 +7321,7 @@ let OllamaEmbeddingModel = /* @__PURE__ */ function(OllamaEmbeddingModel) {
6609
7321
  *
6610
7322
  * Deliberately answers from the ENVIRONMENT only, and deliberately does NOT try to answer "can this
6611
7323
  * deployment actually reach that model". It cannot: on a hosted stage an SST secret arrives as a
6612
- * linked Resource, never as process.env (see apps/client/server/modelDiscovery/adapters.ts), so an
7324
+ * linked Resource, never as process.env (see packages/server-utils/src/modelDiscovery/adapters.ts), so an
6613
7325
  * absent OPENAI_API_KEY here means "unknown", not "no key" - it is absent on production too. This
6614
7326
  * function is also bundled into the browser via settingsMap, where every env read is undefined, so
6615
7327
  * any answer it gives must be one that is safe as a stage-neutral constant.
@@ -6718,6 +7430,7 @@ z$1.object({
6718
7430
  status: ReleaseNoteStatusSchema,
6719
7431
  publishAt: z$1.coerce.date(),
6720
7432
  editedAt: z$1.coerce.date().nullable(),
7433
+ video: ModalVideoShapeSchema.nullable().optional(),
6721
7434
  schemaVersion: z$1.number().int()
6722
7435
  });
6723
7436
  const ReleaseNotesJobPrSchema = z$1.object({
@@ -6745,6 +7458,22 @@ const ReleaseNotesConfigSchema = z$1.object({
6745
7458
  slackTeamId: z$1.string().optional(),
6746
7459
  slackChannelId: z$1.string().optional()
6747
7460
  });
7461
+ /** A release note video on the public wire: ModalVideo with snake_case keys. */
7462
+ const PublicReleaseNoteVideoSchema = z$1.object({
7463
+ landscape_url: z$1.string().nullable().optional(),
7464
+ landscape_poster_url: z$1.string().nullable().optional(),
7465
+ portrait_url: z$1.string().nullable().optional(),
7466
+ portrait_poster_url: z$1.string().nullable().optional()
7467
+ });
7468
+ const ReleaseNoteMediaParamsSchema = z$1.object({
7469
+ id: z$1.string().min(1),
7470
+ asset: z$1.enum(Object.keys({
7471
+ landscape: "landscapeUrl",
7472
+ "landscape-poster": "landscapePosterUrl",
7473
+ portrait: "portraitUrl",
7474
+ "portrait-poster": "portraitPosterUrl"
7475
+ }))
7476
+ });
6748
7477
  /**
6749
7478
  * A release note as GET /api/v1/whats-new serves it. An allowlist: provenance (sourcePrs, deployedSha),
6750
7479
  * workflow state (status, editedAt) and storage fields never reach the public wire.
@@ -6759,7 +7488,8 @@ const PublicReleaseNoteSchema = z$1.object({
6759
7488
  category: ReleaseNoteCategorySchema,
6760
7489
  text: z$1.string(),
6761
7490
  importance: z$1.number().int().min(1).max(3)
6762
- }))
7491
+ })),
7492
+ video: PublicReleaseNoteVideoSchema.nullable().optional()
6763
7493
  });
6764
7494
  /**
6765
7495
  * The measured per-space floors, rendered for an admin-facing description (e.g. "75 for
@@ -7054,6 +7784,7 @@ z$1.enum([
7054
7784
  "serverStatus",
7055
7785
  "defaultSeats",
7056
7786
  "CSMandCTAFlag",
7787
+ "showDesktopAppPromo",
7057
7788
  "SystemFiles",
7058
7789
  "OpenWeatherKey",
7059
7790
  "SerperKey",
@@ -7069,6 +7800,9 @@ z$1.enum([
7069
7800
  "MaxActiveDungeons",
7070
7801
  "DungeonSpawnIntervalHeartbeats",
7071
7802
  "DungeonTTLMinutes",
7803
+ "EnableStudioCreateV2",
7804
+ "EnableStudioImage",
7805
+ "EnableStudioProductions",
7072
7806
  "VectorThreshold",
7073
7807
  "bflApiKey",
7074
7808
  "EnableMCPServer",
@@ -7102,10 +7836,6 @@ z$1.enum([
7102
7836
  "teamPlanCreditsPerSeat",
7103
7837
  "lowCreditsThreshold",
7104
7838
  "syntheticUserEmailDomains",
7105
- "enableGoogleCalendar",
7106
- "googleCalendarServiceAccountEmail",
7107
- "googleCalendarServiceAccountSecret",
7108
- "googleCalendarOrganizerEmail",
7109
7839
  "EnableWeatherService",
7110
7840
  "WeatherUnits",
7111
7841
  "defaultEmbeddingModel",
@@ -7151,6 +7881,9 @@ z$1.enum([
7151
7881
  "SlackFeedbackWebhookUrl",
7152
7882
  "SlackNonProdFeedbackWebhookUrl",
7153
7883
  "SlackEmailAuditWebhookUrl",
7884
+ "SlackSecurityAlertsWebhookUrl",
7885
+ "AdminAuditCriticalGrantCredits",
7886
+ "AdminAuditDailyGrantCredits",
7154
7887
  "slackSigningSecret",
7155
7888
  "slackBotToken",
7156
7889
  "enforceMFA",
@@ -7395,15 +8128,17 @@ function makeNumberSetting(config) {
7395
8128
  schema: z$1.preprocess((val) => isBlankSettingValue(val) ? void 0 : val, numberSchema.prefault(config.defaultValue ?? 0))
7396
8129
  };
7397
8130
  }
7398
- function makeBooleanSetting(config) {
8131
+ function makeBooleanSetting({ acceptNumeric = false, ...config }) {
7399
8132
  return {
7400
8133
  ...config,
7401
8134
  type: "boolean",
7402
8135
  schema: z$1.preprocess((val) => {
7403
8136
  if (typeof val === "string") {
7404
- if (val.toLowerCase() === "true") return true;
7405
- if (val.toLowerCase() === "false") return false;
8137
+ if (val.toLowerCase() === "true" || acceptNumeric && val === "1") return true;
8138
+ if (val.toLowerCase() === "false" || acceptNumeric && val === "0") return false;
7406
8139
  }
8140
+ if (acceptNumeric && val === 1) return true;
8141
+ if (acceptNumeric && val === 0) return false;
7407
8142
  return val;
7408
8143
  }, z$1.boolean()).prefault(config.defaultValue ?? false)
7409
8144
  };
@@ -8345,30 +9080,6 @@ const API_SERVICE_GROUPS = {
8345
9080
  }
8346
9081
  ]
8347
9082
  },
8348
- CALENDAR: {
8349
- id: "calendarAPIService",
8350
- name: "Google Calendar",
8351
- description: "Google Calendar integration settings",
8352
- icon: "CalendarMonth",
8353
- settings: [
8354
- {
8355
- key: "enableGoogleCalendar",
8356
- order: 1
8357
- },
8358
- {
8359
- key: "googleCalendarServiceAccountEmail",
8360
- order: 2
8361
- },
8362
- {
8363
- key: "googleCalendarServiceAccountSecret",
8364
- order: 3
8365
- },
8366
- {
8367
- key: "googleCalendarOrganizerEmail",
8368
- order: 4
8369
- }
8370
- ]
8371
- },
8372
9083
  FEEDBACK: {
8373
9084
  id: "feedbackService",
8374
9085
  name: "Feedback System",
@@ -8690,6 +9401,18 @@ const API_SERVICE_GROUPS = {
8690
9401
  {
8691
9402
  key: "DungeonTTLMinutes",
8692
9403
  order: 293
9404
+ },
9405
+ {
9406
+ key: "EnableStudioCreateV2",
9407
+ order: 300
9408
+ },
9409
+ {
9410
+ key: "EnableStudioImage",
9411
+ order: 301
9412
+ },
9413
+ {
9414
+ key: "EnableStudioProductions",
9415
+ order: 302
8693
9416
  }
8694
9417
  ]
8695
9418
  },
@@ -8728,10 +9451,6 @@ const API_SERVICE_GROUPS = {
8728
9451
  {
8729
9452
  key: "defaultTags",
8730
9453
  order: 5
8731
- },
8732
- {
8733
- key: "enforceCredits",
8734
- order: 6
8735
9454
  }
8736
9455
  ]
8737
9456
  },
@@ -8742,36 +9461,16 @@ const API_SERVICE_GROUPS = {
8742
9461
  icon: "Payments",
8743
9462
  settings: [
8744
9463
  {
8745
- key: "pricePerCredit",
9464
+ key: "billOperationalUsage",
8746
9465
  order: 1
8747
9466
  },
8748
9467
  {
8749
- key: "enforceCredits",
9468
+ key: "blockDisposableEmails",
8750
9469
  order: 2
8751
9470
  },
8752
9471
  {
8753
- key: "billOperationalUsage",
9472
+ key: "syntheticUserEmailDomains",
8754
9473
  order: 3
8755
- },
8756
- {
8757
- key: "enableTeamPlan",
8758
- order: 4
8759
- },
8760
- {
8761
- key: "teamPlanMinSeats",
8762
- order: 5
8763
- },
8764
- {
8765
- key: "teamPlanMaxSeats",
8766
- order: 6
8767
- },
8768
- {
8769
- key: "teamPlanCreditsPerSeat",
8770
- order: 7
8771
- },
8772
- {
8773
- key: "lowCreditsThreshold",
8774
- order: 8
8775
9474
  }
8776
9475
  ]
8777
9476
  },
@@ -8849,6 +9548,10 @@ const API_SERVICE_GROUPS = {
8849
9548
  {
8850
9549
  key: "RedditLink",
8851
9550
  order: 8
9551
+ },
9552
+ {
9553
+ key: "showDesktopAppPromo",
9554
+ order: 9
8852
9555
  }
8853
9556
  ]
8854
9557
  },
@@ -9544,7 +10247,7 @@ const settingsMap = {
9544
10247
  key: "SkillMarketUrlAllowlist",
9545
10248
  name: "Skill Market URL Allowlist",
9546
10249
  defaultValue: "",
9547
- description: "Comma-separated host names a Skill Market skill may reference without a finding (subdomains included). Any other URL holds a Public listing for review.",
10250
+ description: "Comma-separated host names a Skill Market skill may reference without a finding (subdomains included). Any other URL holds a listing for review at every visibility.",
9548
10251
  category: "AI Moderation"
9549
10252
  }),
9550
10253
  SkillMarketClassifierMinConfidence: makeNumberSetting({
@@ -9679,9 +10382,7 @@ const settingsMap = {
9679
10382
  name: "Price Per Credit",
9680
10383
  defaultValue: 50,
9681
10384
  description: `The price in USD of one credit bought pay-as-you-go. Cannot be set below ${formatUsdPerCredit(MIN_PRICE_PER_CREDIT_USD)}, the per-credit sale price including markup.`,
9682
- category: "Users",
9683
- group: API_SERVICE_GROUPS.CREDITS.id,
9684
- order: 1
10385
+ category: "Users"
9685
10386
  }),
9686
10387
  apiRateLimitFreePerMin: makeNumberSetting({
9687
10388
  key: "apiRateLimitFreePerMin",
@@ -9887,6 +10588,32 @@ const settingsMap = {
9887
10588
  order: 8,
9888
10589
  isSensitive: true
9889
10590
  }),
10591
+ SlackSecurityAlertsWebhookUrl: makeStringSetting({
10592
+ key: "SlackSecurityAlertsWebhookUrl",
10593
+ name: "Security Alerts Channel Webhook URL",
10594
+ defaultValue: "",
10595
+ description: "Incoming-webhook URL for the security Slack channel that receives critical Admin Audit Log entries and credit-grant bursts. Leave empty to turn alerts off; the Audit Log page then shows a banner. It never falls back to the default webhook.",
10596
+ category: "SecOps",
10597
+ isSensitive: true
10598
+ }),
10599
+ AdminAuditCriticalGrantCredits: makeNumberSetting({
10600
+ key: "AdminAuditCriticalGrantCredits",
10601
+ name: "Critical Single Credit Grant",
10602
+ defaultValue: ADMIN_AUDIT_DEFAULT_CRITICAL_GRANT_CREDITS,
10603
+ min: 1,
10604
+ int: true,
10605
+ description: "An admin credit grant above this many credits is recorded as critical in the Admin Audit Log and alerts the security channel.",
10606
+ category: "SecOps"
10607
+ }),
10608
+ AdminAuditDailyGrantCredits: makeNumberSetting({
10609
+ key: "AdminAuditDailyGrantCredits",
10610
+ name: "Critical Daily Credit Grants per Admin",
10611
+ defaultValue: ADMIN_AUDIT_DEFAULT_DAILY_GRANT_CREDITS,
10612
+ min: 1,
10613
+ int: true,
10614
+ description: "A grant that takes one admin past this many credits granted in the last 24 hours is recorded as critical in the Admin Audit Log and alerts the security channel.",
10615
+ category: "SecOps"
10616
+ }),
9890
10617
  liveFeedbackEmail: makeStringSetting({
9891
10618
  key: "liveFeedbackEmail",
9892
10619
  name: "Live Feedback Email",
@@ -10025,51 +10752,12 @@ const settingsMap = {
10025
10752
  description: "The default number of seats for new organizations.",
10026
10753
  category: "Users"
10027
10754
  }),
10028
- enableGoogleCalendar: makeBooleanSetting({
10029
- key: "enableGoogleCalendar",
10030
- name: "Enable Google Calendar Integration",
10031
- defaultValue: false,
10032
- description: "Whether to enable scheduling of briefing/advisory using google calendar",
10033
- category: "Calendar",
10034
- group: API_SERVICE_GROUPS.CALENDAR.id,
10035
- order: 1,
10036
- userReadable: true
10037
- }),
10038
- googleCalendarServiceAccountEmail: makeStringSetting({
10039
- key: "googleCalendarServiceAccountEmail",
10040
- name: "Service Account Email",
10041
- defaultValue: "",
10042
- description: "The service account email address that has access to google calendar API.",
10043
- category: "Calendar",
10044
- group: API_SERVICE_GROUPS.CALENDAR.id,
10045
- order: 2
10046
- }),
10047
- googleCalendarServiceAccountSecret: makeStringSetting({
10048
- key: "googleCalendarServiceAccountSecret",
10049
- name: "Service Account Secret",
10050
- defaultValue: "",
10051
- description: "The base64 encoded service account secret that is associated with the service account email.",
10052
- category: "Calendar",
10053
- group: API_SERVICE_GROUPS.CALENDAR.id,
10054
- order: 3,
10055
- isSensitive: true
10056
- }),
10057
- googleCalendarOrganizerEmail: makeStringSetting({
10058
- key: "googleCalendarOrganizerEmail",
10059
- name: "Organizer Email",
10060
- defaultValue: "",
10061
- description: "The organizer email for creating events.",
10062
- category: "Calendar",
10063
- group: API_SERVICE_GROUPS.CALENDAR.id,
10064
- order: 4
10065
- }),
10066
10755
  enforceCredits: makeBooleanSetting({
10067
10756
  key: "enforceCredits",
10068
10757
  userReadable: true,
10069
10758
  name: "Enforce Credits",
10070
10759
  defaultValue: process.env.B4M_SELF_HOST === "true" ? false : true,
10071
10760
  description: "Whether to enforce credits for users",
10072
- group: API_SERVICE_GROUPS.CREDITS.id,
10073
10761
  category: "Users"
10074
10762
  }),
10075
10763
  billOperationalUsage: makeBooleanSetting({
@@ -10081,12 +10769,12 @@ const settingsMap = {
10081
10769
  category: "Users"
10082
10770
  }),
10083
10771
  enableTeamPlan: makeBooleanSetting({
10772
+ acceptNumeric: true,
10084
10773
  key: "enableTeamPlan",
10085
10774
  userReadable: true,
10086
10775
  name: "Enable Team Plan",
10087
10776
  defaultValue: false,
10088
10777
  description: "Whether to enable team plans",
10089
- group: API_SERVICE_GROUPS.CREDITS.id,
10090
10778
  category: "Users"
10091
10779
  }),
10092
10780
  allowOpenRegistration: makeBooleanSetting({
@@ -10094,7 +10782,6 @@ const settingsMap = {
10094
10782
  name: "Allow Open Registration",
10095
10783
  defaultValue: false,
10096
10784
  description: "Master switch for self-serve signup. When OFF (default), a valid invite code is required to register. When ON, users may register without an invite code; the Default Free Credits grant is then applied after they verify their email (anti-spam — see Default Free Credits). Safe to enable: the pre-request credit reservation caps every free user at the credits they are granted.",
10097
- group: API_SERVICE_GROUPS.CREDITS.id,
10098
10785
  category: "Users"
10099
10786
  }),
10100
10787
  blockDisposableEmails: makeBooleanSetting({
@@ -10111,7 +10798,6 @@ const settingsMap = {
10111
10798
  name: "Default Free Credits",
10112
10799
  defaultValue: 0,
10113
10800
  description: "Base credits granted after email verification to a user who registers WITHOUT an invite code, including eligible partner signups when Allow Open Registration is OFF. Partner-rule credits are added on top of this amount. An unverified account receives no credits. A free user cannot spend beyond their total granted balance.",
10114
- group: API_SERVICE_GROUPS.CREDITS.id,
10115
10801
  category: "Users"
10116
10802
  }),
10117
10803
  teamPlanMinSeats: makeNumberSetting({
@@ -10123,7 +10809,6 @@ const settingsMap = {
10123
10809
  min: 1,
10124
10810
  max: TEAM_PLAN_SEATS_HARD_LIMIT,
10125
10811
  description: "Fewest seats a paid team subscription can be bought with. Enforced at checkout, on seat changes from Stripe and on admin conversion to paid. Admin grants may still go down to 1. If set above the maximum seats, the maximum wins.",
10126
- group: API_SERVICE_GROUPS.CREDITS.id,
10127
10812
  category: "Users"
10128
10813
  }),
10129
10814
  teamPlanMaxSeats: makeNumberSetting({
@@ -10135,7 +10820,6 @@ const settingsMap = {
10135
10820
  min: 1,
10136
10821
  max: TEAM_PLAN_SEATS_HARD_LIMIT,
10137
10822
  description: "Most seats any team can hold (owner + members + pending invites). Enforced at checkout, on every seat change, on admin grants and on the partner-signup auto-raise. Lowering it below a team that is already larger does not remove anyone; that team can only be set down to this cap.",
10138
- group: API_SERVICE_GROUPS.CREDITS.id,
10139
10823
  category: "Users"
10140
10824
  }),
10141
10825
  teamPlanCreditsPerSeat: makeNumberSetting({
@@ -10146,7 +10830,6 @@ const settingsMap = {
10146
10830
  min: 0,
10147
10831
  max: 1e8,
10148
10832
  description: "Credits added to a team pool per paid seat each billing cycle (and per seat-month on admin team grants). Applies to invoices issued after the change; credits already granted are not adjusted.",
10149
- group: API_SERVICE_GROUPS.CREDITS.id,
10150
10833
  category: "Users",
10151
10834
  userReadable: true
10152
10835
  }),
@@ -10159,7 +10842,6 @@ const settingsMap = {
10159
10842
  min: 0,
10160
10843
  max: 1e8,
10161
10844
  description: "Below this many credits a user sees the low-credit warning state. Set to 0 to show warnings only once credits run out.",
10162
- group: API_SERVICE_GROUPS.CREDITS.id,
10163
10845
  category: "Users"
10164
10846
  }),
10165
10847
  syntheticUserEmailDomains: makeStringSetting({
@@ -10238,6 +10920,16 @@ const settingsMap = {
10238
10920
  group: API_SERVICE_GROUPS.USER_MANAGEMENT.id,
10239
10921
  order: 3
10240
10922
  }),
10923
+ showDesktopAppPromo: makeBooleanSetting({
10924
+ key: "showDesktopAppPromo",
10925
+ name: "Promote the Desktop App",
10926
+ defaultValue: true,
10927
+ description: "Show signed-in users the \"Try the desktop app\" button and the profile-menu link to the desktop download page. Turn off for deployments that do not want to point users at the published desktop builds.",
10928
+ category: "Branding",
10929
+ group: API_SERVICE_GROUPS.BRANDING.id,
10930
+ order: 9,
10931
+ userReadable: true
10932
+ }),
10241
10933
  SystemFiles: makeStringSetting({
10242
10934
  key: "SystemFiles",
10243
10935
  name: "System Prompt Files",
@@ -10330,6 +11022,35 @@ const settingsMap = {
10330
11022
  order: 4,
10331
11023
  isSensitive: true
10332
11024
  }),
11025
+ EnableStudioCreateV2: makeBooleanSetting({
11026
+ key: "EnableStudioCreateV2",
11027
+ name: "Enable Studio Create v2",
11028
+ defaultValue: false,
11029
+ description: "Admin-only preview of the restructured Studio Create area. Non-admins never see it, whatever this is set to.",
11030
+ category: "Experimental",
11031
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
11032
+ order: 300
11033
+ }),
11034
+ EnableStudioImage: makeBooleanSetting({
11035
+ key: "EnableStudioImage",
11036
+ name: "Enable Image in Studio",
11037
+ defaultValue: false,
11038
+ description: "Admin-only preview of the Image tab in Studio. Non-admins never see it, whatever this is set to.",
11039
+ category: "Experimental",
11040
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
11041
+ order: 301,
11042
+ dependsOn: "EnableStudioCreateV2"
11043
+ }),
11044
+ EnableStudioProductions: makeBooleanSetting({
11045
+ key: "EnableStudioProductions",
11046
+ name: "Enable Studio Productions",
11047
+ defaultValue: false,
11048
+ description: "Admin-only preview of Studio Productions (assembling many assets into one piece). Non-admins never see it, whatever this is set to.",
11049
+ category: "Experimental",
11050
+ group: API_SERVICE_GROUPS.EXPERIMENTAL.id,
11051
+ order: 302,
11052
+ dependsOn: "EnableStudioCreateV2"
11053
+ }),
10333
11054
  EnablePotionQuest: makeBooleanSetting({
10334
11055
  key: "EnablePotionQuest",
10335
11056
  name: "Enable PotionQuest Tools",
@@ -11998,6 +12719,41 @@ const musicRequestSchema = z.object({
11998
12719
  format: z.string().optional(),
11999
12720
  ...generatedAudioRequestFields
12000
12721
  });
12722
+ const turnRetryShape = {
12723
+ reason: z$1.enum(TURN_RETRY_REASONS),
12724
+ status: z$1.number().optional(),
12725
+ retryAfterMs: z$1.number().optional(),
12726
+ requestId: z$1.string().optional(),
12727
+ delayMs: z$1.number(),
12728
+ model: z$1.string()
12729
+ };
12730
+ const turnTimingShape = {
12731
+ queueMs: z$1.number(),
12732
+ contextBuildMs: z$1.number().optional(),
12733
+ semaphoreWaitMs: z$1.number(),
12734
+ totalMs: z$1.number().optional(),
12735
+ fallbackHops: z$1.number()
12736
+ };
12737
+ /** Wire shape of ITurnTiming for the live chat frame; hydrates `at` into a Date server-side. */
12738
+ const TurnTimingSchema = z$1.object({
12739
+ ...turnTimingShape,
12740
+ retries: z$1.array(z$1.object({
12741
+ ...turnRetryShape,
12742
+ at: z$1.coerce.date()
12743
+ }))
12744
+ });
12745
+ /**
12746
+ * The same record for the public quest poll. `at` is an ISO string on the wire (a Date only
12747
+ * before JSON serialization). Not z.coerce.date() here: like the other public response schemas
12748
+ * (schemas/chat.ts, schemas/session.ts), it would document the field as nullable in OpenAPI.
12749
+ */
12750
+ const TurnTimingPollSchema = z$1.object({
12751
+ ...turnTimingShape,
12752
+ retries: z$1.array(z$1.object({
12753
+ ...turnRetryShape,
12754
+ at: z$1.union([z$1.string().datetime(), z$1.date()])
12755
+ }))
12756
+ });
12001
12757
  /**
12002
12758
  * Canonical agent execution status tuple. Shared between the database model
12003
12759
  * (`AgentExecutionModel`), the client store (`useAgentExecutionStore`), and
@@ -12182,7 +12938,7 @@ const CliCompletionRequestAction = z$1.object({
12182
12938
  ]),
12183
12939
  content: z$1.union([z$1.string(), z$1.array(z$1.unknown())]),
12184
12940
  cache: z$1.boolean().optional()
12185
- })),
12941
+ })).min(1),
12186
12942
  options: z$1.object({
12187
12943
  temperature: z$1.number().optional(),
12188
12944
  maxTokens: z$1.number().optional(),
@@ -12517,6 +13273,7 @@ const StreamedChatCompletionAction = z$1.object({
12517
13273
  endTime: z$1.number().optional()
12518
13274
  }).optional(),
12519
13275
  fallbackInfo: FallbackInfoSchema.nullish(),
13276
+ turnTiming: TurnTimingSchema.optional(),
12520
13277
  pendingAction: z$1.object({
12521
13278
  tool: z$1.string(),
12522
13279
  params: z$1.record(z$1.string(), z$1.unknown()),
@@ -13476,6 +14233,16 @@ const PermissionRequestAction = z$1.object({
13476
14233
  */
13477
14234
  toolCallId: z$1.string().optional()
13478
14235
  });
14236
+ const ConfidenceGateAction = z$1.object({
14237
+ action: z$1.literal("confidence_gate"),
14238
+ executionId: z$1.string(),
14239
+ /** 0-indexed wire convention, matching `permission_request` and `IterationStream`. */
14240
+ iteration: z$1.number(),
14241
+ /** 0..1, the agent's reported confidence for the paused iteration. */
14242
+ confidence: z$1.number(),
14243
+ /** Pre-formatted server copy, e.g. "2 iterations in a row below the 60% confidence threshold (latest 42%)". */
14244
+ reason: z$1.string()
14245
+ });
13479
14246
  const ChildExecutionSnapshotSchema = z$1.lazy(() => z$1.object({
13480
14247
  executionId: z$1.string(),
13481
14248
  agentName: z$1.string(),
@@ -13490,6 +14257,7 @@ const ChildExecutionSnapshotSchema = z$1.lazy(() => z$1.object({
13490
14257
  }));
13491
14258
  const ReconnectResultAction = z$1.object({
13492
14259
  action: z$1.literal("reconnect_result"),
14260
+ requestId: z$1.string().optional(),
13493
14261
  found: z$1.boolean(),
13494
14262
  executionId: z$1.string().optional(),
13495
14263
  sessionId: z$1.string().optional(),
@@ -13500,6 +14268,13 @@ const ReconnectResultAction = z$1.object({
13500
14268
  requestedAt: z$1.union([z$1.string(), z$1.date()]),
13501
14269
  toolCallId: z$1.string().optional()
13502
14270
  }).optional(),
14271
+ pendingGate: z$1.object({
14272
+ /** 0-indexed wire convention, matching `ConfidenceGateAction` and `IterationStream`. */
14273
+ iteration: z$1.number(),
14274
+ confidence: z$1.number(),
14275
+ reason: z$1.string(),
14276
+ requestedAt: z$1.union([z$1.string(), z$1.date()])
14277
+ }).optional(),
13503
14278
  totalCreditsUsed: z$1.number().optional(),
13504
14279
  iterationCount: z$1.number().optional(),
13505
14280
  steps: z$1.array(AgentStepSchema).optional(),
@@ -13591,6 +14366,7 @@ z$1.discriminatedUnion("action", [
13591
14366
  SubagentFailedAction,
13592
14367
  SubagentProgressAction,
13593
14368
  PermissionRequestAction,
14369
+ ConfidenceGateAction,
13594
14370
  ReconnectResultAction
13595
14371
  ]);
13596
14372
  const FLEET_USAGE_MAX_ROWS = 1e4;
@@ -13708,11 +14484,7 @@ const AgentExecutionStartRequestSchema = z$1.object({
13708
14484
  * is what reproduces the product UI's Agent Mode toggle.
13709
14485
  */
13710
14486
  agent_id: z$1.string().optional(),
13711
- /**
13712
- * Bill this run to an organization's credit pool. The caller must belong to it; a
13713
- * non-member gets 404. Omit to bill the caller personally.
13714
- */
13715
- organization_id: z$1.string().optional(),
14487
+ organization_id: z$1.string().optional().describe("The organization whose credit pool pays for this run. You must belong to it; a non-member gets 404. Omit it to bill your personal credits. An organization-billed API key always bills its own organization: omit this or set it to the key's; any other value is a 403 (`errorCode: billing_organization_mismatch`)."),
13716
14488
  /**
13717
14489
  * Tool-id allowlist for the run. Omit to use the resolved profile's own list.
13718
14490
  *
@@ -13839,7 +14611,8 @@ const ToolExecutionRequestSchema = z$1.object({
13839
14611
  toolName: z$1.enum([
13840
14612
  "weather_info",
13841
14613
  "web_search",
13842
- "web_fetch"
14614
+ "web_fetch",
14615
+ "decide"
13843
14616
  ]),
13844
14617
  input: z$1.record(z$1.string(), z$1.unknown())
13845
14618
  });
@@ -14376,7 +15149,9 @@ z.object({
14376
15149
  /** Set to true if the file should be publicly accessible */
14377
15150
  public: z.boolean().optional(),
14378
15151
  /** The prefix to use for the file path */
14379
- prefix: z.string().optional()
15152
+ prefix: z.string().optional(),
15153
+ /** Admin What's New media: the server stores it under its modal prefix and admits mp4 */
15154
+ purpose: z.literal("modal-media").optional()
14380
15155
  });
14381
15156
  /**
14382
15157
  * Constants for BFL safety tolerance settings.
@@ -14978,6 +15753,7 @@ const QuestPollResponseSchema = ChatQuestPollResultSchema.extend({
14978
15753
  promptMeta: z$1.record(z$1.string(), z$1.unknown()).optional(),
14979
15754
  attachmentNotices: z$1.array(z$1.string()).optional(),
14980
15755
  attachmentDelivery: z$1.record(z$1.string(), z$1.unknown()).optional(),
15756
+ turnTiming: TurnTimingPollSchema.optional(),
14981
15757
  executionTracking: z$1.unknown().optional()
14982
15758
  });
14983
15759
  /**
@@ -15208,6 +15984,7 @@ z$2.object(passthroughShape({
15208
15984
  pendingAction: true,
15209
15985
  attachmentNotices: true,
15210
15986
  attachmentDelivery: true,
15987
+ turnTiming: true,
15211
15988
  navigationIntents: true,
15212
15989
  suggestedChoices: true,
15213
15990
  attachmentList: true,
@@ -15255,6 +16032,123 @@ z$2.object({
15255
16032
  envVariables: envVariableResponseSchema,
15256
16033
  metadata: metadataResponseSchema.nullish()
15257
16034
  });
16035
+ const SAML_CONFIG_RESPONSE_FIELDS = {
16036
+ entryPoint: true,
16037
+ issuer: true,
16038
+ cert: true,
16039
+ callbackUrl: true,
16040
+ identifierFormat: true,
16041
+ acceptedClockSkewMs: true,
16042
+ attributeConsumingServiceIndex: true,
16043
+ disableRequestedAuthnContext: true,
16044
+ attributeMappings: true
16045
+ };
16046
+ const OKTA_CONFIG_RESPONSE_FIELDS = {
16047
+ audience: true,
16048
+ clientId: true,
16049
+ authServerId: true,
16050
+ useOrgAuthServer: true
16051
+ };
16052
+ /** An identity provider as the admin routes return it: configuration only, no credential values. */
16053
+ const identityProviderResponseSchema = z$2.object({
16054
+ ...passthroughShape({
16055
+ id: true,
16056
+ name: true,
16057
+ emailDomain: true,
16058
+ type: true,
16059
+ isActive: true,
16060
+ samlConfig: true,
16061
+ oktaConfig: true,
16062
+ createdAt: true,
16063
+ updatedAt: true,
16064
+ createdBy: true
16065
+ }),
16066
+ samlConfig: z$2.object(passthroughShape(SAML_CONFIG_RESPONSE_FIELDS)).nullish(),
16067
+ oktaConfig: z$2.object(passthroughShape(OKTA_CONFIG_RESPONSE_FIELDS)).nullish()
16068
+ });
16069
+ /**
16070
+ * Which credentials an identity provider has stored: flags, never values. The single source for
16071
+ * the repository that computes them and the admin UI that reads them.
16072
+ */
16073
+ const identityProviderSecretPresenceSchema = z$2.object({
16074
+ decryptionPvk: z$2.boolean(),
16075
+ privateCert: z$2.boolean(),
16076
+ clientSecret: z$2.boolean()
16077
+ });
16078
+ /** The admin list row: the provider plus which credentials are stored. */
16079
+ const identityProviderListItemResponseSchema = identityProviderResponseSchema.extend({ hasSecrets: identityProviderSecretPresenceSchema });
16080
+ z$2.array(identityProviderListItemResponseSchema);
16081
+ /** An API key as the admin routes return it: everything but the secret material. */
16082
+ const userApiKeyResponseSchema = z$2.object(passthroughShape({
16083
+ id: true,
16084
+ userId: true,
16085
+ name: true,
16086
+ keyPrefix: true,
16087
+ scopes: true,
16088
+ status: true,
16089
+ expiresAt: true,
16090
+ lastUsedAt: true,
16091
+ revokedAt: true,
16092
+ revokedBy: true,
16093
+ revokedReason: true,
16094
+ callbackSigningSecretCreatedAt: true,
16095
+ rateLimit: true,
16096
+ usage: true,
16097
+ metadata: true,
16098
+ productId: true,
16099
+ productName: true,
16100
+ billingOwnerType: true,
16101
+ organizationId: true,
16102
+ agentId: true,
16103
+ allowedOrigins: true,
16104
+ identifiedClientIds: true,
16105
+ preauthorizedLakeIds: true,
16106
+ branding: true,
16107
+ spendCap: true,
16108
+ ownerHasWhitelabel: true,
16109
+ createdAt: true,
16110
+ updatedAt: true
16111
+ }));
16112
+ /**
16113
+ * A key's live minute/day rate-limit counters. The single source for the server's
16114
+ * `RateLimitUsage` and the admin client's response type, so a new field cannot be stripped here
16115
+ * while the producer and the UI both expect it.
16116
+ */
16117
+ const apiKeyRateLimitUsageSchema = z$2.object({
16118
+ minute: z$2.number(),
16119
+ day: z$2.number(),
16120
+ /** Epoch seconds when the current minute window ends; undefined when no window is open (count 0). */
16121
+ minuteResetAt: z$2.number().optional(),
16122
+ /** Epoch seconds when the current day window ends; undefined when no window is open (count 0). */
16123
+ dayResetAt: z$2.number().optional()
16124
+ });
16125
+ z$2.object({
16126
+ apiKeys: z$2.array(userApiKeyResponseSchema),
16127
+ liveUsage: z$2.record(z$2.string(), apiKeyRateLimitUsageSchema)
16128
+ });
16129
+ const emailVerificationUserResponseSchema = z$2.object(passthroughShape({
16130
+ _id: true,
16131
+ username: true,
16132
+ email: true,
16133
+ name: true,
16134
+ emailVerified: true,
16135
+ emailVerifiedAt: true,
16136
+ emailVerificationSentAt: true,
16137
+ emailVerificationExpires: true,
16138
+ pendingEmail: true,
16139
+ pendingEmailSentAt: true,
16140
+ pendingEmailExpires: true,
16141
+ createdAt: true
16142
+ }));
16143
+ z$2.object({
16144
+ users: z$2.array(emailVerificationUserResponseSchema),
16145
+ pagination: z$2.object({
16146
+ page: z$2.number(),
16147
+ limit: z$2.number(),
16148
+ totalPages: z$2.number(),
16149
+ totalCount: z$2.number()
16150
+ })
16151
+ });
15258
16152
  const questMasterPlanResponseSchema = z$2.object(passthroughShape({
15259
16153
  id: true,
15260
16154
  createdAt: true,
@@ -15281,6 +16175,86 @@ z$2.object({
15281
16175
  success: z$2.literal(true),
15282
16176
  plan: questMasterPlanResponseSchema
15283
16177
  }).extend({ metrics: passthroughField });
16178
+ /** A lake document as returned by the SPA data-lake routes (editor document or reader projection). */
16179
+ const dataLakeResponseSchema = z$2.object(passthroughShape({
16180
+ id: true,
16181
+ createdAt: true,
16182
+ updatedAt: true,
16183
+ name: true,
16184
+ slug: true,
16185
+ description: true,
16186
+ systemPrompt: true,
16187
+ injectPromptForReaders: true,
16188
+ preferredSystemPromptId: true,
16189
+ groundingMode: true,
16190
+ requiredPassageTokenTarget: true,
16191
+ inconsistencyReport: true,
16192
+ inconsistencyComputedAt: true,
16193
+ lastInconsistencyScanAt: true,
16194
+ fileTagPrefix: true,
16195
+ datalakeTag: true,
16196
+ requiredUserTag: true,
16197
+ requiredEntitlement: true,
16198
+ createdByUserId: true,
16199
+ lastUpdatedByUserId: true,
16200
+ organizationId: true,
16201
+ isPublic: true,
16202
+ auditQueryTextEnabled: true,
16203
+ status: true,
16204
+ fileCount: true,
16205
+ totalSizeBytes: true,
16206
+ totalChunkedChars: true,
16207
+ embeddingSpendMicroUsd: true,
16208
+ lastSyncAt: true,
16209
+ lastHealthCheckedAt: true,
16210
+ filesDeletedAt: true,
16211
+ filesArchivedAt: true,
16212
+ purgeClaimId: true,
16213
+ purgeStartedAt: true,
16214
+ lakeMemoryEnabled: true,
16215
+ lakeMemoryExtractionAt: true,
16216
+ lakeMemoryCursor: true,
16217
+ lakeMemoryPurgedAt: true,
16218
+ origin: true,
16219
+ pendingConnector: true,
16220
+ modelInconsistencyRunAt: true
16221
+ }));
16222
+ z$2.object({ data: z$2.array(dataLakeResponseSchema) });
16223
+ z$2.object({ data: z$2.array(z$2.object(passthroughShape({
16224
+ id: true,
16225
+ slug: true,
16226
+ name: true,
16227
+ requiredUserTag: true,
16228
+ requiredEntitlement: true,
16229
+ fileTagPrefix: true,
16230
+ datalakeTag: true,
16231
+ organizationId: true,
16232
+ description: true,
16233
+ isPublic: true,
16234
+ lakeMemoryEnabled: true,
16235
+ injectPromptForReaders: true,
16236
+ canManage: true,
16237
+ status: true,
16238
+ origin: true,
16239
+ systemPrompt: true,
16240
+ requiredPassageTokenTarget: true,
16241
+ isOwn: true,
16242
+ isCreator: true,
16243
+ canPreauthorize: true,
16244
+ ownerDisplayName: true,
16245
+ ownerUserId: true,
16246
+ ownerUsername: true,
16247
+ preferredSystemPromptId: true,
16248
+ pendingProposalCount: true,
16249
+ groundingMode: true,
16250
+ pendingConnector: true,
16251
+ hasGitHubConnection: true,
16252
+ embeddingSpendMicroUsd: true,
16253
+ canRebuild: true,
16254
+ canManageSettings: true,
16255
+ canManageMemory: true,
16256
+ retrievable: true
16257
+ }))) });
15284
16258
  z$1.preprocess((val) => {
15285
16259
  if (typeof val === "string") {
15286
16260
  if (val.toLowerCase() === "true") return true;
@@ -15625,6 +16599,14 @@ const EmbeddingsErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.enum([
15625
16599
  "provider_not_configured",
15626
16600
  "provider_rejected"
15627
16601
  ]).optional() });
16602
+ const MAX_REFINE_TEXT_CHARS = 1e4;
16603
+ const MAX_REFINE_CONTEXT_CHARS = 1e4;
16604
+ const TextRefinementRequestSchema = z$1.object({
16605
+ text: z$1.string().min(1).max(MAX_REFINE_TEXT_CHARS),
16606
+ context: z$1.string().max(MAX_REFINE_CONTEXT_CHARS).optional()
16607
+ });
16608
+ const TextRefinementResponseSchema = z$1.object({ text: z$1.string() });
16609
+ const TextRefinementErrorSchema = ApiErrorSchema.extend({ errorCode: z$1.enum(["provider_not_configured", "provider_rejected"]).optional() });
15628
16610
  /**
15629
16611
  * Every tool name in `b4mLLMTools`. Exhaustive by annotation - do not widen the type.
15630
16612
  */
@@ -15652,6 +16634,7 @@ const CORE_TOOL_SIDE_EFFECTS = {
15652
16634
  fmp_financial_data: "none",
15653
16635
  bob_panel_read: "none",
15654
16636
  skill: "none",
16637
+ decide: "none",
15655
16638
  recharts: "local",
15656
16639
  mermaid_chart: "local",
15657
16640
  navigate_view: "local",
@@ -15725,6 +16708,19 @@ const InternalTeamMemberSchema = z$1.object({
15725
16708
  isActive: z$1.boolean().optional()
15726
16709
  });
15727
16710
  z$1.array(InternalTeamMemberSchema);
16711
+ /** An internal team member as the admin routes return it. */
16712
+ const internalTeamMemberResponseSchema = z$1.object(passthroughShape({
16713
+ id: true,
16714
+ name: true,
16715
+ phone: true,
16716
+ email: true,
16717
+ role: true,
16718
+ department: true,
16719
+ isActive: true,
16720
+ createdAt: true,
16721
+ updatedAt: true
16722
+ }));
16723
+ z$1.array(internalTeamMemberResponseSchema);
15728
16724
  /**
15729
16725
  * Lattice Zod Schemas
15730
16726
  *
@@ -16511,6 +17507,32 @@ const UpdateProjectRequestSchema = z$1.object({
16511
17507
  description: z$1.string().min(1).optional()
16512
17508
  }).strict();
16513
17509
  /**
17510
+ * Wire schemas for the Skill Market's Claude Code plugin feed
17511
+ * (api-contract/contracts/skillMarket.contract.ts). The 200 body is a Claude Code
17512
+ * `marketplace.json`, so its field names follow Claude Code, not this API's snake_case.
17513
+ */
17514
+ const ClaudeMarketplaceQuerySchema = z$1.object({ orgId: z$1.string().min(1) });
17515
+ const ClaudePluginZipParamSchema = z$1.object({ id: z$1.string().min(1) });
17516
+ const ClaudePluginZipQuerySchema = z$1.object({ version: z$1.coerce.number().int().positive().optional() });
17517
+ const ClaudeMarketplacePluginSchema = z$1.object({
17518
+ name: z$1.string(),
17519
+ description: z$1.string(),
17520
+ /** The listing's approved version number; Claude Code offers an update when it changes. */
17521
+ version: z$1.string(),
17522
+ source: z$1.object({
17523
+ source: z$1.literal("archive"),
17524
+ url: z$1.string(),
17525
+ sha256: z$1.string()
17526
+ })
17527
+ });
17528
+ const ClaudeMarketplaceSchema = z$1.object({
17529
+ name: z$1.string(),
17530
+ owner: z$1.object({ name: z$1.string() }),
17531
+ /** Claude Code uninstalls installed plugins this feed stops listing, at the next session start. */
17532
+ forceRemoveDeletedPlugins: z$1.boolean(),
17533
+ plugins: z$1.array(ClaudeMarketplacePluginSchema)
17534
+ });
17535
+ /**
16514
17536
  * The public projection of `ModelInfo` served by `GET /api/v1/models`.
16515
17537
  *
16516
17538
  * Deliberately narrower than `ModelInfo`: every field here is one an integrator can pin, so
@@ -16590,8 +17612,8 @@ const ListModelsResponseSchema = paginatedResponseSchema(z$1.object({
16590
17612
  reasons_within_output_budget: z$1.boolean().describe("Whether reasoning tokens are spent inside `max_output_tokens` on every turn. When true, a small `max_tokens` can be used up by reasoning alone and return an empty reply, so leave room above the answer you expect."),
16591
17613
  reasoning_efforts: z$1.array(CompletionReasoningEffortSchema).describe("The `reasoningEffort` levels this model acts on, lowest first; empty when it ignores the option. `none` is listed only where it turns reasoning off. Each level is mapped to the nearest one the provider offers. A listed level can still be dropped on one request: some OpenAI models drop it on tool turns, and Claude models with a fixed thinking budget drop it when `max_tokens` leaves no room for the budget plus the answer."),
16592
17614
  supports_tools: z$1.boolean(),
16593
- supports_tool_search: z$1.boolean().describe("Whether the model accepts the tool search server tool and `defer_loading: true` on tool definitions, so a large tool catalog is searched on demand instead of loaded into context up front. Describes the model: `/api/ai/v1/completions` honours it only for models whose `backend` is `anthropic`. A `bedrock` model with this flag set is not served with tool search there yet: a `serverTool` entry is refused with `errorCode: \"tool_search_unsupported\"` and `defer_loading` is ignored."),
16594
- supports_tool_changes: z$1.boolean().describe("Whether a system-role message can carry `tool_addition` and `tool_removal` blocks, which add or remove a declared tool mid-conversation. Describes the model only: `/api/ai/v1/completions` does not accept these blocks yet, and will accept them first for models whose `backend` is `anthropic`."),
17615
+ supports_tool_search: z$1.boolean().describe("Whether the model accepts the tool search server tool and `defer_loading: true` on tool definitions, so a large tool catalog is searched on demand instead of loaded into context up front. Describes the model: `/api/ai/v1/completions` honours it for every model with this flag set, whether its `backend` is `anthropic` or `bedrock`."),
17616
+ supports_tool_changes: z$1.boolean().describe("Whether a system-role message can carry `tool_addition` and `tool_removal` blocks, which add or remove a declared tool mid-conversation. Describes the model: `/api/ai/v1/completions` honours it only for models whose `backend` is `anthropic`; a `bedrock` model with this flag set is refused there with `errorCode: \"tool_changes_unsupported\"`."),
16595
17617
  supports_vision: z$1.boolean(),
16596
17618
  deprecation_date: z$1.string().nullable().describe("`YYYY-MM-DD`; null when no retirement is scheduled."),
16597
17619
  replaced_by: z$1.string().nullable().describe("The id of the recommended successor, when there is one."),
@@ -16717,7 +17739,7 @@ const GenerateImageIvokeParamsSchema = OpenAIImageGenerationInput.extend({
16717
17739
  referenceImageFabFileIds: z$1.array(z$1.string()).max(15).optional(),
16718
17740
  tools: z$1.array(z$1.union([b4mLLMTools, z$1.string()])).optional(),
16719
17741
  safety_tolerance: BFLSafetyToleranceSchema,
16720
- prompt_upsampling: z$1.boolean().optional(),
17742
+ prompt_upsampling: z$1.boolean().prefault(false).describe("Let the provider rewrite and expand the prompt before rendering. Applies to Black Forest Labs (FLUX) models only; other models ignore it. Defaults to false."),
16721
17743
  seed: z$1.number().nullable().optional(),
16722
17744
  output_format: ImageOutputFormatSchema.nullable().optional(),
16723
17745
  /** Resolved by the API route's prompt resolver. Defaults to 'fresh' for first-turn or sessions with no prior image. */
@@ -16847,7 +17869,13 @@ z$1.object({
16847
17869
  "tool"
16848
17870
  ]),
16849
17871
  content: z$1.union([z$1.string(), z$1.array(z$1.any())]),
16850
- fabFileIds: z$1.array(z$1.string()).optional()
17872
+ fabFileIds: z$1.array(z$1.string()).optional(),
17873
+ /**
17874
+ * Send this message in the current user turn instead of the system prompt, keeping the
17875
+ * system prefix byte-stable for prompt caching. Only for content that changes every turn
17876
+ * (a view description, a per-send brief); a caller's actual system prompt must not set it.
17877
+ */
17878
+ perTurn: z$1.boolean().optional()
16851
17879
  })).optional(),
16852
17880
  /** Dashboard related params */
16853
17881
  dashboardParams: DashboardParamsSchema.optional(),
@@ -17036,11 +18064,15 @@ const VideoGenerationErrorResponseSchema = ApiErrorSchema.extend({ errorCode: z$
17036
18064
  const CreateVideoGenerationBodySchema = z$1.strictObject({
17037
18065
  model: z$1.string().min(1).describe("A model id from GET /api/v1/video-models."),
17038
18066
  prompt: z$1.string().min(1).max(4e3),
17039
- mode: z$1.enum(VIDEO_MODES).optional().describe("Defaults to image_to_video when input_image_file_id is set, else text_to_video."),
18067
+ mode: z$1.enum(VIDEO_MODES).optional().describe("Defaults to image_to_video when a first_frame input (or input_image_file_id) is set, else text_to_video."),
17040
18068
  duration_seconds: z$1.number().positive().optional().describe("Defaults to the model's default duration."),
17041
18069
  aspect_ratio: z$1.enum(ASPECT_RATIOS).optional(),
17042
18070
  resolution: z$1.enum(RESOLUTION_TIERS).optional(),
17043
- input_image_file_id: z$1.string().min(1).optional().describe("An image file you own, for image_to_video."),
18071
+ inputs: z$1.array(z$1.strictObject({
18072
+ file_id: z$1.string().min(1),
18073
+ role: z$1.enum(VIDEO_INPUT_ROLES)
18074
+ })).max(8).optional().describe("Image files you own, each tagged with what it is for. image_to_video needs a first_frame. The roles a model accepts, and how many of each, are its input_roles in GET /api/v1/video-models."),
18075
+ input_image_file_id: z$1.string().min(1).optional().describe("Deprecated: send inputs with role first_frame instead. Cannot be combined with inputs."),
17044
18076
  audio: z$1.boolean().optional(),
17045
18077
  organization_id: BillingOrganizationIdSchema
17046
18078
  });
@@ -17082,6 +18114,10 @@ const VideoModelSchema = z$1.object({
17082
18114
  display_name: z$1.string(),
17083
18115
  provider: z$1.string(),
17084
18116
  modes: z$1.array(z$1.enum(VIDEO_MODES)),
18117
+ input_roles: z$1.array(z$1.object({
18118
+ role: z$1.string(),
18119
+ max: z$1.number().int().positive()
18120
+ })).optional().describe(`The inputs[] roles this model accepts and the most of each one request may carry. Roles today: ${VIDEO_INPUT_ROLES.join(", ")}; ignore any you do not know.`),
17085
18121
  duration: z$1.union([z$1.object({
17086
18122
  kind: z$1.literal("range"),
17087
18123
  min: z$1.number(),
@@ -17388,9 +18424,13 @@ defineEndpoint({
17388
18424
  schema: AgentExecutionAckSchema
17389
18425
  },
17390
18426
  400: {
17391
- description: "No usable default chat model is configured and none was supplied.",
18427
+ description: `No usable default chat model is configured and none was supplied. ${BILLING_ORGANIZATION_STALE_KEY_DESCRIPTION}`,
17392
18428
  schema: ApiErrorSchema
17393
18429
  },
18430
+ 403: {
18431
+ description: `The API key holds neither the \`ai:chat\` nor the \`ai:generate\` scope, or an organization-billed API key names any organization but its own (\`errorCode: ${BILLING_ORGANIZATION_MISMATCH_ERROR_CODE}\`).`,
18432
+ schema: BillingOrganizationForbiddenErrorSchema
18433
+ },
17394
18434
  404: {
17395
18435
  description: "No session with the given `session_id` is owned by the caller, or `organization_id` names an organization the caller does not belong to. \"Exists but is not yours\" is reported as 404 too, so neither session ids nor org membership can be probed through this endpoint.",
17396
18436
  schema: ApiErrorSchema
@@ -17600,7 +18640,7 @@ defineEndpoint({
17600
18640
  path: "/api/ai/v1/tools",
17601
18641
  operationId: "executeTool",
17602
18642
  summary: "Execute a server-side tool",
17603
- description: "Runs one of the built-in server-side tools (`weather_info`, `web_search`, `web_fetch`) and returns its result as JSON. Authenticate with a JWT access token only - API keys are NOT accepted on this endpoint. Rate-limited to 100 requests/hour per user, counted separately from other endpoints. `request_id` echoes the X-Request-ID response header.",
18643
+ description: "Runs one of the built-in server-side tools (`weather_info`, `web_search`, `web_fetch`, `decide`) and returns its result as JSON. Authenticate with a JWT access token only - API keys are NOT accepted on this endpoint. Rate-limited to 100 requests/hour per user, counted separately from other endpoints. `request_id` echoes the X-Request-ID response header.",
17604
18644
  tags: ["AI"],
17605
18645
  auth: "jwtOnly",
17606
18646
  request: ToolExecutionRequestSchema,
@@ -17655,7 +18695,7 @@ defineEndpoint({
17655
18695
  path: "/api/ai/v1/completions",
17656
18696
  operationId: "createCompletion",
17657
18697
  summary: "Create a chat completion",
17658
- description: "OpenAI-compatible completion. A successful request is ALWAYS answered with an SSE stream (`text/event-stream`), regardless of the `stream` flag: a `meta` event, then `content`/`tool_use` events carrying `usage`/`credits`, terminated by `data: [DONE]`. The terminal event carries `stopReason`; treat `max_tokens` as a TRUNCATED reply rather than a complete one, and note that omitting `max_tokens` on the request lets the server size the output ceiling for the model (recommended for reasoning models, which spend thinking tokens inside that ceiling). A message `content` may be a string or an array of parts; image parts are accepted in OpenAI Chat (`image_url`), OpenAI Responses (`input_image`) or Anthropic (`image` with a `source`) form and are translated to whatever the target model speaks. Authenticate with an API key (`b4m_live_`) or a JWT.\n\nTOOL SEARCH. On a model whose `GET /api/v1/models` row has `supports_tool_search: true` and `backend: \"anthropic\"`, a `tools` entry may set `toolSchema.defer_loading: true`, and a `{ \"serverTool\": { \"type\": \"tool_search_tool_regex_20251119\", \"name\": \"tool_search_tool_regex\" } }` (or the `bm25` pair) entry adds Anthropic's tool search, so deferred definitions load only when the model searches for them. The stream then carries `provider_block` events (`{ type, provider: \"anthropic\", block }`) for the server-run search; echo each block back verbatim, in order, in the assistant `messages` content of the next request, and never answer one with a `tool_result`. When the server-run search loop hits its iteration limit the stream ends with `stopReason: \"pause_turn\"`; continue by re-sending the conversation with that assistant turn's provider blocks echoed. A client-side search may return `{ \"type\": \"tool_reference\", \"tool_name\": \"...\" }` blocks inside a `tool_result`'s content. On every other model, Bedrock-served Claude included, `defer_loading` is ignored (every tool is sent as usual) and a server tool, a `tool_reference` or an echoed tool search block is refused with a `400` rather than run without it.\n\nERRORS. Everything checked before the model provider is called fails with a JSON error body and a real HTTP status, before any stream opens: a malformed or otherwise invalid request or an unknown `model` `400` (`errorCode: \"unknown_model\"` for the latter), a bad credential `401`, a key without a required scope `403` (with `required_scopes`), a body over 25 MB `413`, a body that fails schema validation `422`, insufficient credits `422` with `errorCode: \"insufficient_credits\"` (personal or org balance, or an org per-member credit cap), a rate limit `429` with `Retry-After`, and a catalogued model whose provider is not configured on this deployment `503` with `errorCode: \"provider_not_configured\"` (retryable). Once the stream has opened the status stays `200` and a failure arrives as one terminal in-band `error` event. Branch on that event's `code`, never on `message`, which is prose and may change: `insufficient_credits` (buy credits) when credits run out mid-generation, `provider_rejected` when the model provider refused our credentials, `content_blocked` when the model declined the request (do not retry it unchanged), and `provider_error` for any other provider failure. Treat `code` as optional.",
18698
+ description: "OpenAI-compatible completion. A successful request is ALWAYS answered with an SSE stream (`text/event-stream`), regardless of the `stream` flag: a `meta` event, then `content`/`tool_use` events carrying `usage`/`credits`, terminated by `data: [DONE]`. The terminal event carries `stopReason`; treat `max_tokens` as a TRUNCATED reply rather than a complete one, and note that omitting `max_tokens` on the request lets the server size the output ceiling for the model (recommended for reasoning models, which spend thinking tokens inside that ceiling). A message `content` may be a string or an array of parts; image parts are accepted in OpenAI Chat (`image_url`), OpenAI Responses (`input_image`) or Anthropic (`image` with a `source`) form and are translated to whatever the target model speaks. Authenticate with an API key (`b4m_live_`) or a JWT.\n\nTOOL SEARCH. On a model whose `GET /api/v1/models` row has `supports_tool_search: true` (`backend` `\"anthropic\"` or `\"bedrock\"`), a `tools` entry may set `toolSchema.defer_loading: true`, and a `{ \"serverTool\": { \"type\": \"tool_search_tool_regex_20251119\", \"name\": \"tool_search_tool_regex\" } }` (or the `bm25` pair) entry adds Anthropic's tool search, so deferred definitions load only when the model searches for them. The stream then carries `provider_block` events (`{ type, provider: \"anthropic\", block }`) for the server-run search; echo each block back verbatim, in order, in the assistant `messages` content of the next request, and never answer one with a `tool_result`. When the server-run search loop hits its iteration limit the stream ends with `stopReason: \"pause_turn\"`; continue by re-sending the conversation with that assistant turn's provider blocks echoed. A client-side search may return `{ \"type\": \"tool_reference\", \"tool_name\": \"...\" }` blocks inside a `tool_result`'s content. On every other model `defer_loading` is ignored (every tool is sent as usual) and a server tool, a `tool_reference` or an echoed tool search block is refused with a `400` rather than run without it.\n\nERRORS. Everything checked before the model provider is called fails with a JSON error body and a real HTTP status, before any stream opens: an empty `messages` array, a conversation with no non-empty user or assistant message (system-only, or only blank content), a malformed request, or an unknown `model` `400` (`errorCode: \"unknown_model\"` for an unknown model), a bad credential `401`, a key without a required scope `403` (with `required_scopes`), a body over 25 MB `413`, any other body that fails schema validation `422`, insufficient credits `422` with `errorCode: \"insufficient_credits\"` (personal or org balance, or an org per-member credit cap), a rate limit `429` with `Retry-After`, and a catalogued model whose provider is not configured on this deployment, or a model that could only come from a self-hosted model server (Ollama, local image) that is currently unreachable, `503` with `errorCode: \"provider_not_configured\"` (retryable). A provider rejection of caller input that happens before the stream opens is also a JSON `400`, with `errorCode: \"invalid_request\"` (do not retry it unchanged). Once the stream has opened the status stays `200` and a failure arrives as one terminal in-band `error` event. Branch on that event's `code`, never on `message`, which is prose and may change: `insufficient_credits` (buy credits) when credits run out mid-generation, `invalid_request` when the provider rejects caller input (do not retry it unchanged), `provider_rejected` when the model provider refused our credentials, `content_blocked` when the model declined the request (do not retry it unchanged), and `provider_error` for any other provider failure. Treat `code` as optional.",
17659
18699
  tags: ["AI"],
17660
18700
  auth: "apiKeyOrJwt",
17661
18701
  scopes: ["ai:chat", "ai:generate"],
@@ -17689,7 +18729,7 @@ defineEndpoint({
17689
18729
  }
17690
18730
  },
17691
18731
  400: {
17692
- description: "Malformed or otherwise invalid request (no `errorCode`), or a `model` the caller cannot use (`errorCode: \"unknown_model\"`): not in the catalog or not available to the caller. List valid ids with `GET /api/v1/models`. Tool search requests are refused with: `tool_search_unsupported` (a server tool, `tool_reference` or echoed tool search block on a model that cannot serve tool search here), `all_tools_deferred` (every tool deferred and no server tool), `deferred_tool_cache_control` (`cache_control` on a deferred tool), `unknown_tool_reference` (a `tool_reference` naming a tool not in `tools`, or carrying no string `tool_name`). Mid-conversation tool changes are refused with: `tool_changes_unsupported` (a `tool_addition` / `tool_removal` system message on a model that cannot serve it here), `invalid_tool_change` (a malformed block, or one placed where the provider does not accept it), and `unknown_tool_reference` (a changed tool not in `tools`).",
18732
+ description: "Malformed or otherwise invalid request (no `errorCode`), provider-rejected caller input (`errorCode: \"invalid_request\"`), or a `model` the caller cannot use (`errorCode: \"unknown_model\"`): not in the catalog or not available to the caller. List valid ids with `GET /api/v1/models`. Tool search requests are refused with: `tool_search_unsupported` (a server tool, `tool_reference` or echoed tool search block on a model that cannot serve tool search here), `all_tools_deferred` (every tool deferred and no server tool), `deferred_tool_cache_control` (`cache_control` on a deferred tool), `unknown_tool_reference` (a `tool_reference` naming a tool not in `tools`, or carrying no string `tool_name`). Mid-conversation tool changes are refused with: `tool_changes_unsupported` (a `tool_addition` / `tool_removal` system message on a model that cannot serve it here), `invalid_tool_change` (a malformed block, or one placed where the provider does not accept it), and `unknown_tool_reference` (a changed tool not in `tools`).",
17693
18733
  schema: CompletionsBadRequestErrorSchema
17694
18734
  },
17695
18735
  413: {
@@ -17710,7 +18750,7 @@ defineEndpoint({
17710
18750
  schema: ApiErrorSchema
17711
18751
  },
17712
18752
  503: {
17713
- description: "Temporarily unavailable: the model's provider is not configured on this deployment (`errorCode: \"provider_not_configured\"`), or the database is unreachable (no `errorCode`). Retry later.",
18753
+ description: "Temporarily unavailable: the model's provider is not configured on this deployment, or the self-hosted model server that lists it (Ollama, local image) is unreachable (`errorCode: \"provider_not_configured\"`), or the database is unreachable (no `errorCode`). Retry later.",
17714
18754
  schema: ProviderNotConfiguredErrorSchema
17715
18755
  }
17716
18756
  },
@@ -18513,7 +19553,7 @@ defineEndpoint({
18513
19553
  streaming: false
18514
19554
  }
18515
19555
  });
18516
- const EXAMPLE_REQUEST$1 = {
19556
+ const EXAMPLE_REQUEST$2 = {
18517
19557
  model: "text-embedding-3-small",
18518
19558
  input: ["The quick brown fox", "jumps over the lazy dog"],
18519
19559
  dimensions: 1024
@@ -18528,7 +19568,7 @@ defineEndpoint({
18528
19568
  auth: "apiKeyOrJwt",
18529
19569
  scopes: ["ai:generate"],
18530
19570
  request: EmbeddingsRequestSchema,
18531
- requestExample: EXAMPLE_REQUEST$1,
19571
+ requestExample: EXAMPLE_REQUEST$2,
18532
19572
  responses: {
18533
19573
  200: {
18534
19574
  description: "One embedding per input, in input order.",
@@ -18585,6 +19625,55 @@ defineEndpoint({
18585
19625
  }
18586
19626
  },
18587
19627
  emitsRateLimitHeaders: true,
19628
+ codeSample: {
19629
+ authToken: "b4m_live_<key>",
19630
+ streaming: false,
19631
+ body: EXAMPLE_REQUEST$2
19632
+ }
19633
+ });
19634
+ const EXAMPLE_REQUEST$1 = {
19635
+ text: "a dog on a beach",
19636
+ context: "prompt for an image generator"
19637
+ };
19638
+ defineEndpoint({
19639
+ method: "post",
19640
+ path: "/api/v1/text-refinements",
19641
+ operationId: "createTextRefinement",
19642
+ summary: "Refine text",
19643
+ description: `Rewrites \`text\` to be more concise, clear and detailed while keeping its intent. \`context\` is optional background that steers the rewrite. \`text\` is at most ${MAX_REFINE_TEXT_CHARS} characters and \`context\` at most ${MAX_REFINE_CONTEXT_CHARS}. Output is capped at about 800 tokens, and identical requests are served from a cache for about five minutes. Not billed in credits. Authenticate with an API key (\`b4m_live_\`) carrying \`ai:generate\`, or a JWT.`,
19644
+ tags: ["AI"],
19645
+ auth: "apiKeyOrJwt",
19646
+ scopes: ["ai:generate"],
19647
+ request: TextRefinementRequestSchema,
19648
+ requestExample: EXAMPLE_REQUEST$1,
19649
+ responses: {
19650
+ 200: {
19651
+ description: "The refined text.",
19652
+ schema: TextRefinementResponseSchema,
19653
+ example: { text: "A golden retriever running along a sunlit beach at sunset, waves breaking behind it." }
19654
+ },
19655
+ 401: {
19656
+ description: "Missing/invalid credentials, or the text-refinement provider refused the configured key (`errorCode: \"provider_rejected\"`).",
19657
+ schema: TextRefinementErrorSchema
19658
+ },
19659
+ 429: {
19660
+ description: "Per-key rate limit exceeded.",
19661
+ schema: ApiErrorSchema
19662
+ },
19663
+ 500: {
19664
+ description: "Unexpected server error (for example the refinement cache is unavailable).",
19665
+ schema: ApiErrorSchema
19666
+ },
19667
+ 502: {
19668
+ description: "The model call failed for a reason other than a rejected credential, or returned no refined text.",
19669
+ schema: ApiErrorSchema
19670
+ },
19671
+ 503: {
19672
+ description: "This deployment has no operations model configured (`errorCode: \"provider_not_configured\"`).",
19673
+ schema: TextRefinementErrorSchema
19674
+ }
19675
+ },
19676
+ emitsRateLimitHeaders: true,
18588
19677
  codeSample: {
18589
19678
  authToken: "b4m_live_<key>",
18590
19679
  streaming: false,
@@ -19238,7 +20327,7 @@ defineEndpoint({
19238
20327
  path: "/api/v1/transcriptions",
19239
20328
  operationId: "createTranscription",
19240
20329
  summary: "Transcribe audio",
19241
- description: "Transcribes an upload minted by `POST /api/v1/transcriptions/uploads` and returns the text. Call it within 5 minutes of minting the upload. Credits are charged by file size. The uploaded audio is deleted when the call returns, whether it succeeds or fails, so each upload can be transcribed once.\n\n**Limit:** this call is synchronous. The request times out after about 60 seconds, while the AWS speech backend can take up to about 5 minutes on long audio, so a long recording can fail with a gateway timeout (`504`) instead of returning text. A timed-out upload may not be deleted right away (it expires on its own), so do not retry with the same `file_key`: mint a new upload, or send shorter audio.",
20330
+ description: "Transcribes an upload minted by `POST /api/v1/transcriptions/uploads` and returns the text. Call it within 5 minutes of minting the upload. Credits are charged by file size. The uploaded audio is deleted when the call returns, whether it succeeds or fails, so each upload can be transcribed once.\n\n**Limit:** this call is synchronous. Provider work stops being awaited after 45 seconds from request arrival, leaving time to return reserved credits and delete the upload before the hosted server stops at 60 seconds. A slow recording returns a gateway timeout (`504`) instead of text. AWS batch jobs cannot be cancelled remotely and may finish later; their output expires automatically. Do not retry with the same `file_key`: mint a new upload and send shorter audio. If cleanup fails, the upload may not be deleted right away (it expires on its own).",
19242
20331
  tags: ["Transcriptions"],
19243
20332
  auth: "apiKeyOrJwt",
19244
20333
  scopes: ["ai:generate"],
@@ -19268,6 +20357,10 @@ defineEndpoint({
19268
20357
  503: {
19269
20358
  description: "This deployment has no speech model, or no credential for its provider, configured (`errorCode: \"provider_not_configured\"`).",
19270
20359
  schema: ProviderNotConfiguredErrorSchema
20360
+ },
20361
+ 504: {
20362
+ description: "The transcription exceeded the request deadline; reserved credits are returned.",
20363
+ schema: ApiErrorSchema
19271
20364
  }
19272
20365
  },
19273
20366
  codeSample: {
@@ -19374,6 +20467,35 @@ defineEndpoint({
19374
20467
  }
19375
20468
  }
19376
20469
  });
20470
+ defineEndpoint({
20471
+ method: "get",
20472
+ path: "/api/v1/whats-new/media/{id}/{asset}",
20473
+ operationId: "getWhatsNewMedia",
20474
+ summary: "Get release note media",
20475
+ description: "Redirects to one file of a published release note video: `landscape` and `portrait` are the 16:9 and 9:16 MP4s, the `-poster` assets their still images. The `video` URLs that listWhatsNew returns for uploaded files point here, so a browser can use them as `src` and `poster` as they are. Public: no credentials are needed, and the note must be one listWhatsNew would show. The redirect is cacheable for up to 10 minutes and its target is signed for 1 hour, longer than that, so follow it rather than storing the target. Rate limited per client IP.",
20476
+ tags: ["Release notes"],
20477
+ auth: "public",
20478
+ pathParams: ReleaseNoteMediaParamsSchema,
20479
+ responses: {
20480
+ 303: {
20481
+ description: "`Location` is a time-limited signed GET for the file.",
20482
+ noBody: true,
20483
+ headers: { Location: "Signed URL of the file." }
20484
+ },
20485
+ 404: {
20486
+ description: "No published note has this id, or the note has no such file, or the file is not an upload this deployment serves (a linked URL is listed as is, not through this route).",
20487
+ schema: ApiErrorSchema
20488
+ },
20489
+ 429: {
20490
+ description: "Per-IP rate limit exceeded.",
20491
+ schema: ApiErrorSchema
20492
+ },
20493
+ 500: {
20494
+ description: "Unexpected server error.",
20495
+ schema: ApiErrorSchema
20496
+ }
20497
+ }
20498
+ });
19377
20499
  /**
19378
20500
  * The read/write/query API-key scope sets the data-lake doors accept (OR semantics), shared by the
19379
20501
  * SPA routes (`apps/client/server/dataLakes/dataLakeScopes.ts` re-exports these) and the public
@@ -19726,11 +20848,11 @@ defineEndpoint({
19726
20848
  schema: ApiErrorSchema
19727
20849
  },
19728
20850
  404: {
19729
- description: "`input_image_file_id` does not name an image you own (`errorCode: input_image_not_found`). A nonexistent `organization_id` is `404` only for an admin caller; for anyone else it is `403`.",
20851
+ description: "An `inputs` file (or `input_image_file_id`) does not name an image you own (`errorCode: input_image_not_found`). A nonexistent `organization_id` is `404` only for an admin caller; for anyone else it is `403`.",
19730
20852
  schema: VideoGenerationErrorResponseSchema
19731
20853
  },
19732
20854
  422: {
19733
- description: "The request cannot run: a body or capability violation (`errorCode` names it, e.g. `unsupported_duration`), an unknown or unavailable model (`model_unavailable`), a model your administrator disabled (`model_disabled`), too few credits (`insufficient_credits`), or an Idempotency-Key problem (`invalid_idempotency_key`, `idempotency_key_reused`).",
20855
+ description: "The request cannot run: a body or capability violation (`errorCode` names it, e.g. `unsupported_duration`, or `unsupported_input_role` for an input role the model does not take), an unknown or unavailable model (`model_unavailable`), a model your administrator disabled (`model_disabled`), too few credits (`insufficient_credits`), or an Idempotency-Key problem (`invalid_idempotency_key`, `idempotency_key_reused`).",
19734
20856
  schema: VideoGenerationErrorResponseSchema
19735
20857
  },
19736
20858
  429: rateLimited$1
@@ -20573,6 +21695,94 @@ defineEndpoint({
20573
21695
  streaming: false
20574
21696
  }
20575
21697
  });
21698
+ const AUTH_NOTE = "Authenticate with an API key (`b4m_live_`) carrying `skills:read`, sent as `Authorization: Bearer`. Claude Code sends that header when it is set as the marketplace source's `headers` in its settings, and forwards it to the same-origin plugin archives.";
21699
+ defineEndpoint({
21700
+ method: "get",
21701
+ path: "/api/v1/skills/market/claude-marketplace.json",
21702
+ operationId: "getClaudeMarketplace",
21703
+ summary: "Get an organization's Claude Code plugin marketplace",
21704
+ description: `Returns a Claude Code \`marketplace.json\` listing the organization's approved Organization-visibility Skill Market listings, one \`archive\` plugin each, pinned by \`sha256\`. Public and unlisted listings are not included. Responses are never cacheable. ${AUTH_NOTE}`,
21705
+ tags: ["Skills"],
21706
+ auth: "apiKeyOrJwt",
21707
+ scopes: ["skills:read"],
21708
+ queryParams: ClaudeMarketplaceQuerySchema,
21709
+ emitsRateLimitHeaders: true,
21710
+ responses: {
21711
+ 200: {
21712
+ description: "The marketplace.",
21713
+ schema: ClaudeMarketplaceSchema,
21714
+ example: {
21715
+ name: `b4m-org-${EXAMPLE_RESOURCE_ID}`,
21716
+ owner: { name: "Acme" },
21717
+ forceRemoveDeletedPlugins: true,
21718
+ plugins: [{
21719
+ name: "review-pr",
21720
+ description: "Review a pull request against the team checklist.",
21721
+ version: "3",
21722
+ source: {
21723
+ source: "archive",
21724
+ url: `https://app.bike4mind.com/api/v1/skills/market/${EXAMPLE_RESOURCE_ID}/claude-plugin.zip?version=3`,
21725
+ sha256: "a3f1c0de9b8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a392817060f5e"
21726
+ }
21727
+ }]
21728
+ }
21729
+ },
21730
+ 403: {
21731
+ description: "The API key lacks `skills:read`, or the caller is not a member of the organization.",
21732
+ schema: ScopeForbiddenErrorSchema
21733
+ },
21734
+ 429: {
21735
+ description: "Per-user rate limit exceeded.",
21736
+ schema: ApiErrorSchema
21737
+ }
21738
+ },
21739
+ codeSample: {
21740
+ authToken: "b4m_live_<key>",
21741
+ streaming: false,
21742
+ body: {}
21743
+ }
21744
+ });
21745
+ defineEndpoint({
21746
+ method: "get",
21747
+ path: "/api/v1/skills/market/{id}/claude-plugin.zip",
21748
+ operationId: "getClaudePluginZip",
21749
+ summary: "Download a Skill Market listing as a Claude Code plugin",
21750
+ description: `A zip holding an approved version as \`skills/<name>/SKILL.md\`. The optional \`version\` query pins the listing version advertised by the feed, so later approvals do not change its bytes or \`sha256\`. Without it, returns the current approved version. ${AUTH_NOTE}`,
21751
+ tags: ["Skills"],
21752
+ auth: "apiKeyOrJwt",
21753
+ scopes: ["skills:read"],
21754
+ pathParams: ClaudePluginZipParamSchema,
21755
+ queryParams: ClaudePluginZipQuerySchema,
21756
+ validationErrorStatus: 400,
21757
+ emitsRateLimitHeaders: true,
21758
+ responses: {
21759
+ 200: {
21760
+ description: "The plugin archive.",
21761
+ contentType: "application/zip"
21762
+ },
21763
+ 400: {
21764
+ description: "Invalid version query.",
21765
+ schema: ApiErrorSchema
21766
+ },
21767
+ 403: {
21768
+ description: "The API key lacks `skills:read`.",
21769
+ schema: ScopeForbiddenErrorSchema
21770
+ },
21771
+ 404: {
21772
+ description: "No Organization listing with that id is visible to the caller. A listing in an org you are not in, a public or unlisted listing, and a malformed id are all reported as 404.",
21773
+ schema: ApiErrorSchema
21774
+ },
21775
+ 429: {
21776
+ description: "Per-user rate limit exceeded.",
21777
+ schema: ApiErrorSchema
21778
+ }
21779
+ },
21780
+ codeSample: {
21781
+ authToken: "b4m_live_<key>",
21782
+ streaming: false,
21783
+ body: {}
21784
+ }
21785
+ });
20576
21786
  /**
20577
21787
  * The fence language the model writes to place an inline map of search-result places in a reply.
20578
21788
  *
@@ -23301,7 +24511,7 @@ async function sleep(ms, abortSignal) {
23301
24511
  * Respects Retry-After headers when present (e.g., for 429 rate limiting).
23302
24512
  */
23303
24513
  async function withRetry(fn, options = {}) {
23304
- const { maxRetries = 3, initialDelayMs = 100, maxDelayMs = 5e3, jitterFactor = .1, isRetryable = isRetryableError, logger, abortSignal } = options;
24514
+ const { maxRetries = 3, initialDelayMs = 100, maxDelayMs = 5e3, jitterFactor = .1, isRetryable = isRetryableError, logger, abortSignal, onRetry } = options;
23305
24515
  let attempts = 0;
23306
24516
  let totalDelayMs = 0;
23307
24517
  while (true) try {
@@ -23332,6 +24542,15 @@ async function withRetry(fn, options = {}) {
23332
24542
  attempt: attempts,
23333
24543
  delayMs
23334
24544
  });
24545
+ try {
24546
+ onRetry?.({
24547
+ attempt: attempts,
24548
+ delayMs,
24549
+ error: err
24550
+ });
24551
+ } catch (callbackError) {
24552
+ logger?.warn("onRetry callback threw; continuing the retry", { error: String(callbackError) });
24553
+ }
23335
24554
  try {
23336
24555
  await sleep(delayMs, abortSignal);
23337
24556
  } catch {
@@ -24298,6 +25517,7 @@ const CORE_TOOL_DESCRIPTORS = new Map(Object.entries({
24298
25517
  weather_info: reader(CORE, { openWorld: true }),
24299
25518
  web_search: reader(CORE, { openWorld: true }),
24300
25519
  web_fetch: reader(CORE, { openWorld: true }),
25520
+ decide: reader(CORE, { openWorld: true }),
24301
25521
  deep_research: reader(CORE, { openWorld: true }),
24302
25522
  check_shell_output: reader(),
24303
25523
  list_background_shells: reader(),
@@ -25867,4 +27087,4 @@ var ConfigStore = class {
25867
27087
  }
25868
27088
  };
25869
27089
  //#endregion
25870
- export { PROMPT_TEXT_MAX as $, ARTIFACT_REMOVED_PLACEHOLDER as A, splitEquals as At, CreateArtifactRequestSchema as B, DELEGATION_PAYLOAD_SCHEMA_NAME as Bt, htmlErrorTitle as C, isUserInitiatedAbort as Ct, AGENT_QUEST_MANIFEST as D, replacePromptVariables as Dt, AGENT_QUEST_ID as E, questReplyText as Et, BriefcasePromptIdSchema as F, ttsBase64ResponseSchema as Ft, ImagePromptResolutionSchema as G, selfClaimedActorKindSchema as Gt, DEGENERATE_FINISH_REASON as H, actorKindMarker as Ht, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as I, ttsRequestSchema as It, McpServerName as J, LOCATION_MAP_LANGUAGE as K, APP_NAME as Kt, CREDIT_DEDUCT_TRANSACTION_TYPES as L, ttsResponseTooLargeSchema as Lt, ArtifactTypeSchema as M, stripToolArtifactMarkup as Mt, BRIEFCASE_CATALOG_QUERIES as N, supportedVoiceGenerationVendor as Nt, AGENT_QUEST_MCP_URI as O, secureParameters as Ot, BedrockEmbeddingModel as P, tokenEstimateMultiplier as Pt, OpenAIEmbeddingModel as Q, ChatModels as R, withRetry as Rt, isValidSessionId as S, isRetryableError as St, escapeArtifactBodyJson as T, obfuscateApiKey as Tt, GENERATED_IMAGE_EXTENSION_RE as U, actorKindSchema as Ut, DEFAULT_TTS_PROVIDER as V, actorColorIndex as Vt, ImageModels as W, delegationPayloadSchema as Wt, NotFoundError as X, ModelBackend as Y, OllamaEmbeddingModel as Z, LOCAL_DEV_URL as _, getQuestErrorCode as _t, commandTextOf as a, SupportedFabFileMimeTypes as at, logger as b, isEarlyStop as bt, toolCategory as c, VoyageAIEmbeddingModel as ct, getCreditsUrl as d, buildPromptContext as dt, PermissionDeniedError as et, getEnvironmentName as f, createThinkMarkerEscaper as ft, DeviceFlowClient as g, getMcpProviderMetadata as gt, AuthenticatedApiClient as h, generatedAudioResponseSchema as ht, ToolCatalog as i, SUBQUEST_STATUS_VALUES as it, ApiKeyType as j, stripDeliveredArtifactBlocks as jt, ARTIFACT_DELIVERED_PLACEHOLDER as k, signImageUrl as kt, writePathOf as l, WORK_ITEM_STATUSES as lt, resolveApiEndpoint as m, filterToolArtifactMarkup as mt, extractCompactInstructions as n, SEARCH_RESULT_CARDS_LANGUAGE as nt, isReadOnlyTool as o, UnprocessableEntityError as ot, requireApiUrl as p, escapeThinkMarkers as pt, MODEL_INFO_FIELD_GROUP_OF as q, WEBSITE_URL as qt, loadContextFiles as r, SLUG_OWNED_BY_OTHER as rt, sandboxedCommandArg as s, VisibilitySchema as st, ConfigStore as t, REVIEW_GATE_STATUS_VALUES as tt, ApiEndpointUnconfiguredError as u, assertAuthOriginMatchesApiKey as ut, NotAuthenticatedError as v, googleMapsSearchUrl as vt, htmlFirstH1 as w, joinReplySlots as wt, SESSION_ID_PATTERN as x, isPlaceholderImageSigningSecret as xt, parseApiUrl as y, hasDeliverablePinnedArtifact as yt, CompletionReasoningEffortSchema as z, ACTOR_COLOR_SLOTS as zt };
27090
+ export { ModelBackend as $, selfClaimedActorKindSchema as $t, ARTIFACT_REMOVED_PLACEHOLDER as A, isUserInitiatedAbort as At, ChatModels as B, supportedVoiceGenerationVendor as Bt, htmlErrorTitle as C, getMcpProviderMetadata as Ct, AGENT_QUEST_MANIFEST as D, isEarlyStop as Dt, AGENT_QUEST_ID as E, hasDeliverablePinnedArtifact as Et, BRIEFCASE_CATALOG_QUERIES as F, secureParameters as Ft, DecisionTextPartSchema as G, visibleReplyText as Gt, CreateArtifactRequestSchema as H, ttsBase64ResponseSchema as Ht, BedrockEmbeddingModel as I, signImageUrl as It, ImageModels as J, DELEGATION_PAYLOAD_SCHEMA_NAME as Jt, DecisionsRequestSchema as K, withRetry as Kt, BriefcasePromptIdSchema as L, splitEquals as Lt, ApiKeyType as M, obfuscateApiKey as Mt, ArtifactTypeSchema as N, questReplyText as Nt, AGENT_QUEST_MCP_URI as O, isPlaceholderImageSigningSecret as Ot, AudioMimeType as P, replacePromptVariables as Pt, McpServerName as Q, delegationPayloadSchema as Qt, CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS as R, stripDeliveredArtifactBlocks as Rt, isValidSessionId as S, generatedAudioResponseSchema as St, escapeArtifactBodyJson as T, googleMapsSearchUrl as Tt, DEFAULT_TTS_PROVIDER as U, ttsRequestSchema as Ut, CompletionReasoningEffortSchema as V, tokenEstimateMultiplier as Vt, DEGENERATE_FINISH_REASON as W, ttsResponseTooLargeSchema as Wt, LOCATION_MAP_LANGUAGE as X, actorKindMarker as Xt, ImagePromptResolutionSchema as Y, actorColorIndex as Yt, MODEL_INFO_FIELD_GROUP_OF as Z, actorKindSchema as Zt, LOCAL_DEV_URL as _, buildPromptContext as _t, commandTextOf as a, REVIEW_GATE_STATUS_VALUES as at, logger as b, escapeThinkMarkers as bt, toolCategory as c, SUBQUEST_STATUS_VALUES as ct, getCreditsUrl as d, TTS_MAX_INPUT_CHARS as dt, frontmatterHasSourceStamp as en, NotFoundError as et, getEnvironmentName as f, UnprocessableEntityError as ft, DeviceFlowClient as g, assertAuthOriginMatchesApiKey as gt, AuthenticatedApiClient as h, WORK_ITEM_STATUSES as ht, ToolCatalog as i, WEBSITE_URL as in, PermissionDeniedError as it, ApiKeyScope as j, joinReplySlots as jt, ARTIFACT_DELIVERED_PLACEHOLDER as k, isRetryableError as kt, writePathOf as l, SupportedFabFileMimeTypes as lt, resolveApiEndpoint as m, VoyageAIEmbeddingModel as mt, extractCompactInstructions as n, splitSkillMdFrontmatter as nn, OpenAIEmbeddingModel as nt, isReadOnlyTool as o, SEARCH_RESULT_CARDS_LANGUAGE as ot, requireApiUrl as p, VisibilitySchema as pt, GENERATED_IMAGE_EXTENSION_RE as q, ACTOR_COLOR_SLOTS as qt, loadContextFiles as r, APP_NAME as rn, PROMPT_TEXT_MAX as rt, sandboxedCommandArg as s, SLUG_OWNED_BY_OTHER as st, ConfigStore as t, readSkillMdSourceUrl as tn, OllamaEmbeddingModel as tt, ApiEndpointUnconfiguredError as u, TTS_DEFAULT_MODEL as ut, NotAuthenticatedError as v, completionsSupportsToolSearch as vt, htmlFirstH1 as w, getQuestErrorCode as wt, SESSION_ID_PATTERN as x, filterToolArtifactMarkup as xt, parseApiUrl as y, createThinkMarkerEscaper as yt, CREDIT_DEDUCT_TRANSACTION_TYPES as z, stripToolArtifactMarkup as zt };