deepspace 0.23.2 → 0.24.1

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
  }
@@ -5117,13 +5173,15 @@ function validateDoManifest(manifest) {
5117
5173
  }
5118
5174
 
5119
5175
  // src/server/rooms/binding-manifest.ts
5176
+ var MAX_ENV_VAR_BYTES = 5 * 1024;
5120
5177
  var AUTO_PROVISION_SENTINEL = "auto";
5121
5178
  var AUTO_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
5122
5179
  "d1",
5123
5180
  "kv_namespace",
5124
5181
  "vectorize",
5125
5182
  "r2_bucket",
5126
- "queue"
5183
+ "queue",
5184
+ "ai_search"
5127
5185
  ]);
5128
5186
  var VECTORIZE_METRICS = /* @__PURE__ */ new Set(["cosine", "euclidean", "dot-product"]);
5129
5187
  function isAutoProvision(b) {
@@ -5138,11 +5196,15 @@ function isAutoProvision(b) {
5138
5196
  return b.bucket_name === AUTO_PROVISION_SENTINEL;
5139
5197
  case "queue":
5140
5198
  return b.queue_name === AUTO_PROVISION_SENTINEL;
5199
+ case "ai_search":
5200
+ return b.instance_name === AUTO_PROVISION_SENTINEL;
5141
5201
  default:
5142
5202
  return false;
5143
5203
  }
5144
5204
  }
5145
5205
  var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
5206
+ "plain_text",
5207
+ "json",
5146
5208
  "vectorize",
5147
5209
  "ai",
5148
5210
  "r2_bucket",
@@ -5152,7 +5214,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
5152
5214
  "browser_rendering",
5153
5215
  "analytics_engine",
5154
5216
  "hyperdrive",
5155
- "ratelimit"
5217
+ "ratelimit",
5218
+ "ai_search"
5156
5219
  ]);
5157
5220
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5158
5221
  "ASSETS",
@@ -5176,6 +5239,7 @@ function validateBindingManifest(manifest) {
5176
5239
  }
5177
5240
  const errors = [];
5178
5241
  const seenNames = /* @__PURE__ */ new Set();
