deepspace 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/worker.js CHANGED
@@ -105,11 +105,11 @@ function decodeRoomIdentityHeader(value) {
105
105
  return void 0;
106
106
  }
107
107
  }
108
- function authenticatedRoomRequest(request, auth, identity) {
109
- const url = new URL(request.url);
108
+ function authenticatedRoomRequest(request2, auth, identity) {
109
+ const url = new URL(request2.url);
110
110
  url.searchParams.delete("token");
111
111
  for (const key of IDENTITY_QUERY_PARAMS) url.searchParams.delete(key);
112
- const forwarded = new Request(url.toString(), request);
112
+ const forwarded = new Request(url.toString(), request2);
113
113
  for (const header of IDENTITY_HEADERS) forwarded.headers.delete(header);
114
114
  if (!auth) return forwarded;
115
115
  forwarded.headers.set("x-user-id", encodeRoomIdentityHeader(auth.userId));
@@ -129,13 +129,18 @@ function authenticatedRoomRequest(request, auth, identity) {
129
129
  }
130
130
 
131
131
  // src/server/rooms/base-room.ts
132
- function connectionAttachmentFromRequest(request) {
132
+ function connectionAttachmentFromRequest(request2) {
133
+ if (!request2.headers.has("x-user-id") && new URL(request2.url).searchParams.has("userId")) {
134
+ console.warn(
135
+ "[BaseRoom] Connection carries identity in the URL (?userId=) but no x-user-id header, so this user joins as anonymous. The app worker's room proxy predates 0.19 or forwards the client's URL unstripped: forward with authenticatedRoomRequest() from deepspace/worker."
136
+ );
137
+ }
133
138
  return {
134
- userId: decodeRoomIdentityHeader(request.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
135
- userName: decodeRoomIdentityHeader(request.headers.get("x-user-name")) || "Anonymous",
136
- userEmail: decodeRoomIdentityHeader(request.headers.get("x-user-email")) || "",
137
- userImageUrl: decodeRoomIdentityHeader(request.headers.get("x-user-image-url")),
138
- role: decodeRoomIdentityHeader(request.headers.get("x-user-role"))
139
+ userId: decodeRoomIdentityHeader(request2.headers.get("x-user-id")) || `${ANONYMOUS_USER_ID_PREFIX}${crypto.randomUUID()}`,
140
+ userName: decodeRoomIdentityHeader(request2.headers.get("x-user-name")) || "Anonymous",
141
+ userEmail: decodeRoomIdentityHeader(request2.headers.get("x-user-email")) || "",
142
+ userImageUrl: decodeRoomIdentityHeader(request2.headers.get("x-user-image-url")),
143
+ role: decodeRoomIdentityHeader(request2.headers.get("x-user-role"))
139
144
  };
140
145
  }
141
146
  var BaseRoom = class {
@@ -151,15 +156,15 @@ var BaseRoom = class {
151
156
  // ==========================================================================
152
157
  // HTTP Entry Point
153
158
  // ==========================================================================
154
- async fetch(request) {
155
- const url = new URL(request.url);
156
- const internal = await this.handleInternalRequest(request, url);
159
+ async fetch(request2) {
160
+ const url = new URL(request2.url);
161
+ const internal = await this.handleInternalRequest(request2, url);
157
162
  if (internal) return internal;
158
- if (request.headers.get("Upgrade") === "websocket") {
159
- return this.handleWebSocketUpgrade(request, url);
163
+ if (request2.headers.get("Upgrade") === "websocket") {
164
+ return this.handleWebSocketUpgrade(request2, url);
160
165
  }
161
166
  if (this.onRequest) {
162
- return this.onRequest(request);
167
+ return this.onRequest(request2);
163
168
  }
164
169
  return new Response("Not Found", { status: 404 });
165
170
  }
@@ -180,11 +185,11 @@ var BaseRoom = class {
180
185
  * See the security note on `fetch()`: this path is only reachable via DO
181
186
  * stub fetch from the app worker, never from the public internet.
182
187
  */
183
- async handleInternalRequest(request, url) {
184
- if (request.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
188
+ async handleInternalRequest(request2, url) {
189
+ if (request2.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
185
190
  let options;
186
191
  try {
187
- const text = await request.text();
192
+ const text = await request2.text();
188
193
  if (text) options = JSON.parse(text);
189
194
  } catch {
190
195
  }
@@ -196,11 +201,11 @@ var BaseRoom = class {
196
201
  // ==========================================================================
197
202
  // WebSocket Connection
198
203
  // ==========================================================================
199
- async handleWebSocketUpgrade(request, _url) {
204
+ async handleWebSocketUpgrade(request2, _url) {
200
205
  const pair = new WebSocketPair();
201
206
  const [client, server] = Object.values(pair);
202
207
  this.state.acceptWebSocket(server);
203
- const attachment = connectionAttachmentFromRequest(request);
208
+ const attachment = connectionAttachmentFromRequest(request2);
204
209
  const augmented = await this.onConnect(server, attachment) ?? attachment;
205
210
  server.serializeAttachment(augmented);
206
211
  return new Response(null, { status: 101, webSocket: client });
@@ -436,7 +441,8 @@ function dataToColumnValues(data, columns) {
436
441
  return values;
437
442
  }
438
443
  function coerceValue(value, storage, interpretation) {
439
- if (value === null || value === void 0 || value === "") return null;
444
+ if (value === null || value === void 0) return null;
445
+ if (value === "") return storage === "text" ? "" : null;
440
446
  if (storage === "text") {
441
447
  if (interpretation.kind === "json") {
442
448
  if (typeof value === "string") return value;
@@ -470,7 +476,7 @@ function coerceValue(value, storage, interpretation) {
470
476
  }
471
477
  if (interpretation.kind === "date" || interpretation.kind === "datetime") {
472
478
  const d = new Date(s);
473
- if (!isNaN(d.getTime())) return Math.floor(d.getTime() / 1e3);
479
+ if (!isNaN(d.getTime())) return epochSeconds(d);
474
480
  return s;
475
481
  }
476
482
  const cleaned = s.replace(/[$€£¥,\s]/g, "");
@@ -478,6 +484,9 @@ function coerceValue(value, storage, interpretation) {
478
484
  if (!isNaN(n) && isFinite(n)) return n;
479
485
  return s;
480
486
  }
487
+ function epochSeconds(date = /* @__PURE__ */ new Date()) {
488
+ return Math.floor(date.getTime() / 1e3);
489
+ }
481
490
  function buildTableSelect(collectionName, columns) {
482
491
  const tbl = collectionTableName(collectionName);
483
492
  const parts = ["_row_id", "_created_by", "_created_at", "_updated_at"];
@@ -1142,7 +1151,7 @@ var BUILT_IN_TOOLS = [
1142
1151
  },
1143
1152
  {
1144
1153
  name: "user.list",
1145
- description: "List all users in this room",
1154
+ description: "List the users this caller may see, exactly as the room's roster does: an admin gets whole rows (including email and any app-defined column) through the users collection's read policy; everyone else gets the public identity projection (id, name, imageUrl, role, lastSeenAt) of the roster the users policy allows them \u2014 nothing if that policy denies reads, and only their own scope if the schema sets `roster: 'read-policy'`. A caller with no identity gets an empty list. Called from a server action (X-App-Action), it returns whole rows like the other RBAC-off tools.",
1146
1155
  params: {}
1147
1156
  },
1148
1157
  // Yjs tools (collaborative document access)
@@ -1316,6 +1325,9 @@ function encode(message) {
1316
1325
  function getResolvedColumns(schema) {
1317
1326
  return (schema.columns ?? []).map(resolveColumn);
1318
1327
  }
1328
+ function triggerTimestamp(col) {
1329
+ return col.storage === "number" ? epochSeconds() : (/* @__PURE__ */ new Date()).toISOString();
1330
+ }
1319
1331
  function systemManagedColumnError(column, isUpdate) {
1320
1332
  const roleHint = column === "role" ? ", or the admin-gated useUsers().setRole() to change a role" : "";
1321
1333
  const remedy = isUpdate ? `Re-sending the current value of '${column}' unchanged is accepted and ignored.` : `A users row is created by registration, so no value for '${column}' is accepted here \u2014 omit it.`;
@@ -1434,7 +1446,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1434
1446
  if (!isUpdate) {
1435
1447
  if (mergedData[triggerField] !== void 0 && mergedData[triggerField] !== null) {
1436
1448
  if (triggerValue === void 0 || mergedData[triggerField] === triggerValue) {
1437
- mergedData[col.name] = (/* @__PURE__ */ new Date()).toISOString();
1449
+ mergedData[col.name] = triggerTimestamp(col);
1438
1450
  }
1439
1451
  }
1440
1452
  } else {
@@ -1442,7 +1454,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1442
1454
  const newVal = mergedData[triggerField];
1443
1455
  if (newVal !== oldVal) {
1444
1456
  if (triggerValue === void 0 || newVal === triggerValue) {
1445
- mergedData[col.name] = (/* @__PURE__ */ new Date()).toISOString();
1457
+ mergedData[col.name] = triggerTimestamp(col);
1446
1458
  }
1447
1459
  }
1448
1460
  }
@@ -1487,27 +1499,27 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1487
1499
  if (SYSTEM_ASSIGNED_COLUMNS.has(col.name) && Object.hasOwn(data, col.name) && coerceValue(data[col.name], col.storage, col.interpretation) !== coerceValue(current, col.storage, col.interpretation)) {
1488
1500
  return { success: false, error: systemManagedColumnError(col.name, isUpdate) };
1489
1501
  }
1490
- if (current !== void 0) finalData[col.name] = current;
1502
+ if (current !== void 0)
1503
+ finalData[col.name] = current;
1491
1504
  else delete finalData[col.name];
1492
1505
  }
1493
1506
  }
1494
1507
  const colValues = dataToColumnValues(finalData, columns);
1495
1508
  const now = (/* @__PURE__ */ new Date()).toISOString();
1496
1509
  const tbl = collectionTableName(collection);
1497
- if (!isUpdate && schema.uniqueOn && schema.uniqueOn.length > 0) {
1498
- const uniqueWhere = schema.uniqueOn.map((fieldName) => {
1499
- const colSqlId = columnId(fieldName);
1500
- return `"${colSqlId}" = ?`;
1501
- });
1502
- const uniqueParams = schema.uniqueOn.map((fieldName) => {
1503
- const val = finalData[fieldName];
1504
- return val !== void 0 ? val : null;
1510
+ if (schema.uniqueOn && schema.uniqueOn.length > 0) {
1511
+ const uniqueColumns = schema.uniqueOn.map((fieldName) => {
1512
+ const id = columns.find((column) => column.name === fieldName)?.id ?? columnId(fieldName);
1513
+ return { id, value: colValues[id] ?? null };
1505
1514
  });
1506
- const existing2 = ctx.sql.exec(
1507
- `SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")} LIMIT 1`,
1515
+ const uniqueWhere = uniqueColumns.map(({ id }) => `"${id}" = ?`);
1516
+ const uniqueParams = uniqueColumns.map(({ value }) => value);
1517
+ if (isUpdate) uniqueParams.push(recordId);
1518
+ const conflict = ctx.sql.exec(
1519
+ `SELECT _row_id FROM "${tbl}" WHERE ${uniqueWhere.join(" AND ")}${isUpdate ? " AND _row_id <> ?" : ""} LIMIT 1`,
1508
1520
  ...uniqueParams
1509
1521
  );
1510
- if (existing2.toArray().length > 0) {
1522
+ if (conflict.toArray().length > 0) {
1511
1523
  const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
1512
1524
  return {
1513
1525
  success: false,
@@ -1574,8 +1586,21 @@ function deleteRecord(ctx, collection, recordId, userId, userRole, skipUserRbac
1574
1586
  return { success: true, data: { deleted: true } };
1575
1587
  }
1576
1588
  function refuseUnknownWhere(collection, schema, where) {
1577
- const columns = (schema.columns ?? []).map(resolveColumn);
1589
+ const columns = (schema?.columns ?? []).map(resolveColumn);
1578
1590
  const filterable = ["recordId", "createdBy", ...columns.map((c) => c.name)];
1591
+ if (typeof where !== "object" || where === null || Array.isArray(where)) {
1592
+ const got = where === null ? "null" : Array.isArray(where) ? "an array" : typeof where;
1593
+ return {
1594
+ success: false,
1595
+ error: `where for "${collection}" must be an object of field=value pairs, not ${got} \u2014 filterable fields: ${filterable.join(", ")}`
1596
+ };
1597
+ }
1598
+ if (!schema && Object.keys(where).length > 0) {
1599
+ return {
1600
+ success: false,
1601
+ error: `"${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
1602
+ };
1603
+ }
1579
1604
  const unknown = Object.keys(where).filter(
1580
1605
  (key) => key !== "recordId" && key !== "createdBy" && !columns.some((c) => c.id === columnId(key) || c.name === key)
1581
1606
  );
@@ -1604,11 +1629,14 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
1604
1629
  error: `records.deleteWhere: collection "${collection}" has no schema, so it has no filterable fields \u2014 delete its rows by recordId with records.delete`
1605
1630
  };
1606
1631
  }
1607
- if (!where || typeof where !== "object" || Array.isArray(where) || Object.keys(where).length === 0) {
1608
- return { success: false, error: "Missing required param: where (must match at least one field)" };
1609
- }
1610
1632
  const whereRefusal = refuseUnknownWhere(collection, schema, where);
1611
1633
  if (whereRefusal) return whereRefusal;
1634
+ if (Object.keys(where).length === 0) {
1635
+ return {
1636
+ success: false,
1637
+ error: "Missing required param: where (must match at least one field)"
1638
+ };
1639
+ }
1612
1640
  if (limit !== void 0 && !(typeof limit === "number" && Number.isFinite(limit))) {
1613
1641
  return { success: false, error: "limit must be a finite number" };
1614
1642
  }
@@ -1616,32 +1644,27 @@ function deleteWhere(ctx, collection, where, limit, userId, userRole, skipUserRb
1616
1644
  Math.max(1, Math.floor(limit ?? DEFAULT_DELETE_WHERE_LIMIT)),
1617
1645
  MAX_DELETE_WHERE_LIMIT
1618
1646
  );
1619
- const scan = { capped: false };
1620
1647
  const matched = executeQuery(
1621
1648
  ctx,
1622
1649
  { collection, where, limit: page },
1623
1650
  userId,
1624
1651
  userRole,
1625
- skipUserRbac,
1626
- scan
1652
+ true
1627
1653
  );
1628
- if (scan.capped && matched.length < page) {
1629
- return {
1630
- success: false,
1631
- error: `records.deleteWhere scanned ${MAX_FILTERED_SCAN_ROWS} rows without filling a page of readable matches in "${collection}" \u2014 narrow \`where\` (or delete by recordId) and retry`
1632
- };
1633
- }
1634
- if (!skipUserRbac && schema) {
1654
+ if (!skipUserRbac) {
1635
1655
  const permCtx = ctx.getPermissionContext();
1636
1656
  for (const record of matched) {
1637
1657
  if (!canDelete(schema, userRole, record, userId, permCtx)) {
1638
- return { success: false, error: `DELETE DENIED: role=${userRole}, collection=${collection}` };
1658
+ return {
1659
+ success: false,
1660
+ error: `DELETE DENIED: role=${userRole}, collection=${collection}`
1661
+ };
1639
1662
  }
1640
1663
  }
1641
1664
  }
1642
1665
  let deleted = 0;
1643
1666
  for (const record of matched) {
1644
- if (deleteRecord(ctx, collection, record.recordId, userId, userRole, skipUserRbac).success) {
1667
+ if (deleteRecord(ctx, collection, record.recordId, userId, userRole, true).success) {
1645
1668
  deleted++;
1646
1669
  }
1647
1670
  }
@@ -2340,17 +2363,17 @@ async function registerUser(sql, userId, name, email, imageUrl, isAdmin, default
2340
2363
  );
2341
2364
  return { id: userId, email, name, imageUrl, role, createdAt: now, lastSeenAt: now };
2342
2365
  }
2343
- function rosterFor(ctx, schema, records, publicRoster, attachment) {
2344
- if (isAnonymousUserId(attachment.userId)) return [];
2345
- if (attachment.role === "admin") {
2366
+ function rosterFor(ctx, schema, records, publicRoster, userId, role) {
2367
+ if (!userId || isAnonymousUserId(userId)) return [];
2368
+ if (role === "admin") {
2346
2369
  const permissionContext = ctx.getPermissionContext();
2347
- return records.filter((record) => canRead(schema, "admin", record, attachment.userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
2370
+ return records.filter((record) => canRead(schema, "admin", record, userId, permissionContext)).map((record) => ({ id: record.recordId, ...record.data }));
2348
2371
  }
2349
- if (!getRolePermissions(schema, attachment.role).read) return [];
2372
+ if (!getRolePermissions(schema, role).read) return [];
2350
2373
  if (schema.roster === "read-policy") {
2351
2374
  const permissionContext = ctx.getPermissionContext();
2352
2375
  const readable = new Set(
2353
- records.filter((record) => canRead(schema, attachment.role, record, attachment.userId, permissionContext)).map((record) => record.recordId)
2376
+ records.filter((record) => canRead(schema, role, record, userId, permissionContext)).map((record) => record.recordId)
2354
2377
  );
2355
2378
  return publicRoster.filter((user) => readable.has(user.id));
2356
2379
  }
@@ -2361,17 +2384,18 @@ function publicIdentities(records) {
2361
2384
  id: recordId,
2362
2385
  name: data.name,
2363
2386
  ...data.imageUrl ? { imageUrl: data.imageUrl } : {},
2364
- role: data.role
2387
+ role: data.role,
2388
+ lastSeenAt: data.lastSeenAt
2365
2389
  }));
2366
2390
  }
2367
- function handleUserList(ctx, ws, attachment) {
2391
+ function rosterForCaller(ctx, userId, role) {
2368
2392
  const schema = ctx.schemaRegistry.get("users");
2369
- if (!schema) {
2370
- ctx.send(ws, { type: MSG.USER_LIST, payload: { users: [] } });
2371
- return;
2372
- }
2393
+ if (!schema) return [];
2373
2394
  const records = getAllUserRecords(ctx.sql, ctx.schemaRegistry);
2374
- const users = rosterFor(ctx, schema, records, publicIdentities(records), attachment);
2395
+ return rosterFor(ctx, schema, records, publicIdentities(records), userId, role);
2396
+ }
2397
+ function handleUserList(ctx, ws, attachment) {
2398
+ const users = rosterForCaller(ctx, attachment.userId, attachment.role);
2375
2399
  ctx.send(ws, { type: MSG.USER_LIST, payload: { users } });
2376
2400
  }
2377
2401
  function broadcastUserList(ctx, except) {
@@ -2386,7 +2410,9 @@ function broadcastUserList(ctx, except) {
2386
2410
  if (!attachment) continue;
2387
2411
  ctx.send(other, {
2388
2412
  type: MSG.USER_LIST,
2389
- payload: { users: rosterFor(ctx, schema, records, publicRoster, attachment) }
2413
+ payload: {
2414
+ users: rosterFor(ctx, schema, records, publicRoster, attachment.userId, attachment.role)
2415
+ }
2390
2416
  });
2391
2417
  }
2392
2418
  }
@@ -2439,8 +2465,8 @@ var CORS_HEADERS = {
2439
2465
  "Access-Control-Allow-Headers": "Content-Type, X-User-Id, X-App-Action",
2440
2466
  "Content-Type": "application/json"
2441
2467
  };
2442
- async function handleToolsRequest(ctx, request, path) {
2443
- if (request.method === "OPTIONS") {
2468
+ async function handleToolsRequest(ctx, request2, path) {
2469
+ if (request2.method === "OPTIONS") {
2444
2470
  return new Response(null, { headers: CORS_HEADERS });
2445
2471
  }
2446
2472
  if (path === "tools/list") {
@@ -2457,15 +2483,15 @@ async function handleToolsRequest(ctx, request, path) {
2457
2483
  }
2458
2484
  return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
2459
2485
  }
2460
- if (path.startsWith("tools/execute") && request.method === "POST") {
2461
- return handleToolExecute(ctx, request);
2486
+ if (path.startsWith("tools/execute") && request2.method === "POST") {
2487
+ return handleToolExecute(ctx, request2);
2462
2488
  }
2463
2489
  return Response.json({ error: "Not found" }, { status: 404, headers: CORS_HEADERS });
2464
2490
  }
2465
- async function handleToolExecute(ctx, request) {
2491
+ async function handleToolExecute(ctx, request2) {
2466
2492
  let body;
2467
2493
  try {
2468
- body = await request.json();
2494
+ body = await request2.json();
2469
2495
  } catch {
2470
2496
  return Response.json(
2471
2497
  { success: false, error: "Invalid JSON body" },
@@ -2479,8 +2505,8 @@ async function handleToolExecute(ctx, request) {
2479
2505
  { status: 400, headers: CORS_HEADERS }
2480
2506
  );
2481
2507
  }
2482
- const userId = request.headers.get("x-user-id") ?? "";
2483
- const isAppAction = request.headers.get("x-app-action")?.toLowerCase() === "true";
2508
+ const userId = request2.headers.get("x-user-id") ?? "";
2509
+ const isAppAction = request2.headers.get("x-app-action")?.toLowerCase() === "true";
2484
2510
  let userRole = "viewer";
2485
2511
  if (userId && ctx.ownerUserId && userId === ctx.ownerUserId) {
2486
2512
  userRole = "admin";
@@ -2515,17 +2541,11 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
2515
2541
  if (!collection) return { success: false, error: "Missing required param: collection" };
2516
2542
  const resolved = resolveCollection(ctx, collection);
2517
2543
  if (!resolved.ok) return { success: false, error: resolved.error };
2518
- const where = params.where;
2519
- if (where && typeof where === "object" && Object.keys(where).length > 0) {
2520
- if (!resolved.schema) {
2521
- return {
2522
- success: false,
2523
- error: `records.query: collection "${collection}" has no schema, so it has no filterable fields \u2014 query it without \`where\``
2524
- };
2525
- }
2526
- const refusal = refuseUnknownWhere(collection, resolved.schema, where);
2544
+ if (params.where !== void 0) {
2545
+ const refusal = refuseUnknownWhere(collection, resolved.schema, params.where);
2527
2546
  if (refusal) return refusal;
2528
2547
  }
2548
+ const where = params.where;
2529
2549
  const query = {
2530
2550
  collection,
2531
2551
  where,
@@ -2634,8 +2654,16 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
2634
2654
  if (!user) return { success: false, error: "User not found" };
2635
2655
  return { success: true, data: { user } };
2636
2656
  }
2657
+ // The same roster the socket's `user.list` returns, for the same caller.
2658
+ // Reading the users table directly here handed any member every peer's
2659
+ // email regardless of the room's users policy, while their own socket
2660
+ // roster and `useQuery('users')` obeyed it.
2661
+ //
2662
+ // An app action (`X-App-Action`) is the app's own server-side code, which
2663
+ // is already the trust boundary and reads every other collection RBAC-off;
2664
+ // it keeps the full rows.
2637
2665
  case "user.list": {
2638
- const users = getAllUsers(ctx.sql, ctx.schemaRegistry);
2666
+ const users = skipUserRbac ? getAllUsers(ctx.sql, ctx.schemaRegistry) : rosterForCaller(ctx, userId, userRole);
2639
2667
  return { success: true, data: { users } };
2640
2668
  }
2641
2669
  default:
@@ -2770,25 +2798,25 @@ var CORS_HEADERS2 = {
2770
2798
  "Access-Control-Allow-Headers": "Content-Type",
2771
2799
  "Content-Type": "application/json"
2772
2800
  };
2773
- async function handleApiRequest(ctx, request, url) {
2774
- if (request.method === "OPTIONS") {
2801
+ async function handleApiRequest(ctx, request2, url) {
2802
+ if (request2.method === "OPTIONS") {
2775
2803
  return new Response(null, { headers: CORS_HEADERS2 });
2776
2804
  }
2777
2805
  const path = url.pathname.replace("/api/", "");
2778
2806
  if (path.startsWith("tools/")) {
2779
- return handleToolsRequest(ctx, request, path);
2807
+ return handleToolsRequest(ctx, request2, path);
2780
2808
  }
2781
2809
  if (path === "debug/status") {
2782
2810
  return handleDebugStatus(ctx);
2783
2811
  }
2784
2812
  if (path === "debug/sql") {
2785
- return handleDebugSql(ctx, request, url);
2813
+ return handleDebugSql(ctx, request2, url);
2786
2814
  }
2787
2815
  if (path === "debug/user-role") {
2788
2816
  return handleDebugUserRole(ctx, url);
2789
2817
  }
2790
- if (path === "debug/set-role" && request.method === "POST") {
2791
- return handleDebugSetRole(ctx, request);
2818
+ if (path === "debug/set-role" && request2.method === "POST") {
2819
+ return handleDebugSetRole(ctx, request2);
2792
2820
  }
2793
2821
  if (path.startsWith("debug/records/")) {
2794
2822
  return handleDebugRecords(ctx, path);
@@ -2819,8 +2847,8 @@ function handleDebugUserRole(ctx, url) {
2819
2847
  role: user.role
2820
2848
  }, { headers: CORS_HEADERS2 });
2821
2849
  }
2822
- async function handleDebugSetRole(ctx, request) {
2823
- const body = await request.json();
2850
+ async function handleDebugSetRole(ctx, request2) {
2851
+ const body = await request2.json();
2824
2852
  if (!body.role) {
2825
2853
  return Response.json({ error: "role is required" }, { status: 400, headers: CORS_HEADERS2 });
2826
2854
  }
@@ -2928,11 +2956,11 @@ function handleDebugQuery(ctx, url) {
2928
2956
  records
2929
2957
  }, { headers: CORS_HEADERS2 });
2930
2958
  }
2931
- async function handleDebugSql(ctx, request, url) {
2959
+ async function handleDebugSql(ctx, request2, url) {
2932
2960
  let sql;
2933
2961
  let params = [];
2934
- if (request.method === "POST") {
2935
- const body = await request.json();
2962
+ if (request2.method === "POST") {
2963
+ const body = await request2.json();
2936
2964
  sql = body.sql;
2937
2965
  params = body.params ?? [];
2938
2966
  } else {
@@ -3052,10 +3080,10 @@ var RecordRoom = class extends BaseRoom {
3052
3080
  // ============================================================================
3053
3081
  // HTTP Entry Point (overrides BaseRoom.fetch)
3054
3082
  // ============================================================================
3055
- async fetch(request) {
3056
- const url = new URL(request.url);
3083
+ async fetch(request2) {
3084
+ const url = new URL(request2.url);
3057
3085
  if (url.pathname.startsWith("/internal/")) {
3058
- const internal = await this.handleInternalRequest(request, url);
3086
+ const internal = await this.handleInternalRequest(request2, url);
3059
3087
  if (internal) return internal;
3060
3088
  }
3061
3089
  const fetchStart = Date.now();
@@ -3068,11 +3096,11 @@ var RecordRoom = class extends BaseRoom {
3068
3096
  if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
3069
3097
  return new Response("Not Found", { status: 404 });
3070
3098
  }
3071
- return handleApiRequest(this.createHandlerContext(), request, url);
3099
+ return handleApiRequest(this.createHandlerContext(), request2, url);
3072
3100
  }
3073
- if (request.headers.get("Upgrade") === "websocket") {
3101
+ if (request2.headers.get("Upgrade") === "websocket") {
3074
3102
  this._fetchTiming = { fetchStart, isColdStart, needsInit, initMs };
3075
- return super.fetch(request);
3103
+ return super.fetch(request2);
3076
3104
  }
3077
3105
  return new Response("Not Found", { status: 404 });
3078
3106
  }
@@ -3654,9 +3682,9 @@ var CanvasRoom = class extends BaseRoom {
3654
3682
  // --------------------------------------------------------------------------
3655
3683
  // BaseRoom Lifecycle
3656
3684
  // --------------------------------------------------------------------------
3657
- async fetch(request) {
3685
+ async fetch(request2) {
3658
3686
  this.ensureInitialized();
3659
- return super.fetch(request);
3687
+ return super.fetch(request2);
3660
3688
  }
3661
3689
  onConnect(ws, user) {
3662
3690
  this.ensureInitialized();
@@ -4135,6 +4163,7 @@ function cronMatches(parsed, timezone, date) {
4135
4163
  }
4136
4164
 
4137
4165
  // src/server/rooms/cron-room.ts
4166
+ var CRON_TASKS_INTERNAL_PATH = "/internal/cron/tasks";
4138
4167
  var CRON_WRITE_TYPES = /* @__PURE__ */ new Set([
4139
4168
  MSG.CRON_TRIGGER,
4140
4169
  MSG.CRON_PAUSE,
@@ -4209,9 +4238,15 @@ var CronRoom = class extends BaseRoom {
4209
4238
  // ==========================================================================
4210
4239
  // BaseRoom Lifecycle
4211
4240
  // ==========================================================================
4212
- async fetch(request) {
4241
+ async fetch(request2) {
4213
4242
  this.ensureInitialized();
4214
- return super.fetch(request);
4243
+ return super.fetch(request2);
4244
+ }
4245
+ async handleInternalRequest(request2, url) {
4246
+ if (request2.method === "GET" && url.pathname === CRON_TASKS_INTERNAL_PATH) {
4247
+ return Response.json({ tasks: this.getTaskStates() });
4248
+ }
4249
+ return super.handleInternalRequest(request2, url);
4215
4250
  }
4216
4251
  onConnect(ws, user) {
4217
4252
  this.ensureInitialized();
@@ -4359,15 +4394,22 @@ var CronRoom = class extends BaseRoom {
4359
4394
  const start = Date.now();
4360
4395
  let success = true;
4361
4396
  let error;
4397
+ let thrown;
4362
4398
  try {
4363
4399
  await this.onTask(taskName);
4364
4400
  } catch (e) {
4365
4401
  success = false;
4402
+ thrown = e;
4366
4403
  error = e instanceof Error ? e.message : String(e);
4367
- console.error(`[CronRoom] Task "${taskName}" failed:`, e);
4368
4404
  }
4369
4405
  const durationMs = Date.now() - start;
4370
4406
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
4407
+ if (success) {
4408
+ console.log(`[cron] ${taskName} ok ${durationMs}ms`);
4409
+ } else {
4410
+ const stack = thrown instanceof Error ? thrown.stack : void 0;
4411
+ console.error(`[cron] ${taskName} failed ${durationMs}ms: ${error}`, ...stack ? [stack] : []);
4412
+ }
4371
4413
  this.sql.exec(
4372
4414
  `INSERT INTO cron_history (task_name, started_at, completed_at, success, duration_ms, error, created_at)
4373
4415
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
@@ -4476,6 +4518,19 @@ var CronRoom = class extends BaseRoom {
4476
4518
  );
4477
4519
  }
4478
4520
  };
4521
+ var armedCronRooms = /* @__PURE__ */ new Set();
4522
+ function armCronRoom(ctx, namespace, roomId, tasks) {
4523
+ if (tasks.length === 0 || armedCronRooms.has(roomId)) return;
4524
+ armedCronRooms.add(roomId);
4525
+ ctx.waitUntil(
4526
+ namespace.get(namespace.idFromName(roomId)).fetch(`https://internal${CRON_TASKS_INTERNAL_PATH}`).then((response) => {
4527
+ if (!response.ok) armedCronRooms.delete(roomId);
4528
+ return response;
4529
+ }).catch(() => {
4530
+ armedCronRooms.delete(roomId);
4531
+ })
4532
+ );
4533
+ }
4479
4534
  function computeNextRunAt(task, from) {
4480
4535
  if (task.interval_minutes) {
4481
4536
  return new Date(from.getTime() + task.interval_minutes * 60 * 1e3).toISOString();
@@ -4548,9 +4603,9 @@ var JobRoom = class extends BaseRoom {
4548
4603
  // ==========================================================================
4549
4604
  // BaseRoom Lifecycle
4550
4605
  // ==========================================================================
4551
- async fetch(request) {
4606
+ async fetch(request2) {
4552
4607
  this.ensureInitialized();
4553
- return super.fetch(request);
4608
+ return super.fetch(request2);
4554
4609
  }
4555
4610
  async onConnect(ws, user) {
4556
4611
  this.ensureInitialized();
@@ -4637,13 +4692,13 @@ var JobRoom = class extends BaseRoom {
4637
4692
  * Subclasses overriding `onRequest` should call `super.onRequest(req)`
4638
4693
  * for paths they don't handle.
4639
4694
  */
4640
- async onRequest(request) {
4695
+ async onRequest(request2) {
4641
4696
  this.ensureInitialized();
4642
- const url = new URL(request.url);
4643
- if (request.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
4697
+ const url = new URL(request2.url);
4698
+ if (request2.method !== "POST" || !url.pathname.endsWith("/enqueue")) {
4644
4699
  return new Response("Not Found", { status: 404 });
4645
4700
  }
4646
- const body = await request.json().catch(() => null);
4701
+ const body = await request2.json().catch(() => null);
4647
4702
  if (!body || typeof body.type !== "string" || !body.type) {
4648
4703
  return new Response(JSON.stringify({ error: "type required" }), {
4649
4704
  status: 400,
@@ -4809,7 +4864,8 @@ var JobRoom = class extends BaseRoom {
4809
4864
  } catch (e) {
4810
4865
  outcome = "failed";
4811
4866
  errorMessage = e instanceof Error ? e.message : String(e);
4812
- console.error(`[JobRoom] Job "${row.type}" (${row.id}) failed:`, e);
4867
+ const stack = e instanceof Error ? e.stack : void 0;
4868
+ console.error(`[jobs] ${row.type} (${row.id}) failed: ${errorMessage}`, ...stack ? [stack] : []);
4813
4869
  } finally {
4814
4870
  this.inFlight.delete(row.id);
4815
4871
  }
@@ -5123,7 +5179,8 @@ var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
5123
5179
  "kv_namespace",
5124
5180
  "vectorize",
5125
5181
  "r2_bucket",
5126
- "queue"
5182
+ "queue",
5183
+ "ai_search"
5127
5184
  ]);
5128
5185
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
5129
5186
  function isAutoProvision(b) {
@@ -5138,6 +5195,8 @@ function isAutoProvision(b) {
5138
5195
  return b.bucket_name === AUTO_PROVISION_SENTINEL;
5139
5196
  case "queue":
5140
5197
  return b.queue_name === AUTO_PROVISION_SENTINEL;
5198
+ case "ai_search":
5199
+ return b.instance_name === AUTO_PROVISION_SENTINEL;
5141
5200
  default:
5142
5201
  return false;
5143
5202
  }
@@ -5152,7 +5211,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
5152
5211
  "browser_rendering",
5153
5212
  "analytics_engine",
5154
5213
  "hyperdrive",
5155
- "ratelimit"
5214
+ "ratelimit",
5215
+ "ai_search"
5156
5216
  ]);
5157
5217
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5158
5218
  "ASSETS",
@@ -5176,6 +5236,7 @@ function validateBindingManifest(manifest) {
5176
5236
  }
5177
5237
  const errors = [];
5178
5238
  const seenNames = /* @__PURE__ */ new Set();
5239
+ let aiSearchCount = 0;
5179
5240
  for (const entry of manifest) {
5180
5241
  if (!entry || typeof entry !== "object") {
5181
5242
  errors.push({
@@ -5206,14 +5267,23 @@ function validateBindingManifest(manifest) {
5206
5267
  }
5207
5268
  seenNames.add(name);
5208
5269
  const binding = entry;
5270
+ if (binding.type === "ai_search") aiSearchCount++;
5209
5271
  const fieldErr = requiredFieldError(binding);
5210
5272
  if (fieldErr) errors.push({ binding, reason: fieldErr });
5211
5273
  }
5274
+ if (aiSearchCount > 1) {
5275
+ errors.push({ reason: "Only one ai_search binding may be declared per app" });
5276
+ }
5212
5277
  if (errors.length) return { valid: false, errors };
5213
5278
  return { valid: true, bindings: manifest };
5214
5279
  }
5215
5280
  function requiredFieldError(b) {
5216
5281
  switch (b.type) {
5282
+ case "ai_search":
5283
+ if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
5284
+ return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
5285
+ }
5286
+ return null;
5217
5287
  case "vectorize": {
5218
5288
  if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
5219
5289
  if (b.index_name === AUTO_PROVISION_SENTINEL) {
@@ -5275,6 +5345,13 @@ function requiredFieldError(b) {
5275
5345
  }
5276
5346
  }
5277
5347
  function bindingManifestFromOutputConfig(outputConfig) {
5348
+ const aiSearchNamespaces = outputConfig.ai_search_namespaces;
5349
+ const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
5350
+ if (declaresAiSearchNamespace) {
5351
+ throw new Error(
5352
+ 'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
5353
+ );
5354
+ }
5278
5355
  const out = [];
5279
5356
  const aiBinding = pluckString(outputConfig.ai, "binding");
5280
5357
  if (aiBinding) out.push({ type: "ai", name: aiBinding });
@@ -5292,6 +5369,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
5292
5369
  ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
5293
5370
  });
5294
5371
  }
5372
+ for (const a of asObjectArray(outputConfig.ai_search)) {
5373
+ const name = pluckString(a, "binding");
5374
+ const instance_name = pluckString(a, "instance_name");
5375
+ if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
5376
+ }
5295
5377
  for (const r of asObjectArray(outputConfig.r2_buckets)) {
5296
5378
  const name = pluckString(r, "binding");
5297
5379
  const bucket_name = pluckString(r, "bucket_name");
@@ -5827,218 +5909,8 @@ var DIRECTORY_SCHEMAS = [
5827
5909
  postsSchema
5828
5910
  ];
5829
5911
 
5830
- // src/server/schemas/workspace.ts
5831
- var workspaceTeamsSchema = {
5832
- name: "teams",
5833
- columns: [
5834
- { name: "Name", storage: "text", interpretation: "plain" },
5835
- { name: "CreatedBy", storage: "text", interpretation: "plain" },
5836
- { name: "IsOpen", storage: "number", interpretation: { kind: "boolean" } }
5837
- ],
5838
- teamField: "_rowId",
5839
- permissions: {
5840
- admin: { read: true, create: true, update: true, delete: true },
5841
- member: { read: "team", create: true, update: "team", delete: "own" }
5842
- }
5843
- };
5844
- var workspaceTeamMembersSchema = {
5845
- name: "team_members",
5846
- columns: [
5847
- { name: "TeamId", storage: "text", interpretation: "plain" },
5848
- { name: "UserId", storage: "text", interpretation: "plain" },
5849
- { name: "RoleInTeam", storage: "text", interpretation: { kind: "select", options: ["admin", "member"] } },
5850
- { name: "JoinedAt", storage: "text", interpretation: { kind: "datetime" } },
5851
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
5852
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "invited"] } }
5853
- ],
5854
- teamField: "TeamId",
5855
- permissions: {
5856
- admin: { read: true, create: true, update: true, delete: true },
5857
- member: { read: "team", create: true, update: "team", delete: "team" }
5858
- }
5859
- };
5860
- var workspaceTasksSchema = {
5861
- name: "tasks",
5862
- columns: [
5863
- { name: "TeamId", storage: "text", interpretation: "plain" },
5864
- { name: "Title", storage: "text", interpretation: "plain" },
5865
- { name: "Notes", storage: "text", interpretation: "plain" },
5866
- { name: "Completed", storage: "number", interpretation: { kind: "boolean" } },
5867
- { name: "CompletedAt", storage: "number", interpretation: "plain" },
5868
- { name: "Deleted", storage: "number", interpretation: { kind: "boolean" } },
5869
- { name: "DeletedAt", storage: "number", interpretation: "plain" },
5870
- { name: "Priority", storage: "text", interpretation: { kind: "select", options: ["none", "low", "medium", "high"] } },
5871
- { name: "DueDate", storage: "text", interpretation: "plain" },
5872
- { name: "ProjectId", storage: "text", interpretation: "plain" },
5873
- { name: "KanbanStatus", storage: "text", interpretation: { kind: "select", options: ["backlog", "ready", "in_progress", "review", "done"] } },
5874
- { name: "Order", storage: "number", interpretation: "plain" },
5875
- { name: "AssignedUser", storage: "text", interpretation: { kind: "json" } },
5876
- { name: "AssignedBy", storage: "text", interpretation: { kind: "json" } },
5877
- { name: "TagIds", storage: "text", interpretation: { kind: "json" } },
5878
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
5879
- ],
5880
- teamField: "TeamId",
5881
- permissions: {
5882
- admin: { read: true, create: true, update: true, delete: true },
5883
- member: { read: "team", create: true, update: "team", delete: "team" },
5884
- viewer: { read: false, create: false, update: false, delete: false }
5885
- }
5886
- };
5887
- var workspaceProjectsSchema = {
5888
- name: "projects",
5889
- columns: [
5890
- { name: "TeamId", storage: "text", interpretation: "plain" },
5891
- { name: "Title", storage: "text", interpretation: "plain" },
5892
- { name: "Notes", storage: "text", interpretation: "plain" },
5893
- { name: "Color", storage: "text", interpretation: "plain" },
5894
- { name: "ParentId", storage: "text", interpretation: "plain" },
5895
- { name: "Order", storage: "number", interpretation: "plain" },
5896
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
5897
- ],
5898
- teamField: "TeamId",
5899
- permissions: {
5900
- admin: { read: true, create: true, update: true, delete: true },
5901
- member: { read: "team", create: true, update: "team", delete: "team" },
5902
- viewer: { read: false, create: false, update: false, delete: false }
5903
- }
5904
- };
5905
- var workspaceTagsSchema = {
5906
- name: "tags",
5907
- columns: [
5908
- { name: "TeamId", storage: "text", interpretation: "plain" },
5909
- { name: "Name", storage: "text", interpretation: "plain" },
5910
- { name: "Color", storage: "text", interpretation: "plain" },
5911
- { name: "CreatedAt", storage: "number", interpretation: "plain" }
5912
- ],
5913
- teamField: "TeamId",
5914
- permissions: {
5915
- admin: { read: true, create: true, update: true, delete: true },
5916
- member: { read: "team", create: true, update: "team", delete: "team" },
5917
- viewer: { read: false, create: false, update: false, delete: false }
5918
- }
5919
- };
5920
- var workspacePeopleSchema = {
5921
- name: "people",
5922
- columns: [
5923
- { name: "Name", storage: "text", interpretation: "plain" },
5924
- { name: "Email", storage: "text", interpretation: { kind: "email" } },
5925
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["employee", "customer", "vendor", "contact"] } },
5926
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "inactive", "archived"] } },
5927
- { name: "CompanyId", storage: "text", interpretation: "plain" },
5928
- { name: "Department", storage: "text", interpretation: "plain" },
5929
- { name: "Title", storage: "text", interpretation: "plain" },
5930
- { name: "LastContactedAt", storage: "text", interpretation: { kind: "datetime" } },
5931
- { name: "Metadata", storage: "text", interpretation: { kind: "json" } }
5932
- ],
5933
- permissions: {
5934
- "*": { read: true, create: true, update: true, delete: false },
5935
- admin: { read: true, create: true, update: true, delete: true }
5936
- }
5937
- };
5938
- var workspaceTransactionsSchema = {
5939
- name: "transactions",
5940
- columns: [
5941
- { name: "Type", storage: "text", interpretation: { kind: "select", options: ["revenue", "expense", "payment", "transfer", "invoice", "refund"] } },
5942
- { name: "Amount", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
5943
- { name: "Currency", storage: "text", interpretation: "plain" },
5944
- { name: "Description", storage: "text", interpretation: "plain" },
5945
- { name: "Category", storage: "text", interpretation: "plain" },
5946
- { name: "CounterpartyRef", storage: "text", interpretation: "plain" },
5947
- { name: "OrderRef", storage: "text", interpretation: "plain" },
5948
- { name: "Account", storage: "text", interpretation: "plain" }
5949
- ],
5950
- permissions: {
5951
- "*": { read: true, create: true, update: true, delete: false },
5952
- admin: { read: true, create: true, update: true, delete: true }
5953
- }
5954
- };
5955
- var workspaceAccountsSchema = {
5956
- name: "accounts",
5957
- columns: [
5958
- { name: "Name", storage: "text", interpretation: "plain" },
5959
- { name: "Balance", storage: "number", interpretation: { kind: "currency", symbol: "$", decimals: 2 } },
5960
- { name: "Currency", storage: "text", interpretation: "plain" }
5961
- ],
5962
- permissions: {
5963
- "*": { read: true, create: true, update: true, delete: false },
5964
- admin: { read: true, create: true, update: true, delete: true }
5965
- }
5966
- };
5967
- var workspaceContentSharesSchema = {
5968
- name: "content_shares",
5969
- ownerField: "OwnerId",
5970
- columns: [
5971
- { name: "ContentType", storage: "text", interpretation: { kind: "select", options: ["document", "slide", "spreadsheet"] } },
5972
- { name: "ContentId", storage: "text", interpretation: "plain" },
5973
- { name: "OwnerId", storage: "text", interpretation: "plain" },
5974
- { name: "OwnerName", storage: "text", interpretation: "plain" },
5975
- { name: "Title", storage: "text", interpretation: "plain" },
5976
- { name: "ShareType", storage: "text", interpretation: { kind: "select", options: ["self", "channel", "team", "direct", "link", "org"] } },
5977
- { name: "ShareTarget", storage: "text", interpretation: "plain" },
5978
- { name: "Permission", storage: "text", interpretation: { kind: "select", options: ["view", "edit"] } },
5979
- { name: "SharedAt", storage: "text", interpretation: { kind: "datetime" } },
5980
- { name: "SharedBy", storage: "text", interpretation: "plain" },
5981
- { name: "SourceApp", storage: "text", interpretation: "plain" },
5982
- { name: "WordCount", storage: "number", interpretation: "plain" },
5983
- { name: "LastEditedAt", storage: "text", interpretation: { kind: "datetime" } }
5984
- ],
5985
- permissions: {
5986
- "*": { read: true, create: true, update: true, delete: "own" },
5987
- admin: { read: true, create: true, update: true, delete: true }
5988
- }
5989
- };
5990
- var workspaceFormResponsesSchema = {
5991
- name: "form_responses",
5992
- columns: [
5993
- { name: "FormId", storage: "text", interpretation: "plain" },
5994
- { name: "FormTitle", storage: "text", interpretation: "plain" },
5995
- { name: "RespondentId", storage: "text", interpretation: "plain" },
5996
- { name: "RespondentEmail", storage: "text", interpretation: { kind: "email" } },
5997
- { name: "RespondentName", storage: "text", interpretation: "plain" },
5998
- { name: "Data", storage: "text", interpretation: { kind: "json" } },
5999
- { name: "SubmittedAt", storage: "text", interpretation: { kind: "datetime" } }
6000
- ],
6001
- permissions: {
6002
- "*": { read: true, create: true, update: false, delete: false },
6003
- admin: { read: true, create: true, update: true, delete: true }
6004
- }
6005
- };
6006
- var workspaceEmailHandlesSchema = {
6007
- name: "email_handles",
6008
- columns: [
6009
- { name: "UserId", storage: "text", interpretation: "plain" },
6010
- { name: "Handle", storage: "text", interpretation: "plain" },
6011
- { name: "EmailAddress", storage: "text", interpretation: { kind: "email" } },
6012
- { name: "Status", storage: "text", interpretation: { kind: "select", options: ["active", "disabled"] } }
6013
- ],
6014
- uniqueOn: ["Handle"],
6015
- ownerField: "UserId",
6016
- permissions: {
6017
- "*": { read: true, create: true, update: "own", delete: false },
6018
- admin: { read: true, create: true, update: true, delete: true }
6019
- }
6020
- };
6021
- var WORKSPACE_SCHEMAS = [
6022
- workspaceTeamsSchema,
6023
- workspaceTeamMembersSchema,
6024
- workspaceTasksSchema,
6025
- workspaceProjectsSchema,
6026
- workspaceTagsSchema,
6027
- workspacePeopleSchema,
6028
- workspaceTransactionsSchema,
6029
- workspaceAccountsSchema,
6030
- workspaceContentSharesSchema,
6031
- workspaceFormResponsesSchema,
6032
- workspaceEmailHandlesSchema
6033
- ];
6034
-
6035
5912
  // src/server/schemas/shared-do.ts
6036
5913
  var GLOBAL_DO_TYPES = [
6037
- {
6038
- name: "workspace",
6039
- schemas: WORKSPACE_SCHEMAS,
6040
- description: "Shared workspace data (teams, tasks, projects, people, ledger)"
6041
- },
6042
5914
  {
6043
5915
  name: "conv",
6044
5916
  schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
@@ -6066,6 +5938,9 @@ var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
6066
5938
  var MAX_APP_MIGRATION_IDS = 256;
6067
5939
  var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
6068
5940
  var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
5941
+ var ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID = "2026-08-action-tools-delete-where";
5942
+ var BUILD_INJECTED_APP_ID_MIGRATION_ID = "2026-08-build-injected-app-id";
5943
+ var ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID = "2026-08-action-routes-bearer-guard";
6069
5944
  function validateAppMigrationIds(value) {
6070
5945
  if (!Array.isArray(value)) {
6071
5946
  return { valid: false, reason: "app migrations must be a JSON string array" };
@@ -6934,8 +6809,8 @@ function overPartBound(bytes) {
6934
6809
  if (bytes <= UPLOAD_PART_BYTES) return null;
6935
6810
  return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
6936
6811
  }
6937
- function declaredLength(request, what) {
6938
- const raw = request.headers.get("content-length");
6812
+ function declaredLength(request2, what) {
6813
+ const raw = request2.headers.get("content-length");
6939
6814
  const declared = raw === null ? NaN : Number(raw);
6940
6815
  if (!Number.isSafeInteger(declared) || declared < 0) {
6941
6816
  return fail2(
@@ -7013,7 +6888,7 @@ function multipartFailure(error) {
7013
6888
  }
7014
6889
  function createScopedR2Handler(config) {
7015
6890
  const requireAuthForMutations = config.requireAuthForMutations ?? true;
7016
- return async (request, url, bucket, auth) => {
6891
+ return async (request2, url, bucket, auth) => {
7017
6892
  let subpathRaw;
7018
6893
  try {
7019
6894
  subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
@@ -7042,26 +6917,26 @@ function createScopedR2Handler(config) {
7042
6917
  }
7043
6918
  const prefix = result.prefix;
7044
6919
  const excludedPrefixes = result.excludedPrefixes ?? [];
7045
- if (isFilesVerbPath(request.method, subpath)) {
6920
+ if (isFilesVerbPath(request2.method, subpath)) {
7046
6921
  if (requireAuthForMutations && !auth.userId) {
7047
- await request.body?.cancel().catch(() => void 0);
6922
+ await request2.body?.cancel().catch(() => void 0);
7048
6923
  return fail2(401, "Authentication required", "unauthorized");
7049
6924
  }
7050
- return subpath === "upload" ? handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
6925
+ return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
7051
6926
  }
7052
- if (!subpath && request.method === "GET") {
6927
+ if (!subpath && request2.method === "GET") {
7053
6928
  return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
7054
6929
  }
7055
- if (subpath && request.method === "GET") {
6930
+ if (subpath && request2.method === "GET") {
7056
6931
  if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
7057
6932
  return Response.json(
7058
6933
  { error: "Access denied: key outside scope" },
7059
6934
  { status: 403, headers: CORS_HEADERS4 }
7060
6935
  );
7061
6936
  }
7062
- return handleDownload(request, bucket, subpath, scope);
6937
+ return handleDownload(request2, bucket, subpath, scope);
7063
6938
  }
7064
- if (subpath && request.method === "DELETE") {
6939
+ if (subpath && request2.method === "DELETE") {
7065
6940
  if (requireAuthForMutations && !auth.userId) {
7066
6941
  return Response.json(
7067
6942
  { error: "Authentication required" },
@@ -7087,18 +6962,18 @@ function createScopedR2Handler(config) {
7087
6962
  return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS4 });
7088
6963
  };
7089
6964
  }
7090
- async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
6965
+ async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
7091
6966
  try {
7092
- const declaredBytes = declaredLength(request, "File uploads");
6967
+ const declaredBytes = declaredLength(request2, "File uploads");
7093
6968
  if (declaredBytes instanceof Response) return declaredBytes;
7094
6969
  const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
7095
6970
  if (tooLargeRequest) return tooLargeRequest;
7096
- const contentType = request.headers.get("content-type") || "";
6971
+ const contentType = request2.headers.get("content-type") || "";
7097
6972
  let fileData;
7098
6973
  let fileName;
7099
6974
  let rawMimeType;
7100
6975
  if (contentType.includes("multipart/form-data")) {
7101
- const formData = await request.formData();
6976
+ const formData = await request2.formData();
7102
6977
  const file = formData.get("file");
7103
6978
  if (!file) return fail2(400, "No file provided", "bad_request");
7104
6979
  const tooBig = overRequestBound(file.size);
@@ -7107,7 +6982,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
7107
6982
  fileName = formData.get("name")?.toString() || file.name;
7108
6983
  rawMimeType = file.type || "application/octet-stream";
7109
6984
  } else {
7110
- const body = await request.json();
6985
+ const body = await request2.json();
7111
6986
  if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
7112
6987
  const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
7113
6988
  const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
@@ -7188,15 +7063,15 @@ function storedFileJson(key, prefix, name, url, scope) {
7188
7063
  );
7189
7064
  }
7190
7065
  var MAX_CONTROL_BODY_BYTES = 64 * 1024;
7191
- async function readControlJson(request) {
7192
- const declared = declaredLength(request, "This request");
7066
+ async function readControlJson(request2) {
7067
+ const declared = declaredLength(request2, "This request");
7193
7068
  if (declared instanceof Response) return declared;
7194
7069
  if (declared > MAX_CONTROL_BODY_BYTES) {
7195
7070
  return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
7196
7071
  }
7197
7072
  let parsed;
7198
7073
  try {
7199
- parsed = await request.json();
7074
+ parsed = await request2.json();
7200
7075
  } catch {
7201
7076
  return fail2(400, "Body must be JSON", "bad_request");
7202
7077
  }
@@ -7248,7 +7123,7 @@ function multipartLayoutError(declaredBytes) {
7248
7123
  { partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
7249
7124
  );
7250
7125
  }
7251
- async function streamPart(upload, partNumber, body, expectedBytes) {
7126
+ async function streamPart(upload2, partNumber, body, expectedBytes) {
7252
7127
  let observedBytes = 0;
7253
7128
  const sized = new FixedLengthStream(expectedBytes);
7254
7129
  const pumped = body.pipeThrough(
@@ -7260,7 +7135,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
7260
7135
  })
7261
7136
  ).pipeTo(sized.writable);
7262
7137
  const [uploaded, drained] = await Promise.allSettled([
7263
- upload.uploadPart(partNumber, sized.readable),
7138
+ upload2.uploadPart(partNumber, sized.readable),
7264
7139
  pumped
7265
7140
  ]);
7266
7141
  if (uploaded.status === "rejected" || drained.status === "rejected") {
@@ -7269,20 +7144,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
7269
7144
  }
7270
7145
  return { ok: true, part: uploaded.value };
7271
7146
  }
7272
- async function handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
7273
- if (subpath === "multipart" && request.method === "POST") {
7274
- return multipartInit(request, url, bucket, prefix, excludedPrefixes, auth);
7147
+ async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
7148
+ if (subpath === "multipart" && request2.method === "POST") {
7149
+ return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
7275
7150
  }
7276
7151
  if (subpath === "multipart/part") {
7277
- return multipartPart(request, url, bucket, prefix, excludedPrefixes, auth);
7152
+ return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
7278
7153
  }
7279
7154
  if (subpath === "multipart/complete") {
7280
- return multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope);
7155
+ return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
7281
7156
  }
7282
7157
  return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
7283
7158
  }
7284
- async function multipartInit(request, url, bucket, prefix, excludedPrefixes, auth) {
7285
- const body = await readControlJson(request);
7159
+ async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
7160
+ const body = await readControlJson(request2);
7286
7161
  if (body instanceof Response) return body;
7287
7162
  const size = body.size;
7288
7163
  if (!Number.isSafeInteger(size) || size <= 0) {
@@ -7323,9 +7198,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7323
7198
  "request_id_conflict"
7324
7199
  );
7325
7200
  }
7326
- let upload;
7201
+ let upload2;
7327
7202
  try {
7328
- upload = await bucket.createMultipartUpload(located.key, {
7203
+ upload2 = await bucket.createMultipartUpload(located.key, {
7329
7204
  httpMetadata: { contentType: mimeType },
7330
7205
  customMetadata: fileMetadata(name, auth.userId, requestId, size)
7331
7206
  });
@@ -7337,7 +7212,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7337
7212
  body: JSON.stringify({
7338
7213
  version: 1,
7339
7214
  reservationId: requestId,
7340
- uploadId: upload.uploadId,
7215
+ uploadId: upload2.uploadId,
7341
7216
  objectKey: located.key,
7342
7217
  uploadKey: located.subkey,
7343
7218
  admitted: false,
@@ -7351,7 +7226,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7351
7226
  const reservedBytes = Math.max(size, 5 * 1024 * 1024);
7352
7227
  const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
7353
7228
  if (admission instanceof Response) {
7354
- await upload.abort().catch(() => void 0);
7229
+ await upload2.abort().catch(() => void 0);
7355
7230
  if (admission.status === 409 && auth.storage) {
7356
7231
  const winner = await readMultipartReservation(bucket, auth.storage, requestId);
7357
7232
  if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
@@ -7361,11 +7236,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7361
7236
  return admission;
7362
7237
  }
7363
7238
  if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
7364
- await upload.abort().catch(() => void 0);
7239
+ await upload2.abort().catch(() => void 0);
7365
7240
  await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
7366
7241
  return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
7367
7242
  }
7368
- return multipartSessionJson(upload.uploadId, located.subkey, requestId);
7243
+ return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
7369
7244
  }
7370
7245
  async function abandonMultipart(bucket, session, auth, reservation) {
7371
7246
  let result = "aborted";
@@ -7383,12 +7258,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
7383
7258
  }
7384
7259
  return result;
7385
7260
  }
7386
- async function multipartPart(request, url, bucket, prefix, excludedPrefixes, auth) {
7261
+ async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
7387
7262
  const refuse = async (response) => {
7388
- await request.body?.cancel().catch(() => void 0);
7263
+ await request2.body?.cancel().catch(() => void 0);
7389
7264
  return response;
7390
7265
  };
7391
- const declaredBytes = declaredLength(request, "Part uploads");
7266
+ const declaredBytes = declaredLength(request2, "Part uploads");
7392
7267
  if (declaredBytes instanceof Response) return refuse(declaredBytes);
7393
7268
  const tooBig = overPartBound(declaredBytes);
7394
7269
  if (tooBig) return refuse(tooBig);
@@ -7400,7 +7275,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
7400
7275
  }
7401
7276
  const session = resolveSession(url, prefix, excludedPrefixes);
7402
7277
  if (session instanceof Response) return refuse(session);
7403
- if (!request.body) return fail2(400, "A part body is required", "bad_request");
7278
+ if (!request2.body) return fail2(400, "A part body is required", "bad_request");
7404
7279
  const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
7405
7280
  if (reservation === "invalid") {
7406
7281
  return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
@@ -7442,8 +7317,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
7442
7317
  if (refreshed === "busy") {
7443
7318
  return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
7444
7319
  }
7445
- const upload = bucket.resumeMultipartUpload(session.key, session.uploadId);
7446
- const stored = await streamPart(upload, partNumber, request.body, declaredBytes);
7320
+ const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
7321
+ const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
7447
7322
  if (!stored.ok) {
7448
7323
  if (r2ErrorCode(stored.error) === 10024 && reservation) {
7449
7324
  await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
@@ -7499,8 +7374,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
7499
7374
  const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
7500
7375
  return storedFileJson(session.key, prefix, name, url, scope);
7501
7376
  }
7502
- async function multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
7503
- const body = await readControlJson(request);
7377
+ async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
7378
+ const body = await readControlJson(request2);
7504
7379
  if (body instanceof Response) return body;
7505
7380
  const session = resolveSession(url, prefix, excludedPrefixes);
7506
7381
  if (session instanceof Response) return session;
@@ -7659,7 +7534,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
7659
7534
  { headers: CORS_HEADERS4 }
7660
7535
  );
7661
7536
  }
7662
- async function handleDownload(request, bucket, key, scope) {
7537
+ async function handleDownload(request2, bucket, key, scope) {
7663
7538
  const object = await bucket.get(key);
7664
7539
  if (!object) {
7665
7540
  return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS4 });
@@ -7681,7 +7556,7 @@ async function handleDownload(request, bucket, key, scope) {
7681
7556
  contentDisposition(originalName, mustDownload ? "attachment" : "inline")
7682
7557
  );
7683
7558
  if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
7684
- if (scope === "app" && request.headers.get("if-none-match") === object.httpEtag) {
7559
+ if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
7685
7560
  return new Response(null, { status: 304, headers });
7686
7561
  }
7687
7562
  return new Response(object.body, { headers });
@@ -8134,8 +8009,8 @@ async function composioTools(env, options) {
8134
8009
  if (!list.ok || !list.success) {
8135
8010
  throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
8136
8011
  }
8137
- const listItems = list.data?.items;
8138
- const items = Array.isArray(listItems) ? listItems : [];
8012
+ const listItems2 = list.data?.items;
8013
+ const items = Array.isArray(listItems2) ? listItems2 : [];
8139
8014
  const tools = {};
8140
8015
  for (const item of items) {
8141
8016
  if (!item?.slug) continue;
@@ -8720,18 +8595,272 @@ var COST_RATES = {
8720
8595
  queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
8721
8596
  /** USD per stored dimension per month. */
8722
8597
  storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
8598
+ },
8599
+ aiSearch: {
8600
+ ingestPerToken: 0.75 / 1e6,
8601
+ ingestImagePerToken: 0.5 / 1e6,
8602
+ storagePerByteMonth: 2 / 1e9,
8603
+ hybridOrSemanticPerQuery: 0.75 / 1e3,
8604
+ fulltextPerQuery: 0.1 / 1e3
8723
8605
  }
8724
8606
  };
8725
8607
  function priceBindingUsageEvent(kind, op, units) {
8608
+ if (!Number.isFinite(units) || units < 0) return 0;
8726
8609
  if (kind === "ai" && op === "input") {
8727
8610
  return units * COST_RATES.ai.embedInputPerChar;
8728
8611
  }
8729
8612
  if (kind === "vectorize" && op === "query") {
8730
8613
  return units * COST_RATES.vectorize.queriedPerDim;
8731
8614
  }
8615
+ if (kind === "ai_search") {
8616
+ if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
8617
+ if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
8618
+ if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
8619
+ if (op === "search-hybrid" || op === "search-semantic")
8620
+ return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
8621
+ if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
8622
+ }
8732
8623
  return 0;
8733
8624
  }
8734
8625
 
8626
+ // src/server/utils/knowledge-base.ts
8627
+ var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
8628
+ var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
8629
+ var MAX_QUERY_CHARS = 4096;
8630
+ function hasControlChar(value) {
8631
+ for (let i = 0; i < value.length; i++) {
8632
+ const code = value.charCodeAt(i);
8633
+ if (code < 32 || code === 127) return true;
8634
+ }
8635
+ return false;
8636
+ }
8637
+ var KnowledgeError = class extends Error {
8638
+ constructor(status, code, message, uploadedItems) {
8639
+ super(message);
8640
+ this.status = status;
8641
+ this.code = code;
8642
+ this.uploadedItems = uploadedItems;
8643
+ this.name = "KnowledgeError";
8644
+ }
8645
+ };
8646
+ function normalizeKnowledgeFolder(folder) {
8647
+ if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
8648
+ throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
8649
+ const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
8650
+ if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
8651
+ throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
8652
+ const parts = withoutTrailingSlash.split("/");
8653
+ if (parts.some((part) => !part || part === "." || part === ".."))
8654
+ throw new KnowledgeError(
8655
+ 400,
8656
+ "invalid_folder",
8657
+ "Folder cannot contain empty, dot, or parent segments"
8658
+ );
8659
+ const normalized = `${parts.join("/")}/`;
8660
+ if (normalized.length > 128)
8661
+ throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
8662
+ return normalized;
8663
+ }
8664
+ function validateKnowledgeFilename(name) {
8665
+ if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
8666
+ throw new KnowledgeError(
8667
+ 400,
8668
+ "invalid_filename",
8669
+ "File name must be a non-empty base name without path separators"
8670
+ );
8671
+ }
8672
+ }
8673
+ function folderFilter(folder) {
8674
+ const value = normalizeKnowledgeFolder(folder);
8675
+ return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
8676
+ }
8677
+ function knowledge(env) {
8678
+ const add = (file, options = {}) => addFile(env, file, options);
8679
+ const list = (options = {}) => listItems(env, options);
8680
+ const search = (query, options = {}) => searchItems(env, query, options);
8681
+ return {
8682
+ add,
8683
+ list,
8684
+ search,
8685
+ async remove(itemId) {
8686
+ if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
8687
+ throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
8688
+ await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
8689
+ },
8690
+ scoped({ folder }) {
8691
+ const fixed = normalizeKnowledgeFolder(folder);
8692
+ return {
8693
+ add: (file) => add(file, { folder: fixed }),
8694
+ list: (options = {}) => list({ ...options, folder: fixed }),
8695
+ search: (query, options = {}) => search(query, { ...options, folder: fixed })
8696
+ };
8697
+ }
8698
+ };
8699
+ }
8700
+ async function addFile(env, file, options) {
8701
+ validateKnowledgeFilename(file.name);
8702
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
8703
+ if ((folder?.length ?? 0) + file.name.length > 128)
8704
+ throw new KnowledgeError(
8705
+ 400,
8706
+ "invalid_key",
8707
+ "Folder and filename together exceed the 128-character AI Search key limit"
8708
+ );
8709
+ if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
8710
+ if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
8711
+ throw new KnowledgeError(
8712
+ 413,
8713
+ "file_too_large",
8714
+ "This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
8715
+ );
8716
+ const bytes = new Uint8Array(await file.arrayBuffer());
8717
+ const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
8718
+ const uploaded = [];
8719
+ try {
8720
+ for (let i = 0; i < parts.length; i++) {
8721
+ const bytes2 = parts[i];
8722
+ const contents = bytes2.buffer.slice(
8723
+ bytes2.byteOffset,
8724
+ bytes2.byteOffset + bytes2.byteLength
8725
+ );
8726
+ const part = new File([contents], partName(file.name, i + 1), {
8727
+ type: file.type || "text/plain"
8728
+ });
8729
+ if ((folder?.length ?? 0) + part.name.length > 128)
8730
+ throw new KnowledgeError(
8731
+ 400,
8732
+ "invalid_key",
8733
+ "Folder and split filename together exceed the 128-character AI Search key limit"
8734
+ );
8735
+ uploaded.push(...(await upload(env, part, folder)).items);
8736
+ }
8737
+ } catch (error) {
8738
+ if (error instanceof KnowledgeError)
8739
+ throw new KnowledgeError(error.status, error.code, error.message, uploaded);
8740
+ throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
8741
+ }
8742
+ return { items: uploaded };
8743
+ }
8744
+ function splitUtf8(bytes, max) {
8745
+ const out = [];
8746
+ for (let start = 0; start < bytes.length; ) {
8747
+ let end = Math.min(start + max, bytes.length);
8748
+ if (end < bytes.length) {
8749
+ let cut = end;
8750
+ while (cut > start && (bytes[cut] & 192) === 128) cut--;
8751
+ for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
8752
+ if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
8753
+ cut = i + 1;
8754
+ break;
8755
+ }
8756
+ end = cut > start ? cut : end;
8757
+ }
8758
+ out.push(bytes.slice(start, end));
8759
+ start = end;
8760
+ }
8761
+ return out;
8762
+ }
8763
+ function partName(name, n) {
8764
+ const dot = name.lastIndexOf(".");
8765
+ return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
8766
+ }
8767
+ async function upload(env, file, folder) {
8768
+ const body = new FormData();
8769
+ body.append("file", file);
8770
+ if (folder) body.append("folder", folder);
8771
+ return await request(env, "/api/knowledge/items", {
8772
+ method: "POST",
8773
+ body
8774
+ });
8775
+ }
8776
+ async function listItems(env, options) {
8777
+ if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
8778
+ throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
8779
+ if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
8780
+ throw new KnowledgeError(
8781
+ 400,
8782
+ "invalid_per_page",
8783
+ "List perPage must be an integer from 1 to 50"
8784
+ );
8785
+ if (options.search !== void 0 && options.search.length > 256)
8786
+ throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
8787
+ if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
8788
+ throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
8789
+ const q = new URLSearchParams();
8790
+ if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
8791
+ if (options.page !== void 0) q.set("page", String(options.page));
8792
+ if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
8793
+ if (options.status) q.set("status", options.status);
8794
+ if (options.search) q.set("search", options.search);
8795
+ return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
8796
+ }
8797
+ async function searchItems(env, query, options) {
8798
+ const normalizedQuery = query.trim();
8799
+ if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
8800
+ if (normalizedQuery.length > MAX_QUERY_CHARS)
8801
+ throw new KnowledgeError(
8802
+ 400,
8803
+ "invalid_query",
8804
+ `Search query cannot exceed ${MAX_QUERY_CHARS} characters`
8805
+ );
8806
+ if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
8807
+ throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
8808
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
8809
+ throw new KnowledgeError(
8810
+ 400,
8811
+ "invalid_limit",
8812
+ "Search limit must be an integer from 1 through 50"
8813
+ );
8814
+ if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
8815
+ throw new KnowledgeError(
8816
+ 400,
8817
+ "invalid_match_threshold",
8818
+ "Match threshold must be between 0 and 1"
8819
+ );
8820
+ if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
8821
+ throw new KnowledgeError(
8822
+ 400,
8823
+ "invalid_query_rewrite",
8824
+ "queryRewrite must be a boolean when provided"
8825
+ );
8826
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
8827
+ return await request(env, "/api/knowledge/search", {
8828
+ method: "POST",
8829
+ headers: { "content-type": "application/json" },
8830
+ body: JSON.stringify({
8831
+ query: normalizedQuery,
8832
+ mode: options.mode ?? "hybrid",
8833
+ limit: options.limit ?? 10,
8834
+ queryRewrite: options.queryRewrite ?? false,
8835
+ ...options.matchThreshold !== void 0 && {
8836
+ matchThreshold: options.matchThreshold
8837
+ },
8838
+ ...folder && { folder }
8839
+ })
8840
+ });
8841
+ }
8842
+ async function request(env, path, init = {}) {
8843
+ const headers = new Headers(init.headers);
8844
+ appendAppIdentity(headers, env);
8845
+ const response = await apiWorkerFetch(env, path, { ...init, headers });
8846
+ const text = await response.text();
8847
+ let data;
8848
+ try {
8849
+ data = text ? JSON.parse(text) : void 0;
8850
+ } catch {
8851
+ data = void 0;
8852
+ }
8853
+ if (!response.ok) {
8854
+ const body = data && typeof data === "object" ? data : {};
8855
+ throw new KnowledgeError(
8856
+ response.status,
8857
+ typeof body.code === "string" ? body.code : "knowledge_request_failed",
8858
+ typeof body.error === "string" ? body.error : "Knowledge request failed"
8859
+ );
8860
+ }
8861
+ return data;
8862
+ }
8863
+
8735
8864
  // src/server/utils/d1-migrations.ts
8736
8865
  var META_TABLE = "_dpc_migrations";
8737
8866
  var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
@@ -9242,16 +9371,16 @@ var RequestBodyTooLargeError = class extends Error {
9242
9371
  this.name = "RequestBodyTooLargeError";
9243
9372
  }
9244
9373
  };
9245
- async function readBoundedRequestText(request, maxBytes) {
9374
+ async function readBoundedRequestText(request2, maxBytes) {
9246
9375
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
9247
9376
  throw new TypeError("maxBytes must be a positive safe integer");
9248
9377
  }
9249
- const declared = request.headers.get("content-length");
9378
+ const declared = request2.headers.get("content-length");
9250
9379
  if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
9251
9380
  throw new RequestBodyTooLargeError(maxBytes);
9252
9381
  }
9253
- if (!request.body) return "";
9254
- const reader = request.body.getReader();
9382
+ if (!request2.body) return "";
9383
+ const reader = request2.body.getReader();
9255
9384
  const decoder = new TextDecoder();
9256
9385
  let bytes = 0;
9257
9386
  let text = "";
@@ -9465,10 +9594,10 @@ function documentationRequestPath(pathname, publicBasePath) {
9465
9594
  const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
9466
9595
  return canonicalNativeDocumentationRequestPath(clean);
9467
9596
  }
9468
- async function fetchDocumentationAsset(env, request, initialUrl, assetBasePath) {
9597
+ async function fetchDocumentationAsset(env, request2, initialUrl, assetBasePath) {
9469
9598
  let url = initialUrl;
9470
9599
  for (let redirects = 0; redirects < 3; redirects++) {
9471
- const response = await env.ASSETS.fetch(new Request(url, request));
9600
+ const response = await env.ASSETS.fetch(new Request(url, request2));
9472
9601
  const location = response.headers.get("Location");
9473
9602
  if (response.status < 300 || response.status >= 400 || !location) return response;
9474
9603
  const next = new URL(location, url);
@@ -9508,11 +9637,11 @@ function secureDocumentationResponse(response, status, pathname) {
9508
9637
  var MCP_SERVER_VERSION = "1.0.0";
9509
9638
  var CURRENT_PROTOCOL_VERSION = "2025-11-25";
9510
9639
  var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
9511
- function initializeResponse(request) {
9512
- const params = objectValue(request.params);
9640
+ function initializeResponse(request2) {
9641
+ const params = objectValue(request2.params);
9513
9642
  const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
9514
9643
  const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
9515
- return mcpResult(request.id, {
9644
+ return mcpResult(request2.id, {
9516
9645
  protocolVersion,
9517
9646
  capabilities: {
9518
9647
  tools: { listChanged: false },
@@ -9526,11 +9655,11 @@ function initializeResponse(request) {
9526
9655
  instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
9527
9656
  });
9528
9657
  }
9529
- function hasValidOrigin(request) {
9530
- const origin = request.headers.get("Origin");
9658
+ function hasValidOrigin(request2) {
9659
+ const origin = request2.headers.get("Origin");
9531
9660
  if (!origin) return true;
9532
9661
  try {
9533
- return new URL(origin).origin === new URL(request.url).origin;
9662
+ return new URL(origin).origin === new URL(request2.url).origin;
9534
9663
  } catch {
9535
9664
  return false;
9536
9665
  }
@@ -9656,8 +9785,8 @@ var DOCUMENTATION_TOOLS = [
9656
9785
  }
9657
9786
  }
9658
9787
  ];
9659
- async function callDocumentationTool(rawRequest, request, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
9660
- const params = objectValue(request.params);
9788
+ async function callDocumentationTool(rawRequest, request2, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
9789
+ const params = objectValue(request2.params);
9661
9790
  const name = typeof params?.name === "string" ? params.name : "";
9662
9791
  const args = objectValue(params?.arguments) ?? {};
9663
9792
  const origin = new URL(rawRequest.url).origin;
@@ -9665,13 +9794,13 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9665
9794
  const query = typeof args.query === "string" ? args.query.trim() : "";
9666
9795
  const limit = args.limit === void 0 ? 6 : args.limit;
9667
9796
  if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
9668
- return mcpError(request.id, -32602, "Invalid documentation_search arguments");
9797
+ return mcpError(request2.id, -32602, "Invalid documentation_search arguments");
9669
9798
  }
9670
9799
  const results = searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
9671
9800
  ...result,
9672
9801
  url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
9673
9802
  }));
9674
- return toolResult(request.id, {
9803
+ return toolResult(request2.id, {
9675
9804
  ...results[0]?.termCoverage === 1 ? {} : {
9676
9805
  notice: "No section matches every search term \u2014 the topic may be absent or spread across sections."
9677
9806
  },
@@ -9681,7 +9810,7 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9681
9810
  if (name === "documentation_read") {
9682
9811
  const route = typeof args.route === "string" ? args.route.trim() : "";
9683
9812
  if (!route || route.length > 500) {
9684
- return mcpError(request.id, -32602, "Invalid documentation_read arguments");
9813
+ return mcpError(request2.id, -32602, "Invalid documentation_read arguments");
9685
9814
  }
9686
9815
  const hash = route.indexOf("#");
9687
9816
  const fragment = hash === -1 ? "" : route.slice(hash + 1);
@@ -9693,9 +9822,9 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9693
9822
  rawRequest.url,
9694
9823
  assetBasePath
9695
9824
  );
9696
- if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
9825
+ if (!page) return toolError(request2.id, `No published documentation page exists at ${route}`);
9697
9826
  const section = fragment ? documentationPageSection(page.markdown, fragment) : null;
9698
- return toolResult(request.id, {
9827
+ return toolResult(request2.id, {
9699
9828
  ...fragment && section === null ? { notice: `The fragment #${fragment} was not found; returning the whole page.` } : {},
9700
9829
  ...page,
9701
9830
  ...section === null ? {} : { markdown: section },
@@ -9703,10 +9832,10 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9703
9832
  });
9704
9833
  } catch (error) {
9705
9834
  console.error("[documentation-mcp] page unavailable:", error);
9706
- return toolError(request.id, "The documentation page could not be read");
9835
+ return toolError(request2.id, "The documentation page could not be read");
9707
9836
  }
9708
9837
  }
9709
- return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
9838
+ return mcpError(request2.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
9710
9839
  }
9711
9840
 
9712
9841
  // src/documentation/worker/mcp-routes.ts
@@ -9743,27 +9872,27 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9743
9872
  }
9744
9873
  const parsed = parseRequest(raw);
9745
9874
  if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
9746
- const request = parsed.request;
9747
- if (request.method !== "initialize") {
9875
+ const request2 = parsed.request;
9876
+ if (request2.method !== "initialize") {
9748
9877
  const protocolVersion = c.req.header("MCP-Protocol-Version");
9749
9878
  if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
9750
- return mcpError(request.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
9879
+ return mcpError(request2.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
9751
9880
  }
9752
9881
  }
9753
- const requestId = request.id;
9882
+ const requestId = request2.id;
9754
9883
  if (requestId === void 0) return new Response(null, { status: 202 });
9755
- const requestWithId = { ...request, id: requestId };
9756
- if (request.method === "initialize") return initializeResponse(requestWithId);
9757
- if (request.method === "ping") return mcpResult(requestId, {});
9758
- if (request.method === "tools/list") {
9884
+ const requestWithId = { ...request2, id: requestId };
9885
+ if (request2.method === "initialize") return initializeResponse(requestWithId);
9886
+ if (request2.method === "ping") return mcpResult(requestId, {});
9887
+ if (request2.method === "tools/list") {
9759
9888
  return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
9760
9889
  }
9761
- if (request.method === "resources/list") {
9890
+ if (request2.method === "resources/list") {
9762
9891
  return mcpResult(requestId, {
9763
9892
  resources: [skillResource(c.req.raw, manifest, publicBasePath)]
9764
9893
  });
9765
9894
  }
9766
- if (request.method === "resources/read") {
9895
+ if (request2.method === "resources/read") {
9767
9896
  return readSkillResource(
9768
9897
  c.req.raw,
9769
9898
  requestWithId,
@@ -9773,8 +9902,8 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9773
9902
  assetBasePath
9774
9903
  );
9775
9904
  }
9776
- if (request.method !== "tools/call") {
9777
- return mcpError(requestId, -32601, `Method not found: ${request.method}`);
9905
+ if (request2.method !== "tools/call") {
9906
+ return mcpError(requestId, -32601, `Method not found: ${request2.method}`);
9778
9907
  }
9779
9908
  const clientKey = await documentationPublicClientKey(
9780
9909
  c.req.header("CF-Connecting-IP") ?? "unknown"
@@ -9831,44 +9960,44 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9831
9960
  }
9832
9961
  );
9833
9962
  }
9834
- async function readSkillResource(rawRequest, request, manifest, env, publicBasePath, assetBasePath) {
9835
- const params = objectValue(request.params);
9963
+ async function readSkillResource(rawRequest, request2, manifest, env, publicBasePath, assetBasePath) {
9964
+ const params = objectValue(request2.params);
9836
9965
  const requestedUri = typeof params?.uri === "string" ? params.uri : "";
9837
9966
  const resource = skillResource(rawRequest, manifest, publicBasePath);
9838
9967
  if (requestedUri !== resource.uri) {
9839
- return mcpError(request.id, -32602, "Unknown documentation resource");
9968
+ return mcpError(request2.id, -32602, "Unknown documentation resource");
9840
9969
  }
9841
9970
  const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
9842
9971
  const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
9843
- if (!response.ok) return mcpError(request.id, -32002, "Documentation skill is unavailable", 503);
9972
+ if (!response.ok) return mcpError(request2.id, -32002, "Documentation skill is unavailable", 503);
9844
9973
  const text = await response.text();
9845
9974
  if (text.length > MAX_SKILL_CHARS) {
9846
- return mcpError(request.id, -32002, "Documentation skill exceeds the published size limit", 503);
9975
+ return mcpError(request2.id, -32002, "Documentation skill exceeds the published size limit", 503);
9847
9976
  }
9848
- return mcpResult(request.id, {
9977
+ return mcpResult(request2.id, {
9849
9978
  contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
9850
9979
  });
9851
9980
  }
9852
- function skillResource(request, manifest, publicBasePath) {
9981
+ function skillResource(request2, manifest, publicBasePath) {
9853
9982
  const subject = documentationSubject(manifest.name);
9854
9983
  return {
9855
- uri: new URL(`${publicBasePath}/skill.md`, request.url).toString(),
9984
+ uri: new URL(`${publicBasePath}/skill.md`, request2.url).toString(),
9856
9985
  name: `${subject} skill`,
9857
9986
  title: `Use ${manifest.name}`,
9858
9987
  description: `Agent instructions grounded in the published ${subject}.`,
9859
9988
  mimeType: "text/markdown"
9860
9989
  };
9861
9990
  }
9862
- async function publicManifest(request, env, assetBasePath) {
9863
- const manifest = await loadDocumentationPublishedManifest(env, request.url, assetBasePath);
9991
+ async function publicManifest(request2, env, assetBasePath) {
9992
+ const manifest = await loadDocumentationPublishedManifest(env, request2.url, assetBasePath);
9864
9993
  return manifest?.mcp.access === "public" ? manifest : null;
9865
9994
  }
9866
- function discoveryResponse(request, manifest, publicBasePath) {
9867
- return jsonResponse(serverCard(request, manifest, publicBasePath), 200, {
9995
+ function discoveryResponse(request2, manifest, publicBasePath) {
9996
+ return jsonResponse(serverCard(request2, manifest, publicBasePath), 200, {
9868
9997
  "Cache-Control": "public, max-age=300"
9869
9998
  });
9870
9999
  }
9871
- function serverCard(request, manifest, publicBasePath) {
10000
+ function serverCard(request2, manifest, publicBasePath) {
9872
10001
  return {
9873
10002
  name: manifest.name,
9874
10003
  description: `Search and read the published ${documentationSubject(manifest.name)}.`,
@@ -9881,7 +10010,7 @@ function serverCard(request, manifest, publicBasePath) {
9881
10010
  protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
9882
10011
  transport: {
9883
10012
  type: "streamable-http",
9884
- url: new URL(`${publicBasePath}/mcp`, request.url).toString()
10013
+ url: new URL(`${publicBasePath}/mcp`, request2.url).toString()
9885
10014
  },
9886
10015
  authentication: { required: false },
9887
10016
  capabilities: {
@@ -9890,7 +10019,7 @@ function serverCard(request, manifest, publicBasePath) {
9890
10019
  title,
9891
10020
  description
9892
10021
  })),
9893
- resources: [skillResource(request, manifest, publicBasePath)]
10022
+ resources: [skillResource(request2, manifest, publicBasePath)]
9894
10023
  }
9895
10024
  };
9896
10025
  }
@@ -10313,21 +10442,21 @@ function jsonResponse2(body, status) {
10313
10442
  headers: { "Content-Type": "application/json" }
10314
10443
  });
10315
10444
  }
10316
- async function handleClientErrorReport(request) {
10317
- const site = request.headers.get("sec-fetch-site");
10445
+ async function handleClientErrorReport(request2) {
10446
+ const site = request2.headers.get("sec-fetch-site");
10318
10447
  if (site && site !== "same-origin" && site !== "none") {
10319
10448
  return new Response(null, { status: 403 });
10320
10449
  }
10321
10450
  if (shouldThrottleClientError()) {
10322
10451
  return new Response(null, { status: 429, headers: { "Retry-After": "1" } });
10323
10452
  }
10324
- const declared = Number(request.headers.get("content-length"));
10453
+ const declared = Number(request2.headers.get("content-length"));
10325
10454
  if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
10326
10455
  return jsonResponse2({ error: "payload_too_large" }, 413);
10327
10456
  }
10328
10457
  let body;
10329
10458
  try {
10330
- body = await request.json();
10459
+ body = await request2.json();
10331
10460
  } catch {
10332
10461
  return jsonResponse2({ error: "invalid_json" }, 400);
10333
10462
  }
@@ -10355,6 +10484,8 @@ var APP_LOG_EVENT_KEYS = [
10355
10484
  ];
10356
10485
  export {
10357
10486
  ACCOUNT_STORAGE_LIMIT_BYTES,
10487
+ ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID,
10488
+ ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID,
10358
10489
  AI_CHATS_SCHEMA,
10359
10490
  AI_MESSAGES_SCHEMA,
10360
10491
  ALLOWED_BINDING_TYPES,
@@ -10368,6 +10499,7 @@ export {
10368
10499
  Awareness,
10369
10500
  BASE_USERS_SCHEMA,
10370
10501
  BROWSER_PROXY_ROUTES,
10502
+ BUILD_INJECTED_APP_ID_MIGRATION_ID,
10371
10503
  BUILT_IN_TOOLS,
10372
10504
  BaseRoom,
10373
10505
  CHANNELS_SCHEMA,
@@ -10393,6 +10525,7 @@ export {
10393
10525
  GLOBAL_DO_TYPES,
10394
10526
  GLOBAL_DO_TYPE_NAMES,
10395
10527
  JobRoom,
10528
+ KnowledgeError,
10396
10529
  LOG_LEVELS,
10397
10530
  MAX_APP_FILE_BYTES,
10398
10531
  MAX_DELETE_WHERE_LIMIT,
@@ -10439,13 +10572,13 @@ export {
10439
10572
  USERS_COLUMNS,
10440
10573
  VOTING_SCHEMAS,
10441
10574
  WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
10442
- WORKSPACE_SCHEMAS,
10443
10575
  YjsRoom,
10444
10576
  analyzePermissions,
10445
10577
  apiWorkerFetch,
10446
10578
  appendMessage,
10447
10579
  applyAiToolDefaults,
10448
10580
  applySlidingWindow,
10581
+ armCronRoom,
10449
10582
  authWorkerFetch,
10450
10583
  authenticatedRoomRequest,
10451
10584
  bindingManifestFromOutputConfig,
@@ -10485,6 +10618,8 @@ export {
10485
10618
  encodeSyncStep2,
10486
10619
  encodeUpdate,
10487
10620
  enqueueJob,
10621
+ epochSeconds,
10622
+ folderFilter,
10488
10623
  formatBytes,
10489
10624
  getChat,
10490
10625
  getDeepSpaceAIModel,
@@ -10505,6 +10640,7 @@ export {
10505
10640
  isTestAccountClaims,
10506
10641
  isTestAccountEmail,
10507
10642
  isTestAccountTier,
10643
+ knowledge,
10508
10644
  lintSchema,
10509
10645
  lintSchemaSet,
10510
10646
  lintSchemas,
@@ -10518,6 +10654,7 @@ export {
10518
10654
  mintAppId,
10519
10655
  mintUlid,
10520
10656
  noopPermissionContext,
10657
+ normalizeKnowledgeFolder,
10521
10658
  parseQuotaLimit,
10522
10659
  platformCorsPatterns,
10523
10660
  platformWorkerFetch,
@@ -10553,17 +10690,6 @@ export {
10553
10690
  validateBindingManifest,
10554
10691
  validateDoManifest,
10555
10692
  verifyJwt,
10556
- workspaceAccountsSchema,
10557
- workspaceContentSharesSchema,
10558
- workspaceEmailHandlesSchema,
10559
- workspaceFormResponsesSchema,
10560
- workspacePeopleSchema,
10561
- workspaceProjectsSchema,
10562
- workspaceTagsSchema,
10563
- workspaceTasksSchema,
10564
- workspaceTeamMembersSchema,
10565
- workspaceTeamsSchema,
10566
- workspaceTransactionsSchema,
10567
10693
  writeVarUint,
10568
10694
  writeVarUint8Array
10569
10695
  };