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/cli.js +1442 -275
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +197 -425
- package/dist/index.js +621 -1665
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +207 -28
- package/dist/server.js +713 -26
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +207 -28
- package/dist/worker.js +713 -26
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/AdminPage.tsx +1 -1
- package/features/canvas/src/CanvasListPage.tsx +1 -1
- package/features/canvas/src/components/CanvasView.tsx +1 -1
- package/features/docs/src/DocsEditorPage.tsx +1 -1
- package/features/docs/src/InviteDialog.tsx +130 -139
- package/features/file-attachments/src/AttachmentsPage.tsx +1 -4
- package/features/file-manager/src/FileManagerPage.tsx +2 -1
- package/features/items/src/ItemsPage.tsx +1 -1
- package/features/landing/src/LandingPage.tsx +1 -1
- package/features/landing/src/primitives.tsx +1 -1
- package/features/leaderboard/src/LeaderboardPage.tsx +1 -1
- package/features/leaderboard/src/leaderboard-constants.ts +1 -1
- package/features/search-bar/src/SearchOverlay.tsx +1 -1
- package/features/tasks/src/TasksPage.tsx +1 -1
- package/features/tasks/src/tasks-constants.ts +1 -1
- package/package.json +1 -18
- package/dist/testing.d.ts +0 -115
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:
|
|
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:
|
|
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(
|
|
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 ${
|
|
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,
|
|
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 ${
|
|
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
|
|
2794
|
-
if (!
|
|
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 (!
|
|
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 (
|
|
2833
|
-
const result2 = await executeYjsTool(ctx,
|
|
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,
|
|
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
|
|
3830
|
-
const decoder = createDecoder(
|
|
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,
|
|
3985
|
+
this.handleSync(ws, decoder, bytes2);
|
|
3834
3986
|
} else if (messageType === MSG_AWARENESS) {
|
|
3835
|
-
this.handleAwareness(ws, decoder,
|
|
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(
|
|
6494
|
-
const view =
|
|
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
|
|
6518
|
-
const
|
|
6519
|
-
const payload = JSON.parse(
|
|
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,
|