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/worker.js CHANGED
@@ -1952,17 +1952,24 @@ async function handleSetRole(ctx, ws, attachment, payload) {
1952
1952
  import * as Y3 from "yjs";
1953
1953
 
1954
1954
  // src/server/utils/tools.ts
1955
+ var DEFAULT_QUERY_LIMIT = 50;
1956
+ function applyAiToolDefaults(toolName, params) {
1957
+ if (toolName === "records.query" && params.limit === void 0) {
1958
+ return { ...params, limit: DEFAULT_QUERY_LIMIT };
1959
+ }
1960
+ return params;
1961
+ }
1955
1962
  var BUILT_IN_TOOLS = [
1956
1963
  // Record tools (structured collections with RBAC)
1957
1964
  {
1958
1965
  name: "records.query",
1959
- description: "Query records from a collection with optional filtering, ordering, and limits",
1966
+ 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.`,
1960
1967
  params: {
1961
1968
  collection: { type: "string", description: "Collection name to query", required: true },
1962
1969
  where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
1963
1970
  orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
1964
1971
  orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
1965
- limit: { type: "number", description: "Maximum number of records to return", required: false }
1972
+ 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 }
1966
1973
  }
1967
1974
  },
1968
1975
  {
@@ -2132,11 +2139,11 @@ async function handleToolsRequest(ctx, request, path) {
2132
2139
  }
2133
2140
  if (path.startsWith("tools/describe/")) {
2134
2141
  const toolName = path.replace("tools/describe/", "");
2135
- const tool = BUILT_IN_TOOLS.find((t) => t.name === toolName);
2136
- if (!tool) {
2142
+ const tool2 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
2143
+ if (!tool2) {
2137
2144
  return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS });
2138
2145
  }
2139
- return Response.json({ tool }, { headers: CORS_HEADERS });
2146
+ return Response.json({ tool: tool2 }, { headers: CORS_HEADERS });
2140
2147
  }
2141
2148
  if (path.startsWith("tools/execute") && request.method === "POST") {
2142
2149
  return handleToolExecute(ctx, request);
@@ -2153,8 +2160,8 @@ async function handleToolExecute(ctx, request) {
2153
2160
  { status: 400, headers: CORS_HEADERS }
2154
2161
  );
2155
2162
  }
2156
- const { tool, params = {} } = body;
2157
- if (!tool) {
2163
+ const { tool: tool2, params = {} } = body;
2164
+ if (!tool2) {
2158
2165
  return Response.json(
2159
2166
  { success: false, error: 'Missing "tool" in request body' },
2160
2167
  { status: 400, headers: CORS_HEADERS }
@@ -2171,8 +2178,8 @@ async function handleToolExecute(ctx, request) {
2171
2178
  userRole = user.role;
2172
2179
  }
2173
2180
  }
2174
- if (tool.startsWith("yjs.")) {
2175
- const result2 = await executeYjsTool(ctx, tool, params, userId, userRole);
2181
+ if (tool2.startsWith("yjs.")) {
2182
+ const result2 = await executeYjsTool(ctx, tool2, params, userId, userRole);
2176
2183
  const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
2177
2184
  return Response.json(result2, { status: status2, headers: CORS_HEADERS });
2178
2185
  }
@@ -2183,7 +2190,7 @@ async function handleToolExecute(ctx, request) {
2183
2190
  getPermissionContext: ctx.getPermissionContext,
2184
2191
  send: ctx.send
2185
2192
  };
2186
- const result = await executeTool(recordCtx, tool, params, userId, userRole, isAppAction);
2193
+ const result = await executeTool(recordCtx, tool2, params, userId, userRole, isAppAction);
2187
2194
  const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
2188
2195
  return Response.json(result, { status, headers: CORS_HEADERS });
2189
2196
  }
@@ -3168,13 +3175,13 @@ var YjsRoom = class extends BaseRoom {
3168
3175
  onMessage(ws, user, message) {
3169
3176
  }
3170
3177
  onBinaryMessage(ws, user, data) {
3171
- const bytes = new Uint8Array(data);
3172
- const decoder = createDecoder(bytes);
3178
+ const bytes2 = new Uint8Array(data);
3179
+ const decoder = createDecoder(bytes2);
3173
3180
  const messageType = readVarUint(decoder);
3174
3181
  if (messageType === MSG_SYNC) {
3175
- this.handleSync(ws, decoder, bytes);
3182
+ this.handleSync(ws, decoder, bytes2);
3176
3183
  } else if (messageType === MSG_AWARENESS) {
3177
- this.handleAwareness(ws, decoder, bytes);
3184
+ this.handleAwareness(ws, decoder, bytes2);
3178
3185
  }
3179
3186
  }
3180
3187
  onDisconnect(ws, user) {
@@ -4379,6 +4386,319 @@ function cronMatches(parsed, timezone, date) {
4379
4386
  return parsed.minute.has(minute) && parsed.hour.has(hour) && parsed.dayOfMonth.has(dayOfMonth) && parsed.month.has(month) && parsed.dayOfWeek.has(dayOfWeek);
4380
4387
  }
4381
4388
 
4389
+ // src/server/rooms/secrets-room.ts
4390
+ var INTERNAL_HEADER = "x-deepspace-internal";
4391
+ var INTERNAL_VALUE = "app-secrets";
4392
+ var VERSION = "app-secret-v1";
4393
+ var NAME_INFO = "deepspace app secret name v1";
4394
+ var VALUE_INFO = "deepspace app secret value v1";
4395
+ var HMAC_INFO = "deepspace app secret lookup v1";
4396
+ var STORE_ID = "default";
4397
+ var SecretsRoom = class extends BaseRoom {
4398
+ initialized = false;
4399
+ onMessage(_ws, _user, _message) {
4400
+ }
4401
+ async fetch(request) {
4402
+ this.ensureInitialized();
4403
+ return super.fetch(request);
4404
+ }
4405
+ async onRequest(request) {
4406
+ if (request.headers.get(INTERNAL_HEADER) !== INTERNAL_VALUE) {
4407
+ return json({ error: "Not found" }, 404);
4408
+ }
4409
+ const url = new URL(request.url);
4410
+ try {
4411
+ if (request.method === "GET" && url.pathname.endsWith("/list")) {
4412
+ return json({ version: this.storeVersion(), secrets: await this.list() });
4413
+ }
4414
+ if (request.method === "GET" && url.pathname.endsWith("/pull")) {
4415
+ return json({ version: this.storeVersion(), secrets: await this.pull() });
4416
+ }
4417
+ if (request.method === "GET" && url.pathname.endsWith("/version")) {
4418
+ return json({ version: this.storeVersion() });
4419
+ }
4420
+ if (request.method === "PATCH" && url.pathname.endsWith("/patch")) {
4421
+ const body = await request.json();
4422
+ const result = await this.patch(body);
4423
+ return json(result);
4424
+ }
4425
+ if (request.method === "PUT" && url.pathname.endsWith("/replace")) {
4426
+ const body = await request.json();
4427
+ const result = await this.replace(body);
4428
+ return json(result);
4429
+ }
4430
+ } catch (err) {
4431
+ const message = err instanceof Error ? err.message : String(err);
4432
+ const status = message.includes("conflict") ? 409 : 500;
4433
+ return json({ error: message }, status);
4434
+ }
4435
+ return json({ error: "Not found" }, 404);
4436
+ }
4437
+ async list() {
4438
+ const rows = this.sql.exec(
4439
+ `SELECT name_encrypted, version, updated_by, created_at, updated_at
4440
+ FROM app_secrets_store ORDER BY updated_at DESC`
4441
+ ).toArray();
4442
+ const out = [];
4443
+ for (const row of rows) {
4444
+ out.push({
4445
+ name: await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO),
4446
+ version: row.version,
4447
+ createdAt: row.created_at,
4448
+ updatedAt: row.updated_at,
4449
+ updatedBy: row.updated_by
4450
+ });
4451
+ }
4452
+ return out.sort((a, b) => a.name.localeCompare(b.name));
4453
+ }
4454
+ async pull() {
4455
+ const rows = this.sql.exec(
4456
+ `SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
4457
+ FROM app_secrets_store`
4458
+ ).toArray();
4459
+ const out = {};
4460
+ for (const row of rows) {
4461
+ const name = await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO);
4462
+ out[name] = await decryptText(this.requireKey(), row.value_encrypted, VALUE_INFO);
4463
+ }
4464
+ return out;
4465
+ }
4466
+ async patch(body) {
4467
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4468
+ const actor = body.updatedBy ?? null;
4469
+ const key = this.requireKey();
4470
+ const setEntries = Object.entries(body.set ?? {});
4471
+ const deleteNames = body.delete ?? [];
4472
+ for (const name of [...setEntries.map(([name2]) => name2), ...deleteNames]) {
4473
+ validateSecretName(name);
4474
+ }
4475
+ const writes = [];
4476
+ for (const [name, value] of setEntries) {
4477
+ const nameHash = await hashName(key, name);
4478
+ const existing = this.sql.exec(
4479
+ `SELECT version, created_at FROM app_secrets_store WHERE name_hash = ?`,
4480
+ nameHash
4481
+ ).toArray()[0];
4482
+ writes.push({
4483
+ nameHash,
4484
+ nameEncrypted: await encryptText(key, name, NAME_INFO),
4485
+ valueEncrypted: await encryptText(key, value, VALUE_INFO),
4486
+ version: (existing?.version ?? 0) + 1,
4487
+ createdAt: existing?.created_at ?? now
4488
+ });
4489
+ }
4490
+ const deletes = [];
4491
+ for (const name of deleteNames) {
4492
+ deletes.push(await hashName(key, name));
4493
+ }
4494
+ this.transaction(() => {
4495
+ for (const write of writes) {
4496
+ this.sql.exec(
4497
+ `INSERT OR REPLACE INTO app_secrets_store
4498
+ (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
4499
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
4500
+ write.nameHash,
4501
+ write.nameEncrypted,
4502
+ write.valueEncrypted,
4503
+ write.version,
4504
+ actor,
4505
+ write.createdAt,
4506
+ now
4507
+ );
4508
+ }
4509
+ for (const nameHash of deletes) {
4510
+ this.sql.exec(`DELETE FROM app_secrets_store WHERE name_hash = ?`, nameHash);
4511
+ }
4512
+ this.bumpStoreVersion(now);
4513
+ });
4514
+ return { version: this.storeVersion(), secrets: await this.list() };
4515
+ }
4516
+ async replace(body) {
4517
+ const next = body.secrets ?? {};
4518
+ for (const name of Object.keys(next)) validateSecretName(name);
4519
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4520
+ const actor = body.updatedBy ?? null;
4521
+ const key = this.requireKey();
4522
+ const writes = [];
4523
+ for (const [name, value] of Object.entries(next)) {
4524
+ writes.push({
4525
+ nameHash: await hashName(key, name),
4526
+ nameEncrypted: await encryptText(key, name, NAME_INFO),
4527
+ valueEncrypted: await encryptText(key, value, VALUE_INFO)
4528
+ });
4529
+ }
4530
+ this.transaction(() => {
4531
+ this.sql.exec(`DELETE FROM app_secrets_store`);
4532
+ for (const write of writes) {
4533
+ this.sql.exec(
4534
+ `INSERT INTO app_secrets_store
4535
+ (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
4536
+ VALUES (?, ?, ?, 1, ?, ?, ?)`,
4537
+ write.nameHash,
4538
+ write.nameEncrypted,
4539
+ write.valueEncrypted,
4540
+ actor,
4541
+ now,
4542
+ now
4543
+ );
4544
+ }
4545
+ this.bumpStoreVersion(now);
4546
+ });
4547
+ return { version: this.storeVersion(), secrets: await this.list() };
4548
+ }
4549
+ transaction(run) {
4550
+ this.state.storage.transactionSync(run);
4551
+ }
4552
+ migrateLegacyProductionSecrets() {
4553
+ const legacyExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secrets'`).toArray().length > 0;
4554
+ if (!legacyExists) return;
4555
+ const currentCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
4556
+ if ((currentCount?.count ?? 0) > 0) return;
4557
+ this.sql.exec(`
4558
+ INSERT OR IGNORE INTO app_secrets_store
4559
+ (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
4560
+ SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
4561
+ FROM app_secrets
4562
+ WHERE env = 'production'
4563
+ `);
4564
+ const legacyVersionExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secret_env_versions'`).toArray().length > 0;
4565
+ const legacyVersion = legacyVersionExists ? this.sql.exec(`SELECT version, updated_at FROM app_secret_env_versions WHERE env = 'production'`).toArray()[0] : void 0;
4566
+ if (legacyVersion) {
4567
+ this.sql.exec(
4568
+ `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
4569
+ STORE_ID,
4570
+ legacyVersion.version,
4571
+ legacyVersion.updated_at
4572
+ );
4573
+ }
4574
+ const migratedCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
4575
+ if (!legacyVersion && (migratedCount?.count ?? 0) > 0) {
4576
+ this.sql.exec(
4577
+ `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
4578
+ STORE_ID,
4579
+ 1,
4580
+ (/* @__PURE__ */ new Date()).toISOString()
4581
+ );
4582
+ }
4583
+ }
4584
+ ensureInitialized() {
4585
+ if (this.initialized) return;
4586
+ this.initialized = true;
4587
+ this.sql.exec(`
4588
+ CREATE TABLE IF NOT EXISTS app_secrets_store (
4589
+ name_hash TEXT NOT NULL,
4590
+ name_encrypted TEXT NOT NULL,
4591
+ value_encrypted TEXT NOT NULL,
4592
+ version INTEGER NOT NULL,
4593
+ updated_by TEXT,
4594
+ created_at TEXT NOT NULL,
4595
+ updated_at TEXT NOT NULL,
4596
+ PRIMARY KEY (name_hash)
4597
+ )
4598
+ `);
4599
+ this.sql.exec(`
4600
+ CREATE TABLE IF NOT EXISTS app_secret_store_versions (
4601
+ id TEXT PRIMARY KEY,
4602
+ version INTEGER NOT NULL,
4603
+ updated_at TEXT NOT NULL
4604
+ )
4605
+ `);
4606
+ this.migrateLegacyProductionSecrets();
4607
+ }
4608
+ requireKey() {
4609
+ if (!this.env.APP_SECRETS_KEY) {
4610
+ throw new Error("APP_SECRETS_KEY binding is missing");
4611
+ }
4612
+ return this.env.APP_SECRETS_KEY;
4613
+ }
4614
+ storeVersion() {
4615
+ const row = this.sql.exec(
4616
+ `SELECT version FROM app_secret_store_versions WHERE id = ?`,
4617
+ STORE_ID
4618
+ ).toArray()[0];
4619
+ return row?.version ?? 0;
4620
+ }
4621
+ bumpStoreVersion(now) {
4622
+ const next = this.storeVersion() + 1;
4623
+ this.sql.exec(
4624
+ `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
4625
+ STORE_ID,
4626
+ next,
4627
+ now
4628
+ );
4629
+ }
4630
+ };
4631
+ function validateSecretName(name) {
4632
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
4633
+ throw new Error(`Invalid secret name "${name}"`);
4634
+ }
4635
+ }
4636
+ function json(body, status = 200) {
4637
+ return new Response(JSON.stringify(body), {
4638
+ status,
4639
+ headers: { "Content-Type": "application/json" }
4640
+ });
4641
+ }
4642
+ async function encryptText(root, plaintext, info) {
4643
+ const salt = crypto.getRandomValues(new Uint8Array(16));
4644
+ const iv = crypto.getRandomValues(new Uint8Array(12));
4645
+ const key = await aesKey(root, salt, info);
4646
+ const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
4647
+ return [VERSION, b64(salt), b64(iv), b64(new Uint8Array(ciphertext))].join(":");
4648
+ }
4649
+ async function decryptText(root, encoded, info) {
4650
+ const [version, saltB64, ivB64, bodyB64] = encoded.split(":");
4651
+ if (version !== VERSION || !saltB64 || !ivB64 || !bodyB64) {
4652
+ throw new Error("Corrupt app secret ciphertext");
4653
+ }
4654
+ const key = await aesKey(root, unb64(saltB64), info);
4655
+ const plaintext = await crypto.subtle.decrypt(
4656
+ { name: "AES-GCM", iv: bytes(unb64(ivB64)) },
4657
+ key,
4658
+ bytes(unb64(bodyB64))
4659
+ );
4660
+ return new TextDecoder().decode(plaintext);
4661
+ }
4662
+ async function hashName(root, name) {
4663
+ const key = await hmacKey(root, HMAC_INFO);
4664
+ const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`production:${name}`));
4665
+ return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
4666
+ }
4667
+ async function aesKey(root, salt, info) {
4668
+ const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
4669
+ return crypto.subtle.deriveKey(
4670
+ { name: "HKDF", hash: "SHA-256", salt: bytes(salt), info: new TextEncoder().encode(info) },
4671
+ base,
4672
+ { name: "AES-GCM", length: 256 },
4673
+ false,
4674
+ ["encrypt", "decrypt"]
4675
+ );
4676
+ }
4677
+ async function hmacKey(root, info) {
4678
+ const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
4679
+ return crypto.subtle.deriveKey(
4680
+ { name: "HKDF", hash: "SHA-256", salt: bytes(new Uint8Array(16)), info: new TextEncoder().encode(info) },
4681
+ base,
4682
+ { name: "HMAC", hash: "SHA-256", length: 256 },
4683
+ false,
4684
+ ["sign"]
4685
+ );
4686
+ }
4687
+ function b64(bytes2) {
4688
+ let s = "";
4689
+ for (const b of bytes2) s += String.fromCharCode(b);
4690
+ return btoa(s);
4691
+ }
4692
+ function unb64(value) {
4693
+ const s = atob(value);
4694
+ const out = new Uint8Array(s.length);
4695
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
4696
+ return out;
4697
+ }
4698
+ function bytes(input) {
4699
+ return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
4700
+ }
4701
+
4382
4702
  // src/server/rooms/job-room.ts
4383
4703
  var RUNNING_STALE_MS = 16 * 60 * 1e3;
4384
4704
  var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
@@ -4996,6 +5316,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
4996
5316
  "AUTH_WORKER_URL",
4997
5317
  "APP_IDENTITY_TOKEN",
4998
5318
  "APP_OWNER_JWT",
5319
+ "APP_SECRETS_KEY",
4999
5320
  "USAGE_EVENTS"
5000
5321
  ]);
5001
5322
  function validateBindingManifest(manifest) {
@@ -6110,7 +6431,7 @@ function authWorkerFetch(env, path, init) {
6110
6431
  function buildCronContext(env, ownerUserId, roomId = "default") {
6111
6432
  const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
6112
6433
  const room = env.RECORD_ROOMS.get(roomIdObj);
6113
- async function executeTool3(tool, params) {
6434
+ async function executeTool3(tool2, params) {
6114
6435
  const response = await room.fetch(new Request("https://internal/api/tools/execute", {
6115
6436
  method: "POST",
6116
6437
  headers: {
@@ -6118,11 +6439,11 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
6118
6439
  "X-User-Id": ownerUserId,
6119
6440
  "X-App-Action": "true"
6120
6441
  },
6121
- body: JSON.stringify({ tool, params })
6442
+ body: JSON.stringify({ tool: tool2, params })
6122
6443
  }));
6123
6444
  const result = await response.json();
6124
6445
  if (!result.success) {
6125
- throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
6446
+ throw new Error(`RecordRoom tool ${tool2} failed: ${result.error || "Unknown error"}`);
6126
6447
  }
6127
6448
  return result.data;
6128
6449
  }
@@ -6243,6 +6564,87 @@ function createDeepSpaceAI(env, provider, options = {}) {
6243
6564
  }
6244
6565
  }
6245
6566
 
6567
+ // src/server/utils/composio-tools.ts
6568
+ import { tool, jsonSchema } from "ai";
6569
+ var EMPTY_SCHEMA = { type: "object", properties: {} };
6570
+ async function requestComposio(env, endpoint, params, authToken) {
6571
+ const res = await apiWorkerFetch(env, `/api/integrations/composio/${endpoint}`, {
6572
+ method: "POST",
6573
+ headers: {
6574
+ "Content-Type": "application/json",
6575
+ Authorization: `Bearer ${authToken}`
6576
+ },
6577
+ body: JSON.stringify(params)
6578
+ });
6579
+ const text = await res.text();
6580
+ let parsed = {};
6581
+ try {
6582
+ parsed = text ? JSON.parse(text) : {};
6583
+ } catch {
6584
+ }
6585
+ const detail = parsed.message || parsed.error || text || `HTTP ${res.status}`;
6586
+ return { ok: res.ok, success: parsed.success === true, data: parsed.data, detail };
6587
+ }
6588
+ function toolKey(slug) {
6589
+ const key = slug.replace(/[^a-zA-Z0-9_-]/g, "_");
6590
+ return key.length <= 64 ? key : key.slice(0, 64);
6591
+ }
6592
+ async function composioTools(env, options) {
6593
+ const { authToken } = options;
6594
+ if (!authToken) {
6595
+ throw new Error(
6596
+ "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."
6597
+ );
6598
+ }
6599
+ const list = await requestComposio(
6600
+ env,
6601
+ "list-tools",
6602
+ {
6603
+ toolkit: options.toolkit,
6604
+ toolSlugs: options.tools?.length ? options.tools.join(",") : void 0,
6605
+ search: options.search,
6606
+ limit: options.limit ?? 20
6607
+ },
6608
+ authToken
6609
+ );
6610
+ if (!list.ok || !list.success) {
6611
+ throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
6612
+ }
6613
+ const items = Array.isArray(list.data?.items) ? list.data.items : [];
6614
+ const tools = {};
6615
+ for (const item of items) {
6616
+ if (!item?.slug) continue;
6617
+ const slug = item.slug;
6618
+ const schema = item.input_parameters ?? EMPTY_SCHEMA;
6619
+ tools[toolKey(slug)] = tool({
6620
+ description: item.description ?? item.name ?? slug,
6621
+ inputSchema: jsonSchema(schema),
6622
+ execute: async (args) => {
6623
+ const r = await requestComposio(
6624
+ env,
6625
+ "execute-tool",
6626
+ { slug, arguments: args ?? {} },
6627
+ authToken
6628
+ );
6629
+ if (!r.ok || !r.success) {
6630
+ return { error: true, message: `Composio tool ${slug} failed: ${r.detail}` };
6631
+ }
6632
+ const data = r.data;
6633
+ if (data && typeof data === "object" && data.requiresConnection) {
6634
+ const toolkit = data.toolkit;
6635
+ return {
6636
+ requiresConnection: true,
6637
+ toolkit,
6638
+ message: `The user has not connected ${toolkit ?? "this app"}. Ask them to connect it before using this tool.`
6639
+ };
6640
+ }
6641
+ return data;
6642
+ }
6643
+ });
6644
+ }
6645
+ return tools;
6646
+ }
6647
+
6246
6648
  // src/server/utils/screenshot.ts
6247
6649
  async function captureScreenshot(env, opts) {
6248
6650
  const headers = new Headers({
@@ -6330,6 +6732,45 @@ function applySlidingWindow(messages, charCap, minKept) {
6330
6732
  }
6331
6733
  return out;
6332
6734
  }
6735
+ var TRIMMABLE_ARRAY_PATHS = [
6736
+ ["records"],
6737
+ ["data", "records"],
6738
+ ["items"],
6739
+ ["data", "items"],
6740
+ ["data"]
6741
+ ];
6742
+ function getPath(root, path) {
6743
+ let cur = root;
6744
+ for (const key of path) {
6745
+ if (!cur || typeof cur !== "object") return void 0;
6746
+ cur = cur[key];
6747
+ }
6748
+ return cur;
6749
+ }
6750
+ function setPath(root, path, value, flags) {
6751
+ const obj = root && typeof root === "object" ? root : {};
6752
+ const [head, ...tail] = path;
6753
+ if (tail.length === 0) {
6754
+ return { ...obj, [head]: value, ...flags };
6755
+ }
6756
+ return { ...obj, [head]: setPath(obj[head], tail, value, flags) };
6757
+ }
6758
+ function locateTrimmableArray(result) {
6759
+ for (const path of TRIMMABLE_ARRAY_PATHS) {
6760
+ const arr = getPath(result, path);
6761
+ if (Array.isArray(arr)) return { path, items: arr };
6762
+ }
6763
+ for (const [key, value] of Object.entries(result)) {
6764
+ if (Array.isArray(value)) return { path: [key], items: value };
6765
+ }
6766
+ const data = result.data;
6767
+ if (data && typeof data === "object") {
6768
+ for (const [key, value] of Object.entries(data)) {
6769
+ if (Array.isArray(value)) return { path: ["data", key], items: value };
6770
+ }
6771
+ }
6772
+ return void 0;
6773
+ }
6333
6774
  function capToolResultSize(result, byteCap) {
6334
6775
  let serialized;
6335
6776
  try {
@@ -6339,6 +6780,31 @@ function capToolResultSize(result, byteCap) {
6339
6780
  }
6340
6781
  if (typeof serialized !== "string") return result;
6341
6782
  if (serialized.length <= byteCap) return result;
6783
+ if (result && typeof result === "object") {
6784
+ const located = locateTrimmableArray(result);
6785
+ if (located) {
6786
+ const { path, items } = located;
6787
+ const total = items.length;
6788
+ const build = (k) => setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total });
6789
+ const fits = (k) => {
6790
+ try {
6791
+ return JSON.stringify(build(k)).length <= byteCap;
6792
+ } catch {
6793
+ return false;
6794
+ }
6795
+ };
6796
+ if (fits(0)) {
6797
+ let lo = 0;
6798
+ let hi = total;
6799
+ while (lo < hi) {
6800
+ const mid = Math.ceil((lo + hi) / 2);
6801
+ if (fits(mid)) lo = mid;
6802
+ else hi = mid - 1;
6803
+ }
6804
+ if (lo > 0) return build(lo);
6805
+ }
6806
+ }
6807
+ }
6342
6808
  return {
6343
6809
  success: false,
6344
6810
  truncated: true,
@@ -6547,7 +7013,7 @@ function makeDefaultSummarizer(env, options = {}) {
6547
7013
  }
6548
7014
 
6549
7015
  // src/server/utils/chat-history.ts
6550
- async function executeTool2(stub, userId, tool, params) {
7016
+ async function executeTool2(stub, userId, tool2, params) {
6551
7017
  const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
6552
7018
  method: "POST",
6553
7019
  headers: {
@@ -6555,11 +7021,11 @@ async function executeTool2(stub, userId, tool, params) {
6555
7021
  "X-User-Id": userId,
6556
7022
  "X-App-Action": "true"
6557
7023
  },
6558
- body: JSON.stringify({ tool, params })
7024
+ body: JSON.stringify({ tool: tool2, params })
6559
7025
  }));
6560
7026
  const body = await res.json();
6561
7027
  if (!body.success) {
6562
- throw new Error(`chat-history ${tool} failed: ${body.error ?? "unknown error"}`);
7028
+ throw new Error(`chat-history ${tool2} failed: ${body.error ?? "unknown error"}`);
6563
7029
  }
6564
7030
  return body.data;
6565
7031
  }
@@ -6778,8 +7244,8 @@ import { importSPKI, jwtVerify } from "jose";
6778
7244
 
6779
7245
  // src/server/auth/utils.ts
6780
7246
  var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
6781
- function bufferToHex(bytes) {
6782
- const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
7247
+ function bufferToHex(bytes2) {
7248
+ const view = bytes2 instanceof Uint8Array ? bytes2 : new Uint8Array(bytes2);
6783
7249
  let hex = "";
6784
7250
  for (const byte of view) {
6785
7251
  hex += byte.toString(16).padStart(2, "0");
@@ -6802,9 +7268,9 @@ function decodeJwtPayload(token) {
6802
7268
  if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
6803
7269
  try {
6804
7270
  const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
6805
- const bytes = base64ToUint8Array(normalized);
6806
- const json = new TextDecoder().decode(bytes);
6807
- const payload = JSON.parse(json);
7271
+ const bytes2 = base64ToUint8Array(normalized);
7272
+ const json2 = new TextDecoder().decode(bytes2);
7273
+ const payload = JSON.parse(json2);
6808
7274
  return {
6809
7275
  iss: payload.iss ?? null,
6810
7276
  aud: payload.aud ?? null,
@@ -6956,6 +7422,7 @@ async function timingSafeEqualHex(a, b) {
6956
7422
  // src/server/auth/betterAuth.ts
6957
7423
  import { betterAuth } from "better-auth";
6958
7424
  import { organization, twoFactor } from "better-auth/plugins";
7425
+ import { SignJWT, importPKCS8 } from "jose";
6959
7426
  function createDeepSpaceAuth(config) {
6960
7427
  const socialProviders = {};
6961
7428
  if (config.google) {
@@ -6964,6 +7431,20 @@ function createDeepSpaceAuth(config) {
6964
7431
  if (config.github) {
6965
7432
  socialProviders.github = config.github;
6966
7433
  }
7434
+ if (config.microsoft) {
7435
+ socialProviders.microsoft = config.microsoft;
7436
+ }
7437
+ if (config.apple) {
7438
+ const apple = config.apple;
7439
+ socialProviders.apple = async () => {
7440
+ try {
7441
+ return { clientId: apple.clientId, clientSecret: await generateAppleClientSecret(apple) };
7442
+ } catch (err) {
7443
+ console.error("[deepspace] failed to mint Apple client secret; Apple sign-in disabled", err);
7444
+ return { clientId: apple.clientId, clientSecret: "" };
7445
+ }
7446
+ };
7447
+ }
6967
7448
  return betterAuth({
6968
7449
  database: config.database,
6969
7450
  baseURL: config.baseURL,
@@ -6976,11 +7457,212 @@ function createDeepSpaceAuth(config) {
6976
7457
  "https://deep.space",
6977
7458
  "https://*.deep.space",
6978
7459
  "https://*.app.space",
6979
- "http://localhost:*"
7460
+ "http://localhost:*",
7461
+ // Apple posts its OAuth callback from this origin (form_post).
7462
+ "https://appleid.apple.com"
6980
7463
  ],
7464
+ // Apple returns its callback as a cross-site form_post, which a
7465
+ // SameSite=Lax cookie won't ride along with — so the OAuth handshake
7466
+ // cookie must be SameSite=None. Scoped to just that cookie; the session
7467
+ // cookie stays Lax. Gated on Apple being configured: deployments without
7468
+ // Apple keep Lax entirely. Note that when Apple IS configured, this also
7469
+ // relaxes the handshake cookie for Google/GitHub/Microsoft on the same
7470
+ // instance — which is safe, since the state is encrypted and verified
7471
+ // independently of SameSite (plus a separate per-flow CSRF cookie).
7472
+ ...config.apple ? {
7473
+ advanced: {
7474
+ cookies: {
7475
+ state: { attributes: { sameSite: "none", secure: true } },
7476
+ oauth_state: { attributes: { sameSite: "none", secure: true } }
7477
+ }
7478
+ }
7479
+ } : {},
6981
7480
  plugins: [organization(), twoFactor()]
6982
7481
  });
6983
7482
  }
7483
+ var cachedAppleSecret = null;
7484
+ async function generateAppleClientSecret(apple) {
7485
+ const now = Math.floor(Date.now() / 1e3);
7486
+ if (cachedAppleSecret && cachedAppleSecret.keyId === apple.keyId && cachedAppleSecret.expiresAt - now > 3600) {
7487
+ return cachedAppleSecret.token;
7488
+ }
7489
+ const pem = apple.privateKey.replace(/\\n/g, "\n");
7490
+ const key = await importPKCS8(pem, "ES256");
7491
+ const expiresAt = now + 180 * 24 * 60 * 60;
7492
+ 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);
7493
+ cachedAppleSecret = { keyId: apple.keyId, token, expiresAt };
7494
+ return token;
7495
+ }
7496
+
7497
+ // src/server/secrets.ts
7498
+ var DEFAULT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7499
+ function createSecrets(env, options = {}) {
7500
+ const refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
7501
+ const now = options.now ?? (() => Date.now());
7502
+ const cache = /* @__PURE__ */ new Map();
7503
+ let loadedAll = null;
7504
+ let loadedVersion = 0;
7505
+ let lastCheckedAt = 0;
7506
+ function stub() {
7507
+ if (!env.SECRETS_ROOMS) throw new Error("SECRETS_ROOMS binding is missing");
7508
+ if (!env.APP_SECRETS_KEY) throw new Error("APP_SECRETS_KEY binding is missing");
7509
+ const id = env.SECRETS_ROOMS.idFromName("app-secrets");
7510
+ return env.SECRETS_ROOMS.get(id);
7511
+ }
7512
+ async function fetchJson(path) {
7513
+ const res = await stub().fetch(`https://app-secrets/${path}`, {
7514
+ headers: { "x-deepspace-internal": "app-secrets" }
7515
+ });
7516
+ const body = await res.json().catch(() => ({}));
7517
+ if (!res.ok) throw new Error(body.error ?? `App secrets unavailable (${res.status})`);
7518
+ return body;
7519
+ }
7520
+ async function pullAll() {
7521
+ const body = await fetchJson("pull");
7522
+ const version = typeof body.version === "number" ? body.version : 0;
7523
+ cache.clear();
7524
+ for (const [name, value] of Object.entries(body.secrets ?? {})) {
7525
+ cache.set(name, value);
7526
+ }
7527
+ loadedVersion = version;
7528
+ lastCheckedAt = now();
7529
+ return version;
7530
+ }
7531
+ async function loadAll() {
7532
+ await pullAll();
7533
+ }
7534
+ async function ensureLoaded() {
7535
+ if (!loadedAll) {
7536
+ loadedAll = loadAll().catch((err) => {
7537
+ loadedAll = null;
7538
+ throw err;
7539
+ });
7540
+ }
7541
+ await loadedAll;
7542
+ }
7543
+ async function maybeCheckForUpdates() {
7544
+ if (!loadedAll) return;
7545
+ if (refreshIntervalMs < 0) return;
7546
+ if (now() - lastCheckedAt < refreshIntervalMs) return;
7547
+ try {
7548
+ await client.checkForUpdates();
7549
+ } catch {
7550
+ }
7551
+ }
7552
+ function localDevValue(name) {
7553
+ const value = env[name];
7554
+ return env.APP_SECRETS_LOCAL_FALLBACK === "true" && typeof value === "string" ? value : void 0;
7555
+ }
7556
+ const client = {
7557
+ async get(name) {
7558
+ const local = localDevValue(name);
7559
+ if (local !== void 0) return local;
7560
+ if (!cache.has(name)) await ensureLoaded();
7561
+ else await maybeCheckForUpdates();
7562
+ return cache.get(name);
7563
+ },
7564
+ async require(name) {
7565
+ const value = await this.get(name);
7566
+ if (value === void 0) throw new Error(`Missing app secret "${name}"`);
7567
+ return value;
7568
+ },
7569
+ async getMany(names) {
7570
+ const out = {};
7571
+ for (const name of names) out[name] = await this.get(name);
7572
+ return out;
7573
+ },
7574
+ async preload(names) {
7575
+ if (env.APP_SECRETS_LOCAL_FALLBACK === "true") {
7576
+ const missing = [];
7577
+ for (const name of names) {
7578
+ const local = localDevValue(name);
7579
+ if (local !== void 0) cache.set(name, local);
7580
+ else missing.push(name);
7581
+ }
7582
+ if (missing.length === 0) {
7583
+ await maybeCheckForUpdates();
7584
+ return;
7585
+ }
7586
+ }
7587
+ await ensureLoaded();
7588
+ await maybeCheckForUpdates();
7589
+ },
7590
+ async refresh(refreshOptions = {}) {
7591
+ if (refreshOptions.force || !loadedAll) {
7592
+ const previous = loadedVersion;
7593
+ const version = await pullAll();
7594
+ loadedAll = Promise.resolve();
7595
+ return { changed: version !== previous, version };
7596
+ }
7597
+ return this.checkForUpdates();
7598
+ },
7599
+ async checkForUpdates() {
7600
+ await ensureLoaded();
7601
+ const body = await fetchJson("version");
7602
+ const version = typeof body.version === "number" ? body.version : 0;
7603
+ lastCheckedAt = now();
7604
+ if (version === loadedVersion) return { changed: false, version };
7605
+ const pulledVersion = await pullAll();
7606
+ return { changed: true, version: pulledVersion };
7607
+ },
7608
+ invalidate() {
7609
+ cache.clear();
7610
+ loadedAll = null;
7611
+ loadedVersion = 0;
7612
+ lastCheckedAt = 0;
7613
+ },
7614
+ async withFreshSecrets(operation, retryOptions = {}) {
7615
+ let first;
7616
+ try {
7617
+ first = await operation({ secrets: client, attempt: 1, refreshed: false });
7618
+ } catch (err) {
7619
+ if (!shouldRefreshOnError(err)) throw err;
7620
+ const refresh = await refreshAfterFailure(err);
7621
+ if (!refresh.changed && !retryOptions.retryWhenUnchanged) throw err;
7622
+ return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7623
+ }
7624
+ if (first instanceof Response && shouldRefreshOnStatus(first.status)) {
7625
+ const refresh = await refreshAfterFailure(first);
7626
+ if (refresh.changed || retryOptions.retryWhenUnchanged) {
7627
+ return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7628
+ }
7629
+ }
7630
+ return first;
7631
+ }
7632
+ };
7633
+ async function refreshAfterFailure(original) {
7634
+ try {
7635
+ return await client.refresh({ force: true });
7636
+ } catch (err) {
7637
+ const originalMessage = describeFailure(original);
7638
+ const refreshMessage = err instanceof Error ? err.message : String(err);
7639
+ throw new Error(`Secret refresh failed after API failure (${originalMessage}): ${refreshMessage}`);
7640
+ }
7641
+ }
7642
+ return client;
7643
+ }
7644
+ function shouldRefreshOnError(error) {
7645
+ const status = statusFromError(error);
7646
+ return status !== void 0 && shouldRefreshOnStatus(status);
7647
+ }
7648
+ function shouldRefreshOnStatus(status) {
7649
+ return status === 401 || status === 403;
7650
+ }
7651
+ function statusFromError(error) {
7652
+ if (!error || typeof error !== "object") return void 0;
7653
+ const record = error;
7654
+ if (typeof record.status === "number") return record.status;
7655
+ if (typeof record.statusCode === "number") return record.statusCode;
7656
+ if (typeof record.response?.status === "number") return record.response.status;
7657
+ return void 0;
7658
+ }
7659
+ function describeFailure(value) {
7660
+ if (value instanceof Response) return `HTTP ${value.status}`;
7661
+ if (value instanceof Error) return value.message;
7662
+ const status = statusFromError(value);
7663
+ if (status) return `HTTP ${status}`;
7664
+ return String(value);
7665
+ }
6984
7666
  export {
6985
7667
  AI_CHATS_SCHEMA,
6986
7668
  AI_MESSAGES_SCHEMA,
@@ -7001,6 +7683,7 @@ export {
7001
7683
  CronRoom,
7002
7684
  DEFAULT_CONTEXT_CONFIG,
7003
7685
  DEFAULT_DO_MANIFEST,
7686
+ DEFAULT_QUERY_LIMIT,
7004
7687
  DIRECTORY_SCHEMAS,
7005
7688
  GLOBAL_DO_TYPES,
7006
7689
  GLOBAL_DO_TYPE_NAMES,
@@ -7027,6 +7710,7 @@ export {
7027
7710
  SYSTEM_COLLECTIONS,
7028
7711
  SYSTEM_MANAGED_COLUMNS,
7029
7712
  SchemaRegistry,
7713
+ SecretsRoom,
7030
7714
  USERS_COLUMNS,
7031
7715
  VOTING_SCHEMAS,
7032
7716
  WORKSPACE_SCHEMAS,
@@ -7034,6 +7718,7 @@ export {
7034
7718
  analyzePermissions,
7035
7719
  apiWorkerFetch,
7036
7720
  appendMessage,
7721
+ applyAiToolDefaults,
7037
7722
  applySlidingWindow,
7038
7723
  authWorkerFetch,
7039
7724
  bindingManifestFromOutputConfig,
@@ -7051,6 +7736,7 @@ export {
7051
7736
  coerceValue,
7052
7737
  collectionTableName,
7053
7738
  columnId,
7739
+ composioTools,
7054
7740
  computeDoMigration,
7055
7741
  computeHmacHex,
7056
7742
  createChat,
@@ -7059,6 +7745,7 @@ export {
7059
7745
  createDeepSpaceAuth,
7060
7746
  createEncoder,
7061
7747
  createScopedR2Handler,
7748
+ createSecrets,
7062
7749
  dataToColumnValues,
7063
7750
  decodeJwtPayload,
7064
7751
  deleteChatCascade,