deepspace 0.4.2 → 0.4.4

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/server.js CHANGED
@@ -1,15 +1,22 @@
1
1
  // src/server/utils/tools.ts
2
+ var DEFAULT_QUERY_LIMIT = 50;
3
+ function applyAiToolDefaults(toolName, params) {
4
+ if (toolName === "records.query" && params.limit === void 0) {
5
+ return { ...params, limit: DEFAULT_QUERY_LIMIT };
6
+ }
7
+ return params;
8
+ }
2
9
  var BUILT_IN_TOOLS = [
3
10
  // Record tools (structured collections with RBAC)
4
11
  {
5
12
  name: "records.query",
6
- description: "Query records from a collection with optional filtering, ordering, and limits",
13
+ description: `Query records from a collection with optional filtering, ordering, and pagination. Returns at most \`limit\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: an oversized page is truncated to the leading records that fit and flagged with \`{ truncated, returned, total }\`, so narrow with a \`where\` filter (or page via \`orderBy\` + \`limit\`) to see the rest.`,
7
14
  params: {
8
15
  collection: { type: "string", description: "Collection name to query", required: true },
9
16
  where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
10
17
  orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
11
18
  orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
12
- limit: { type: "number", description: "Maximum number of records to return", required: false }
19
+ limit: { type: "number", description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false }
13
20
  }
14
21
  },
15
22
  {
@@ -446,7 +453,7 @@ function authWorkerFetch(env, path, init) {
446
453
  function buildCronContext(env, ownerUserId, roomId = "default") {
447
454
  const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
448
455
  const room = env.RECORD_ROOMS.get(roomIdObj);
449
- async function executeTool3(tool, params) {
456
+ async function executeTool3(tool2, params) {
450
457
  const response = await room.fetch(new Request("https://internal/api/tools/execute", {
451
458
  method: "POST",
452
459
  headers: {
@@ -454,11 +461,11 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
454
461
  "X-User-Id": ownerUserId,
455
462
  "X-App-Action": "true"
456
463
  },
457
- body: JSON.stringify({ tool, params })
464
+ body: JSON.stringify({ tool: tool2, params })
458
465
  }));
459
466
  const result = await response.json();
460
467
  if (!result.success) {
461
- throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
468
+ throw new Error(`RecordRoom tool ${tool2} failed: ${result.error || "Unknown error"}`);
462
469
  }
463
470
  return result.data;
464
471
  }
@@ -579,6 +586,87 @@ function createDeepSpaceAI(env, provider, options = {}) {
579
586
  }
580
587
  }
581
588
 
589
+ // src/server/utils/composio-tools.ts
590
+ import { tool, jsonSchema } from "ai";
591
+ var EMPTY_SCHEMA = { type: "object", properties: {} };
592
+ async function requestComposio(env, endpoint, params, authToken) {
593
+ const res = await apiWorkerFetch(env, `/api/integrations/composio/${endpoint}`, {
594
+ method: "POST",
595
+ headers: {
596
+ "Content-Type": "application/json",
597
+ Authorization: `Bearer ${authToken}`
598
+ },
599
+ body: JSON.stringify(params)
600
+ });
601
+ const text = await res.text();
602
+ let parsed = {};
603
+ try {
604
+ parsed = text ? JSON.parse(text) : {};
605
+ } catch {
606
+ }
607
+ const detail = parsed.message || parsed.error || text || `HTTP ${res.status}`;
608
+ return { ok: res.ok, success: parsed.success === true, data: parsed.data, detail };
609
+ }
610
+ function toolKey(slug) {
611
+ const key = slug.replace(/[^a-zA-Z0-9_-]/g, "_");
612
+ return key.length <= 64 ? key : key.slice(0, 64);
613
+ }
614
+ async function composioTools(env, options) {
615
+ const { authToken } = options;
616
+ if (!authToken) {
617
+ throw new Error(
618
+ "composioTools: options.authToken is required. Pass the end user's JWT so the tools run as that user (Composio resolves their connected accounts by it) and the calls are billed to them."
619
+ );
620
+ }
621
+ const list = await requestComposio(
622
+ env,
623
+ "list-tools",
624
+ {
625
+ toolkit: options.toolkit,
626
+ toolSlugs: options.tools?.length ? options.tools.join(",") : void 0,
627
+ search: options.search,
628
+ limit: options.limit ?? 20
629
+ },
630
+ authToken
631
+ );
632
+ if (!list.ok || !list.success) {
633
+ throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
634
+ }
635
+ const items = Array.isArray(list.data?.items) ? list.data.items : [];
636
+ const tools = {};
637
+ for (const item of items) {
638
+ if (!item?.slug) continue;
639
+ const slug = item.slug;
640
+ const schema = item.input_parameters ?? EMPTY_SCHEMA;
641
+ tools[toolKey(slug)] = tool({
642
+ description: item.description ?? item.name ?? slug,
643
+ inputSchema: jsonSchema(schema),
644
+ execute: async (args) => {
645
+ const r = await requestComposio(
646
+ env,
647
+ "execute-tool",
648
+ { slug, arguments: args ?? {} },
649
+ authToken
650
+ );
651
+ if (!r.ok || !r.success) {
652
+ return { error: true, message: `Composio tool ${slug} failed: ${r.detail}` };
653
+ }
654
+ const data = r.data;
655
+ if (data && typeof data === "object" && data.requiresConnection) {
656
+ const toolkit = data.toolkit;
657
+ return {
658
+ requiresConnection: true,
659
+ toolkit,
660
+ message: `The user has not connected ${toolkit ?? "this app"}. Ask them to connect it before using this tool.`
661
+ };
662
+ }
663
+ return data;
664
+ }
665
+ });
666
+ }
667
+ return tools;
668
+ }
669
+
582
670
  // src/server/utils/screenshot.ts
583
671
  async function captureScreenshot(env, opts) {
584
672
  const headers = new Headers({
@@ -666,6 +754,45 @@ function applySlidingWindow(messages, charCap, minKept) {
666
754
  }
667
755
  return out;
668
756
  }
757
+ var TRIMMABLE_ARRAY_PATHS = [
758
+ ["records"],
759
+ ["data", "records"],
760
+ ["items"],
761
+ ["data", "items"],
762
+ ["data"]
763
+ ];
764
+ function getPath(root, path) {
765
+ let cur = root;
766
+ for (const key of path) {
767
+ if (!cur || typeof cur !== "object") return void 0;
768
+ cur = cur[key];
769
+ }
770
+ return cur;
771
+ }
772
+ function setPath(root, path, value, flags) {
773
+ const obj = root && typeof root === "object" ? root : {};
774
+ const [head, ...tail] = path;
775
+ if (tail.length === 0) {
776
+ return { ...obj, [head]: value, ...flags };
777
+ }
778
+ return { ...obj, [head]: setPath(obj[head], tail, value, flags) };
779
+ }
780
+ function locateTrimmableArray(result) {
781
+ for (const path of TRIMMABLE_ARRAY_PATHS) {
782
+ const arr = getPath(result, path);
783
+ if (Array.isArray(arr)) return { path, items: arr };
784
+ }
785
+ for (const [key, value] of Object.entries(result)) {
786
+ if (Array.isArray(value)) return { path: [key], items: value };
787
+ }
788
+ const data = result.data;
789
+ if (data && typeof data === "object") {
790
+ for (const [key, value] of Object.entries(data)) {
791
+ if (Array.isArray(value)) return { path: ["data", key], items: value };
792
+ }
793
+ }
794
+ return void 0;
795
+ }
669
796
  function capToolResultSize(result, byteCap) {
670
797
  let serialized;
671
798
  try {
@@ -675,6 +802,31 @@ function capToolResultSize(result, byteCap) {
675
802
  }
676
803
  if (typeof serialized !== "string") return result;
677
804
  if (serialized.length <= byteCap) return result;
805
+ if (result && typeof result === "object") {
806
+ const located = locateTrimmableArray(result);
807
+ if (located) {
808
+ const { path, items } = located;
809
+ const total = items.length;
810
+ const build = (k) => setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total });
811
+ const fits = (k) => {
812
+ try {
813
+ return JSON.stringify(build(k)).length <= byteCap;
814
+ } catch {
815
+ return false;
816
+ }
817
+ };
818
+ if (fits(0)) {
819
+ let lo = 0;
820
+ let hi = total;
821
+ while (lo < hi) {
822
+ const mid = Math.ceil((lo + hi) / 2);
823
+ if (fits(mid)) lo = mid;
824
+ else hi = mid - 1;
825
+ }
826
+ if (lo > 0) return build(lo);
827
+ }
828
+ }
829
+ }
678
830
  return {
679
831
  success: false,
680
832
  truncated: true,
@@ -883,7 +1035,7 @@ function makeDefaultSummarizer(env, options = {}) {
883
1035
  }
884
1036
 
885
1037
  // src/server/utils/chat-history.ts
886
- async function executeTool(stub, userId, tool, params) {
1038
+ async function executeTool(stub, userId, tool2, params) {
887
1039
  const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
888
1040
  method: "POST",
889
1041
  headers: {
@@ -891,11 +1043,11 @@ async function executeTool(stub, userId, tool, params) {
891
1043
  "X-User-Id": userId,
892
1044
  "X-App-Action": "true"
893
1045
  },
894
- body: JSON.stringify({ tool, params })
1046
+ body: JSON.stringify({ tool: tool2, params })
895
1047
  }));
896
1048
  const body = await res.json();
897
1049
  if (!body.success) {
898
- throw new Error(`chat-history ${tool} failed: ${body.error ?? "unknown error"}`);
1050
+ throw new Error(`chat-history ${tool2} failed: ${body.error ?? "unknown error"}`);
899
1051
  }
900
1052
  return body.data;
901
1053
  }
@@ -2790,11 +2942,11 @@ async function handleToolsRequest(ctx, request, path) {
2790
2942
  }
2791
2943
  if (path.startsWith("tools/describe/")) {
2792
2944
  const toolName = path.replace("tools/describe/", "");
2793
- const tool = BUILT_IN_TOOLS.find((t) => t.name === toolName);
2794
- if (!tool) {
2945
+ const tool2 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
2946
+ if (!tool2) {
2795
2947
  return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS2 });
2796
2948
  }
2797
- return Response.json({ tool }, { headers: CORS_HEADERS2 });
2949
+ return Response.json({ tool: tool2 }, { headers: CORS_HEADERS2 });
2798
2950
  }
2799
2951
  if (path.startsWith("tools/execute") && request.method === "POST") {
2800
2952
  return handleToolExecute(ctx, request);
@@ -2811,8 +2963,8 @@ async function handleToolExecute(ctx, request) {
2811
2963
  { status: 400, headers: CORS_HEADERS2 }
2812
2964
  );
2813
2965
  }
2814
- const { tool, params = {} } = body;
2815
- if (!tool) {
2966
+ const { tool: tool2, params = {} } = body;
2967
+ if (!tool2) {
2816
2968
  return Response.json(
2817
2969
  { success: false, error: 'Missing "tool" in request body' },
2818
2970
  { status: 400, headers: CORS_HEADERS2 }
@@ -2829,8 +2981,8 @@ async function handleToolExecute(ctx, request) {
2829
2981
  userRole = user.role;
2830
2982
  }
2831
2983
  }
2832
- if (tool.startsWith("yjs.")) {
2833
- const result2 = await executeYjsTool(ctx, tool, params, userId, userRole);
2984
+ if (tool2.startsWith("yjs.")) {
2985
+ const result2 = await executeYjsTool(ctx, tool2, params, userId, userRole);
2834
2986
  const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
2835
2987
  return Response.json(result2, { status: status2, headers: CORS_HEADERS2 });
2836
2988
  }
@@ -2841,7 +2993,7 @@ async function handleToolExecute(ctx, request) {
2841
2993
  getPermissionContext: ctx.getPermissionContext,
2842
2994
  send: ctx.send
2843
2995
  };
2844
- const result = await executeTool2(recordCtx, tool, params, userId, userRole, isAppAction);
2996
+ const result = await executeTool2(recordCtx, tool2, params, userId, userRole, isAppAction);
2845
2997
  const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
2846
2998
  return Response.json(result, { status, headers: CORS_HEADERS2 });
2847
2999
  }
@@ -3826,13 +3978,13 @@ var YjsRoom = class extends BaseRoom {
3826
3978
  onMessage(ws, user, message) {
3827
3979
  }
3828
3980
  onBinaryMessage(ws, user, data) {
3829
- const bytes = new Uint8Array(data);
3830
- const decoder = createDecoder(bytes);
3981
+ const bytes2 = new Uint8Array(data);
3982
+ const decoder = createDecoder(bytes2);
3831
3983
  const messageType = readVarUint(decoder);
3832
3984
  if (messageType === MSG_SYNC) {
3833
- this.handleSync(ws, decoder, bytes);
3985
+ this.handleSync(ws, decoder, bytes2);
3834
3986
  } else if (messageType === MSG_AWARENESS) {
3835
- this.handleAwareness(ws, decoder, bytes);
3987
+ this.handleAwareness(ws, decoder, bytes2);
3836
3988
  }
3837
3989
  }
3838
3990
  onDisconnect(ws, user) {
@@ -5037,6 +5189,319 @@ function cronMatches(parsed, timezone, date) {
5037
5189
  return parsed.minute.has(minute) && parsed.hour.has(hour) && parsed.dayOfMonth.has(dayOfMonth) && parsed.month.has(month) && parsed.dayOfWeek.has(dayOfWeek);
5038
5190
  }
5039
5191
 
5192
+ // src/server/rooms/secrets-room.ts
5193
+ var INTERNAL_HEADER = "x-deepspace-internal";
5194
+ var INTERNAL_VALUE = "app-secrets";
5195
+ var VERSION = "app-secret-v1";
5196
+ var NAME_INFO = "deepspace app secret name v1";
5197
+ var VALUE_INFO = "deepspace app secret value v1";
5198
+ var HMAC_INFO = "deepspace app secret lookup v1";
5199
+ var STORE_ID = "default";
5200
+ var SecretsRoom = class extends BaseRoom {
5201
+ initialized = false;
5202
+ onMessage(_ws, _user, _message) {
5203
+ }
5204
+ async fetch(request) {
5205
+ this.ensureInitialized();
5206
+ return super.fetch(request);
5207
+ }
5208
+ async onRequest(request) {
5209
+ if (request.headers.get(INTERNAL_HEADER) !== INTERNAL_VALUE) {
5210
+ return json({ error: "Not found" }, 404);
5211
+ }
5212
+ const url = new URL(request.url);
5213
+ try {
5214
+ if (request.method === "GET" && url.pathname.endsWith("/list")) {
5215
+ return json({ version: this.storeVersion(), secrets: await this.list() });
5216
+ }
5217
+ if (request.method === "GET" && url.pathname.endsWith("/pull")) {
5218
+ return json({ version: this.storeVersion(), secrets: await this.pull() });
5219
+ }
5220
+ if (request.method === "GET" && url.pathname.endsWith("/version")) {
5221
+ return json({ version: this.storeVersion() });
5222
+ }
5223
+ if (request.method === "PATCH" && url.pathname.endsWith("/patch")) {
5224
+ const body = await request.json();
5225
+ const result = await this.patch(body);
5226
+ return json(result);
5227
+ }
5228
+ if (request.method === "PUT" && url.pathname.endsWith("/replace")) {
5229
+ const body = await request.json();
5230
+ const result = await this.replace(body);
5231
+ return json(result);
5232
+ }
5233
+ } catch (err) {
5234
+ const message = err instanceof Error ? err.message : String(err);
5235
+ const status = message.includes("conflict") ? 409 : 500;
5236
+ return json({ error: message }, status);
5237
+ }
5238
+ return json({ error: "Not found" }, 404);
5239
+ }
5240
+ async list() {
5241
+ const rows = this.sql.exec(
5242
+ `SELECT name_encrypted, version, updated_by, created_at, updated_at
5243
+ FROM app_secrets_store ORDER BY updated_at DESC`
5244
+ ).toArray();
5245
+ const out = [];
5246
+ for (const row of rows) {
5247
+ out.push({
5248
+ name: await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO),
5249
+ version: row.version,
5250
+ createdAt: row.created_at,
5251
+ updatedAt: row.updated_at,
5252
+ updatedBy: row.updated_by
5253
+ });
5254
+ }
5255
+ return out.sort((a, b) => a.name.localeCompare(b.name));
5256
+ }
5257
+ async pull() {
5258
+ const rows = this.sql.exec(
5259
+ `SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
5260
+ FROM app_secrets_store`
5261
+ ).toArray();
5262
+ const out = {};
5263
+ for (const row of rows) {
5264
+ const name = await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO);
5265
+ out[name] = await decryptText(this.requireKey(), row.value_encrypted, VALUE_INFO);
5266
+ }
5267
+ return out;
5268
+ }
5269
+ async patch(body) {
5270
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5271
+ const actor = body.updatedBy ?? null;
5272
+ const key = this.requireKey();
5273
+ const setEntries = Object.entries(body.set ?? {});
5274
+ const deleteNames = body.delete ?? [];
5275
+ for (const name of [...setEntries.map(([name2]) => name2), ...deleteNames]) {
5276
+ validateSecretName(name);
5277
+ }
5278
+ const writes = [];
5279
+ for (const [name, value] of setEntries) {
5280
+ const nameHash = await hashName(key, name);
5281
+ const existing = this.sql.exec(
5282
+ `SELECT version, created_at FROM app_secrets_store WHERE name_hash = ?`,
5283
+ nameHash
5284
+ ).toArray()[0];
5285
+ writes.push({
5286
+ nameHash,
5287
+ nameEncrypted: await encryptText(key, name, NAME_INFO),
5288
+ valueEncrypted: await encryptText(key, value, VALUE_INFO),
5289
+ version: (existing?.version ?? 0) + 1,
5290
+ createdAt: existing?.created_at ?? now
5291
+ });
5292
+ }
5293
+ const deletes = [];
5294
+ for (const name of deleteNames) {
5295
+ deletes.push(await hashName(key, name));
5296
+ }
5297
+ this.transaction(() => {
5298
+ for (const write of writes) {
5299
+ this.sql.exec(
5300
+ `INSERT OR REPLACE INTO app_secrets_store
5301
+ (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
5302
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
5303
+ write.nameHash,
5304
+ write.nameEncrypted,
5305
+ write.valueEncrypted,
5306
+ write.version,
5307
+ actor,
5308
+ write.createdAt,
5309
+ now
5310
+ );
5311
+ }
5312
+ for (const nameHash of deletes) {
5313
+ this.sql.exec(`DELETE FROM app_secrets_store WHERE name_hash = ?`, nameHash);
5314
+ }
5315
+ this.bumpStoreVersion(now);
5316
+ });
5317
+ return { version: this.storeVersion(), secrets: await this.list() };
5318
+ }
5319
+ async replace(body) {
5320
+ const next = body.secrets ?? {};
5321
+ for (const name of Object.keys(next)) validateSecretName(name);
5322
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5323
+ const actor = body.updatedBy ?? null;
5324
+ const key = this.requireKey();
5325
+ const writes = [];
5326
+ for (const [name, value] of Object.entries(next)) {
5327
+ writes.push({
5328
+ nameHash: await hashName(key, name),
5329
+ nameEncrypted: await encryptText(key, name, NAME_INFO),
5330
+ valueEncrypted: await encryptText(key, value, VALUE_INFO)
5331
+ });
5332
+ }
5333
+ this.transaction(() => {
5334
+ this.sql.exec(`DELETE FROM app_secrets_store`);
5335
+ for (const write of writes) {
5336
+ this.sql.exec(
5337
+ `INSERT INTO app_secrets_store
5338
+ (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
5339
+ VALUES (?, ?, ?, 1, ?, ?, ?)`,
5340
+ write.nameHash,
5341
+ write.nameEncrypted,
5342
+ write.valueEncrypted,
5343
+ actor,
5344
+ now,
5345
+ now
5346
+ );
5347
+ }
5348
+ this.bumpStoreVersion(now);
5349
+ });
5350
+ return { version: this.storeVersion(), secrets: await this.list() };
5351
+ }
5352
+ transaction(run) {
5353
+ this.state.storage.transactionSync(run);
5354
+ }
5355
+ migrateLegacyProductionSecrets() {
5356
+ const legacyExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secrets'`).toArray().length > 0;
5357
+ if (!legacyExists) return;
5358
+ const currentCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
5359
+ if ((currentCount?.count ?? 0) > 0) return;
5360
+ this.sql.exec(`
5361
+ INSERT OR IGNORE INTO app_secrets_store
5362
+ (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
5363
+ SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
5364
+ FROM app_secrets
5365
+ WHERE env = 'production'
5366
+ `);
5367
+ const legacyVersionExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secret_env_versions'`).toArray().length > 0;
5368
+ const legacyVersion = legacyVersionExists ? this.sql.exec(`SELECT version, updated_at FROM app_secret_env_versions WHERE env = 'production'`).toArray()[0] : void 0;
5369
+ if (legacyVersion) {
5370
+ this.sql.exec(
5371
+ `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
5372
+ STORE_ID,
5373
+ legacyVersion.version,
5374
+ legacyVersion.updated_at
5375
+ );
5376
+ }
5377
+ const migratedCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
5378
+ if (!legacyVersion && (migratedCount?.count ?? 0) > 0) {
5379
+ this.sql.exec(
5380
+ `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
5381
+ STORE_ID,
5382
+ 1,
5383
+ (/* @__PURE__ */ new Date()).toISOString()
5384
+ );
5385
+ }
5386
+ }
5387
+ ensureInitialized() {
5388
+ if (this.initialized) return;
5389
+ this.initialized = true;
5390
+ this.sql.exec(`
5391
+ CREATE TABLE IF NOT EXISTS app_secrets_store (
5392
+ name_hash TEXT NOT NULL,
5393
+ name_encrypted TEXT NOT NULL,
5394
+ value_encrypted TEXT NOT NULL,
5395
+ version INTEGER NOT NULL,
5396
+ updated_by TEXT,
5397
+ created_at TEXT NOT NULL,
5398
+ updated_at TEXT NOT NULL,
5399
+ PRIMARY KEY (name_hash)
5400
+ )
5401
+ `);
5402
+ this.sql.exec(`
5403
+ CREATE TABLE IF NOT EXISTS app_secret_store_versions (
5404
+ id TEXT PRIMARY KEY,
5405
+ version INTEGER NOT NULL,
5406
+ updated_at TEXT NOT NULL
5407
+ )
5408
+ `);
5409
+ this.migrateLegacyProductionSecrets();
5410
+ }
5411
+ requireKey() {
5412
+ if (!this.env.APP_SECRETS_KEY) {
5413
+ throw new Error("APP_SECRETS_KEY binding is missing");
5414
+ }
5415
+ return this.env.APP_SECRETS_KEY;
5416
+ }
5417
+ storeVersion() {
5418
+ const row = this.sql.exec(
5419
+ `SELECT version FROM app_secret_store_versions WHERE id = ?`,
5420
+ STORE_ID
5421
+ ).toArray()[0];
5422
+ return row?.version ?? 0;
5423
+ }
5424
+ bumpStoreVersion(now) {
5425
+ const next = this.storeVersion() + 1;
5426
+ this.sql.exec(
5427
+ `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
5428
+ STORE_ID,
5429
+ next,
5430
+ now
5431
+ );
5432
+ }
5433
+ };
5434
+ function validateSecretName(name) {
5435
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
5436
+ throw new Error(`Invalid secret name "${name}"`);
5437
+ }
5438
+ }
5439
+ function json(body, status = 200) {
5440
+ return new Response(JSON.stringify(body), {
5441
+ status,
5442
+ headers: { "Content-Type": "application/json" }
5443
+ });
5444
+ }
5445
+ async function encryptText(root, plaintext, info) {
5446
+ const salt = crypto.getRandomValues(new Uint8Array(16));
5447
+ const iv = crypto.getRandomValues(new Uint8Array(12));
5448
+ const key = await aesKey(root, salt, info);
5449
+ const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
5450
+ return [VERSION, b64(salt), b64(iv), b64(new Uint8Array(ciphertext))].join(":");
5451
+ }
5452
+ async function decryptText(root, encoded, info) {
5453
+ const [version, saltB64, ivB64, bodyB64] = encoded.split(":");
5454
+ if (version !== VERSION || !saltB64 || !ivB64 || !bodyB64) {
5455
+ throw new Error("Corrupt app secret ciphertext");
5456
+ }
5457
+ const key = await aesKey(root, unb64(saltB64), info);
5458
+ const plaintext = await crypto.subtle.decrypt(
5459
+ { name: "AES-GCM", iv: bytes(unb64(ivB64)) },
5460
+ key,
5461
+ bytes(unb64(bodyB64))
5462
+ );
5463
+ return new TextDecoder().decode(plaintext);
5464
+ }
5465
+ async function hashName(root, name) {
5466
+ const key = await hmacKey(root, HMAC_INFO);
5467
+ const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`production:${name}`));
5468
+ return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
5469
+ }
5470
+ async function aesKey(root, salt, info) {
5471
+ const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
5472
+ return crypto.subtle.deriveKey(
5473
+ { name: "HKDF", hash: "SHA-256", salt: bytes(salt), info: new TextEncoder().encode(info) },
5474
+ base,
5475
+ { name: "AES-GCM", length: 256 },
5476
+ false,
5477
+ ["encrypt", "decrypt"]
5478
+ );
5479
+ }
5480
+ async function hmacKey(root, info) {
5481
+ const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
5482
+ return crypto.subtle.deriveKey(
5483
+ { name: "HKDF", hash: "SHA-256", salt: bytes(new Uint8Array(16)), info: new TextEncoder().encode(info) },
5484
+ base,
5485
+ { name: "HMAC", hash: "SHA-256", length: 256 },
5486
+ false,
5487
+ ["sign"]
5488
+ );
5489
+ }
5490
+ function b64(bytes2) {
5491
+ let s = "";
5492
+ for (const b of bytes2) s += String.fromCharCode(b);
5493
+ return btoa(s);
5494
+ }
5495
+ function unb64(value) {
5496
+ const s = atob(value);
5497
+ const out = new Uint8Array(s.length);
5498
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
5499
+ return out;
5500
+ }
5501
+ function bytes(input) {
5502
+ return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
5503
+ }
5504
+
5040
5505
  // src/server/rooms/job-room.ts
5041
5506
  var RUNNING_STALE_MS = 16 * 60 * 1e3;
5042
5507
  var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
@@ -5654,6 +6119,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5654
6119
  "AUTH_WORKER_URL",
5655
6120
  "APP_IDENTITY_TOKEN",
5656
6121
  "APP_OWNER_JWT",
6122
+ "APP_SECRETS_KEY",
5657
6123
  "USAGE_EVENTS"
5658
6124
  ]);
5659
6125
  function validateBindingManifest(manifest) {
@@ -6490,8 +6956,8 @@ import { importSPKI, jwtVerify } from "jose";
6490
6956
 
6491
6957
  // src/server/auth/utils.ts
6492
6958
  var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
6493
- function bufferToHex(bytes) {
6494
- const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
6959
+ function bufferToHex(bytes2) {
6960
+ const view = bytes2 instanceof Uint8Array ? bytes2 : new Uint8Array(bytes2);
6495
6961
  let hex = "";
6496
6962
  for (const byte of view) {
6497
6963
  hex += byte.toString(16).padStart(2, "0");
@@ -6514,9 +6980,9 @@ function decodeJwtPayload(token) {
6514
6980
  if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
6515
6981
  try {
6516
6982
  const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
6517
- const bytes = base64ToUint8Array(normalized);
6518
- const json = new TextDecoder().decode(bytes);
6519
- const payload = JSON.parse(json);
6983
+ const bytes2 = base64ToUint8Array(normalized);
6984
+ const json2 = new TextDecoder().decode(bytes2);
6985
+ const payload = JSON.parse(json2);
6520
6986
  return {
6521
6987
  iss: payload.iss ?? null,
6522
6988
  aud: payload.aud ?? null,
@@ -6668,6 +7134,7 @@ async function timingSafeEqualHex(a, b) {
6668
7134
  // src/server/auth/betterAuth.ts
6669
7135
  import { betterAuth } from "better-auth";
6670
7136
  import { organization, twoFactor } from "better-auth/plugins";
7137
+ import { SignJWT, importPKCS8 } from "jose";
6671
7138
  function createDeepSpaceAuth(config) {
6672
7139
  const socialProviders = {};
6673
7140
  if (config.google) {
@@ -6676,6 +7143,20 @@ function createDeepSpaceAuth(config) {
6676
7143
  if (config.github) {
6677
7144
  socialProviders.github = config.github;
6678
7145
  }
7146
+ if (config.microsoft) {
7147
+ socialProviders.microsoft = config.microsoft;
7148
+ }
7149
+ if (config.apple) {
7150
+ const apple = config.apple;
7151
+ socialProviders.apple = async () => {
7152
+ try {
7153
+ return { clientId: apple.clientId, clientSecret: await generateAppleClientSecret(apple) };
7154
+ } catch (err) {
7155
+ console.error("[deepspace] failed to mint Apple client secret; Apple sign-in disabled", err);
7156
+ return { clientId: apple.clientId, clientSecret: "" };
7157
+ }
7158
+ };
7159
+ }
6679
7160
  return betterAuth({
6680
7161
  database: config.database,
6681
7162
  baseURL: config.baseURL,
@@ -6688,11 +7169,212 @@ function createDeepSpaceAuth(config) {
6688
7169
  "https://deep.space",
6689
7170
  "https://*.deep.space",
6690
7171
  "https://*.app.space",
6691
- "http://localhost:*"
7172
+ "http://localhost:*",
7173
+ // Apple posts its OAuth callback from this origin (form_post).
7174
+ "https://appleid.apple.com"
6692
7175
  ],
7176
+ // Apple returns its callback as a cross-site form_post, which a
7177
+ // SameSite=Lax cookie won't ride along with — so the OAuth handshake
7178
+ // cookie must be SameSite=None. Scoped to just that cookie; the session
7179
+ // cookie stays Lax. Gated on Apple being configured: deployments without
7180
+ // Apple keep Lax entirely. Note that when Apple IS configured, this also
7181
+ // relaxes the handshake cookie for Google/GitHub/Microsoft on the same
7182
+ // instance — which is safe, since the state is encrypted and verified
7183
+ // independently of SameSite (plus a separate per-flow CSRF cookie).
7184
+ ...config.apple ? {
7185
+ advanced: {
7186
+ cookies: {
7187
+ state: { attributes: { sameSite: "none", secure: true } },
7188
+ oauth_state: { attributes: { sameSite: "none", secure: true } }
7189
+ }
7190
+ }
7191
+ } : {},
6693
7192
  plugins: [organization(), twoFactor()]
6694
7193
  });
6695
7194
  }
7195
+ var cachedAppleSecret = null;
7196
+ async function generateAppleClientSecret(apple) {
7197
+ const now = Math.floor(Date.now() / 1e3);
7198
+ if (cachedAppleSecret && cachedAppleSecret.keyId === apple.keyId && cachedAppleSecret.expiresAt - now > 3600) {
7199
+ return cachedAppleSecret.token;
7200
+ }
7201
+ const pem = apple.privateKey.replace(/\\n/g, "\n");
7202
+ const key = await importPKCS8(pem, "ES256");
7203
+ const expiresAt = now + 180 * 24 * 60 * 60;
7204
+ const token = await new SignJWT({}).setProtectedHeader({ alg: "ES256", kid: apple.keyId }).setIssuer(apple.teamId).setSubject(apple.clientId).setAudience("https://appleid.apple.com").setIssuedAt(now).setExpirationTime(expiresAt).sign(key);
7205
+ cachedAppleSecret = { keyId: apple.keyId, token, expiresAt };
7206
+ return token;
7207
+ }
7208
+
7209
+ // src/server/secrets.ts
7210
+ var DEFAULT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7211
+ function createSecrets(env, options = {}) {
7212
+ const refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
7213
+ const now = options.now ?? (() => Date.now());
7214
+ const cache = /* @__PURE__ */ new Map();
7215
+ let loadedAll = null;
7216
+ let loadedVersion = 0;
7217
+ let lastCheckedAt = 0;
7218
+ function stub() {
7219
+ if (!env.SECRETS_ROOMS) throw new Error("SECRETS_ROOMS binding is missing");
7220
+ if (!env.APP_SECRETS_KEY) throw new Error("APP_SECRETS_KEY binding is missing");
7221
+ const id = env.SECRETS_ROOMS.idFromName("app-secrets");
7222
+ return env.SECRETS_ROOMS.get(id);
7223
+ }
7224
+ async function fetchJson(path) {
7225
+ const res = await stub().fetch(`https://app-secrets/${path}`, {
7226
+ headers: { "x-deepspace-internal": "app-secrets" }
7227
+ });
7228
+ const body = await res.json().catch(() => ({}));
7229
+ if (!res.ok) throw new Error(body.error ?? `App secrets unavailable (${res.status})`);
7230
+ return body;
7231
+ }
7232
+ async function pullAll() {
7233
+ const body = await fetchJson("pull");
7234
+ const version = typeof body.version === "number" ? body.version : 0;
7235
+ cache.clear();
7236
+ for (const [name, value] of Object.entries(body.secrets ?? {})) {
7237
+ cache.set(name, value);
7238
+ }
7239
+ loadedVersion = version;
7240
+ lastCheckedAt = now();
7241
+ return version;
7242
+ }
7243
+ async function loadAll() {
7244
+ await pullAll();
7245
+ }
7246
+ async function ensureLoaded() {
7247
+ if (!loadedAll) {
7248
+ loadedAll = loadAll().catch((err) => {
7249
+ loadedAll = null;
7250
+ throw err;
7251
+ });
7252
+ }
7253
+ await loadedAll;
7254
+ }
7255
+ async function maybeCheckForUpdates() {
7256
+ if (!loadedAll) return;
7257
+ if (refreshIntervalMs < 0) return;
7258
+ if (now() - lastCheckedAt < refreshIntervalMs) return;
7259
+ try {
7260
+ await client.checkForUpdates();
7261
+ } catch {
7262
+ }
7263
+ }
7264
+ function localDevValue(name) {
7265
+ const value = env[name];
7266
+ return env.APP_SECRETS_LOCAL_FALLBACK === "true" && typeof value === "string" ? value : void 0;
7267
+ }
7268
+ const client = {
7269
+ async get(name) {
7270
+ const local = localDevValue(name);
7271
+ if (local !== void 0) return local;
7272
+ if (!cache.has(name)) await ensureLoaded();
7273
+ else await maybeCheckForUpdates();
7274
+ return cache.get(name);
7275
+ },
7276
+ async require(name) {
7277
+ const value = await this.get(name);
7278
+ if (value === void 0) throw new Error(`Missing app secret "${name}"`);
7279
+ return value;
7280
+ },
7281
+ async getMany(names) {
7282
+ const out = {};
7283
+ for (const name of names) out[name] = await this.get(name);
7284
+ return out;
7285
+ },
7286
+ async preload(names) {
7287
+ if (env.APP_SECRETS_LOCAL_FALLBACK === "true") {
7288
+ const missing = [];
7289
+ for (const name of names) {
7290
+ const local = localDevValue(name);
7291
+ if (local !== void 0) cache.set(name, local);
7292
+ else missing.push(name);
7293
+ }
7294
+ if (missing.length === 0) {
7295
+ await maybeCheckForUpdates();
7296
+ return;
7297
+ }
7298
+ }
7299
+ await ensureLoaded();
7300
+ await maybeCheckForUpdates();
7301
+ },
7302
+ async refresh(refreshOptions = {}) {
7303
+ if (refreshOptions.force || !loadedAll) {
7304
+ const previous = loadedVersion;
7305
+ const version = await pullAll();
7306
+ loadedAll = Promise.resolve();
7307
+ return { changed: version !== previous, version };
7308
+ }
7309
+ return this.checkForUpdates();
7310
+ },
7311
+ async checkForUpdates() {
7312
+ await ensureLoaded();
7313
+ const body = await fetchJson("version");
7314
+ const version = typeof body.version === "number" ? body.version : 0;
7315
+ lastCheckedAt = now();
7316
+ if (version === loadedVersion) return { changed: false, version };
7317
+ const pulledVersion = await pullAll();
7318
+ return { changed: true, version: pulledVersion };
7319
+ },
7320
+ invalidate() {
7321
+ cache.clear();
7322
+ loadedAll = null;
7323
+ loadedVersion = 0;
7324
+ lastCheckedAt = 0;
7325
+ },
7326
+ async withFreshSecrets(operation, retryOptions = {}) {
7327
+ let first;
7328
+ try {
7329
+ first = await operation({ secrets: client, attempt: 1, refreshed: false });
7330
+ } catch (err) {
7331
+ if (!shouldRefreshOnError(err)) throw err;
7332
+ const refresh = await refreshAfterFailure(err);
7333
+ if (!refresh.changed && !retryOptions.retryWhenUnchanged) throw err;
7334
+ return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7335
+ }
7336
+ if (first instanceof Response && shouldRefreshOnStatus(first.status)) {
7337
+ const refresh = await refreshAfterFailure(first);
7338
+ if (refresh.changed || retryOptions.retryWhenUnchanged) {
7339
+ return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7340
+ }
7341
+ }
7342
+ return first;
7343
+ }
7344
+ };
7345
+ async function refreshAfterFailure(original) {
7346
+ try {
7347
+ return await client.refresh({ force: true });
7348
+ } catch (err) {
7349
+ const originalMessage = describeFailure(original);
7350
+ const refreshMessage = err instanceof Error ? err.message : String(err);
7351
+ throw new Error(`Secret refresh failed after API failure (${originalMessage}): ${refreshMessage}`);
7352
+ }
7353
+ }
7354
+ return client;
7355
+ }
7356
+ function shouldRefreshOnError(error) {
7357
+ const status = statusFromError(error);
7358
+ return status !== void 0 && shouldRefreshOnStatus(status);
7359
+ }
7360
+ function shouldRefreshOnStatus(status) {
7361
+ return status === 401 || status === 403;
7362
+ }
7363
+ function statusFromError(error) {
7364
+ if (!error || typeof error !== "object") return void 0;
7365
+ const record = error;
7366
+ if (typeof record.status === "number") return record.status;
7367
+ if (typeof record.statusCode === "number") return record.statusCode;
7368
+ if (typeof record.response?.status === "number") return record.response.status;
7369
+ return void 0;
7370
+ }
7371
+ function describeFailure(value) {
7372
+ if (value instanceof Response) return `HTTP ${value.status}`;
7373
+ if (value instanceof Error) return value.message;
7374
+ const status = statusFromError(value);
7375
+ if (status) return `HTTP ${status}`;
7376
+ return String(value);
7377
+ }
6696
7378
 
6697
7379
  // src/shared/subscription.ts
6698
7380
  var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
@@ -6858,6 +7540,7 @@ export {
6858
7540
  CronRoom,
6859
7541
  DEFAULT_CONTEXT_CONFIG,
6860
7542
  DEFAULT_DO_MANIFEST,
7543
+ DEFAULT_QUERY_LIMIT,
6861
7544
  DIRECTORY_SCHEMAS,
6862
7545
  GLOBAL_DO_TYPES,
6863
7546
  GLOBAL_DO_TYPE_NAMES,
@@ -6874,6 +7557,7 @@ export {
6874
7557
  SYSTEM_COLLECTION_SCHEMAS,
6875
7558
  SYSTEM_MANAGED_COLUMNS,
6876
7559
  SchemaRegistry,
7560
+ SecretsRoom,
6877
7561
  SubscriptionAuthError,
6878
7562
  SubscriptionRequiredError,
6879
7563
  USERS_COLUMNS,
@@ -6883,6 +7567,7 @@ export {
6883
7567
  analyzePermissions,
6884
7568
  apiWorkerFetch,
6885
7569
  appendMessage,
7570
+ applyAiToolDefaults,
6886
7571
  applySlidingWindow,
6887
7572
  authWorkerFetch,
6888
7573
  bindingManifestFromOutputConfig,
@@ -6902,12 +7587,14 @@ export {
6902
7587
  coerceValue,
6903
7588
  collectionTableName,
6904
7589
  columnId,
7590
+ composioTools,
6905
7591
  computeDoMigration,
6906
7592
  computeHmacHex,
6907
7593
  createChat,
6908
7594
  createDeepSpaceAI,
6909
7595
  createDeepSpaceAuth,
6910
7596
  createScopedR2Handler,
7597
+ createSecrets,
6911
7598
  dataToColumnValues,
6912
7599
  decodeJwtPayload,
6913
7600
  deleteChatCascade,