5242
+ let aiSearchCount = 0;
5179
5243
  for (const entry of manifest) {
5180
5244
  if (!entry || typeof entry !== "object") {
5181
5245
  errors.push({
@@ -5206,14 +5270,34 @@ function validateBindingManifest(manifest) {
5206
5270
  }
5207
5271
  seenNames.add(name);
5208
5272
  const binding = entry;
5273
+ if (binding.type === "ai_search") aiSearchCount++;
5209
5274
  const fieldErr = requiredFieldError(binding);
5210
5275
  if (fieldErr) errors.push({ binding, reason: fieldErr });
5211
5276
  }
5277
+ if (aiSearchCount > 1) {
5278
+ errors.push({ reason: "Only one ai_search binding may be declared per app" });
5279
+ }
5212
5280
  if (errors.length) return { valid: false, errors };
5213
5281
  return { valid: true, bindings: manifest };
5214
5282
  }
5215
5283
  function requiredFieldError(b) {
5216
5284
  switch (b.type) {
5285
+ case "plain_text":
5286
+ if (typeof b.text !== "string") {
5287
+ return `plain_text binding "${b.name}" missing text (string)`;
5288
+ }
5289
+ return envVarSizeError(b.name, b.text);
5290
+ case "json": {
5291
+ if (!isBindingJsonValue(b.json)) {
5292
+ return `json binding "${b.name}" must contain a JSON-serializable value`;
5293
+ }
5294
+ return envVarSizeError(b.name, JSON.stringify(b.json));
5295
+ }
5296
+ case "ai_search":
5297
+ if (b.instance_name !== AUTO_PROVISION_SENTINEL) {
5298
+ return `ai_search binding "${b.name}" must set instance_name = "auto"; managed instances cannot be selected directly`;
5299
+ }
5300
+ return null;
5217
5301
  case "vectorize": {
5218
5302
  if (!b.index_name) return `vectorize binding "${b.name}" missing index_name (or "auto")`;
5219
5303
  if (b.index_name === AUTO_PROVISION_SENTINEL) {
@@ -5275,7 +5359,26 @@ function requiredFieldError(b) {
5275
5359
  }
5276
5360
  }
5277
5361
  function bindingManifestFromOutputConfig(outputConfig) {
5362
+ const aiSearchNamespaces = outputConfig.ai_search_namespaces;
5363
+ const declaresAiSearchNamespace = Array.isArray(aiSearchNamespaces) ? aiSearchNamespaces.length > 0 : aiSearchNamespaces !== void 0 && aiSearchNamespaces !== null;
5364
+ if (declaresAiSearchNamespace) {
5365
+ throw new Error(
5366
+ 'ai_search_namespaces is not supported by DeepSpace; declare one [[ai_search]] instance_name = "auto" instead'
5367
+ );
5368
+ }
5278
5369
  const out = [];
5370
+ const vars = isObject(outputConfig.vars) ? outputConfig.vars : null;
5371
+ for (const [name, value] of Object.entries(vars ?? {})) {
5372
+ if (name === "DEEPSPACE_APP_ID" || name === "APP_NAME") continue;
5373
+ if (typeof value === "string") {
5374
+ out.push({ type: "plain_text", name, text: value });
5375
+ continue;
5376
+ }
5377
+ if (!isBindingJsonValue(value)) {
5378
+ throw new Error(`Wrangler var "${name}" is not a JSON-serializable value`);
5379
+ }
5380
+ out.push({ type: "json", name, json: value });
5381
+ }
5279
5382
  const aiBinding = pluckString(outputConfig.ai, "binding");
5280
5383
  if (aiBinding) out.push({ type: "ai", name: aiBinding });
5281
5384
  for (const v of asObjectArray(outputConfig.vectorize)) {
@@ -5292,6 +5395,11 @@ function bindingManifestFromOutputConfig(outputConfig) {
5292
5395
  ...metric && (metric === "cosine" || metric === "euclidean" || metric === "dot-product") && { metric }
5293
5396
  });
5294
5397
  }
5398
+ for (const a of asObjectArray(outputConfig.ai_search)) {
5399
+ const name = pluckString(a, "binding");
5400
+ const instance_name = pluckString(a, "instance_name");
5401
+ if (name && instance_name) out.push({ type: "ai_search", name, instance_name });
5402
+ }
5295
5403
  for (const r of asObjectArray(outputConfig.r2_buckets)) {
5296
5404
  const name = pluckString(r, "binding");
5297
5405
  const bucket_name = pluckString(r, "bucket_name");
@@ -5353,6 +5461,25 @@ function bindingManifestFromOutputConfig(outputConfig) {
5353
5461
  function isObject(v) {
5354
5462
  return v !== null && typeof v === "object" && !Array.isArray(v);
5355
5463
  }
5464
+ function isBindingJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
5465
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
5466
+ return true;
5467
+ }
5468
+ if (typeof value !== "object") return false;
5469
+ if (ancestors.has(value)) return false;
5470
+ if (!Array.isArray(value)) {
5471
+ const prototype = Object.getPrototypeOf(value);
5472
+ if (prototype !== Object.prototype && prototype !== null) return false;
5473
+ }
5474
+ ancestors.add(value);
5475
+ const valid = Array.isArray(value) ? value.every((entry) => isBindingJsonValue(entry, ancestors)) : Object.values(value).every((entry) => isBindingJsonValue(entry, ancestors));
5476
+ ancestors.delete(value);
5477
+ return valid;
5478
+ }
5479
+ function envVarSizeError(name, serializedValue) {
5480
+ const size = new TextEncoder().encode(serializedValue).byteLength;
5481
+ return size <= MAX_ENV_VAR_BYTES ? null : `Environment variable binding "${name}" is ${size} bytes; maximum is ${MAX_ENV_VAR_BYTES}`;
5482
+ }
5356
5483
  function pluckString(v, key) {
5357
5484
  if (!isObject(v)) return null;
5358
5485
  const val = v[key];
@@ -5827,218 +5954,8 @@ var DIRECTORY_SCHEMAS = [
5827
5954
  postsSchema
5828
5955
  ];
5829
5956
 
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
5957
  // src/server/schemas/shared-do.ts
6036
5958
  var GLOBAL_DO_TYPES = [
6037
- {
6038
- name: "workspace",
6039
- schemas: WORKSPACE_SCHEMAS,
6040
- description: "Shared workspace data (teams, tasks, projects, people, ledger)"
6041
- },
6042
5959
  {
6043
5960
  name: "conv",
6044
5961
  schemas: [...CONVERSATION_SCHEMAS, ...VOTING_SCHEMAS],
@@ -6066,6 +5983,9 @@ var APP_MIGRATION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/;
6066
5983
  var MAX_APP_MIGRATION_IDS = 256;
6067
5984
  var WORKER_OWNED_NOT_FOUND_MIGRATION_ID = "2026-08-worker-owned-not-found";
6068
5985
  var SECURE_ROOM_BOUNDARIES_MIGRATION_ID = "2026-08-secure-room-boundaries";
5986
+ var ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID = "2026-08-action-tools-delete-where";
5987
+ var BUILD_INJECTED_APP_ID_MIGRATION_ID = "2026-08-build-injected-app-id";
5988
+ var ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID = "2026-08-action-routes-bearer-guard";
6069
5989
  function validateAppMigrationIds(value) {
6070
5990
  if (!Array.isArray(value)) {
6071
5991
  return { valid: false, reason: "app migrations must be a JSON string array" };
@@ -6934,8 +6854,8 @@ function overPartBound(bytes) {
6934
6854
  if (bytes <= UPLOAD_PART_BYTES) return null;
6935
6855
  return fail2(413, oversizePartMessage(bytes), "too_large", { maxBytes: UPLOAD_PART_BYTES });
6936
6856
  }
6937
- function declaredLength(request, what) {
6938
- const raw = request.headers.get("content-length");
6857
+ function declaredLength(request2, what) {
6858
+ const raw = request2.headers.get("content-length");
6939
6859
  const declared = raw === null ? NaN : Number(raw);
6940
6860
  if (!Number.isSafeInteger(declared) || declared < 0) {
6941
6861
  return fail2(
@@ -7013,7 +6933,7 @@ function multipartFailure(error) {
7013
6933
  }
7014
6934
  function createScopedR2Handler(config) {
7015
6935
  const requireAuthForMutations = config.requireAuthForMutations ?? true;
7016
- return async (request, url, bucket, auth) => {
6936
+ return async (request2, url, bucket, auth) => {
7017
6937
  let subpathRaw;
7018
6938
  try {
7019
6939
  subpathRaw = decodeURIComponent(url.pathname.replace("/api/files", "").replace(/^\//, ""));
@@ -7042,26 +6962,26 @@ function createScopedR2Handler(config) {
7042
6962
  }
7043
6963
  const prefix = result.prefix;
7044
6964
  const excludedPrefixes = result.excludedPrefixes ?? [];
7045
- if (isFilesVerbPath(request.method, subpath)) {
6965
+ if (isFilesVerbPath(request2.method, subpath)) {
7046
6966
  if (requireAuthForMutations && !auth.userId) {
7047
- await request.body?.cancel().catch(() => void 0);
6967
+ await request2.body?.cancel().catch(() => void 0);
7048
6968
  return fail2(401, "Authentication required", "unauthorized");
7049
6969
  }
7050
- return subpath === "upload" ? handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
6970
+ return subpath === "upload" ? handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) : handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath);
7051
6971
  }
7052
- if (!subpath && request.method === "GET") {
6972
+ if (!subpath && request2.method === "GET") {
7053
6973
  return handleList(bucket, prefix, excludedPrefixes, url, scope, auth);
7054
6974
  }
7055
- if (subpath && request.method === "GET") {
6975
+ if (subpath && request2.method === "GET") {
7056
6976
  if (!isKeyWithinScope(subpath, prefix, excludedPrefixes)) {
7057
6977
  return Response.json(
7058
6978
  { error: "Access denied: key outside scope" },
7059
6979
  { status: 403, headers: CORS_HEADERS4 }
7060
6980
  );
7061
6981
  }
7062
- return handleDownload(request, bucket, subpath, scope);
6982
+ return handleDownload(request2, bucket, subpath, scope);
7063
6983
  }
7064
- if (subpath && request.method === "DELETE") {
6984
+ if (subpath && request2.method === "DELETE") {
7065
6985
  if (requireAuthForMutations && !auth.userId) {
7066
6986
  return Response.json(
7067
6987
  { error: "Authentication required" },
@@ -7087,18 +7007,18 @@ function createScopedR2Handler(config) {
7087
7007
  return Response.json({ error: "Method not allowed" }, { status: 405, headers: CORS_HEADERS4 });
7088
7008
  };
7089
7009
  }
7090
- async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
7010
+ async function handleUpload(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
7091
7011
  try {
7092
- const declaredBytes = declaredLength(request, "File uploads");
7012
+ const declaredBytes = declaredLength(request2, "File uploads");
7093
7013
  if (declaredBytes instanceof Response) return declaredBytes;
7094
7014
  const tooLargeRequest = overRequestBound(declaredBytes, 8 * 1024);
7095
7015
  if (tooLargeRequest) return tooLargeRequest;
7096
- const contentType = request.headers.get("content-type") || "";
7016
+ const contentType = request2.headers.get("content-type") || "";
7097
7017
  let fileData;
7098
7018
  let fileName;
7099
7019
  let rawMimeType;
7100
7020
  if (contentType.includes("multipart/form-data")) {
7101
- const formData = await request.formData();
7021
+ const formData = await request2.formData();
7102
7022
  const file = formData.get("file");
7103
7023
  if (!file) return fail2(400, "No file provided", "bad_request");
7104
7024
  const tooBig = overRequestBound(file.size);
@@ -7107,7 +7027,7 @@ async function handleUpload(request, url, bucket, prefix, excludedPrefixes, auth
7107
7027
  fileName = formData.get("name")?.toString() || file.name;
7108
7028
  rawMimeType = file.type || "application/octet-stream";
7109
7029
  } else {
7110
- const body = await request.json();
7030
+ const body = await request2.json();
7111
7031
  if (!body.data || !body.name) return fail2(400, "Missing data or name", "bad_request");
7112
7032
  const base64Data = body.data.replace(/^data:[^;]+;base64,/, "");
7113
7033
  const tooBig = overRequestBound(Math.floor(base64Data.length * 3 / 4));
@@ -7188,15 +7108,15 @@ function storedFileJson(key, prefix, name, url, scope) {
7188
7108
  );
7189
7109
  }
7190
7110
  var MAX_CONTROL_BODY_BYTES = 64 * 1024;
7191
- async function readControlJson(request) {
7192
- const declared = declaredLength(request, "This request");
7111
+ async function readControlJson(request2) {
7112
+ const declared = declaredLength(request2, "This request");
7193
7113
  if (declared instanceof Response) return declared;
7194
7114
  if (declared > MAX_CONTROL_BODY_BYTES) {
7195
7115
  return fail2(413, `Request body exceeds ${MAX_CONTROL_BODY_BYTES} bytes`, "too_large");
7196
7116
  }
7197
7117
  let parsed;
7198
7118
  try {
7199
- parsed = await request.json();
7119
+ parsed = await request2.json();
7200
7120
  } catch {
7201
7121
  return fail2(400, "Body must be JSON", "bad_request");
7202
7122
  }
@@ -7248,7 +7168,7 @@ function multipartLayoutError(declaredBytes) {
7248
7168
  { partSize: UPLOAD_PART_BYTES, partCount: multipartPartCount(declaredBytes) }
7249
7169
  );
7250
7170
  }
7251
- async function streamPart(upload, partNumber, body, expectedBytes) {
7171
+ async function streamPart(upload2, partNumber, body, expectedBytes) {
7252
7172
  let observedBytes = 0;
7253
7173
  const sized = new FixedLengthStream(expectedBytes);
7254
7174
  const pumped = body.pipeThrough(
@@ -7260,7 +7180,7 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
7260
7180
  })
7261
7181
  ).pipeTo(sized.writable);
7262
7182
  const [uploaded, drained] = await Promise.allSettled([
7263
- upload.uploadPart(partNumber, sized.readable),
7183
+ upload2.uploadPart(partNumber, sized.readable),
7264
7184
  pumped
7265
7185
  ]);
7266
7186
  if (uploaded.status === "rejected" || drained.status === "rejected") {
@@ -7269,20 +7189,20 @@ async function streamPart(upload, partNumber, body, expectedBytes) {
7269
7189
  }
7270
7190
  return { ok: true, part: uploaded.value };
7271
7191
  }
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);
7192
+ async function handleMultipart(request2, url, bucket, prefix, excludedPrefixes, auth, scope, subpath) {
7193
+ if (subpath === "multipart" && request2.method === "POST") {
7194
+ return multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth);
7275
7195
  }
7276
7196
  if (subpath === "multipart/part") {
7277
- return multipartPart(request, url, bucket, prefix, excludedPrefixes, auth);
7197
+ return multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth);
7278
7198
  }
7279
7199
  if (subpath === "multipart/complete") {
7280
- return multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope);
7200
+ return multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope);
7281
7201
  }
7282
7202
  return multipartAbort(url, bucket, prefix, excludedPrefixes, auth);
7283
7203
  }
7284
- async function multipartInit(request, url, bucket, prefix, excludedPrefixes, auth) {
7285
- const body = await readControlJson(request);
7204
+ async function multipartInit(request2, url, bucket, prefix, excludedPrefixes, auth) {
7205
+ const body = await readControlJson(request2);
7286
7206
  if (body instanceof Response) return body;
7287
7207
  const size = body.size;
7288
7208
  if (!Number.isSafeInteger(size) || size <= 0) {
@@ -7323,9 +7243,9 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7323
7243
  "request_id_conflict"
7324
7244
  );
7325
7245
  }
7326
- let upload;
7246
+ let upload2;
7327
7247
  try {
7328
- upload = await bucket.createMultipartUpload(located.key, {
7248
+ upload2 = await bucket.createMultipartUpload(located.key, {
7329
7249
  httpMetadata: { contentType: mimeType },
7330
7250
  customMetadata: fileMetadata(name, auth.userId, requestId, size)
7331
7251
  });
@@ -7337,7 +7257,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7337
7257
  body: JSON.stringify({
7338
7258
  version: 1,
7339
7259
  reservationId: requestId,
7340
- uploadId: upload.uploadId,
7260
+ uploadId: upload2.uploadId,
7341
7261
  objectKey: located.key,
7342
7262
  uploadKey: located.subkey,
7343
7263
  admitted: false,
@@ -7351,7 +7271,7 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7351
7271
  const reservedBytes = Math.max(size, 5 * 1024 * 1024);
7352
7272
  const admission = await admitStorage(bucket, auth.storage, reservedBytes, 0, marker);
7353
7273
  if (admission instanceof Response) {
7354
- await upload.abort().catch(() => void 0);
7274
+ await upload2.abort().catch(() => void 0);
7355
7275
  if (admission.status === 409 && auth.storage) {
7356
7276
  const winner = await readMultipartReservation(bucket, auth.storage, requestId);
7357
7277
  if (winner && winner !== "invalid" && winner.objectKey === located.key && winner.uploadKey === located.subkey && winner.declaredBytes === size && winner.admitted) {
@@ -7361,11 +7281,11 @@ async function multipartInit(request, url, bucket, prefix, excludedPrefixes, aut
7361
7281
  return admission;
7362
7282
  }
7363
7283
  if (auth.storage && !await activateMultipartReservation(bucket, auth.storage, requestId)) {
7364
- await upload.abort().catch(() => void 0);
7284
+ await upload2.abort().catch(() => void 0);
7365
7285
  await releaseQuotaMarker(bucket, auth.storage, admission.markerKey, true);
7366
7286
  return quotaUnavailable("This upload reservation could not be finalized. Retry shortly.");
7367
7287
  }
7368
- return multipartSessionJson(upload.uploadId, located.subkey, requestId);
7288
+ return multipartSessionJson(upload2.uploadId, located.subkey, requestId);
7369
7289
  }
7370
7290
  async function abandonMultipart(bucket, session, auth, reservation) {
7371
7291
  let result = "aborted";
@@ -7383,12 +7303,12 @@ async function abandonMultipart(bucket, session, auth, reservation) {
7383
7303
  }
7384
7304
  return result;
7385
7305
  }
7386
- async function multipartPart(request, url, bucket, prefix, excludedPrefixes, auth) {
7306
+ async function multipartPart(request2, url, bucket, prefix, excludedPrefixes, auth) {
7387
7307
  const refuse = async (response) => {
7388
- await request.body?.cancel().catch(() => void 0);
7308
+ await request2.body?.cancel().catch(() => void 0);
7389
7309
  return response;
7390
7310
  };
7391
- const declaredBytes = declaredLength(request, "Part uploads");
7311
+ const declaredBytes = declaredLength(request2, "Part uploads");
7392
7312
  if (declaredBytes instanceof Response) return refuse(declaredBytes);
7393
7313
  const tooBig = overPartBound(declaredBytes);
7394
7314
  if (tooBig) return refuse(tooBig);
@@ -7400,7 +7320,7 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
7400
7320
  }
7401
7321
  const session = resolveSession(url, prefix, excludedPrefixes);
7402
7322
  if (session instanceof Response) return refuse(session);
7403
- if (!request.body) return fail2(400, "A part body is required", "bad_request");
7323
+ if (!request2.body) return fail2(400, "A part body is required", "bad_request");
7404
7324
  const reservation = await readMultipartReservation(bucket, auth.storage, session.reservationId);
7405
7325
  if (reservation === "invalid") {
7406
7326
  return refuse(quotaUnavailable("This upload reservation could not be read. Retry shortly."));
@@ -7442,8 +7362,8 @@ async function multipartPart(request, url, bucket, prefix, excludedPrefixes, aut
7442
7362
  if (refreshed === "busy") {
7443
7363
  return refuse(quotaUnavailable("This upload reservation is busy. Retry the part shortly."));
7444
7364
  }
7445
- const upload = bucket.resumeMultipartUpload(session.key, session.uploadId);
7446
- const stored = await streamPart(upload, partNumber, request.body, declaredBytes);
7365
+ const upload2 = bucket.resumeMultipartUpload(session.key, session.uploadId);
7366
+ const stored = await streamPart(upload2, partNumber, request2.body, declaredBytes);
7447
7367
  if (!stored.ok) {
7448
7368
  if (r2ErrorCode(stored.error) === 10024 && reservation) {
7449
7369
  await releaseQuotaMarker(bucket, auth.storage, reservation.key, true);
@@ -7499,8 +7419,8 @@ async function finishCompletedMultipart(bucket, session, reservation, object, au
7499
7419
  const name = object.customMetadata?.originalName ?? session.uploadKey.split("/").pop() ?? "file";
7500
7420
  return storedFileJson(session.key, prefix, name, url, scope);
7501
7421
  }
7502
- async function multipartComplete(request, url, bucket, prefix, excludedPrefixes, auth, scope) {
7503
- const body = await readControlJson(request);
7422
+ async function multipartComplete(request2, url, bucket, prefix, excludedPrefixes, auth, scope) {
7423
+ const body = await readControlJson(request2);
7504
7424
  if (body instanceof Response) return body;
7505
7425
  const session = resolveSession(url, prefix, excludedPrefixes);
7506
7426
  if (session instanceof Response) return session;
@@ -7659,7 +7579,7 @@ async function handleList(bucket, prefix, excludedPrefixes, url, scope, auth) {
7659
7579
  { headers: CORS_HEADERS4 }
7660
7580
  );
7661
7581
  }
7662
- async function handleDownload(request, bucket, key, scope) {
7582
+ async function handleDownload(request2, bucket, key, scope) {
7663
7583
  const object = await bucket.get(key);
7664
7584
  if (!object) {
7665
7585
  return Response.json({ error: "File not found" }, { status: 404, headers: CORS_HEADERS4 });
@@ -7681,7 +7601,7 @@ async function handleDownload(request, bucket, key, scope) {
7681
7601
  contentDisposition(originalName, mustDownload ? "attachment" : "inline")
7682
7602
  );
7683
7603
  if (mustDownload) headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
7684
- if (scope === "app" && request.headers.get("if-none-match") === object.httpEtag) {
7604
+ if (scope === "app" && request2.headers.get("if-none-match") === object.httpEtag) {
7685
7605
  return new Response(null, { status: 304, headers });
7686
7606
  }
7687
7607
  return new Response(object.body, { headers });
@@ -8134,8 +8054,8 @@ async function composioTools(env, options) {
8134
8054
  if (!list.ok || !list.success) {
8135
8055
  throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
8136
8056
  }
8137
- const listItems = list.data?.items;
8138
- const items = Array.isArray(listItems) ? listItems : [];
8057
+ const listItems2 = list.data?.items;
8058
+ const items = Array.isArray(listItems2) ? listItems2 : [];
8139
8059
  const tools = {};
8140
8060
  for (const item of items) {
8141
8061
  if (!item?.slug) continue;
@@ -8720,18 +8640,272 @@ var COST_RATES = {
8720
8640
  queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
8721
8641
  /** USD per stored dimension per month. */
8722
8642
  storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
8643
+ },
8644
+ aiSearch: {
8645
+ ingestPerToken: 0.75 / 1e6,
8646
+ ingestImagePerToken: 0.5 / 1e6,
8647
+ storagePerByteMonth: 2 / 1e9,
8648
+ hybridOrSemanticPerQuery: 0.75 / 1e3,
8649
+ fulltextPerQuery: 0.1 / 1e3
8723
8650
  }
8724
8651
  };
8725
8652
  function priceBindingUsageEvent(kind, op, units) {
8653
+ if (!Number.isFinite(units) || units < 0) return 0;
8726
8654
  if (kind === "ai" && op === "input") {
8727
8655
  return units * COST_RATES.ai.embedInputPerChar;
8728
8656
  }
8729
8657
  if (kind === "vectorize" && op === "query") {
8730
8658
  return units * COST_RATES.vectorize.queriedPerDim;
8731
8659
  }
8660
+ if (kind === "ai_search") {
8661
+ if (op === "ingest") return units * COST_RATES.aiSearch.ingestPerToken;
8662
+ if (op === "ingest-image") return units * COST_RATES.aiSearch.ingestImagePerToken;
8663
+ if (op === "storage") return units * COST_RATES.aiSearch.storagePerByteMonth;
8664
+ if (op === "search-hybrid" || op === "search-semantic")
8665
+ return units * COST_RATES.aiSearch.hybridOrSemanticPerQuery;
8666
+ if (op === "search-fulltext") return units * COST_RATES.aiSearch.fulltextPerQuery;
8667
+ }
8732
8668
  return 0;
8733
8669
  }
8734
8670
 
8671
+ // src/server/utils/knowledge-base.ts
8672
+ var MAX_UPLOAD_BYTES = 4 * 1024 * 1024;
8673
+ var TEXT_MIME = /^(text\/|application\/(json|xml|javascript|x-(javascript|typescript)))/iu;
8674
+ var MAX_QUERY_CHARS = 4096;
8675
+ function hasControlChar(value) {
8676
+ for (let i = 0; i < value.length; i++) {
8677
+ const code = value.charCodeAt(i);
8678
+ if (code < 32 || code === 127) return true;
8679
+ }
8680
+ return false;
8681
+ }
8682
+ var KnowledgeError = class extends Error {
8683
+ constructor(status, code, message, uploadedItems) {
8684
+ super(message);
8685
+ this.status = status;
8686
+ this.code = code;
8687
+ this.uploadedItems = uploadedItems;
8688
+ this.name = "KnowledgeError";
8689
+ }
8690
+ };
8691
+ function normalizeKnowledgeFolder(folder) {
8692
+ if (!folder || folder.includes("\\") || hasControlChar(folder) || folder.startsWith("/"))
8693
+ throw new KnowledgeError(400, "invalid_folder", "Folder must be a relative forward-slash path");
8694
+ const withoutTrailingSlash = folder.endsWith("/") ? folder.slice(0, -1) : folder;
8695
+ if (!withoutTrailingSlash || withoutTrailingSlash.endsWith("/"))
8696
+ throw new KnowledgeError(400, "invalid_folder", "Folder cannot contain empty segments");
8697
+ const parts = withoutTrailingSlash.split("/");
8698
+ if (parts.some((part) => !part || part === "." || part === ".."))
8699
+ throw new KnowledgeError(
8700
+ 400,
8701
+ "invalid_folder",
8702
+ "Folder cannot contain empty, dot, or parent segments"
8703
+ );
8704
+ const normalized = `${parts.join("/")}/`;
8705
+ if (normalized.length > 128)
8706
+ throw new KnowledgeError(400, "invalid_folder", "Folder is too long for an AI Search item key");
8707
+ return normalized;
8708
+ }
8709
+ function validateKnowledgeFilename(name) {
8710
+ if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || hasControlChar(name)) {
8711
+ throw new KnowledgeError(
8712
+ 400,
8713
+ "invalid_filename",
8714
+ "File name must be a non-empty base name without path separators"
8715
+ );
8716
+ }
8717
+ }
8718
+ function folderFilter(folder) {
8719
+ const value = normalizeKnowledgeFolder(folder);
8720
+ return { folder: { $gte: value, $lt: `${value.slice(0, -1)}0` } };
8721
+ }
8722
+ function knowledge(env) {
8723
+ const add = (file, options = {}) => addFile(env, file, options);
8724
+ const list = (options = {}) => listItems(env, options);
8725
+ const search = (query, options = {}) => searchItems(env, query, options);
8726
+ return {
8727
+ add,
8728
+ list,
8729
+ search,
8730
+ async remove(itemId) {
8731
+ if (!/^[A-Za-z0-9_-]{1,256}$/u.test(itemId))
8732
+ throw new KnowledgeError(400, "invalid_item_id", "Invalid knowledge item id");
8733
+ await request(env, `/api/knowledge/items/${encodeURIComponent(itemId)}`, { method: "DELETE" });
8734
+ },
8735
+ scoped({ folder }) {
8736
+ const fixed = normalizeKnowledgeFolder(folder);
8737
+ return {
8738
+ add: (file) => add(file, { folder: fixed }),
8739
+ list: (options = {}) => list({ ...options, folder: fixed }),
8740
+ search: (query, options = {}) => search(query, { ...options, folder: fixed })
8741
+ };
8742
+ }
8743
+ };
8744
+ }
8745
+ async function addFile(env, file, options) {
8746
+ validateKnowledgeFilename(file.name);
8747
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
8748
+ if ((folder?.length ?? 0) + file.name.length > 128)
8749
+ throw new KnowledgeError(
8750
+ 400,
8751
+ "invalid_key",
8752
+ "Folder and filename together exceed the 128-character AI Search key limit"
8753
+ );
8754
+ if (file.size <= MAX_UPLOAD_BYTES) return upload(env, file, folder);
8755
+ if (!TEXT_MIME.test(file.type) && !/\.(txt|md|mdx|csv|json|xml|html?|ya?ml|tsx?|jsx?|css)$/iu.test(file.name))
8756
+ throw new KnowledgeError(
8757
+ 413,
8758
+ "file_too_large",
8759
+ "This file exceeds 4 MB. Split the source document with a format-aware tool before uploading."
8760
+ );
8761
+ const bytes = new Uint8Array(await file.arrayBuffer());
8762
+ const parts = splitUtf8(bytes, MAX_UPLOAD_BYTES);
8763
+ const uploaded = [];
8764
+ try {
8765
+ for (let i = 0; i < parts.length; i++) {
8766
+ const bytes2 = parts[i];
8767
+ const contents = bytes2.buffer.slice(
8768
+ bytes2.byteOffset,
8769
+ bytes2.byteOffset + bytes2.byteLength
8770
+ );
8771
+ const part = new File([contents], partName(file.name, i + 1), {
8772
+ type: file.type || "text/plain"
8773
+ });
8774
+ if ((folder?.length ?? 0) + part.name.length > 128)
8775
+ throw new KnowledgeError(
8776
+ 400,
8777
+ "invalid_key",
8778
+ "Folder and split filename together exceed the 128-character AI Search key limit"
8779
+ );
8780
+ uploaded.push(...(await upload(env, part, folder)).items);
8781
+ }
8782
+ } catch (error) {
8783
+ if (error instanceof KnowledgeError)
8784
+ throw new KnowledgeError(error.status, error.code, error.message, uploaded);
8785
+ throw new KnowledgeError(502, "knowledge_upload_failed", "Knowledge upload failed", uploaded);
8786
+ }
8787
+ return { items: uploaded };
8788
+ }
8789
+ function splitUtf8(bytes, max) {
8790
+ const out = [];
8791
+ for (let start = 0; start < bytes.length; ) {
8792
+ let end = Math.min(start + max, bytes.length);
8793
+ if (end < bytes.length) {
8794
+ let cut = end;
8795
+ while (cut > start && (bytes[cut] & 192) === 128) cut--;
8796
+ for (let i = cut - 1; i > Math.max(start, cut - 8192); i--)
8797
+ if (bytes[i] === 10 && (i === start || bytes[i - 1] === 10)) {
8798
+ cut = i + 1;
8799
+ break;
8800
+ }
8801
+ end = cut > start ? cut : end;
8802
+ }
8803
+ out.push(bytes.slice(start, end));
8804
+ start = end;
8805
+ }
8806
+ return out;
8807
+ }
8808
+ function partName(name, n) {
8809
+ const dot = name.lastIndexOf(".");
8810
+ return dot > 0 ? `${name.slice(0, dot)}.part-${n}${name.slice(dot)}` : `${name}.part-${n}`;
8811
+ }
8812
+ async function upload(env, file, folder) {
8813
+ const body = new FormData();
8814
+ body.append("file", file);
8815
+ if (folder) body.append("folder", folder);
8816
+ return await request(env, "/api/knowledge/items", {
8817
+ method: "POST",
8818
+ body
8819
+ });
8820
+ }
8821
+ async function listItems(env, options) {
8822
+ if (options.page !== void 0 && (!Number.isInteger(options.page) || options.page < 1))
8823
+ throw new KnowledgeError(400, "invalid_page", "List page must be a positive integer");
8824
+ if (options.perPage !== void 0 && (!Number.isInteger(options.perPage) || options.perPage < 1 || options.perPage > 50))
8825
+ throw new KnowledgeError(
8826
+ 400,
8827
+ "invalid_per_page",
8828
+ "List perPage must be an integer from 1 to 50"
8829
+ );
8830
+ if (options.search !== void 0 && options.search.length > 256)
8831
+ throw new KnowledgeError(400, "invalid_search", "List search cannot exceed 256 characters");
8832
+ if (options.status !== void 0 && !["queued", "running", "completed", "error", "skipped", "outdated"].includes(options.status))
8833
+ throw new KnowledgeError(400, "invalid_status", "Invalid knowledge item status");
8834
+ const q = new URLSearchParams();
8835
+ if (options.folder) q.set("folder", normalizeKnowledgeFolder(options.folder));
8836
+ if (options.page !== void 0) q.set("page", String(options.page));
8837
+ if (options.perPage !== void 0) q.set("perPage", String(options.perPage));
8838
+ if (options.status) q.set("status", options.status);
8839
+ if (options.search) q.set("search", options.search);
8840
+ return await request(env, `/api/knowledge/items${q.size ? `?${q}` : ""}`);
8841
+ }
8842
+ async function searchItems(env, query, options) {
8843
+ const normalizedQuery = query.trim();
8844
+ if (!normalizedQuery) throw new KnowledgeError(400, "invalid_query", "Search query is required");
8845
+ if (normalizedQuery.length > MAX_QUERY_CHARS)
8846
+ throw new KnowledgeError(
8847
+ 400,
8848
+ "invalid_query",
8849
+ `Search query cannot exceed ${MAX_QUERY_CHARS} characters`
8850
+ );
8851
+ if (options.mode !== void 0 && options.mode !== "hybrid" && options.mode !== "semantic" && options.mode !== "fulltext")
8852
+ throw new KnowledgeError(400, "invalid_mode", "Invalid knowledge search mode");
8853
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 50))
8854
+ throw new KnowledgeError(
8855
+ 400,
8856
+ "invalid_limit",
8857
+ "Search limit must be an integer from 1 through 50"
8858
+ );
8859
+ if (options.matchThreshold !== void 0 && (!Number.isFinite(options.matchThreshold) || options.matchThreshold < 0 || options.matchThreshold > 1))
8860
+ throw new KnowledgeError(
8861
+ 400,
8862
+ "invalid_match_threshold",
8863
+ "Match threshold must be between 0 and 1"
8864
+ );
8865
+ if (options.queryRewrite !== void 0 && typeof options.queryRewrite !== "boolean")
8866
+ throw new KnowledgeError(
8867
+ 400,
8868
+ "invalid_query_rewrite",
8869
+ "queryRewrite must be a boolean when provided"
8870
+ );
8871
+ const folder = options.folder === void 0 ? void 0 : normalizeKnowledgeFolder(options.folder);
8872
+ return await request(env, "/api/knowledge/search", {
8873
+ method: "POST",
8874
+ headers: { "content-type": "application/json" },
8875
+ body: JSON.stringify({
8876
+ query: normalizedQuery,
8877
+ mode: options.mode ?? "hybrid",
8878
+ limit: options.limit ?? 10,
8879
+ queryRewrite: options.queryRewrite ?? false,
8880
+ ...options.matchThreshold !== void 0 && {
8881
+ matchThreshold: options.matchThreshold
8882
+ },
8883
+ ...folder && { folder }
8884
+ })
8885
+ });
8886
+ }
8887
+ async function request(env, path, init = {}) {
8888
+ const headers = new Headers(init.headers);
8889
+ appendAppIdentity(headers, env);
8890
+ const response = await apiWorkerFetch(env, path, { ...init, headers });
8891
+ const text = await response.text();
8892
+ let data;
8893
+ try {
8894
+ data = text ? JSON.parse(text) : void 0;
8895
+ } catch {
8896
+ data = void 0;
8897
+ }
8898
+ if (!response.ok) {
8899
+ const body = data && typeof data === "object" ? data : {};
8900
+ throw new KnowledgeError(
8901
+ response.status,
8902
+ typeof body.code === "string" ? body.code : "knowledge_request_failed",
8903
+ typeof body.error === "string" ? body.error : "Knowledge request failed"
8904
+ );
8905
+ }
8906
+ return data;
8907
+ }
8908
+
8735
8909
  // src/server/utils/d1-migrations.ts
8736
8910
  var META_TABLE = "_dpc_migrations";
8737
8911
  var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
@@ -9242,16 +9416,16 @@ var RequestBodyTooLargeError = class extends Error {
9242
9416
  this.name = "RequestBodyTooLargeError";
9243
9417
  }
9244
9418
  };
9245
- async function readBoundedRequestText(request, maxBytes) {
9419
+ async function readBoundedRequestText(request2, maxBytes) {
9246
9420
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
9247
9421
  throw new TypeError("maxBytes must be a positive safe integer");
9248
9422
  }
9249
- const declared = request.headers.get("content-length");
9423
+ const declared = request2.headers.get("content-length");
9250
9424
  if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
9251
9425
  throw new RequestBodyTooLargeError(maxBytes);
9252
9426
  }
9253
- if (!request.body) return "";
9254
- const reader = request.body.getReader();
9427
+ if (!request2.body) return "";
9428
+ const reader = request2.body.getReader();
9255
9429
  const decoder = new TextDecoder();
9256
9430
  let bytes = 0;
9257
9431
  let text = "";
@@ -9465,10 +9639,10 @@ function documentationRequestPath(pathname, publicBasePath) {
9465
9639
  const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
9466
9640
  return canonicalNativeDocumentationRequestPath(clean);
9467
9641
  }
9468
- async function fetchDocumentationAsset(env, request, initialUrl, assetBasePath) {
9642
+ async function fetchDocumentationAsset(env, request2, initialUrl, assetBasePath) {
9469
9643
  let url = initialUrl;
9470
9644
  for (let redirects = 0; redirects < 3; redirects++) {
9471
- const response = await env.ASSETS.fetch(new Request(url, request));
9645
+ const response = await env.ASSETS.fetch(new Request(url, request2));
9472
9646
  const location = response.headers.get("Location");
9473
9647
  if (response.status < 300 || response.status >= 400 || !location) return response;
9474
9648
  const next = new URL(location, url);
@@ -9508,11 +9682,11 @@ function secureDocumentationResponse(response, status, pathname) {
9508
9682
  var MCP_SERVER_VERSION = "1.0.0";
9509
9683
  var CURRENT_PROTOCOL_VERSION = "2025-11-25";
9510
9684
  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);
9685
+ function initializeResponse(request2) {
9686
+ const params = objectValue(request2.params);
9513
9687
  const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
9514
9688
  const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
9515
- return mcpResult(request.id, {
9689
+ return mcpResult(request2.id, {
9516
9690
  protocolVersion,
9517
9691
  capabilities: {
9518
9692
  tools: { listChanged: false },
@@ -9526,11 +9700,11 @@ function initializeResponse(request) {
9526
9700
  instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
9527
9701
  });
9528
9702
  }
9529
- function hasValidOrigin(request) {
9530
- const origin = request.headers.get("Origin");
9703
+ function hasValidOrigin(request2) {
9704
+ const origin = request2.headers.get("Origin");
9531
9705
  if (!origin) return true;
9532
9706
  try {
9533
- return new URL(origin).origin === new URL(request.url).origin;
9707
+ return new URL(origin).origin === new URL(request2.url).origin;
9534
9708
  } catch {
9535
9709
  return false;
9536
9710
  }
@@ -9656,8 +9830,8 @@ var DOCUMENTATION_TOOLS = [
9656
9830
  }
9657
9831
  }
9658
9832
  ];
9659
- async function callDocumentationTool(rawRequest, request, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
9660
- const params = objectValue(request.params);
9833
+ async function callDocumentationTool(rawRequest, request2, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
9834
+ const params = objectValue(request2.params);
9661
9835
  const name = typeof params?.name === "string" ? params.name : "";
9662
9836
  const args = objectValue(params?.arguments) ?? {};
9663
9837
  const origin = new URL(rawRequest.url).origin;
@@ -9665,13 +9839,13 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9665
9839
  const query = typeof args.query === "string" ? args.query.trim() : "";
9666
9840
  const limit = args.limit === void 0 ? 6 : args.limit;
9667
9841
  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");
9842
+ return mcpError(request2.id, -32602, "Invalid documentation_search arguments");
9669
9843
  }
9670
9844
  const results = searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
9671
9845
  ...result,
9672
9846
  url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
9673
9847
  }));
9674
- return toolResult(request.id, {
9848
+ return toolResult(request2.id, {
9675
9849
  ...results[0]?.termCoverage === 1 ? {} : {
9676
9850
  notice: "No section matches every search term \u2014 the topic may be absent or spread across sections."
9677
9851
  },
@@ -9681,7 +9855,7 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9681
9855
  if (name === "documentation_read") {
9682
9856
  const route = typeof args.route === "string" ? args.route.trim() : "";
9683
9857
  if (!route || route.length > 500) {
9684
- return mcpError(request.id, -32602, "Invalid documentation_read arguments");
9858
+ return mcpError(request2.id, -32602, "Invalid documentation_read arguments");
9685
9859
  }
9686
9860
  const hash = route.indexOf("#");
9687
9861
  const fragment = hash === -1 ? "" : route.slice(hash + 1);
@@ -9693,9 +9867,9 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9693
9867
  rawRequest.url,
9694
9868
  assetBasePath
9695
9869
  );
9696
- if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
9870
+ if (!page) return toolError(request2.id, `No published documentation page exists at ${route}`);
9697
9871
  const section = fragment ? documentationPageSection(page.markdown, fragment) : null;
9698
- return toolResult(request.id, {
9872
+ return toolResult(request2.id, {
9699
9873
  ...fragment && section === null ? { notice: `The fragment #${fragment} was not found; returning the whole page.` } : {},
9700
9874
  ...page,
9701
9875
  ...section === null ? {} : { markdown: section },
@@ -9703,10 +9877,10 @@ async function callDocumentationTool(rawRequest, request, corpus, env, basePath
9703
9877
  });
9704
9878
  } catch (error) {
9705
9879
  console.error("[documentation-mcp] page unavailable:", error);
9706
- return toolError(request.id, "The documentation page could not be read");
9880
+ return toolError(request2.id, "The documentation page could not be read");
9707
9881
  }
9708
9882
  }
9709
- return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
9883
+ return mcpError(request2.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
9710
9884
  }
9711
9885
 
9712
9886
  // src/documentation/worker/mcp-routes.ts
@@ -9743,27 +9917,27 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9743
9917
  }
9744
9918
  const parsed = parseRequest(raw);
9745
9919
  if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
9746
- const request = parsed.request;
9747
- if (request.method !== "initialize") {
9920
+ const request2 = parsed.request;
9921
+ if (request2.method !== "initialize") {
9748
9922
  const protocolVersion = c.req.header("MCP-Protocol-Version");
9749
9923
  if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
9750
- return mcpError(request.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
9924
+ return mcpError(request2.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
9751
9925
  }
9752
9926
  }
9753
- const requestId = request.id;
9927
+ const requestId = request2.id;
9754
9928
  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") {
9929
+ const requestWithId = { ...request2, id: requestId };
9930
+ if (request2.method === "initialize") return initializeResponse(requestWithId);
9931
+ if (request2.method === "ping") return mcpResult(requestId, {});
9932
+ if (request2.method === "tools/list") {
9759
9933
  return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
9760
9934
  }
9761
- if (request.method === "resources/list") {
9935
+ if (request2.method === "resources/list") {
9762
9936
  return mcpResult(requestId, {
9763
9937
  resources: [skillResource(c.req.raw, manifest, publicBasePath)]
9764
9938
  });
9765
9939
  }
9766
- if (request.method === "resources/read") {
9940
+ if (request2.method === "resources/read") {
9767
9941
  return readSkillResource(
9768
9942
  c.req.raw,
9769
9943
  requestWithId,
@@ -9773,8 +9947,8 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9773
9947
  assetBasePath
9774
9948
  );
9775
9949
  }
9776
- if (request.method !== "tools/call") {
9777
- return mcpError(requestId, -32601, `Method not found: ${request.method}`);
9950
+ if (request2.method !== "tools/call") {
9951
+ return mcpError(requestId, -32601, `Method not found: ${request2.method}`);
9778
9952
  }
9779
9953
  const clientKey = await documentationPublicClientKey(
9780
9954
  c.req.header("CF-Connecting-IP") ?? "unknown"
@@ -9831,44 +10005,44 @@ function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE
9831
10005
  }
9832
10006
  );
9833
10007
  }
9834
- async function readSkillResource(rawRequest, request, manifest, env, publicBasePath, assetBasePath) {
9835
- const params = objectValue(request.params);
10008
+ async function readSkillResource(rawRequest, request2, manifest, env, publicBasePath, assetBasePath) {
10009
+ const params = objectValue(request2.params);
9836
10010
  const requestedUri = typeof params?.uri === "string" ? params.uri : "";
9837
10011
  const resource = skillResource(rawRequest, manifest, publicBasePath);
9838
10012
  if (requestedUri !== resource.uri) {
9839
- return mcpError(request.id, -32602, "Unknown documentation resource");
10013
+ return mcpError(request2.id, -32602, "Unknown documentation resource");
9840
10014
  }
9841
10015
  const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
9842
10016
  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);
10017
+ if (!response.ok) return mcpError(request2.id, -32002, "Documentation skill is unavailable", 503);
9844
10018
  const text = await response.text();
9845
10019
  if (text.length > MAX_SKILL_CHARS) {
9846
- return mcpError(request.id, -32002, "Documentation skill exceeds the published size limit", 503);
10020
+ return mcpError(request2.id, -32002, "Documentation skill exceeds the published size limit", 503);
9847
10021
  }
9848
- return mcpResult(request.id, {
10022
+ return mcpResult(request2.id, {
9849
10023
  contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
9850
10024
  });
9851
10025
  }
9852
- function skillResource(request, manifest, publicBasePath) {
10026
+ function skillResource(request2, manifest, publicBasePath) {
9853
10027
  const subject = documentationSubject(manifest.name);
9854
10028
  return {
9855
- uri: new URL(`${publicBasePath}/skill.md`, request.url).toString(),
10029
+ uri: new URL(`${publicBasePath}/skill.md`, request2.url).toString(),
9856
10030
  name: `${subject} skill`,
9857
10031
  title: `Use ${manifest.name}`,
9858
10032
  description: `Agent instructions grounded in the published ${subject}.`,
9859
10033
  mimeType: "text/markdown"
9860
10034
  };
9861
10035
  }
9862
- async function publicManifest(request, env, assetBasePath) {
9863
- const manifest = await loadDocumentationPublishedManifest(env, request.url, assetBasePath);
10036
+ async function publicManifest(request2, env, assetBasePath) {
10037
+ const manifest = await loadDocumentationPublishedManifest(env, request2.url, assetBasePath);
9864
10038
  return manifest?.mcp.access === "public" ? manifest : null;
9865
10039
  }
9866
- function discoveryResponse(request, manifest, publicBasePath) {
9867
- return jsonResponse(serverCard(request, manifest, publicBasePath), 200, {
10040
+ function discoveryResponse(request2, manifest, publicBasePath) {
10041
+ return jsonResponse(serverCard(request2, manifest, publicBasePath), 200, {
9868
10042
  "Cache-Control": "public, max-age=300"
9869
10043
  });
9870
10044
  }
9871
- function serverCard(request, manifest, publicBasePath) {
10045
+ function serverCard(request2, manifest, publicBasePath) {
9872
10046
  return {
9873
10047
  name: manifest.name,
9874
10048
  description: `Search and read the published ${documentationSubject(manifest.name)}.`,
@@ -9881,7 +10055,7 @@ function serverCard(request, manifest, publicBasePath) {
9881
10055
  protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
9882
10056
  transport: {
9883
10057
  type: "streamable-http",
9884
- url: new URL(`${publicBasePath}/mcp`, request.url).toString()
10058
+ url: new URL(`${publicBasePath}/mcp`, request2.url).toString()
9885
10059
  },
9886
10060
  authentication: { required: false },
9887
10061
  capabilities: {
@@ -9890,7 +10064,7 @@ function serverCard(request, manifest, publicBasePath) {
9890
10064
  title,
9891
10065
  description
9892
10066
  })),
9893
- resources: [skillResource(request, manifest, publicBasePath)]
10067
+ resources: [skillResource(request2, manifest, publicBasePath)]
9894
10068
  }
9895
10069
  };
9896
10070
  }
@@ -10313,21 +10487,21 @@ function jsonResponse2(body, status) {
10313
10487
  headers: { "Content-Type": "application/json" }
10314
10488
  });
10315
10489
  }
10316
- async function handleClientErrorReport(request) {
10317
- const site = request.headers.get("sec-fetch-site");
10490
+ async function handleClientErrorReport(request2) {
10491
+ const site = request2.headers.get("sec-fetch-site");
10318
10492
  if (site && site !== "same-origin" && site !== "none") {
10319
10493
  return new Response(null, { status: 403 });
10320
10494
  }
10321
10495
  if (shouldThrottleClientError()) {
10322
10496
  return new Response(null, { status: 429, headers: { "Retry-After": "1" } });
10323
10497
  }
10324
- const declared = Number(request.headers.get("content-length"));
10498
+ const declared = Number(request2.headers.get("content-length"));
10325
10499
  if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
10326
10500
  return jsonResponse2({ error: "payload_too_large" }, 413);
10327
10501
  }
10328
10502
  let body;
10329
10503
  try {
10330
- body = await request.json();
10504
+ body = await request2.json();
10331
10505
  } catch {
10332
10506
  return jsonResponse2({ error: "invalid_json" }, 400);
10333
10507
  }
@@ -10355,6 +10529,8 @@ var APP_LOG_EVENT_KEYS = [
10355
10529
  ];
10356
10530
  export {
10357
10531
  ACCOUNT_STORAGE_LIMIT_BYTES,
10532
+ ACTION_ROUTES_BEARER_GUARD_MIGRATION_ID,
10533
+ ACTION_TOOLS_DELETE_WHERE_MIGRATION_ID,
10358
10534
  AI_CHATS_SCHEMA,
10359
10535
  AI_MESSAGES_SCHEMA,
10360
10536
  ALLOWED_BINDING_TYPES,
@@ -10368,6 +10544,7 @@ export {
10368
10544
  Awareness,
10369
10545
  BASE_USERS_SCHEMA,
10370
10546
  BROWSER_PROXY_ROUTES,
10547
+ BUILD_INJECTED_APP_ID_MIGRATION_ID,
10371
10548
  BUILT_IN_TOOLS,
10372
10549
  BaseRoom,
10373
10550
  CHANNELS_SCHEMA,
@@ -10393,10 +10570,12 @@ export {
10393
10570
  GLOBAL_DO_TYPES,
10394
10571
  GLOBAL_DO_TYPE_NAMES,
10395
10572
  JobRoom,
10573
+ KnowledgeError,
10396
10574
  LOG_LEVELS,
10397
10575
  MAX_APP_FILE_BYTES,
10398
10576
  MAX_DELETE_WHERE_LIMIT,
10399
10577
  MAX_DEPLOY_ASSET_FILE_BYTES,
10578
+ MAX_ENV_VAR_BYTES,
10400
10579
  MAX_UPLOAD_PARTS,
10401
10580
  MAX_UPLOAD_REQUEST_BYTES,
10402
10581
  MESSAGES_SCHEMA,
@@ -10439,13 +10618,13 @@ export {
10439
10618
  USERS_COLUMNS,
10440
10619
  VOTING_SCHEMAS,
10441
10620
  WORKER_OWNED_NOT_FOUND_MIGRATION_ID,
10442
- WORKSPACE_SCHEMAS,
10443
10621
  YjsRoom,
10444
10622
  analyzePermissions,
10445
10623
  apiWorkerFetch,
10446
10624
  appendMessage,
10447
10625
  applyAiToolDefaults,
10448
10626
  applySlidingWindow,
10627
+ armCronRoom,
10449
10628
  authWorkerFetch,
10450
10629
  authenticatedRoomRequest,
10451
10630
  bindingManifestFromOutputConfig,
@@ -10485,6 +10664,8 @@ export {
10485
10664
  encodeSyncStep2,
10486
10665
  encodeUpdate,
10487
10666
  enqueueJob,
10667
+ epochSeconds,
10668
+ folderFilter,
10488
10669
  formatBytes,
10489
10670
  getChat,
10490
10671
  getDeepSpaceAIModel,
@@ -10505,6 +10686,7 @@ export {
10505
10686
  isTestAccountClaims,
10506
10687
  isTestAccountEmail,
10507
10688
  isTestAccountTier,
10689
+ knowledge,
10508
10690
  lintSchema,
10509
10691
  lintSchemaSet,
10510
10692
  lintSchemas,
@@ -10518,6 +10700,7 @@ export {
10518
10700
  mintAppId,
10519
10701
  mintUlid,
10520
10702
  noopPermissionContext,
10703
+ normalizeKnowledgeFolder,
10521
10704
  parseQuotaLimit,
10522
10705
  platformCorsPatterns,
10523
10706
  platformWorkerFetch,
@@ -10553,17 +10736,6 @@ export {
10553
10736
  validateBindingManifest,
10554
10737
  validateDoManifest,
10555
10738
  verifyJwt,
10556
- workspaceAccountsSchema,
10557
- workspaceContentSharesSchema,
10558
- workspaceEmailHandlesSchema,
10559
- workspaceFormResponsesSchema,
10560
- workspacePeopleSchema,
10561
- workspaceProjectsSchema,
10562
- workspaceTagsSchema,
10563
- workspaceTasksSchema,
10564
- workspaceTeamMembersSchema,
10565
- workspaceTeamsSchema,
10566
- workspaceTransactionsSchema,
10567
10739
  writeVarUint,
10568
10740
  writeVarUint8Array
10569
10741
  };