@tool-bridge/server 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{airtable-NVGQVZJW.js → airtable-S3T4TAN7.js} +4 -4
- package/dist/{alt_text_generator_ai-WF7NHVWE.js → alt_text_generator_ai-4DIRIDKL.js} +4 -4
- package/dist/{amap-EPI7AQ4O.js → amap-LGVGNPML.js} +4 -4
- package/dist/{anthropic-KVYWBZIV.js → anthropic-27RFZO35.js} +4 -4
- package/dist/{apify-MB56RQIA.js → apify-6WLYAIAS.js} +4 -4
- package/dist/{brave_search-JDAGJ7PO.js → brave_search-K2SVJ66F.js} +4 -4
- package/dist/{chunk-HGIPJFHA.js → chunk-2O27H4Y2.js} +1 -1
- package/dist/{chunk-X6LLG5OZ.js → chunk-BB6ZLMTU.js} +1194 -120
- package/dist/{chunk-CDOWN2AB.js → chunk-GKX72JMJ.js} +1 -1
- package/dist/{chunk-BWGAV3MR.js → chunk-VK3V2WTY.js} +2 -2
- package/dist/{chunk-ABHHWYEV.js → chunk-YB2FLNWI.js} +1689 -39
- package/dist/{circleci-2IL5RU2O.js → circleci-WV3ITU7Q.js} +4 -4
- package/dist/{clerk-A5YYGWYM.js → clerk-NFQEFKS4.js} +4 -4
- package/dist/{cloudflare_dns-GUA5BUWO.js → cloudflare_dns-2RK5ASBJ.js} +4 -4
- package/dist/{cohere-BS27QMKR.js → cohere-5KEW7LWT.js} +4 -4
- package/dist/{confluence-675IWLHU.js → confluence-SZE63S46.js} +4 -4
- package/dist/{convertapi-DGKXP2T3.js → convertapi-QPG2JC5K.js} +4 -4
- package/dist/{currencyapi-PFH3UKVU.js → currencyapi-UO2APQRY.js} +4 -4
- package/dist/{deepseek-5U3STDGY.js → deepseek-LJELIMM3.js} +4 -4
- package/dist/{docker_hub-UVXRU2L3.js → docker_hub-2MFXSBOF.js} +4 -4
- package/dist/{dropbox-BC54XUXF.js → dropbox-GQYNTTVA.js} +4 -4
- package/dist/{dub-GV5M6PWD.js → dub-KI2LHXST.js} +4 -4
- package/dist/{e2b-KEQ3PYPV.js → e2b-WO6ISY3C.js} +4 -4
- package/dist/{exa-OGQR7D47.js → exa-IZ7B3BPT.js} +4 -4
- package/dist/{fal_ai-ULI6YF4O.js → fal_ai-LTMF36GJ.js} +4 -4
- package/dist/{feishu-YWLRRUGP.js → feishu-WM7SLSFD.js} +3 -3
- package/dist/{feishu_custom_bot-7HP4UHB7.js → feishu_custom_bot-66W3MXH2.js} +4 -4
- package/dist/{figma-BFGC24YI.js → figma-2Z274VQZ.js} +4 -4
- package/dist/{firehydrant-R6DG7SBL.js → firehydrant-IFI4R65V.js} +4 -4
- package/dist/{fixer-ZVXOVTMF.js → fixer-BCCZEB3D.js} +4 -4
- package/dist/{fly-EBKWMDWV.js → fly-755FXYB5.js} +4 -4
- package/dist/{front-CFWVIQYX.js → front-4FZKKXXK.js} +4 -4
- package/dist/{geocodio-N27RDBFV.js → geocodio-WYKDJ3DP.js} +4 -4
- package/dist/{ghost-2HT2SJYO.js → ghost-5BNQTCOH.js} +4 -4
- package/dist/{github-ZDLAIRXP.js → github-ULELF5VX.js} +4 -4
- package/dist/{gmail-TICKPP6S.js → gmail-G34WT64J.js} +4 -4
- package/dist/{googlecalendar-GUR47UTF.js → googlecalendar-BQU255SC.js} +4 -4
- package/dist/{googledocs-QGDIJA4S.js → googledocs-HPCNQXYJ.js} +4 -4
- package/dist/{grafana-33Y3WEU5.js → grafana-VOV24FIC.js} +4 -4
- package/dist/{graphhopper-KWTMYEVH.js → graphhopper-D5RXW6AP.js} +4 -4
- package/dist/index.d.ts +164 -61
- package/dist/index.js +2 -2
- package/dist/{ip2proxy-4SAYDJPQ.js → ip2proxy-DAYO7EKZ.js} +4 -4
- package/dist/{ipgeolocation_io-4WT2WKHF.js → ipgeolocation_io-XD4PSLII.js} +4 -4
- package/dist/{ipqualityscore-VGZCROJJ.js → ipqualityscore-K27QEAWK.js} +4 -4
- package/dist/{jina_ai-X5VJVORK.js → jina_ai-E6UXFV2P.js} +4 -4
- package/dist/{jira-XUAF2UPM.js → jira-ZDSXGQ6W.js} +4 -4
- package/dist/{langsmith-RRLFNL6D.js → langsmith-TIFJRREW.js} +4 -4
- package/dist/{lemlist-KQKG6JHM.js → lemlist-DASEMJMA.js} +4 -4
- package/dist/{linear-I32UBCBL.js → linear-KE6UP4SP.js} +4 -4
- package/dist/{logsnag-TVNUQEM7.js → logsnag-IMNDN4R5.js} +4 -4
- package/dist/main.js +101 -101
- package/dist/{mattermost-OCIA7Z4P.js → mattermost-HHHMUXQA.js} +4 -4
- package/dist/{mem0-FARYP2ZM.js → mem0-D2FGGOIY.js} +4 -4
- package/dist/{memos-6M6QEUYI.js → memos-4OVIGFPH.js} +4 -4
- package/dist/{metabase-VCNMPHIR.js → metabase-33WCX5YD.js} +4 -4
- package/dist/{mistral_ai-SMPVEWBM.js → mistral_ai-QIMZ2JYL.js} +4 -4
- package/dist/{mother_duck-CMHFH5A6.js → mother_duck-M6VWS2VH.js} +4 -4
- package/dist/{ngrok-WRQJ3X54.js → ngrok-Z2PKQBHH.js} +4 -4
- package/dist/{notes-HLPED7H7.js → notes-SS4RPC4K.js} +2 -2
- package/dist/{notion-FFCK2KZ4.js → notion-FJCNQ6I7.js} +4 -4
- package/dist/{open_exchange_rates-VKQICRS6.js → open_exchange_rates-WU57WTT5.js} +4 -4
- package/dist/{openai-PJDJ2AZ2.js → openai-JODZ4GKT.js} +4 -4
- package/dist/{opengraph_io-DMQQXMEC.js → opengraph_io-SQRAUSV5.js} +4 -4
- package/dist/{openrouter-I52ZZHQT.js → openrouter-Q5XXEJZ7.js} +4 -4
- package/dist/{outline-I6EIBEHQ.js → outline-RT2EHJ2X.js} +4 -4
- package/dist/{paddle-4DU7GKCS.js → paddle-OF2V7GAC.js} +4 -4
- package/dist/{perplexity-DE6K444O.js → perplexity-LNZEWLHG.js} +4 -4
- package/dist/{pinecone-KMRF5YWJ.js → pinecone-JSJIXZYN.js} +4 -4
- package/dist/{pipedrive-4OC5UU3L.js → pipedrive-D4CKVUXR.js} +4 -4
- package/dist/{polar-OACUFN7P.js → polar-M7ZYP6UR.js} +4 -4
- package/dist/{posthog-PSGNT66O.js → posthog-YV7CZOH4.js} +4 -4
- package/dist/{postmark-CSOVREPD.js → postmark-KYHLECRG.js} +4 -4
- package/dist/{prerender-B7M6SAIA.js → prerender-HCMHBKV2.js} +4 -4
- package/dist/{pubmed-JJ5URMS6.js → pubmed-WYVLCCN3.js} +4 -4
- package/dist/{railway-VO42EWHC.js → railway-TWIRD5KR.js} +4 -4
- package/dist/{readwise-O7I4HLU4.js → readwise-UZ467IQ7.js} +4 -4
- package/dist/{render-24X3H3WB.js → render-MECUC5CC.js} +4 -4
- package/dist/{replicate-TO6OPISC.js → replicate-A5MEK3SW.js} +4 -4
- package/dist/{resend-IBVGUV4X.js → resend-BC6EAWJQ.js} +4 -4
- package/dist/{rootly-MD5GFDUC.js → rootly-ZM3QDUIS.js} +4 -4
- package/dist/{scrapfly-ZKDGE4IL.js → scrapfly-WAML42QJ.js} +4 -4
- package/dist/{scrapingbee-2FVX2BFI.js → scrapingbee-DG7PET3V.js} +4 -4
- package/dist/{screenshot_fyi-HNPNS43Z.js → screenshot_fyi-HY6WTHIO.js} +4 -4
- package/dist/{semantic_scholar-EJ2CYAKO.js → semantic_scholar-C5IZA7IP.js} +4 -4
- package/dist/{sentry-DXB3CQZA.js → sentry-OKBPSTDM.js} +4 -4
- package/dist/{shodan-HEFZ6NHP.js → shodan-PPC6DYU4.js} +4 -4
- package/dist/{shopify-SUN5J257.js → shopify-EWGUHBXO.js} +4 -4
- package/dist/{stripe-N3HNTHD5.js → stripe-6RPDPC5W.js} +4 -4
- package/dist/{supabase-IMZ6YRES.js → supabase-SK7DQ42N.js} +4 -4
- package/dist/{tavily-QMPSOIGW.js → tavily-YCG5VQMH.js} +4 -4
- package/dist/{telegram-SHZX3C73.js → telegram-2OES5FYB.js} +4 -4
- package/dist/{telnyx-SF6KMQ7W.js → telnyx-POOSBBR4.js} +4 -4
- package/dist/{todoist-Y252DAZ2.js → todoist-M7YD6YSS.js} +4 -4
- package/dist/{trello-BTRC6I5Y.js → trello-IS6V5W3E.js} +4 -4
- package/dist/{turso-SB4KFTUP.js → turso-RCFHUFVH.js} +4 -4
- package/dist/{twilio-CRRJZ6MR.js → twilio-OS2RXFGO.js} +4 -4
- package/dist/{umami-7PJKITQ5.js → umami-PQ2SJ2LN.js} +4 -4
- package/dist/{unsplash-PYZXMXH2.js → unsplash-C6BPZO4E.js} +4 -4
- package/dist/{upstash_redis-L2IJTHFW.js → upstash_redis-WH6UER7H.js} +4 -4
- package/dist/{uptimerobot-XDYEYFJQ.js → uptimerobot-M3YZGS6Z.js} +4 -4
- package/dist/{v2ex-TNACDCAO.js → v2ex-R4JWTEHN.js} +4 -4
- package/dist/{vercel-QRU4UC5M.js → vercel-FZVYUOQD.js} +4 -4
- package/dist/{wolfram_alpha_api-TS663Q5L.js → wolfram_alpha_api-2KOLO5DC.js} +4 -4
- package/dist/{wordpress-RTEUS26Y.js → wordpress-XRWGO6LG.js} +4 -4
- package/dist/{workos-4NGPU4VP.js → workos-WR4NKOYL.js} +4 -4
- package/dist/{zhihu-GGLOXAVO.js → zhihu-BKZIB3WA.js} +4 -4
- package/package.json +3 -3
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnnotationStore,
|
|
3
3
|
CONTEXT_CAPABILITIES,
|
|
4
|
+
CONTEXT_DIRECT_UPLOAD_CAPABILITY,
|
|
4
5
|
DEFAULT_MAX_NODES,
|
|
6
|
+
DEFAULT_STORE_DRIVER_KEY_ROOT,
|
|
5
7
|
DEVICE_CALL_TIMEOUT_MS,
|
|
6
8
|
DeviceGatewaySession,
|
|
7
9
|
FEEDBACK_HIDE_SCORE,
|
|
@@ -25,11 +27,13 @@ import {
|
|
|
25
27
|
PG_SEARCH_TOOLS_TABLE,
|
|
26
28
|
PG_SEARCH_WRITE_LOCK_KEY,
|
|
27
29
|
PRESIGN_TTL_SEC_DEFAULT,
|
|
30
|
+
PRESIGN_TTL_SEC_MAX,
|
|
28
31
|
RemoteAllowlistStore,
|
|
29
32
|
SKILLHUB_CAPABILITIES,
|
|
30
33
|
SKRegistryStore,
|
|
31
34
|
SecretStoreImpl,
|
|
32
35
|
SqlSearchIndex,
|
|
36
|
+
StoreService,
|
|
33
37
|
TBError,
|
|
34
38
|
TB_ERROR_CODES,
|
|
35
39
|
TOOL_SEARCH_AUDIT_NODE_LIMIT,
|
|
@@ -47,6 +51,7 @@ import {
|
|
|
47
51
|
appendVia,
|
|
48
52
|
array,
|
|
49
53
|
assertPluginPayloadSize,
|
|
54
|
+
assertPresignTtlSec,
|
|
50
55
|
assertSecretRefUse,
|
|
51
56
|
assertSecureUrl,
|
|
52
57
|
authHeaderFor,
|
|
@@ -71,9 +76,12 @@ import {
|
|
|
71
76
|
contextHelpModel,
|
|
72
77
|
contextMethodsOf,
|
|
73
78
|
contextScopeForCmd,
|
|
79
|
+
contextUploadCmd,
|
|
74
80
|
createBuiltins,
|
|
75
81
|
createObjectContextProvider,
|
|
82
|
+
createObjectContextUploadGrant,
|
|
76
83
|
createSkillhubProvider,
|
|
84
|
+
createStoreModule,
|
|
77
85
|
decodeDeviceFrame,
|
|
78
86
|
derivePresence,
|
|
79
87
|
deviceDirectoryHelpModel,
|
|
@@ -131,6 +139,7 @@ import {
|
|
|
131
139
|
shouldRefresh,
|
|
132
140
|
skillhubHelpModel,
|
|
133
141
|
skillhubScopeForCmd,
|
|
142
|
+
storeUri,
|
|
134
143
|
string,
|
|
135
144
|
toJSONSchema,
|
|
136
145
|
toolHelpModel,
|
|
@@ -141,16 +150,21 @@ import {
|
|
|
141
150
|
url,
|
|
142
151
|
validatePath,
|
|
143
152
|
virtualizeTools
|
|
144
|
-
} from "./chunk-
|
|
153
|
+
} from "./chunk-YB2FLNWI.js";
|
|
145
154
|
|
|
146
155
|
// src/config.ts
|
|
147
156
|
var DEFAULT_PORT = 8787;
|
|
148
157
|
var DEFAULT_MAX_HOPS = 4;
|
|
149
158
|
var DEFAULT_DEVICE_RECLAIM_SEC = 24 * 60 * 60;
|
|
159
|
+
var DEFAULT_STORE_CLEANUP_INTERVAL_SEC = 15 * 60;
|
|
150
160
|
function positiveIntEnv(value) {
|
|
151
161
|
const n = Number(value);
|
|
152
162
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
|
|
153
163
|
}
|
|
164
|
+
function presignTtlEnv(value) {
|
|
165
|
+
const ttl = positiveIntEnv(value);
|
|
166
|
+
return ttl === void 0 ? void 0 : Math.min(ttl, PRESIGN_TTL_SEC_MAX);
|
|
167
|
+
}
|
|
154
168
|
function portEnv(value) {
|
|
155
169
|
if (value === void 0 || value === "") return void 0;
|
|
156
170
|
const n = Number(value);
|
|
@@ -174,6 +188,7 @@ function configFromEnv(env = process.env) {
|
|
|
174
188
|
allowInsecure
|
|
175
189
|
},
|
|
176
190
|
deviceReclaimSec: positiveIntEnv(env.TB_DEVICE_RECLAIM_SEC) ?? DEFAULT_DEVICE_RECLAIM_SEC,
|
|
191
|
+
storeCleanupIntervalSec: positiveIntEnv(env.TB_STORE_CLEANUP_INTERVAL_SEC) ?? DEFAULT_STORE_CLEANUP_INTERVAL_SEC,
|
|
177
192
|
// 0 合法(立即关停,本地/单机默认);positiveIntEnv 拒 0,故单独解析。
|
|
178
193
|
shutdownDrainSec: (() => {
|
|
179
194
|
const n = Number(env.TB_SHUTDOWN_DRAIN_SEC);
|
|
@@ -224,8 +239,38 @@ function configFromEnv(env = process.env) {
|
|
|
224
239
|
if (ttl !== void 0) config.toolCacheTtlSec = ttl;
|
|
225
240
|
const refThreshold = positiveIntEnv(env.TB_REF_THRESHOLD_BYTES);
|
|
226
241
|
if (refThreshold !== void 0) config.refThresholdBytes = refThreshold;
|
|
227
|
-
const refTtl =
|
|
242
|
+
const refTtl = presignTtlEnv(env.TB_REF_TTL_SEC);
|
|
228
243
|
if (refTtl !== void 0) config.refTtlSec = refTtl;
|
|
244
|
+
const uploadGrantTtl = presignTtlEnv(env.TB_UPLOAD_GRANT_TTL_SEC);
|
|
245
|
+
if (uploadGrantTtl !== void 0) config.uploadGrantTtlSec = uploadGrantTtl;
|
|
246
|
+
const storeMaxObjectBytes = positiveIntEnv(env.TB_STORE_MAX_OBJECT_BYTES);
|
|
247
|
+
if (storeMaxObjectBytes !== void 0) config.storeMaxObjectBytes = storeMaxObjectBytes;
|
|
248
|
+
const storeRelayMaxBytes = positiveIntEnv(env.TB_STORE_RELAY_MAX_BYTES);
|
|
249
|
+
if (storeRelayMaxBytes !== void 0) config.storeRelayMaxBytes = storeRelayMaxBytes;
|
|
250
|
+
const storeUploadTtlSec = presignTtlEnv(env.TB_STORE_UPLOAD_TTL_SEC);
|
|
251
|
+
if (storeUploadTtlSec !== void 0) config.storeUploadTtlSec = storeUploadTtlSec;
|
|
252
|
+
const storeShareTtlSec = presignTtlEnv(env.TB_STORE_SHARE_TTL_SEC);
|
|
253
|
+
if (storeShareTtlSec !== void 0) config.storeShareTtlSec = storeShareTtlSec;
|
|
254
|
+
const storeReadTtlSec = presignTtlEnv(env.TB_STORE_READ_TTL_SEC);
|
|
255
|
+
if (storeReadTtlSec !== void 0) config.storeReadTtlSec = storeReadTtlSec;
|
|
256
|
+
const storeCallMaxBytes = positiveIntEnv(env.TB_STORE_CALL_MAX_BYTES);
|
|
257
|
+
if (storeCallMaxBytes !== void 0) config.storeCallMaxBytes = storeCallMaxBytes;
|
|
258
|
+
const storeCallMaxObjectBytes = positiveIntEnv(env.TB_STORE_CALL_MAX_OBJECT_BYTES);
|
|
259
|
+
if (storeCallMaxObjectBytes !== void 0) {
|
|
260
|
+
config.storeCallMaxObjectBytes = storeCallMaxObjectBytes;
|
|
261
|
+
}
|
|
262
|
+
const storeCallMaxObjects = positiveIntEnv(env.TB_STORE_CALL_MAX_OBJECTS);
|
|
263
|
+
if (storeCallMaxObjects !== void 0) config.storeCallMaxObjects = storeCallMaxObjects;
|
|
264
|
+
const storeCallAllowedContentTypes = (env.TB_STORE_CALL_ALLOWED_CONTENT_TYPES ?? "").split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
265
|
+
if (storeCallAllowedContentTypes.length > 0) {
|
|
266
|
+
config.storeCallAllowedContentTypes = storeCallAllowedContentTypes;
|
|
267
|
+
}
|
|
268
|
+
if (env.TB_STORE_TOKEN_SECRET !== void 0 && env.TB_STORE_TOKEN_SECRET.length > 0) {
|
|
269
|
+
if (env.TB_STORE_TOKEN_SECRET.length < 16) {
|
|
270
|
+
throw new Error("TB_STORE_TOKEN_SECRET \u81F3\u5C11\u9700\u8981 16 \u4E2A\u5B57\u7B26");
|
|
271
|
+
}
|
|
272
|
+
config.storeTokenSecret = env.TB_STORE_TOKEN_SECRET;
|
|
273
|
+
}
|
|
229
274
|
return config;
|
|
230
275
|
}
|
|
231
276
|
|
|
@@ -428,7 +473,8 @@ var BUILTIN_MODULES = [
|
|
|
428
473
|
"plugin",
|
|
429
474
|
"catalog",
|
|
430
475
|
"federation",
|
|
431
|
-
"annotation"
|
|
476
|
+
"annotation",
|
|
477
|
+
"store"
|
|
432
478
|
];
|
|
433
479
|
var BUILTIN_DESCRIPTIONS = {
|
|
434
480
|
sk: "SecretKey registry",
|
|
@@ -438,7 +484,8 @@ var BUILTIN_DESCRIPTIONS = {
|
|
|
438
484
|
plugin: "Plugin registry (external plugins)",
|
|
439
485
|
catalog: "Built-in integration catalog (read-only)",
|
|
440
486
|
federation: "Remote federation host allowlist",
|
|
441
|
-
annotation: "Admin notes shown in ~help of any path"
|
|
487
|
+
annotation: "Admin notes shown in ~help of any path",
|
|
488
|
+
store: "Deployment-level private object Store"
|
|
442
489
|
};
|
|
443
490
|
async function mintAdminWithPlaintext(store, plaintext, now) {
|
|
444
491
|
const { key } = await mintKey(adminBootstrapInput(), now);
|
|
@@ -533,6 +580,7 @@ function encodeObjectKey(key) {
|
|
|
533
580
|
return key.split("/").map(encodeURIComponent).join("/");
|
|
534
581
|
}
|
|
535
582
|
async function presignS3Url(client, url2, ttlSec) {
|
|
583
|
+
assertPresignTtlSec(ttlSec);
|
|
536
584
|
const target = new URL(url2);
|
|
537
585
|
target.searchParams.set("X-Amz-Expires", String(ttlSec));
|
|
538
586
|
const signed = await client.sign(new Request(target.toString(), { method: "GET" }), {
|
|
@@ -540,6 +588,24 @@ async function presignS3Url(client, url2, ttlSec) {
|
|
|
540
588
|
});
|
|
541
589
|
return signed.url;
|
|
542
590
|
}
|
|
591
|
+
async function presignS3Put(client, url2, ttlSec, opts) {
|
|
592
|
+
assertPresignTtlSec(ttlSec);
|
|
593
|
+
if ("contentLength" in opts && (!Number.isSafeInteger(opts.contentLength) || opts.contentLength < 0)) {
|
|
594
|
+
throw new TBError("invalid_argument", "presigned PUT contentLength \u5FC5\u987B\u662F\u975E\u8D1F\u5B89\u5168\u6574\u6570");
|
|
595
|
+
}
|
|
596
|
+
const target = new URL(url2);
|
|
597
|
+
target.searchParams.set("X-Amz-Expires", String(ttlSec));
|
|
598
|
+
const headers = {
|
|
599
|
+
"content-type": opts.contentType,
|
|
600
|
+
..."contentLength" in opts ? { "content-length": String(opts.contentLength) } : {},
|
|
601
|
+
...opts.ifNoneMatch === void 0 ? {} : { "if-none-match": opts.ifNoneMatch }
|
|
602
|
+
};
|
|
603
|
+
const signed = await client.sign(
|
|
604
|
+
new Request(target.toString(), { method: "PUT", headers }),
|
|
605
|
+
{ aws: { signQuery: true, allHeaders: true } }
|
|
606
|
+
);
|
|
607
|
+
return { method: "PUT", url: signed.url, headers };
|
|
608
|
+
}
|
|
543
609
|
|
|
544
610
|
// ../app/src/providers/s3Object.ts
|
|
545
611
|
var META_HEADER_PREFIX = "x-amz-meta-";
|
|
@@ -576,12 +642,19 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
576
642
|
service: "s3",
|
|
577
643
|
region: cfg.region ?? "auto"
|
|
578
644
|
});
|
|
645
|
+
const putClient = new AwsClient({
|
|
646
|
+
accessKeyId: cfg.accessKeyId,
|
|
647
|
+
secretAccessKey: cfg.secretAccessKey,
|
|
648
|
+
service: "s3",
|
|
649
|
+
region: cfg.region ?? "auto",
|
|
650
|
+
retries: 0
|
|
651
|
+
});
|
|
579
652
|
const base = cfg.endpoint.replace(/\/+$/, "");
|
|
580
653
|
const bucketUrl = `${base}/${cfg.bucket}`;
|
|
581
654
|
const urlFor = (key) => `${bucketUrl}/${encodeObjectKey(key)}`;
|
|
582
|
-
const
|
|
655
|
+
const s3FetchWith = async (aws, url2, init) => {
|
|
583
656
|
try {
|
|
584
|
-
return await
|
|
657
|
+
return await aws.fetch(url2, init);
|
|
585
658
|
} catch (err) {
|
|
586
659
|
throw normalizeUpstreamError({
|
|
587
660
|
kind: "network",
|
|
@@ -589,6 +662,47 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
589
662
|
});
|
|
590
663
|
}
|
|
591
664
|
};
|
|
665
|
+
const s3Fetch = async (url2, init) => {
|
|
666
|
+
return await s3FetchWith(client, url2, init);
|
|
667
|
+
};
|
|
668
|
+
const fetchBody = (body) => {
|
|
669
|
+
if (typeof body === "string" || body instanceof ArrayBuffer) return body;
|
|
670
|
+
if (body instanceof Uint8Array) {
|
|
671
|
+
return body.buffer instanceof ArrayBuffer ? new Uint8Array(body.buffer, body.byteOffset, body.byteLength) : new Uint8Array(body);
|
|
672
|
+
}
|
|
673
|
+
const source = body;
|
|
674
|
+
const reader = source.getReader();
|
|
675
|
+
return new ReadableStream({
|
|
676
|
+
async pull(controller) {
|
|
677
|
+
try {
|
|
678
|
+
const chunk = await reader.read();
|
|
679
|
+
if (chunk.done) {
|
|
680
|
+
reader.releaseLock();
|
|
681
|
+
controller.close();
|
|
682
|
+
} else if (chunk.value !== void 0) {
|
|
683
|
+
controller.enqueue(chunk.value);
|
|
684
|
+
}
|
|
685
|
+
} catch (error) {
|
|
686
|
+
try {
|
|
687
|
+
reader.releaseLock();
|
|
688
|
+
} catch {
|
|
689
|
+
}
|
|
690
|
+
controller.error(error);
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
async cancel(reason) {
|
|
694
|
+
try {
|
|
695
|
+
if (reader.cancel !== void 0) await reader.cancel(reason);
|
|
696
|
+
else await source.cancel?.(reason);
|
|
697
|
+
} finally {
|
|
698
|
+
try {
|
|
699
|
+
reader.releaseLock();
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}, { highWaterMark: 0 });
|
|
705
|
+
};
|
|
592
706
|
const metaFromHeaders = (key, headers) => {
|
|
593
707
|
const metadata = {};
|
|
594
708
|
headers.forEach((value, name) => {
|
|
@@ -607,13 +721,14 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
607
721
|
if (contentType !== null) meta.contentType = contentType;
|
|
608
722
|
return meta;
|
|
609
723
|
};
|
|
724
|
+
const headObject = async (key) => {
|
|
725
|
+
const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
|
|
726
|
+
if (resp.status === 404) return null;
|
|
727
|
+
if (!resp.ok) throw s3Error("head", resp.status);
|
|
728
|
+
return metaFromHeaders(key, resp.headers);
|
|
729
|
+
};
|
|
610
730
|
return {
|
|
611
|
-
|
|
612
|
-
const resp = await s3Fetch(urlFor(key), { method: "HEAD" });
|
|
613
|
-
if (resp.status === 404) return null;
|
|
614
|
-
if (!resp.ok) throw s3Error("head", resp.status);
|
|
615
|
-
return metaFromHeaders(key, resp.headers);
|
|
616
|
-
},
|
|
731
|
+
head: headObject,
|
|
617
732
|
async get(key) {
|
|
618
733
|
const resp = await s3Fetch(urlFor(key), { method: "GET" });
|
|
619
734
|
if (resp.status === 404) {
|
|
@@ -627,27 +742,31 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
627
742
|
return { meta: metaFromHeaders(key, resp.headers), body: resp.body ?? EMPTY_STREAM };
|
|
628
743
|
},
|
|
629
744
|
async put(key, body, putOpts) {
|
|
630
|
-
const bytes = await objectBodyToBytes(body);
|
|
631
745
|
const headers = {};
|
|
746
|
+
headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
|
|
632
747
|
if (putOpts?.contentType !== void 0) headers["content-type"] = putOpts.contentType;
|
|
633
748
|
for (const [name, value] of Object.entries(putOpts?.metadata ?? {})) {
|
|
634
749
|
headers[`${META_HEADER_PREFIX}${name}`] = value;
|
|
635
750
|
}
|
|
636
751
|
if (putOpts?.ifMatchEtag !== void 0) headers["if-match"] = `"${putOpts.ifMatchEtag}"`;
|
|
637
|
-
|
|
752
|
+
if (putOpts?.ifNoneMatch !== void 0) headers["if-none-match"] = putOpts.ifNoneMatch;
|
|
753
|
+
const resp = await s3FetchWith(putClient, urlFor(key), {
|
|
754
|
+
method: "PUT",
|
|
755
|
+
headers,
|
|
756
|
+
body: fetchBody(body)
|
|
757
|
+
});
|
|
638
758
|
await resp.body?.cancel();
|
|
639
|
-
|
|
640
|
-
|
|
759
|
+
const conditional = putOpts?.ifMatchEtag !== void 0 || putOpts?.ifNoneMatch !== void 0;
|
|
760
|
+
if (conditional && (resp.status === 409 || resp.status === 412) || putOpts?.ifMatchEtag !== void 0 && resp.status === 404) {
|
|
761
|
+
throw new TBError("conflict", `\u5BF9\u8C61\u6761\u4EF6\u5199\u51B2\u7A81:'${key}'`);
|
|
641
762
|
}
|
|
642
763
|
if (!resp.ok) throw s3Error("put", resp.status);
|
|
643
|
-
const meta =
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
};
|
|
650
|
-
if (putOpts?.contentType !== void 0) meta.contentType = putOpts.contentType;
|
|
764
|
+
const meta = await headObject(key);
|
|
765
|
+
if (meta === null) {
|
|
766
|
+
throw new TBError("unavailable", "s3 put succeeded but HEAD did not observe the object", {
|
|
767
|
+
retryable: true
|
|
768
|
+
});
|
|
769
|
+
}
|
|
651
770
|
return meta;
|
|
652
771
|
},
|
|
653
772
|
async delete(key) {
|
|
@@ -701,6 +820,12 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
701
820
|
},
|
|
702
821
|
presign(key, ttlSec) {
|
|
703
822
|
return presignS3Url(client, urlFor(key), ttlSec);
|
|
823
|
+
},
|
|
824
|
+
presignPut(key, ttlSec, putOpts) {
|
|
825
|
+
return presignS3Put(client, urlFor(key), ttlSec, putOpts);
|
|
826
|
+
},
|
|
827
|
+
presignPutExact(key, ttlSec, putOpts) {
|
|
828
|
+
return presignS3Put(client, urlFor(key), ttlSec, putOpts);
|
|
704
829
|
}
|
|
705
830
|
};
|
|
706
831
|
}
|
|
@@ -1000,6 +1125,461 @@ var SearchSynchronizer = class {
|
|
|
1000
1125
|
// ../app/src/deps.ts
|
|
1001
1126
|
var TOOL_CACHE_TTL_DEFAULT = 300;
|
|
1002
1127
|
|
|
1128
|
+
// ../app/src/storeRefToken.ts
|
|
1129
|
+
async function hmacKey(secret) {
|
|
1130
|
+
const digest = await crypto.subtle.digest(
|
|
1131
|
+
"SHA-256",
|
|
1132
|
+
new TextEncoder().encode(`tb-store-read-ref:v1:${secret}`)
|
|
1133
|
+
);
|
|
1134
|
+
return crypto.subtle.importKey("raw", digest, { name: "HMAC", hash: "SHA-256" }, false, [
|
|
1135
|
+
"sign",
|
|
1136
|
+
"verify"
|
|
1137
|
+
]);
|
|
1138
|
+
}
|
|
1139
|
+
async function signStoreRefToken(payload, secret) {
|
|
1140
|
+
const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
|
|
1141
|
+
const mac = await crypto.subtle.sign(
|
|
1142
|
+
"HMAC",
|
|
1143
|
+
await hmacKey(secret),
|
|
1144
|
+
new TextEncoder().encode(body)
|
|
1145
|
+
);
|
|
1146
|
+
return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
|
|
1147
|
+
}
|
|
1148
|
+
async function verifyStoreRefToken(token, secret) {
|
|
1149
|
+
const dot = token.indexOf(".");
|
|
1150
|
+
if (dot <= 0) return null;
|
|
1151
|
+
const body = token.slice(0, dot);
|
|
1152
|
+
let signature;
|
|
1153
|
+
try {
|
|
1154
|
+
signature = base64urlDecode(token.slice(dot + 1));
|
|
1155
|
+
} catch {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
const valid = await crypto.subtle.verify(
|
|
1159
|
+
"HMAC",
|
|
1160
|
+
await hmacKey(secret),
|
|
1161
|
+
signature,
|
|
1162
|
+
new TextEncoder().encode(body)
|
|
1163
|
+
);
|
|
1164
|
+
if (!valid) return null;
|
|
1165
|
+
try {
|
|
1166
|
+
const payload = JSON.parse(
|
|
1167
|
+
new TextDecoder().decode(base64urlDecode(body))
|
|
1168
|
+
);
|
|
1169
|
+
if (payload.v !== 1 || typeof payload.objectId !== "string" || payload.objectId.length < 1 || typeof payload.exp !== "number" || !Number.isSafeInteger(payload.exp)) return null;
|
|
1170
|
+
return payload;
|
|
1171
|
+
} catch {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// ../app/src/store.ts
|
|
1177
|
+
var STORE_CALL_CAPABILITY_HEADER = "x-tb-store-capability";
|
|
1178
|
+
var STORE_UPLOAD_HEADER = "x-tb-store-upload";
|
|
1179
|
+
var KEY_STORE_TOKEN_SECRET = "sys:store-token-secret:v1";
|
|
1180
|
+
var KEY_STORE_CLEANUP_PROGRESS = "sys:store-cleanup-progress:v1";
|
|
1181
|
+
var STORE_READ_TTL_SEC_DEFAULT = 15 * 60;
|
|
1182
|
+
var STORE_CALL_MAX_OBJECTS_DEFAULT = 4;
|
|
1183
|
+
var STORE_CALL_MAX_OBJECT_BYTES_DEFAULT = 256 * 1024 * 1024;
|
|
1184
|
+
var STORE_CALL_MAX_BYTES_DEFAULT = 512 * 1024 * 1024;
|
|
1185
|
+
function positiveInt(value, fallback, field) {
|
|
1186
|
+
const actual = value ?? fallback;
|
|
1187
|
+
if (!Number.isSafeInteger(actual) || actual < 1) {
|
|
1188
|
+
throw new TBError("invalid_argument", `${field} must be a positive integer`);
|
|
1189
|
+
}
|
|
1190
|
+
return actual;
|
|
1191
|
+
}
|
|
1192
|
+
function randomSecret() {
|
|
1193
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
1194
|
+
let out = "";
|
|
1195
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
1196
|
+
return out;
|
|
1197
|
+
}
|
|
1198
|
+
async function derivedTokenSecret(root) {
|
|
1199
|
+
const digest = await crypto.subtle.digest(
|
|
1200
|
+
"SHA-256",
|
|
1201
|
+
new TextEncoder().encode(`tb-store-capability-root:v1:${root}`)
|
|
1202
|
+
);
|
|
1203
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1204
|
+
}
|
|
1205
|
+
async function storeTokenSecret(state, configured) {
|
|
1206
|
+
if (configured !== void 0) {
|
|
1207
|
+
if (configured.length < 16) {
|
|
1208
|
+
throw new TBError("invalid_argument", "storeTokenSecret must contain at least 16 characters");
|
|
1209
|
+
}
|
|
1210
|
+
return await derivedTokenSecret(configured);
|
|
1211
|
+
}
|
|
1212
|
+
const current = await state.get(KEY_STORE_TOKEN_SECRET);
|
|
1213
|
+
if (typeof current === "string" && current.length >= 16) return current;
|
|
1214
|
+
if (current !== null) {
|
|
1215
|
+
throw new TBError("internal", "default Store token secret state is invalid", {
|
|
1216
|
+
retryable: false
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
if (state.compareAndSwap === void 0) {
|
|
1220
|
+
throw new TBError("unavailable", "default Store requires StateStore.compareAndSwap", {
|
|
1221
|
+
retryable: false
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
const candidate = randomSecret();
|
|
1225
|
+
if (await state.compareAndSwap(KEY_STORE_TOKEN_SECRET, null, candidate)) return candidate;
|
|
1226
|
+
const winner = await state.get(KEY_STORE_TOKEN_SECRET);
|
|
1227
|
+
if (typeof winner !== "string" || winner.length < 16) {
|
|
1228
|
+
throw new TBError("unavailable", "default Store token secret initialization conflicted", {
|
|
1229
|
+
retryable: true
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
return winner;
|
|
1233
|
+
}
|
|
1234
|
+
function serviceOptions(deps, tokenSecret) {
|
|
1235
|
+
return {
|
|
1236
|
+
tokenSecret,
|
|
1237
|
+
...deps.storeMaxObjectBytes === void 0 ? {} : { maxObjectBytes: deps.storeMaxObjectBytes },
|
|
1238
|
+
...deps.storeRelayMaxBytes === void 0 ? {} : { relayMaxBytes: deps.storeRelayMaxBytes },
|
|
1239
|
+
...deps.storeUploadTtlSec === void 0 ? {} : { uploadTtlSec: deps.storeUploadTtlSec },
|
|
1240
|
+
...deps.storeShareTtlSec === void 0 ? {} : { shareTtlSec: deps.storeShareTtlSec }
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
async function defaultStoreRuntime(deps) {
|
|
1244
|
+
if (deps.objects === void 0) {
|
|
1245
|
+
throw new TBError("unavailable", "default Store object driver is not configured", {
|
|
1246
|
+
retryable: false
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
const [objects, tokenSecret] = await Promise.all([
|
|
1250
|
+
deps.objects(),
|
|
1251
|
+
storeTokenSecret(deps.state, deps.storeTokenSecret ?? deps.encryptionKey)
|
|
1252
|
+
]);
|
|
1253
|
+
return {
|
|
1254
|
+
objects,
|
|
1255
|
+
tokenSecret,
|
|
1256
|
+
service: new StoreService(deps.state, objects, serviceOptions(deps, tokenSecret))
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
function requestOrigin(runtime, deps) {
|
|
1260
|
+
const raw = deps.canonicalOrigin ?? runtime?.requestOrigin;
|
|
1261
|
+
if (raw === void 0) {
|
|
1262
|
+
throw new TBError("unavailable", "request origin is unavailable", { retryable: false });
|
|
1263
|
+
}
|
|
1264
|
+
let url2;
|
|
1265
|
+
try {
|
|
1266
|
+
url2 = new URL(raw);
|
|
1267
|
+
} catch {
|
|
1268
|
+
throw new TBError("unavailable", "request origin is invalid", { retryable: false });
|
|
1269
|
+
}
|
|
1270
|
+
if (url2.protocol !== "https:" && url2.protocol !== "http:" || url2.username !== "" || url2.password !== "") {
|
|
1271
|
+
throw new TBError("unavailable", "request origin is invalid", { retryable: false });
|
|
1272
|
+
}
|
|
1273
|
+
return url2.origin;
|
|
1274
|
+
}
|
|
1275
|
+
function storeUploadGrant(start, origin2) {
|
|
1276
|
+
const useRelay = start.alreadyCompleted || start.signedRequest === void 0;
|
|
1277
|
+
const url2 = useRelay ? `${origin2}/~store/uploads/${encodeURIComponent(start.uploadId)}` : start.signedRequest.url;
|
|
1278
|
+
const headers = useRelay ? { [STORE_UPLOAD_HEADER]: start.uploadToken } : start.signedRequest.headers;
|
|
1279
|
+
return {
|
|
1280
|
+
uploadId: start.uploadId,
|
|
1281
|
+
objectUri: start.objectUri,
|
|
1282
|
+
transport: useRelay ? "relay" : "presigned-put",
|
|
1283
|
+
method: "PUT",
|
|
1284
|
+
url: url2,
|
|
1285
|
+
headers,
|
|
1286
|
+
expiresAt: start.expiresAt,
|
|
1287
|
+
maxBytes: start.maxBytes,
|
|
1288
|
+
uploadToken: start.uploadToken,
|
|
1289
|
+
...start.alreadyCompleted && start.descriptor !== void 0 ? { alreadyCompleted: true, descriptor: start.descriptor } : {}
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function readTtlSec(deps) {
|
|
1293
|
+
return positiveInt(
|
|
1294
|
+
deps.storeReadTtlSec,
|
|
1295
|
+
deps.refTtlSec ?? STORE_READ_TTL_SEC_DEFAULT,
|
|
1296
|
+
"storeReadTtlSec"
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
async function ownerReadGrant(object2, runtime, deps, tokenSecret) {
|
|
1300
|
+
if (object2.status !== "ready" || object2.size === void 0) {
|
|
1301
|
+
throw TBError.notFound("Store object not found");
|
|
1302
|
+
}
|
|
1303
|
+
const ttlSec = readTtlSec(deps);
|
|
1304
|
+
const expiresAt = new Date(Date.now() + ttlSec * 1e3).toISOString();
|
|
1305
|
+
const token = await signStoreRefToken({
|
|
1306
|
+
v: 1,
|
|
1307
|
+
objectId: object2.id,
|
|
1308
|
+
exp: Math.floor(Date.parse(expiresAt) / 1e3)
|
|
1309
|
+
}, tokenSecret);
|
|
1310
|
+
return {
|
|
1311
|
+
$ref: `${requestOrigin(runtime, deps)}/~store/refs/${encodeURIComponent(token)}`,
|
|
1312
|
+
uri: storeUri(object2.id),
|
|
1313
|
+
contentType: object2.contentType,
|
|
1314
|
+
size: object2.size,
|
|
1315
|
+
expiresAt
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function shareReadGrant(result, runtime, deps) {
|
|
1319
|
+
return {
|
|
1320
|
+
shareId: result.shareId,
|
|
1321
|
+
uri: result.uri,
|
|
1322
|
+
expiresAt: result.expiresAt,
|
|
1323
|
+
$ref: `${requestOrigin(runtime, deps)}/~store/shares/${encodeURIComponent(result.token)}`
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function defaultStoreModuleDeps(deps, store) {
|
|
1327
|
+
return {
|
|
1328
|
+
service: store.service,
|
|
1329
|
+
callbacks: {
|
|
1330
|
+
createUpload: (start, runtime) => storeUploadGrant(start, requestOrigin(runtime, deps)),
|
|
1331
|
+
read: async (object2, runtime) => await ownerReadGrant(
|
|
1332
|
+
object2,
|
|
1333
|
+
runtime,
|
|
1334
|
+
deps,
|
|
1335
|
+
store.tokenSecret
|
|
1336
|
+
),
|
|
1337
|
+
share: (result, runtime) => shareReadGrant(result, runtime, deps)
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
function lazyDefaultStoreModule(deps) {
|
|
1342
|
+
const helpOnly = createStoreModule({
|
|
1343
|
+
// createStoreModule.help is static and never reads these placeholders.
|
|
1344
|
+
service: void 0,
|
|
1345
|
+
callbacks: {
|
|
1346
|
+
createUpload: () => {
|
|
1347
|
+
throw new TBError("unavailable", "default Store is unavailable");
|
|
1348
|
+
},
|
|
1349
|
+
read: () => {
|
|
1350
|
+
throw new TBError("unavailable", "default Store is unavailable");
|
|
1351
|
+
},
|
|
1352
|
+
share: () => {
|
|
1353
|
+
throw new TBError("unavailable", "default Store is unavailable");
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
return {
|
|
1358
|
+
module: helpOnly.module,
|
|
1359
|
+
description: helpOnly.description,
|
|
1360
|
+
help: (path) => helpOnly.help(path),
|
|
1361
|
+
async dispatch(cmd, args, ctx, runtime) {
|
|
1362
|
+
const store = await defaultStoreRuntime(deps);
|
|
1363
|
+
return await createStoreModule(defaultStoreModuleDeps(deps, store)).dispatch(
|
|
1364
|
+
cmd,
|
|
1365
|
+
args,
|
|
1366
|
+
ctx,
|
|
1367
|
+
runtime
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
async function beginCapabilityUpload(deps, input, capabilityToken, origin2) {
|
|
1373
|
+
const store = await defaultStoreRuntime(deps);
|
|
1374
|
+
const start = await store.service.beginCallUpload(input, capabilityToken);
|
|
1375
|
+
return storeUploadGrant(start, origin2);
|
|
1376
|
+
}
|
|
1377
|
+
async function completeCapabilityUpload(deps, uploadId2, uploadToken) {
|
|
1378
|
+
return await (await defaultStoreRuntime(deps)).service.completeUploadWithToken(
|
|
1379
|
+
uploadId2,
|
|
1380
|
+
uploadToken
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
async function abortCapabilityUpload(deps, uploadId2, uploadToken) {
|
|
1384
|
+
return await (await defaultStoreRuntime(deps)).service.abortUploadWithToken(
|
|
1385
|
+
uploadId2,
|
|
1386
|
+
uploadToken
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
async function relayStoreUpload(deps, uploadId2, uploadToken, body) {
|
|
1390
|
+
const store = await defaultStoreRuntime(deps);
|
|
1391
|
+
const session = await store.service.verifyUploadToken(uploadToken);
|
|
1392
|
+
if (session.id !== uploadId2) {
|
|
1393
|
+
throw new TBError("permission_denied", "upload capability does not match the URL");
|
|
1394
|
+
}
|
|
1395
|
+
return await store.service.commitRelayUpload({
|
|
1396
|
+
uploadToken,
|
|
1397
|
+
body: body ?? new Uint8Array()
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
async function storeObjectResponse(objects, object2) {
|
|
1401
|
+
const got = await objects.get(object2.driverKey);
|
|
1402
|
+
if (got === null) throw TBError.notFound("not found");
|
|
1403
|
+
const reader = got.body.getReader();
|
|
1404
|
+
const body = new ReadableStream({
|
|
1405
|
+
async pull(controller) {
|
|
1406
|
+
try {
|
|
1407
|
+
const chunk = await reader.read();
|
|
1408
|
+
if (chunk.done) {
|
|
1409
|
+
reader.releaseLock();
|
|
1410
|
+
controller.close();
|
|
1411
|
+
} else if (chunk.value !== void 0) {
|
|
1412
|
+
controller.enqueue(chunk.value);
|
|
1413
|
+
}
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
try {
|
|
1416
|
+
reader.releaseLock();
|
|
1417
|
+
} catch {
|
|
1418
|
+
}
|
|
1419
|
+
controller.error(error);
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
async cancel(reason) {
|
|
1423
|
+
try {
|
|
1424
|
+
if (reader.cancel !== void 0) await reader.cancel(reason);
|
|
1425
|
+
else await got.body.cancel?.(reason);
|
|
1426
|
+
} finally {
|
|
1427
|
+
try {
|
|
1428
|
+
reader.releaseLock();
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
return new Response(body, {
|
|
1435
|
+
headers: {
|
|
1436
|
+
"content-type": object2.contentType,
|
|
1437
|
+
"content-length": String(got.meta.size),
|
|
1438
|
+
"cache-control": "private, no-store"
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
async function issueDeviceCallUpload(deps, deviceId, callId, context) {
|
|
1443
|
+
if (deps.objects === void 0) return null;
|
|
1444
|
+
const store = await defaultStoreRuntime(deps);
|
|
1445
|
+
const maxObjects = positiveInt(
|
|
1446
|
+
deps.storeCallMaxObjects,
|
|
1447
|
+
STORE_CALL_MAX_OBJECTS_DEFAULT,
|
|
1448
|
+
"storeCallMaxObjects"
|
|
1449
|
+
);
|
|
1450
|
+
const maxBytes = positiveInt(
|
|
1451
|
+
deps.storeCallMaxBytes,
|
|
1452
|
+
STORE_CALL_MAX_BYTES_DEFAULT,
|
|
1453
|
+
"storeCallMaxBytes"
|
|
1454
|
+
);
|
|
1455
|
+
const maxObjectBytes = Math.min(
|
|
1456
|
+
positiveInt(
|
|
1457
|
+
deps.storeCallMaxObjectBytes,
|
|
1458
|
+
deps.storeMaxObjectBytes ?? STORE_CALL_MAX_OBJECT_BYTES_DEFAULT,
|
|
1459
|
+
"storeCallMaxObjectBytes"
|
|
1460
|
+
),
|
|
1461
|
+
maxBytes
|
|
1462
|
+
);
|
|
1463
|
+
const issued = await store.service.issueCallUploadCapability({
|
|
1464
|
+
callId,
|
|
1465
|
+
owner: context.caller.owner,
|
|
1466
|
+
producer: `device:${deviceId}`,
|
|
1467
|
+
expiresAt: context.expiresAt,
|
|
1468
|
+
maxObjects,
|
|
1469
|
+
maxBytes,
|
|
1470
|
+
maxObjectBytes,
|
|
1471
|
+
allowedContentTypes: deps.storeCallAllowedContentTypes ?? ["*/*"]
|
|
1472
|
+
});
|
|
1473
|
+
return {
|
|
1474
|
+
context: {
|
|
1475
|
+
...context,
|
|
1476
|
+
upload: {
|
|
1477
|
+
token: issued.token,
|
|
1478
|
+
expiresAt: issued.capability.expiresAt,
|
|
1479
|
+
// The SDK uses this as an early per-object check; the server remains
|
|
1480
|
+
// authoritative for the aggregate call budget.
|
|
1481
|
+
maxBytes: issued.capability.maxObjectBytes,
|
|
1482
|
+
maxObjects: issued.capability.maxObjects
|
|
1483
|
+
}
|
|
1484
|
+
},
|
|
1485
|
+
revoke: async () => await store.service.revokeCallUploadCapability(issued.token)
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function cleanupProgress(value) {
|
|
1489
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
1490
|
+
throw new TBError("internal", "default Store cleanup progress is invalid");
|
|
1491
|
+
}
|
|
1492
|
+
const record2 = value;
|
|
1493
|
+
if (!Number.isSafeInteger(record2.revision) || record2.revision < 1) {
|
|
1494
|
+
throw new TBError("internal", "default Store cleanup progress is invalid");
|
|
1495
|
+
}
|
|
1496
|
+
const cursors = record2.cursors;
|
|
1497
|
+
if (cursors === null || typeof cursors !== "object" || Array.isArray(cursors)) {
|
|
1498
|
+
throw new TBError("internal", "default Store cleanup progress is invalid");
|
|
1499
|
+
}
|
|
1500
|
+
for (const field of [
|
|
1501
|
+
"uploads",
|
|
1502
|
+
"objects",
|
|
1503
|
+
"shares",
|
|
1504
|
+
"callCapabilities",
|
|
1505
|
+
"driverObjects",
|
|
1506
|
+
"idempotencyBindings"
|
|
1507
|
+
]) {
|
|
1508
|
+
const cursor = cursors[field];
|
|
1509
|
+
if (cursor !== null && typeof cursor !== "string") {
|
|
1510
|
+
throw new TBError("internal", "default Store cleanup progress is invalid");
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return value;
|
|
1514
|
+
}
|
|
1515
|
+
function emptyCleanupResult() {
|
|
1516
|
+
return {
|
|
1517
|
+
abandonedObjects: 0,
|
|
1518
|
+
deletedBytes: 0,
|
|
1519
|
+
deletedOrphans: 0,
|
|
1520
|
+
deletedStaging: 0,
|
|
1521
|
+
expiredCallCapabilities: 0,
|
|
1522
|
+
expiredIdempotencyBindings: 0,
|
|
1523
|
+
expiredShares: 0,
|
|
1524
|
+
expiredUploads: 0
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
function addCleanupResult(target, page) {
|
|
1528
|
+
target.abandonedObjects += page.abandonedObjects;
|
|
1529
|
+
target.deletedBytes += page.deletedBytes;
|
|
1530
|
+
target.deletedOrphans += page.deletedOrphans;
|
|
1531
|
+
target.deletedStaging += page.deletedStaging;
|
|
1532
|
+
target.expiredCallCapabilities += page.expiredCallCapabilities;
|
|
1533
|
+
target.expiredIdempotencyBindings += page.expiredIdempotencyBindings;
|
|
1534
|
+
target.expiredShares += page.expiredShares;
|
|
1535
|
+
target.expiredUploads += page.expiredUploads;
|
|
1536
|
+
}
|
|
1537
|
+
async function cleanupDefaultStore(deps, opts = {}) {
|
|
1538
|
+
await deps.ensureReady?.();
|
|
1539
|
+
const store = await defaultStoreRuntime(deps);
|
|
1540
|
+
const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
|
|
1541
|
+
const limit = opts.limit === void 0 ? void 0 : positiveInt(opts.limit, 200, "limit");
|
|
1542
|
+
const rawProgress = await deps.state.get(KEY_STORE_CLEANUP_PROGRESS);
|
|
1543
|
+
let progress = rawProgress === null ? null : cleanupProgress(rawProgress);
|
|
1544
|
+
let cursors = progress?.cursors;
|
|
1545
|
+
const aggregate = emptyCleanupResult();
|
|
1546
|
+
for (let pageNumber = 0; pageNumber < maxPages; pageNumber++) {
|
|
1547
|
+
const page = await store.service.cleanup({
|
|
1548
|
+
...limit === void 0 ? {} : { limit },
|
|
1549
|
+
...cursors === void 0 ? {} : { cursors },
|
|
1550
|
+
// staging 不使用权威状态 cursor;同一 host tick 只运行一次,避免
|
|
1551
|
+
// maxPages 内重复扫描同一 driver maintenance 范围。
|
|
1552
|
+
runDriverMaintenance: pageNumber === 0
|
|
1553
|
+
});
|
|
1554
|
+
addCleanupResult(aggregate, page);
|
|
1555
|
+
if (page.cursors === void 0) {
|
|
1556
|
+
if (progress !== null) {
|
|
1557
|
+
await deps.state.compareAndSwap(
|
|
1558
|
+
KEY_STORE_CLEANUP_PROGRESS,
|
|
1559
|
+
progress.revision,
|
|
1560
|
+
null
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
return aggregate;
|
|
1564
|
+
}
|
|
1565
|
+
const next = {
|
|
1566
|
+
cursors: page.cursors,
|
|
1567
|
+
revision: (progress?.revision ?? 0) + 1
|
|
1568
|
+
};
|
|
1569
|
+
const advanced = await deps.state.compareAndSwap(
|
|
1570
|
+
KEY_STORE_CLEANUP_PROGRESS,
|
|
1571
|
+
progress?.revision ?? null,
|
|
1572
|
+
next
|
|
1573
|
+
);
|
|
1574
|
+
if (!advanced) {
|
|
1575
|
+
return aggregate;
|
|
1576
|
+
}
|
|
1577
|
+
progress = next;
|
|
1578
|
+
cursors = next.cursors;
|
|
1579
|
+
}
|
|
1580
|
+
return { ...aggregate, ...cursors === void 0 ? {} : { cursors } };
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1003
1583
|
// ../app/src/deviceNodes.ts
|
|
1004
1584
|
function tbErrorFromBody(body) {
|
|
1005
1585
|
return new TBError(body.code, body.message, { retryable: body.retryable });
|
|
@@ -1012,7 +1592,19 @@ function requireDevice(deps) {
|
|
|
1012
1592
|
}
|
|
1013
1593
|
async function invokeDevice(deps, deviceId, req) {
|
|
1014
1594
|
const id = crypto.randomUUID();
|
|
1015
|
-
const
|
|
1595
|
+
const upload = req.context === void 0 ? null : await issueDeviceCallUpload(deps, deviceId, id, req.context);
|
|
1596
|
+
let body;
|
|
1597
|
+
try {
|
|
1598
|
+
body = await requireDevice(deps).invoke(deviceId, {
|
|
1599
|
+
id,
|
|
1600
|
+
path: req.path,
|
|
1601
|
+
arguments: req.arguments,
|
|
1602
|
+
...req.context === void 0 ? {} : { context: upload?.context ?? req.context }
|
|
1603
|
+
});
|
|
1604
|
+
} finally {
|
|
1605
|
+
await upload?.revoke().catch(() => {
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1016
1608
|
if (!body || !("ok" in body)) {
|
|
1017
1609
|
throw new TBError("unavailable", "device session returned invalid result");
|
|
1018
1610
|
}
|
|
@@ -7129,9 +7721,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7129
7721
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
7130
7722
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
7131
7723
|
}
|
|
7132
|
-
optimizeNames(names,
|
|
7724
|
+
optimizeNames(names, constants2) {
|
|
7133
7725
|
if (!names[this.name.str]) return;
|
|
7134
|
-
if (this.rhs) this.rhs = optimizeExpr(this.rhs, names,
|
|
7726
|
+
if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
7135
7727
|
return this;
|
|
7136
7728
|
}
|
|
7137
7729
|
get names() {
|
|
@@ -7148,9 +7740,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7148
7740
|
render({ _n }) {
|
|
7149
7741
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
7150
7742
|
}
|
|
7151
|
-
optimizeNames(names,
|
|
7743
|
+
optimizeNames(names, constants2) {
|
|
7152
7744
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
|
|
7153
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
7745
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
7154
7746
|
return this;
|
|
7155
7747
|
}
|
|
7156
7748
|
get names() {
|
|
@@ -7209,8 +7801,8 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7209
7801
|
optimizeNodes() {
|
|
7210
7802
|
return `${this.code}` ? this : void 0;
|
|
7211
7803
|
}
|
|
7212
|
-
optimizeNames(names,
|
|
7213
|
-
this.code = optimizeExpr(this.code, names,
|
|
7804
|
+
optimizeNames(names, constants2) {
|
|
7805
|
+
this.code = optimizeExpr(this.code, names, constants2);
|
|
7214
7806
|
return this;
|
|
7215
7807
|
}
|
|
7216
7808
|
get names() {
|
|
@@ -7236,12 +7828,12 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7236
7828
|
}
|
|
7237
7829
|
return nodes.length > 0 ? this : void 0;
|
|
7238
7830
|
}
|
|
7239
|
-
optimizeNames(names,
|
|
7831
|
+
optimizeNames(names, constants2) {
|
|
7240
7832
|
const { nodes } = this;
|
|
7241
7833
|
let i = nodes.length;
|
|
7242
7834
|
while (i--) {
|
|
7243
7835
|
const n = nodes[i];
|
|
7244
|
-
if (n.optimizeNames(names,
|
|
7836
|
+
if (n.optimizeNames(names, constants2)) continue;
|
|
7245
7837
|
subtractNames(names, n.names);
|
|
7246
7838
|
nodes.splice(i, 1);
|
|
7247
7839
|
}
|
|
@@ -7288,11 +7880,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7288
7880
|
if (cond === false || !this.nodes.length) return void 0;
|
|
7289
7881
|
return this;
|
|
7290
7882
|
}
|
|
7291
|
-
optimizeNames(names,
|
|
7883
|
+
optimizeNames(names, constants2) {
|
|
7292
7884
|
var _a;
|
|
7293
|
-
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
7294
|
-
if (!(super.optimizeNames(names,
|
|
7295
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
7885
|
+
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
|
|
7886
|
+
if (!(super.optimizeNames(names, constants2) || this.else)) return;
|
|
7887
|
+
this.condition = optimizeExpr(this.condition, names, constants2);
|
|
7296
7888
|
return this;
|
|
7297
7889
|
}
|
|
7298
7890
|
get names() {
|
|
@@ -7314,9 +7906,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7314
7906
|
render(opts) {
|
|
7315
7907
|
return `for(${this.iteration})` + super.render(opts);
|
|
7316
7908
|
}
|
|
7317
|
-
optimizeNames(names,
|
|
7318
|
-
if (!super.optimizeNames(names,
|
|
7319
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
7909
|
+
optimizeNames(names, constants2) {
|
|
7910
|
+
if (!super.optimizeNames(names, constants2)) return;
|
|
7911
|
+
this.iteration = optimizeExpr(this.iteration, names, constants2);
|
|
7320
7912
|
return this;
|
|
7321
7913
|
}
|
|
7322
7914
|
get names() {
|
|
@@ -7351,9 +7943,9 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7351
7943
|
render(opts) {
|
|
7352
7944
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
7353
7945
|
}
|
|
7354
|
-
optimizeNames(names,
|
|
7355
|
-
if (!super.optimizeNames(names,
|
|
7356
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
7946
|
+
optimizeNames(names, constants2) {
|
|
7947
|
+
if (!super.optimizeNames(names, constants2)) return;
|
|
7948
|
+
this.iterable = optimizeExpr(this.iterable, names, constants2);
|
|
7357
7949
|
return this;
|
|
7358
7950
|
}
|
|
7359
7951
|
get names() {
|
|
@@ -7392,11 +7984,11 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7392
7984
|
(_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
|
|
7393
7985
|
return this;
|
|
7394
7986
|
}
|
|
7395
|
-
optimizeNames(names,
|
|
7987
|
+
optimizeNames(names, constants2) {
|
|
7396
7988
|
var _a, _b;
|
|
7397
|
-
super.optimizeNames(names,
|
|
7398
|
-
(_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names,
|
|
7399
|
-
(_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names,
|
|
7989
|
+
super.optimizeNames(names, constants2);
|
|
7990
|
+
(_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants2);
|
|
7991
|
+
(_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
|
|
7400
7992
|
return this;
|
|
7401
7993
|
}
|
|
7402
7994
|
get names() {
|
|
@@ -7645,7 +8237,7 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7645
8237
|
function addExprNames(names, from) {
|
|
7646
8238
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
7647
8239
|
}
|
|
7648
|
-
function optimizeExpr(expr, names,
|
|
8240
|
+
function optimizeExpr(expr, names, constants2) {
|
|
7649
8241
|
if (expr instanceof code_1.Name) return replaceName(expr);
|
|
7650
8242
|
if (!canOptimize(expr)) return expr;
|
|
7651
8243
|
return new code_1._Code(expr._items.reduce((items, c) => {
|
|
@@ -7655,13 +8247,13 @@ var require_codegen = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
7655
8247
|
return items;
|
|
7656
8248
|
}, []));
|
|
7657
8249
|
function replaceName(n) {
|
|
7658
|
-
const c =
|
|
8250
|
+
const c = constants2[n.str];
|
|
7659
8251
|
if (c === void 0 || names[n.str] !== 1) return n;
|
|
7660
8252
|
delete names[n.str];
|
|
7661
8253
|
return c;
|
|
7662
8254
|
}
|
|
7663
8255
|
function canOptimize(e) {
|
|
7664
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
8256
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
|
|
7665
8257
|
}
|
|
7666
8258
|
}
|
|
7667
8259
|
function subtractNames(names, from) {
|
|
@@ -18696,8 +19288,8 @@ var GatewayMcpOAuthProvider = class {
|
|
|
18696
19288
|
}
|
|
18697
19289
|
get redirectUrl() {
|
|
18698
19290
|
if (this.opts.redirectUri !== void 0) return this.opts.redirectUri;
|
|
18699
|
-
const
|
|
18700
|
-
return `${
|
|
19291
|
+
const origin2 = this.opts.origin ?? "https://tool-bridge.invalid";
|
|
19292
|
+
return `${origin2}${OAUTH_CALLBACK_PATH}`;
|
|
18701
19293
|
}
|
|
18702
19294
|
get clientMetadata() {
|
|
18703
19295
|
return {
|
|
@@ -19579,7 +20171,7 @@ async function assertToolConfig(config, deps, ctx, mountPath) {
|
|
|
19579
20171
|
}
|
|
19580
20172
|
|
|
19581
20173
|
// ../app/src/refToken.ts
|
|
19582
|
-
async function
|
|
20174
|
+
async function hmacKey2(secret) {
|
|
19583
20175
|
const digest = await crypto.subtle.digest(
|
|
19584
20176
|
"SHA-256",
|
|
19585
20177
|
new TextEncoder().encode(`tb-ref-token:${secret}`)
|
|
@@ -19593,7 +20185,7 @@ async function signRefToken(payload, secret) {
|
|
|
19593
20185
|
const body = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
|
|
19594
20186
|
const mac = await crypto.subtle.sign(
|
|
19595
20187
|
"HMAC",
|
|
19596
|
-
await
|
|
20188
|
+
await hmacKey2(secret),
|
|
19597
20189
|
new TextEncoder().encode(body)
|
|
19598
20190
|
);
|
|
19599
20191
|
return `${body}.${base64urlEncode(new Uint8Array(mac))}`;
|
|
@@ -19610,7 +20202,7 @@ async function verifyRefToken(token, secret) {
|
|
|
19610
20202
|
}
|
|
19611
20203
|
const ok = await crypto.subtle.verify(
|
|
19612
20204
|
"HMAC",
|
|
19613
|
-
await
|
|
20205
|
+
await hmacKey2(secret),
|
|
19614
20206
|
// Node 的 WebCrypto 类型要求 Uint8Array<ArrayBuffer>;base64urlDecode 的产物满足但类型面是 ArrayBufferLike。
|
|
19615
20207
|
sig,
|
|
19616
20208
|
new TextEncoder().encode(body)
|
|
@@ -19692,6 +20284,24 @@ async function contextObjectStoreFor(cfg, deps) {
|
|
|
19692
20284
|
}
|
|
19693
20285
|
throw TBError.unimplemented(`context provider '${cfg.provider}' not implemented yet`);
|
|
19694
20286
|
}
|
|
20287
|
+
async function contextDirectUploadAvailable(cfg, deps) {
|
|
20288
|
+
if (cfg.provider === "s3") return true;
|
|
20289
|
+
try {
|
|
20290
|
+
return (await contextObjectStoreFor(cfg, deps)).presignPut !== void 0;
|
|
20291
|
+
} catch {
|
|
20292
|
+
return false;
|
|
20293
|
+
}
|
|
20294
|
+
}
|
|
20295
|
+
async function createContextUploadGrant(node, cfg, deps, input) {
|
|
20296
|
+
const objects = await contextObjectStoreFor(cfg, deps);
|
|
20297
|
+
const uploadGrantTtlSec = deps.uploadGrantTtlSec ?? Math.min(deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT, PRESIGN_TTL_SEC_DEFAULT);
|
|
20298
|
+
return createObjectContextUploadGrant(objects, {
|
|
20299
|
+
nsPath: node.path,
|
|
20300
|
+
keyPrefix: contextKeyPrefix(cfg, node.path),
|
|
20301
|
+
readOnly: cfg.readOnly ?? false,
|
|
20302
|
+
uploadGrantTtlSec
|
|
20303
|
+
}, input);
|
|
20304
|
+
}
|
|
19695
20305
|
async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
19696
20306
|
const objects = await contextObjectStoreFor(cfg, deps);
|
|
19697
20307
|
const opts = {
|
|
@@ -19703,11 +20313,11 @@ async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
|
19703
20313
|
if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
|
|
19704
20314
|
const encKey = deps.encryptionKey;
|
|
19705
20315
|
if (encKey !== void 0) {
|
|
19706
|
-
const
|
|
20316
|
+
const origin2 = new URL(requestUrl).origin;
|
|
19707
20317
|
const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
|
|
19708
20318
|
opts.relayRefUrl = async (key) => {
|
|
19709
20319
|
const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
|
|
19710
|
-
return `${
|
|
20320
|
+
return `${origin2}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
19711
20321
|
};
|
|
19712
20322
|
}
|
|
19713
20323
|
return createObjectContextProvider(objects, opts);
|
|
@@ -19728,11 +20338,11 @@ async function skillhubProviderFor(node, cfg, deps, requestUrl) {
|
|
|
19728
20338
|
if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
|
|
19729
20339
|
const encKey = deps.encryptionKey;
|
|
19730
20340
|
if (encKey !== void 0) {
|
|
19731
|
-
const
|
|
20341
|
+
const origin2 = new URL(requestUrl).origin;
|
|
19732
20342
|
const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
|
|
19733
20343
|
opts.relayRefUrl = async (key) => {
|
|
19734
20344
|
const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
|
|
19735
|
-
return `${
|
|
20345
|
+
return `${origin2}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
19736
20346
|
};
|
|
19737
20347
|
}
|
|
19738
20348
|
return createSkillhubProvider(objects, opts);
|
|
@@ -23235,8 +23845,8 @@ var LADDER_ERROR_HTTP_STATUS2 = {
|
|
|
23235
23845
|
[ProtocolErrorCode2.MissingRequiredClientCapability]: 400,
|
|
23236
23846
|
[HEADER_MISMATCH_ERROR_CODE2]: 400
|
|
23237
23847
|
};
|
|
23238
|
-
function httpStatusForErrorCode(code,
|
|
23239
|
-
if (
|
|
23848
|
+
function httpStatusForErrorCode(code, origin2) {
|
|
23849
|
+
if (origin2 === "in-band") return code === ProtocolErrorCode2.MissingRequiredClientCapability ? 400 : 200;
|
|
23240
23850
|
return LADDER_ERROR_HTTP_STATUS2[code] ?? 400;
|
|
23241
23851
|
}
|
|
23242
23852
|
function rejection(rung, cell, httpStatus, error, settled) {
|
|
@@ -25288,9 +25898,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25288
25898
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
25289
25899
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
25290
25900
|
}
|
|
25291
|
-
optimizeNames(names,
|
|
25901
|
+
optimizeNames(names, constants2) {
|
|
25292
25902
|
if (!names[this.name.str]) return;
|
|
25293
|
-
if (this.rhs) this.rhs = optimizeExpr(this.rhs, names,
|
|
25903
|
+
if (this.rhs) this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
25294
25904
|
return this;
|
|
25295
25905
|
}
|
|
25296
25906
|
get names() {
|
|
@@ -25307,9 +25917,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25307
25917
|
render({ _n }) {
|
|
25308
25918
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
25309
25919
|
}
|
|
25310
|
-
optimizeNames(names,
|
|
25920
|
+
optimizeNames(names, constants2) {
|
|
25311
25921
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects) return;
|
|
25312
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
25922
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
25313
25923
|
return this;
|
|
25314
25924
|
}
|
|
25315
25925
|
get names() {
|
|
@@ -25368,8 +25978,8 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25368
25978
|
optimizeNodes() {
|
|
25369
25979
|
return `${this.code}` ? this : void 0;
|
|
25370
25980
|
}
|
|
25371
|
-
optimizeNames(names,
|
|
25372
|
-
this.code = optimizeExpr(this.code, names,
|
|
25981
|
+
optimizeNames(names, constants2) {
|
|
25982
|
+
this.code = optimizeExpr(this.code, names, constants2);
|
|
25373
25983
|
return this;
|
|
25374
25984
|
}
|
|
25375
25985
|
get names() {
|
|
@@ -25395,12 +26005,12 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25395
26005
|
}
|
|
25396
26006
|
return nodes.length > 0 ? this : void 0;
|
|
25397
26007
|
}
|
|
25398
|
-
optimizeNames(names,
|
|
26008
|
+
optimizeNames(names, constants2) {
|
|
25399
26009
|
const { nodes } = this;
|
|
25400
26010
|
let i = nodes.length;
|
|
25401
26011
|
while (i--) {
|
|
25402
26012
|
const n = nodes[i];
|
|
25403
|
-
if (n.optimizeNames(names,
|
|
26013
|
+
if (n.optimizeNames(names, constants2)) continue;
|
|
25404
26014
|
subtractNames(names, n.names);
|
|
25405
26015
|
nodes.splice(i, 1);
|
|
25406
26016
|
}
|
|
@@ -25447,11 +26057,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25447
26057
|
if (cond === false || !this.nodes.length) return void 0;
|
|
25448
26058
|
return this;
|
|
25449
26059
|
}
|
|
25450
|
-
optimizeNames(names,
|
|
26060
|
+
optimizeNames(names, constants2) {
|
|
25451
26061
|
var _a;
|
|
25452
|
-
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
25453
|
-
if (!(super.optimizeNames(names,
|
|
25454
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
26062
|
+
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
|
|
26063
|
+
if (!(super.optimizeNames(names, constants2) || this.else)) return;
|
|
26064
|
+
this.condition = optimizeExpr(this.condition, names, constants2);
|
|
25455
26065
|
return this;
|
|
25456
26066
|
}
|
|
25457
26067
|
get names() {
|
|
@@ -25473,9 +26083,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25473
26083
|
render(opts) {
|
|
25474
26084
|
return `for(${this.iteration})` + super.render(opts);
|
|
25475
26085
|
}
|
|
25476
|
-
optimizeNames(names,
|
|
25477
|
-
if (!super.optimizeNames(names,
|
|
25478
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
26086
|
+
optimizeNames(names, constants2) {
|
|
26087
|
+
if (!super.optimizeNames(names, constants2)) return;
|
|
26088
|
+
this.iteration = optimizeExpr(this.iteration, names, constants2);
|
|
25479
26089
|
return this;
|
|
25480
26090
|
}
|
|
25481
26091
|
get names() {
|
|
@@ -25510,9 +26120,9 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25510
26120
|
render(opts) {
|
|
25511
26121
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
25512
26122
|
}
|
|
25513
|
-
optimizeNames(names,
|
|
25514
|
-
if (!super.optimizeNames(names,
|
|
25515
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
26123
|
+
optimizeNames(names, constants2) {
|
|
26124
|
+
if (!super.optimizeNames(names, constants2)) return;
|
|
26125
|
+
this.iterable = optimizeExpr(this.iterable, names, constants2);
|
|
25516
26126
|
return this;
|
|
25517
26127
|
}
|
|
25518
26128
|
get names() {
|
|
@@ -25551,11 +26161,11 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25551
26161
|
(_b = this.finally) === null || _b === void 0 || _b.optimizeNodes();
|
|
25552
26162
|
return this;
|
|
25553
26163
|
}
|
|
25554
|
-
optimizeNames(names,
|
|
26164
|
+
optimizeNames(names, constants2) {
|
|
25555
26165
|
var _a, _b;
|
|
25556
|
-
super.optimizeNames(names,
|
|
25557
|
-
(_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names,
|
|
25558
|
-
(_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names,
|
|
26166
|
+
super.optimizeNames(names, constants2);
|
|
26167
|
+
(_a = this.catch) === null || _a === void 0 || _a.optimizeNames(names, constants2);
|
|
26168
|
+
(_b = this.finally) === null || _b === void 0 || _b.optimizeNames(names, constants2);
|
|
25559
26169
|
return this;
|
|
25560
26170
|
}
|
|
25561
26171
|
get names() {
|
|
@@ -25804,7 +26414,7 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25804
26414
|
function addExprNames(names, from) {
|
|
25805
26415
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
25806
26416
|
}
|
|
25807
|
-
function optimizeExpr(expr, names,
|
|
26417
|
+
function optimizeExpr(expr, names, constants2) {
|
|
25808
26418
|
if (expr instanceof code_1.Name) return replaceName(expr);
|
|
25809
26419
|
if (!canOptimize(expr)) return expr;
|
|
25810
26420
|
return new code_1._Code(expr._items.reduce((items, c) => {
|
|
@@ -25814,13 +26424,13 @@ var require_codegen2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
25814
26424
|
return items;
|
|
25815
26425
|
}, []));
|
|
25816
26426
|
function replaceName(n) {
|
|
25817
|
-
const c =
|
|
26427
|
+
const c = constants2[n.str];
|
|
25818
26428
|
if (c === void 0 || names[n.str] !== 1) return n;
|
|
25819
26429
|
delete names[n.str];
|
|
25820
26430
|
return c;
|
|
25821
26431
|
}
|
|
25822
26432
|
function canOptimize(e) {
|
|
25823
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
26433
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
|
|
25824
26434
|
}
|
|
25825
26435
|
}
|
|
25826
26436
|
function subtractNames(names, from) {
|
|
@@ -32044,9 +32654,9 @@ function createListenRouter(options) {
|
|
|
32044
32654
|
const { bus, onerror } = options;
|
|
32045
32655
|
const maxSubscriptions = options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS;
|
|
32046
32656
|
const keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
32047
|
-
const
|
|
32657
|
+
const open2 = /* @__PURE__ */ new Set();
|
|
32048
32658
|
function serve2(message, signal, capabilities, serverInfo) {
|
|
32049
|
-
if (
|
|
32659
|
+
if (open2.size >= maxSubscriptions) {
|
|
32050
32660
|
onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
|
|
32051
32661
|
return jsonRpcError(message.id, -32603, "Subscription limit reached");
|
|
32052
32662
|
}
|
|
@@ -32102,7 +32712,7 @@ data: ${JSON.stringify({
|
|
|
32102
32712
|
}
|
|
32103
32713
|
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
32104
32714
|
abortCleanup?.();
|
|
32105
|
-
|
|
32715
|
+
open2.delete(teardown);
|
|
32106
32716
|
try {
|
|
32107
32717
|
controller.close();
|
|
32108
32718
|
} catch {
|
|
@@ -32122,7 +32732,7 @@ data: ${JSON.stringify({
|
|
|
32122
32732
|
writeNotification(note.method, note.params);
|
|
32123
32733
|
});
|
|
32124
32734
|
keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
|
|
32125
|
-
|
|
32735
|
+
open2.add(teardown);
|
|
32126
32736
|
},
|
|
32127
32737
|
cancel() {
|
|
32128
32738
|
teardown(false);
|
|
@@ -32147,10 +32757,10 @@ data: ${JSON.stringify({
|
|
|
32147
32757
|
return {
|
|
32148
32758
|
serve: serve2,
|
|
32149
32759
|
closeAll() {
|
|
32150
|
-
for (const teardown of
|
|
32760
|
+
for (const teardown of open2) teardown(true);
|
|
32151
32761
|
},
|
|
32152
32762
|
get openCount() {
|
|
32153
|
-
return
|
|
32763
|
+
return open2.size;
|
|
32154
32764
|
}
|
|
32155
32765
|
};
|
|
32156
32766
|
}
|
|
@@ -36473,7 +37083,10 @@ async function handleDescribe(c, env) {
|
|
|
36473
37083
|
await assertContextAlive(node, node.config, registry);
|
|
36474
37084
|
const cfg = node.config;
|
|
36475
37085
|
const local = cfg.provider !== "r2" && cfg.provider !== "s3" ? localContext(deps, node) : null;
|
|
36476
|
-
|
|
37086
|
+
let capabilities = cfg.provider === "r2" || cfg.provider === "s3" || cfg.provider === "device-fs" || deviceMarkerOf(cfg.providerConfig) !== null ? CONTEXT_CAPABILITIES : local !== null ? localCapabilities(local) : (await requirePluginExport(deps, cfg.provider, "context", "context", cfg.export)).export.capabilities ?? [];
|
|
37087
|
+
if ((cfg.provider === "r2" || cfg.provider === "s3") && cfg.readOnly !== true && await contextDirectUploadAvailable(cfg, deps)) {
|
|
37088
|
+
capabilities = [...capabilities, CONTEXT_DIRECT_UPLOAD_CAPABILITY];
|
|
37089
|
+
}
|
|
36477
37090
|
return new Response(JSON.stringify({ kind: "context", capabilities }), {
|
|
36478
37091
|
headers: { "content-type": contentTypeFor("json") }
|
|
36479
37092
|
});
|
|
@@ -36488,6 +37101,147 @@ async function handleDescribe(c, env) {
|
|
|
36488
37101
|
throw TBError.notFound(`no capabilities for kind '${node.kind}'`);
|
|
36489
37102
|
}
|
|
36490
37103
|
|
|
37104
|
+
// ../app/src/routes/store.ts
|
|
37105
|
+
var SECRET_BODY_FIELDS = [
|
|
37106
|
+
"callCapability",
|
|
37107
|
+
"capability",
|
|
37108
|
+
"uploadToken",
|
|
37109
|
+
"shareToken"
|
|
37110
|
+
];
|
|
37111
|
+
function hideCapabilityFailure(error) {
|
|
37112
|
+
if (isTBError(error) && error.code !== "permission_denied" && error.code !== "not_found") throw error;
|
|
37113
|
+
throw TBError.notFound("not found");
|
|
37114
|
+
}
|
|
37115
|
+
async function jsonObject(c) {
|
|
37116
|
+
const parsed = await c.req.json().catch(() => null);
|
|
37117
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
37118
|
+
throw new TBError("invalid_argument", "body must be a JSON object");
|
|
37119
|
+
}
|
|
37120
|
+
const body = parsed;
|
|
37121
|
+
for (const field of SECRET_BODY_FIELDS) {
|
|
37122
|
+
if (field in body) {
|
|
37123
|
+
throw new TBError(
|
|
37124
|
+
"invalid_argument",
|
|
37125
|
+
`${field} is not accepted in the request body; use the dedicated capability header`
|
|
37126
|
+
);
|
|
37127
|
+
}
|
|
37128
|
+
}
|
|
37129
|
+
return body;
|
|
37130
|
+
}
|
|
37131
|
+
function requiredHeader(c, name) {
|
|
37132
|
+
const value = c.req.header(name);
|
|
37133
|
+
if (value === void 0 || value.trim() === "") {
|
|
37134
|
+
throw new TBError("permission_denied", "Store capability is missing or invalid");
|
|
37135
|
+
}
|
|
37136
|
+
return value;
|
|
37137
|
+
}
|
|
37138
|
+
function uploadId(body) {
|
|
37139
|
+
if (typeof body.uploadId !== "string" || body.uploadId.trim() === "") {
|
|
37140
|
+
throw new TBError("invalid_argument", "uploadId must be a non-empty string");
|
|
37141
|
+
}
|
|
37142
|
+
return body.uploadId;
|
|
37143
|
+
}
|
|
37144
|
+
function origin(c, canonicalOrigin) {
|
|
37145
|
+
let url2;
|
|
37146
|
+
try {
|
|
37147
|
+
url2 = new URL(canonicalOrigin ?? c.req.url);
|
|
37148
|
+
} catch {
|
|
37149
|
+
throw new TBError("unavailable", "request origin is invalid", { retryable: false });
|
|
37150
|
+
}
|
|
37151
|
+
if (url2.protocol !== "https:" && url2.protocol !== "http:" || url2.username !== "" || url2.password !== "") throw new TBError("unavailable", "request origin is invalid", { retryable: false });
|
|
37152
|
+
return url2.origin;
|
|
37153
|
+
}
|
|
37154
|
+
function registerStoreCapabilityRoutes(app, deps) {
|
|
37155
|
+
app.post("/system/store/create_upload", async (c, next) => {
|
|
37156
|
+
if (c.req.header(STORE_CALL_CAPABILITY_HEADER) === void 0) return await next();
|
|
37157
|
+
return await runHandler(async () => {
|
|
37158
|
+
await deps.ensureReady?.();
|
|
37159
|
+
const body = await jsonObject(c);
|
|
37160
|
+
const capability = requiredHeader(c, STORE_CALL_CAPABILITY_HEADER);
|
|
37161
|
+
const result = await beginCapabilityUpload(
|
|
37162
|
+
deps,
|
|
37163
|
+
body,
|
|
37164
|
+
capability,
|
|
37165
|
+
origin(c, deps.canonicalOrigin)
|
|
37166
|
+
);
|
|
37167
|
+
return c.json(result);
|
|
37168
|
+
});
|
|
37169
|
+
});
|
|
37170
|
+
for (const command of ["complete_upload", "abort_upload"]) {
|
|
37171
|
+
app.post(`/system/store/${command}`, async (c, next) => {
|
|
37172
|
+
if (c.req.header(STORE_UPLOAD_HEADER) === void 0) return await next();
|
|
37173
|
+
return await runHandler(async () => {
|
|
37174
|
+
await deps.ensureReady?.();
|
|
37175
|
+
const body = await jsonObject(c);
|
|
37176
|
+
const token = requiredHeader(c, STORE_UPLOAD_HEADER);
|
|
37177
|
+
const result = command === "complete_upload" ? await completeCapabilityUpload(deps, uploadId(body), token) : await abortCapabilityUpload(deps, uploadId(body), token);
|
|
37178
|
+
return c.json(result);
|
|
37179
|
+
});
|
|
37180
|
+
});
|
|
37181
|
+
}
|
|
37182
|
+
app.put(
|
|
37183
|
+
"/~store/uploads/:uploadId",
|
|
37184
|
+
(c) => runHandler(async () => {
|
|
37185
|
+
await deps.ensureReady?.();
|
|
37186
|
+
const descriptor = await relayStoreUpload(
|
|
37187
|
+
deps,
|
|
37188
|
+
c.req.param("uploadId"),
|
|
37189
|
+
requiredHeader(c, STORE_UPLOAD_HEADER),
|
|
37190
|
+
c.req.raw.body
|
|
37191
|
+
);
|
|
37192
|
+
return c.json(descriptor);
|
|
37193
|
+
})
|
|
37194
|
+
);
|
|
37195
|
+
app.get(
|
|
37196
|
+
"/~store/refs/:token",
|
|
37197
|
+
(c) => runHandler(async () => {
|
|
37198
|
+
await deps.ensureReady?.();
|
|
37199
|
+
const store = await defaultStoreRuntime(deps);
|
|
37200
|
+
const payload = await verifyStoreRefToken(c.req.param("token"), store.tokenSecret);
|
|
37201
|
+
if (payload === null || payload.exp * 1e3 <= Date.now()) {
|
|
37202
|
+
throw TBError.notFound("not found");
|
|
37203
|
+
}
|
|
37204
|
+
let object2;
|
|
37205
|
+
try {
|
|
37206
|
+
object2 = await store.service.authorizeRead(storeUri(payload.objectId), {
|
|
37207
|
+
admin: true
|
|
37208
|
+
});
|
|
37209
|
+
} catch (error) {
|
|
37210
|
+
hideCapabilityFailure(error);
|
|
37211
|
+
}
|
|
37212
|
+
return await storeObjectResponse(store.objects, object2);
|
|
37213
|
+
})
|
|
37214
|
+
);
|
|
37215
|
+
app.get(
|
|
37216
|
+
"/~store/shares/:token",
|
|
37217
|
+
(c) => runHandler(async () => {
|
|
37218
|
+
await deps.ensureReady?.();
|
|
37219
|
+
const token = c.req.param("token");
|
|
37220
|
+
const store = await defaultStoreRuntime(deps);
|
|
37221
|
+
let object2;
|
|
37222
|
+
try {
|
|
37223
|
+
const grant = await store.service.verifyShareToken(token);
|
|
37224
|
+
const uri3 = storeUri(grant.objectId);
|
|
37225
|
+
object2 = await store.service.authorizeSharedRead(uri3, token);
|
|
37226
|
+
} catch (error) {
|
|
37227
|
+
hideCapabilityFailure(error);
|
|
37228
|
+
}
|
|
37229
|
+
return await storeObjectResponse(store.objects, object2);
|
|
37230
|
+
})
|
|
37231
|
+
);
|
|
37232
|
+
}
|
|
37233
|
+
function rejectStoreCapabilityBodyFields(module, args) {
|
|
37234
|
+
if (module !== "store") return;
|
|
37235
|
+
for (const field of SECRET_BODY_FIELDS) {
|
|
37236
|
+
if (field in args) {
|
|
37237
|
+
throw new TBError(
|
|
37238
|
+
"invalid_argument",
|
|
37239
|
+
`${field} is not accepted in the request body; use the dedicated capability header`
|
|
37240
|
+
);
|
|
37241
|
+
}
|
|
37242
|
+
}
|
|
37243
|
+
}
|
|
37244
|
+
|
|
36491
37245
|
// ../app/src/routes/publicRoutes.ts
|
|
36492
37246
|
async function finishToolAuthorization(opts) {
|
|
36493
37247
|
const config = opts.node.config;
|
|
@@ -36857,7 +37611,7 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
36857
37611
|
readOnly: (node.config.readOnly ?? false) || isReadOnlyProvider(local)
|
|
36858
37612
|
});
|
|
36859
37613
|
}
|
|
36860
|
-
const
|
|
37614
|
+
const model2 = contextHelpModel(node, { readOnly: node.config.readOnly ?? false });
|
|
36861
37615
|
const { export: exported } = await requirePluginExport(
|
|
36862
37616
|
deps,
|
|
36863
37617
|
node.config.provider,
|
|
@@ -36872,9 +37626,13 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
36872
37626
|
"update",
|
|
36873
37627
|
...optionalMethodsForCapabilities(exported.capabilities ?? [])
|
|
36874
37628
|
]);
|
|
36875
|
-
return { ...
|
|
37629
|
+
return { ...model2, cmds: model2.cmds.filter((c) => declared.has(c.name)) };
|
|
36876
37630
|
}
|
|
36877
|
-
|
|
37631
|
+
const model = contextHelpModel(node, { readOnly: node.config.readOnly ?? false });
|
|
37632
|
+
if (node.config.readOnly !== true && opts.includeDirectUpload !== false && await contextDirectUploadAvailable(node.config, deps)) {
|
|
37633
|
+
return { ...model, cmds: [...model.cmds, contextUploadCmd(node.path)] };
|
|
37634
|
+
}
|
|
37635
|
+
return model;
|
|
36878
37636
|
}
|
|
36879
37637
|
if (node.kind === "skillhub" && node.config?.kind === "skillhub") {
|
|
36880
37638
|
await assertContextAlive(node, node.config, registry);
|
|
@@ -37135,6 +37893,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
37135
37893
|
continue;
|
|
37136
37894
|
}
|
|
37137
37895
|
const model = await helpModelFor(node, registry, ctx, builtinsOf(c.get("store")), deps, {
|
|
37896
|
+
includeDirectUpload: check(ctx, node.path, "write").allow,
|
|
37138
37897
|
refresh: false,
|
|
37139
37898
|
now
|
|
37140
37899
|
});
|
|
@@ -37332,7 +38091,8 @@ async function handleInvoke(c, env) {
|
|
|
37332
38091
|
await assertContextAlive(node, cfg, registry);
|
|
37333
38092
|
const args2 = await readInvokeBody();
|
|
37334
38093
|
const scope = contextScopeForCmd(command);
|
|
37335
|
-
|
|
38094
|
+
const directUpload = command === "create_upload";
|
|
38095
|
+
if (scope === null || directUpload && cfg.provider !== "r2" && cfg.provider !== "s3") {
|
|
37336
38096
|
throw new TBError("invalid_argument", `unknown cmd '${command}' on '${node.path}'`);
|
|
37337
38097
|
}
|
|
37338
38098
|
if (!check(ctx, node.path, scope).allow) {
|
|
@@ -37341,6 +38101,15 @@ async function handleInvoke(c, env) {
|
|
|
37341
38101
|
if (cfg.readOnly === true && scope === "write") {
|
|
37342
38102
|
throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
|
|
37343
38103
|
}
|
|
38104
|
+
if (directUpload) {
|
|
38105
|
+
const result3 = await createContextUploadGrant(
|
|
38106
|
+
node,
|
|
38107
|
+
cfg,
|
|
38108
|
+
deps,
|
|
38109
|
+
args2
|
|
38110
|
+
);
|
|
38111
|
+
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
38112
|
+
}
|
|
37344
38113
|
if (cfg.provider === "device-fs") {
|
|
37345
38114
|
const result3 = await invokeDevice(deps, deviceIdForDeviceFs(cfg), {
|
|
37346
38115
|
path: `fs/${command}`,
|
|
@@ -37414,6 +38183,7 @@ async function handleInvoke(c, env) {
|
|
|
37414
38183
|
if (!mod) throw TBError.unimplemented(`builtin module '${node.config.module}' not available`);
|
|
37415
38184
|
const args = await readInvokeBody();
|
|
37416
38185
|
const cmd = command;
|
|
38186
|
+
rejectStoreCapabilityBodyFields(node.config.module, args);
|
|
37417
38187
|
const spec = scopeForCmd(mod, node.path, cmd);
|
|
37418
38188
|
if (!spec) throw new TBError("invalid_argument", `unknown cmd '${cmd}' on '${node.path}'`);
|
|
37419
38189
|
if (!check(ctx, node.path, spec.scope).allow) {
|
|
@@ -37457,7 +38227,9 @@ async function handleInvoke(c, env) {
|
|
|
37457
38227
|
const registryMarker = registryTarget === void 0 ? void 0 : await searchSync?.markNode(registryTarget);
|
|
37458
38228
|
let result;
|
|
37459
38229
|
try {
|
|
37460
|
-
result = await mod.dispatch(cmd, args, ctx
|
|
38230
|
+
result = await mod.dispatch(cmd, args, ctx, {
|
|
38231
|
+
requestOrigin: deps.canonicalOrigin ?? new URL(c.req.url).origin
|
|
38232
|
+
});
|
|
37461
38233
|
} catch (error) {
|
|
37462
38234
|
await searchSync?.abort(registryMarker);
|
|
37463
38235
|
await Promise.all(pluginMounts.map(async (mount) => await searchSync?.abort(mount.marker)));
|
|
@@ -37508,17 +38280,21 @@ async function handleInvoke(c, env) {
|
|
|
37508
38280
|
// ../app/src/routes/env.ts
|
|
37509
38281
|
function createRouteEnv(deps) {
|
|
37510
38282
|
const searchSync = isMutableSearchIndex(deps.search) ? new SearchSynchronizer(deps.state, deps.search) : void 0;
|
|
37511
|
-
const builtinsOf = (store) =>
|
|
37512
|
-
|
|
37513
|
-
|
|
37514
|
-
|
|
37515
|
-
|
|
37516
|
-
|
|
37517
|
-
|
|
37518
|
-
|
|
37519
|
-
|
|
37520
|
-
|
|
37521
|
-
|
|
38283
|
+
const builtinsOf = (store) => {
|
|
38284
|
+
const builtins = createBuiltins(
|
|
38285
|
+
buildDeps({
|
|
38286
|
+
store,
|
|
38287
|
+
secrets: deps.secrets,
|
|
38288
|
+
version: deps.version,
|
|
38289
|
+
allowInsecureHttp: deps.allowInsecureHttp,
|
|
38290
|
+
remoteAllowlistBase: deps.remote.allowlist,
|
|
38291
|
+
...deps.pluginBindings !== void 0 ? { pluginBindings: deps.pluginBindings } : {},
|
|
38292
|
+
...deps.pluginCatalog !== void 0 ? { pluginCatalog: deps.pluginCatalog } : {}
|
|
38293
|
+
})
|
|
38294
|
+
);
|
|
38295
|
+
builtins.set("store", lazyDefaultStoreModule(deps));
|
|
38296
|
+
return builtins;
|
|
38297
|
+
};
|
|
37522
38298
|
const globalSearchCapabilities = () => {
|
|
37523
38299
|
const declared = new Set(deps.search?.capabilities ?? []);
|
|
37524
38300
|
if (!declared.has("search")) return [];
|
|
@@ -37659,6 +38435,7 @@ function createTbApp(deps) {
|
|
|
37659
38435
|
if (secured !== response) c.res = secured;
|
|
37660
38436
|
});
|
|
37661
38437
|
registerPublicRoutes(app, env);
|
|
38438
|
+
registerStoreCapabilityRoutes(app, deps);
|
|
37662
38439
|
app.use("*", async (c, next) => {
|
|
37663
38440
|
const store = deps.state;
|
|
37664
38441
|
try {
|
|
@@ -38185,6 +38962,19 @@ var DeviceRouter = class _DeviceRouter {
|
|
|
38185
38962
|
}
|
|
38186
38963
|
};
|
|
38187
38964
|
|
|
38965
|
+
// src/objects.ts
|
|
38966
|
+
import {
|
|
38967
|
+
link,
|
|
38968
|
+
mkdir as mkdir2,
|
|
38969
|
+
open,
|
|
38970
|
+
readdir as readdir2,
|
|
38971
|
+
realpath as realpath2,
|
|
38972
|
+
rename,
|
|
38973
|
+
rm as rm2,
|
|
38974
|
+
stat as stat2
|
|
38975
|
+
} from "fs/promises";
|
|
38976
|
+
import { constants, mkdirSync } from "fs";
|
|
38977
|
+
|
|
38188
38978
|
// ../core/src/node/fsObjectStore.ts
|
|
38189
38979
|
import { mkdir, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
|
|
38190
38980
|
import { basename, dirname, join, resolve, sep } from "path";
|
|
@@ -38284,6 +39074,9 @@ var FsObjectStore = class {
|
|
|
38284
39074
|
throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':\u672A\u77E5\u6839\u6216\u7F3A\u76F8\u5BF9\u8DEF\u5F84`);
|
|
38285
39075
|
}
|
|
38286
39076
|
await this.assertInRoot(r.root, r.full, key);
|
|
39077
|
+
if (opts?.ifMatchEtag !== void 0 && opts.ifNoneMatch !== void 0) {
|
|
39078
|
+
throw new TBError("invalid_argument", "ifMatchEtag \u4E0E ifNoneMatch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
|
|
39079
|
+
}
|
|
38287
39080
|
if (opts?.ifMatchEtag !== void 0) {
|
|
38288
39081
|
const existing = await this.head(key);
|
|
38289
39082
|
if (opts.ifMatchEtag !== existing?.etag) {
|
|
@@ -38292,7 +39085,14 @@ var FsObjectStore = class {
|
|
|
38292
39085
|
}
|
|
38293
39086
|
const bytes = await objectBodyToBytes(body);
|
|
38294
39087
|
await mkdir(dirname(r.full), { recursive: true });
|
|
38295
|
-
|
|
39088
|
+
try {
|
|
39089
|
+
await writeFile(r.full, bytes, opts?.ifNoneMatch === "*" ? { flag: "wx" } : void 0);
|
|
39090
|
+
} catch (e) {
|
|
39091
|
+
if (opts?.ifNoneMatch === "*" && isErrnoCode(e, "EEXIST")) {
|
|
39092
|
+
throw new TBError("conflict", `\u5BF9\u8C61\u5DF2\u5B58\u5728:'${key}'`);
|
|
39093
|
+
}
|
|
39094
|
+
throw e;
|
|
39095
|
+
}
|
|
38296
39096
|
const st = await stat(r.full);
|
|
38297
39097
|
return this.metaOf(r.key, st);
|
|
38298
39098
|
}
|
|
@@ -38426,9 +39226,14 @@ import { spawn as nodeSpawn } from "child_process";
|
|
|
38426
39226
|
var SHELL_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
38427
39227
|
|
|
38428
39228
|
// src/objects.ts
|
|
38429
|
-
import {
|
|
38430
|
-
import {
|
|
39229
|
+
import { dirname as dirname2, join as join2, sep as sep2 } from "path";
|
|
39230
|
+
import { randomUUID } from "crypto";
|
|
39231
|
+
import { Readable } from "stream";
|
|
38431
39232
|
var INTERNAL_ROOT = "objects";
|
|
39233
|
+
var TEMP_PREFIX = ".tb-object-upload-";
|
|
39234
|
+
var STORE_KEY_PREFIX = `${DEFAULT_STORE_DRIVER_KEY_ROOT}/`;
|
|
39235
|
+
var STORE_STAGING_DIR = ".tb-store-staging-v1";
|
|
39236
|
+
var UPLOAD_TEMP_NAME_RE = /^\.tb-object-upload-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/i;
|
|
38432
39237
|
function toInternal(key) {
|
|
38433
39238
|
return `${INTERNAL_ROOT}/${key}`;
|
|
38434
39239
|
}
|
|
@@ -38438,6 +39243,108 @@ function toExternal(key) {
|
|
|
38438
39243
|
function externalMeta(meta) {
|
|
38439
39244
|
return { ...meta, key: toExternal(meta.key) };
|
|
38440
39245
|
}
|
|
39246
|
+
function isErrnoCode2(error, code) {
|
|
39247
|
+
return typeof error === "object" && error !== null && error.code === code;
|
|
39248
|
+
}
|
|
39249
|
+
function isUploadTempKey(key) {
|
|
39250
|
+
return key.split("/").some((segment) => segment.startsWith(TEMP_PREFIX));
|
|
39251
|
+
}
|
|
39252
|
+
async function writeChunk(handle, chunk) {
|
|
39253
|
+
let offset = 0;
|
|
39254
|
+
while (offset < chunk.byteLength) {
|
|
39255
|
+
const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset, null);
|
|
39256
|
+
if (bytesWritten === 0) throw new Error("\u5BF9\u8C61\u4E34\u65F6\u6587\u4EF6\u5199\u5165\u672A\u53D6\u5F97\u8FDB\u5C55");
|
|
39257
|
+
offset += bytesWritten;
|
|
39258
|
+
}
|
|
39259
|
+
}
|
|
39260
|
+
async function writeStream(handle, stream) {
|
|
39261
|
+
const reader = stream.getReader();
|
|
39262
|
+
try {
|
|
39263
|
+
for (; ; ) {
|
|
39264
|
+
const { done, value } = await reader.read();
|
|
39265
|
+
if (done) break;
|
|
39266
|
+
if (value !== void 0 && value.byteLength > 0) await writeChunk(handle, value);
|
|
39267
|
+
}
|
|
39268
|
+
} catch (error) {
|
|
39269
|
+
try {
|
|
39270
|
+
await reader.cancel?.(error);
|
|
39271
|
+
} catch {
|
|
39272
|
+
}
|
|
39273
|
+
throw error;
|
|
39274
|
+
} finally {
|
|
39275
|
+
reader.releaseLock();
|
|
39276
|
+
}
|
|
39277
|
+
}
|
|
39278
|
+
async function writeBody(handle, body) {
|
|
39279
|
+
if (typeof body === "string") {
|
|
39280
|
+
await writeChunk(handle, Buffer.from(body));
|
|
39281
|
+
return;
|
|
39282
|
+
}
|
|
39283
|
+
if (body instanceof Uint8Array) {
|
|
39284
|
+
await writeChunk(handle, body);
|
|
39285
|
+
return;
|
|
39286
|
+
}
|
|
39287
|
+
if (body instanceof ArrayBuffer) {
|
|
39288
|
+
await writeChunk(handle, new Uint8Array(body));
|
|
39289
|
+
return;
|
|
39290
|
+
}
|
|
39291
|
+
await writeStream(handle, body);
|
|
39292
|
+
}
|
|
39293
|
+
async function assertParentInRoot(root, full, key) {
|
|
39294
|
+
const rootReal = await realpath2(root);
|
|
39295
|
+
const parentReal = await realpath2(dirname2(full));
|
|
39296
|
+
if (parentReal !== rootReal && !parentReal.startsWith(rootReal + sep2)) {
|
|
39297
|
+
throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
|
|
39298
|
+
}
|
|
39299
|
+
}
|
|
39300
|
+
async function assertAncestorInRoot(root, full, key) {
|
|
39301
|
+
const rootReal = await realpath2(root);
|
|
39302
|
+
let probe = dirname2(full);
|
|
39303
|
+
for (; ; ) {
|
|
39304
|
+
try {
|
|
39305
|
+
const ancestorReal = await realpath2(probe);
|
|
39306
|
+
if (ancestorReal !== rootReal && !ancestorReal.startsWith(rootReal + sep2)) {
|
|
39307
|
+
throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
|
|
39308
|
+
}
|
|
39309
|
+
return;
|
|
39310
|
+
} catch (error) {
|
|
39311
|
+
if (error instanceof TBError) throw error;
|
|
39312
|
+
if (!isErrnoCode2(error, "ENOENT")) throw error;
|
|
39313
|
+
const parent = dirname2(probe);
|
|
39314
|
+
if (parent === probe) {
|
|
39315
|
+
throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
|
|
39316
|
+
}
|
|
39317
|
+
probe = parent;
|
|
39318
|
+
}
|
|
39319
|
+
}
|
|
39320
|
+
}
|
|
39321
|
+
async function cleanupStagingTree(dir, cutoffMs) {
|
|
39322
|
+
let entries;
|
|
39323
|
+
try {
|
|
39324
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
39325
|
+
} catch (error) {
|
|
39326
|
+
if (isErrnoCode2(error, "ENOENT")) return 0;
|
|
39327
|
+
throw error;
|
|
39328
|
+
}
|
|
39329
|
+
let deleted = 0;
|
|
39330
|
+
for (const entry of entries) {
|
|
39331
|
+
const path = join2(dir, entry.name);
|
|
39332
|
+
if (entry.isDirectory()) {
|
|
39333
|
+
deleted += await cleanupStagingTree(path, cutoffMs);
|
|
39334
|
+
continue;
|
|
39335
|
+
}
|
|
39336
|
+
if (!entry.isFile() || !UPLOAD_TEMP_NAME_RE.test(entry.name)) continue;
|
|
39337
|
+
try {
|
|
39338
|
+
const info = await stat2(path);
|
|
39339
|
+
if (info.mtimeMs > cutoffMs) continue;
|
|
39340
|
+
await rm2(path);
|
|
39341
|
+
deleted++;
|
|
39342
|
+
} catch (error) {
|
|
39343
|
+
if (!isErrnoCode2(error, "ENOENT")) throw error;
|
|
39344
|
+
}
|
|
39345
|
+
}
|
|
39346
|
+
return deleted;
|
|
39347
|
+
}
|
|
38441
39348
|
function createDataObjectStore(dataDir) {
|
|
38442
39349
|
const root = join2(dataDir, INTERNAL_ROOT);
|
|
38443
39350
|
mkdirSync(root, { recursive: true });
|
|
@@ -38448,11 +39355,77 @@ function createDataObjectStore(dataDir) {
|
|
|
38448
39355
|
return meta === null ? null : externalMeta(meta);
|
|
38449
39356
|
},
|
|
38450
39357
|
async get(key) {
|
|
38451
|
-
const
|
|
38452
|
-
|
|
39358
|
+
const normalizedKey = normalizeEntryPath(key);
|
|
39359
|
+
const meta = await fs.head(toInternal(normalizedKey));
|
|
39360
|
+
if (meta === null) return null;
|
|
39361
|
+
const finalPath = join2(root, ...normalizedKey.split("/"));
|
|
39362
|
+
let handle;
|
|
39363
|
+
try {
|
|
39364
|
+
handle = await open(finalPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
39365
|
+
} catch (error) {
|
|
39366
|
+
if (isErrnoCode2(error, "ENOENT")) return null;
|
|
39367
|
+
if (isErrnoCode2(error, "ELOOP")) {
|
|
39368
|
+
throw new TBError("invalid_argument", `\u975E\u6CD5 entry \u8DEF\u5F84 '${key}':symlink \u9003\u9038\u6839\u76EE\u5F55`);
|
|
39369
|
+
}
|
|
39370
|
+
throw error;
|
|
39371
|
+
}
|
|
39372
|
+
const body = Readable.toWeb(handle.createReadStream());
|
|
39373
|
+
return { meta: externalMeta(meta), body };
|
|
38453
39374
|
},
|
|
38454
39375
|
async put(key, body, opts) {
|
|
38455
|
-
|
|
39376
|
+
const normalizedKey = normalizeEntryPath(key);
|
|
39377
|
+
const internalKey = toInternal(normalizedKey);
|
|
39378
|
+
const finalPath = join2(root, ...normalizedKey.split("/"));
|
|
39379
|
+
const existing = await fs.head(internalKey);
|
|
39380
|
+
if (opts?.ifMatchEtag !== void 0 && opts.ifNoneMatch !== void 0) {
|
|
39381
|
+
throw new TBError("invalid_argument", "ifMatchEtag \u4E0E ifNoneMatch \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528");
|
|
39382
|
+
}
|
|
39383
|
+
if (opts?.ifMatchEtag !== void 0) {
|
|
39384
|
+
if (opts.ifMatchEtag !== existing?.etag) {
|
|
39385
|
+
throw new TBError("conflict", `etag \u4E0D\u5339\u914D:'${key}'`);
|
|
39386
|
+
}
|
|
39387
|
+
}
|
|
39388
|
+
await assertAncestorInRoot(root, finalPath, key);
|
|
39389
|
+
await mkdir2(dirname2(finalPath), { recursive: true });
|
|
39390
|
+
await assertParentInRoot(root, finalPath, key);
|
|
39391
|
+
const tempDir = normalizedKey.startsWith(STORE_KEY_PREFIX) ? join2(root, STORE_STAGING_DIR) : dirname2(finalPath);
|
|
39392
|
+
await assertAncestorInRoot(root, join2(tempDir, "staging-probe"), key);
|
|
39393
|
+
await mkdir2(tempDir, { recursive: true });
|
|
39394
|
+
const tempPath = join2(tempDir, `${TEMP_PREFIX}${randomUUID()}.tmp`);
|
|
39395
|
+
await assertParentInRoot(root, tempPath, key);
|
|
39396
|
+
let handle;
|
|
39397
|
+
try {
|
|
39398
|
+
handle = await open(tempPath, "wx");
|
|
39399
|
+
await writeBody(handle, body);
|
|
39400
|
+
await handle.sync();
|
|
39401
|
+
await handle.close();
|
|
39402
|
+
handle = void 0;
|
|
39403
|
+
if (opts?.ifNoneMatch === "*") {
|
|
39404
|
+
try {
|
|
39405
|
+
await link(tempPath, finalPath);
|
|
39406
|
+
} catch (error) {
|
|
39407
|
+
if (isErrnoCode2(error, "EEXIST")) {
|
|
39408
|
+
throw new TBError("conflict", `\u5BF9\u8C61\u5DF2\u5B58\u5728:'${key}'`);
|
|
39409
|
+
}
|
|
39410
|
+
throw error;
|
|
39411
|
+
}
|
|
39412
|
+
await rm2(tempPath);
|
|
39413
|
+
} else {
|
|
39414
|
+
await rename(tempPath, finalPath);
|
|
39415
|
+
}
|
|
39416
|
+
} catch (error) {
|
|
39417
|
+
if (handle !== void 0) {
|
|
39418
|
+
try {
|
|
39419
|
+
await handle.close();
|
|
39420
|
+
} catch {
|
|
39421
|
+
}
|
|
39422
|
+
}
|
|
39423
|
+
await rm2(tempPath, { force: true });
|
|
39424
|
+
throw error;
|
|
39425
|
+
}
|
|
39426
|
+
const meta = await fs.head(internalKey);
|
|
39427
|
+
if (meta === null) throw new TBError("unavailable", `\u5BF9\u8C61\u63D0\u4EA4\u540E\u4E0D\u53EF\u89C1:'${key}'`);
|
|
39428
|
+
return externalMeta(meta);
|
|
38456
39429
|
},
|
|
38457
39430
|
async delete(key) {
|
|
38458
39431
|
await fs.delete(toInternal(key));
|
|
@@ -38462,9 +39435,24 @@ function createDataObjectStore(dataDir) {
|
|
|
38462
39435
|
return {
|
|
38463
39436
|
items: result.items.map(
|
|
38464
39437
|
(item) => "prefix" in item ? { prefix: toExternal(item.prefix) } : externalMeta(item)
|
|
38465
|
-
),
|
|
39438
|
+
).filter((item) => !isUploadTempKey("prefix" in item ? item.prefix : item.key)),
|
|
38466
39439
|
...result.cursor !== void 0 ? { cursor: result.cursor } : {}
|
|
38467
39440
|
};
|
|
39441
|
+
},
|
|
39442
|
+
async cleanupStaging(prefix, olderThan) {
|
|
39443
|
+
const cutoffMs = Date.parse(olderThan);
|
|
39444
|
+
if (!Number.isFinite(cutoffMs)) {
|
|
39445
|
+
throw new TBError("invalid_argument", "cleanupStaging olderThan \u5FC5\u987B\u662F ISO timestamp");
|
|
39446
|
+
}
|
|
39447
|
+
const normalizedPrefix = normalizeEntryPath(prefix.replace(/\/+$/, ""));
|
|
39448
|
+
const isDefaultStore = normalizedPrefix === STORE_KEY_PREFIX.slice(0, -1);
|
|
39449
|
+
const prefixDir = isDefaultStore ? join2(root, STORE_STAGING_DIR) : join2(root, ...normalizedPrefix.split("/"));
|
|
39450
|
+
await assertAncestorInRoot(root, join2(prefixDir, "staging-probe"), prefix);
|
|
39451
|
+
if (isDefaultStore) {
|
|
39452
|
+
await mkdir2(prefixDir, { recursive: true });
|
|
39453
|
+
await assertParentInRoot(root, join2(prefixDir, "staging-probe"), prefix);
|
|
39454
|
+
}
|
|
39455
|
+
return await cleanupStagingTree(prefixDir, cutoffMs);
|
|
38468
39456
|
}
|
|
38469
39457
|
};
|
|
38470
39458
|
}
|
|
@@ -38587,6 +39575,29 @@ var PgStateStore = class {
|
|
|
38587
39575
|
ON CONFLICT (key) DO UPDATE SET value = excluded.value
|
|
38588
39576
|
`;
|
|
38589
39577
|
}
|
|
39578
|
+
async compareAndSwap(key, expectedRevision, value) {
|
|
39579
|
+
if (expectedRevision === null) {
|
|
39580
|
+
if (value === null) return false;
|
|
39581
|
+
const result2 = await this.sql`
|
|
39582
|
+
INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
|
|
39583
|
+
ON CONFLICT (key) DO NOTHING
|
|
39584
|
+
`;
|
|
39585
|
+
return result2.count > 0;
|
|
39586
|
+
}
|
|
39587
|
+
if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return false;
|
|
39588
|
+
const predicate = this.sql`
|
|
39589
|
+
key = ${key}
|
|
39590
|
+
AND jsonb_typeof(value) = 'object'
|
|
39591
|
+
AND jsonb_typeof(value -> 'revision') = 'number'
|
|
39592
|
+
AND (value ->> 'revision') ~ '^(0|[1-9][0-9]*)$'
|
|
39593
|
+
AND (value ->> 'revision')::numeric = ${expectedRevision}
|
|
39594
|
+
`;
|
|
39595
|
+
const result = value === null ? await this.sql`DELETE FROM tb_kv WHERE ${predicate}` : await this.sql`
|
|
39596
|
+
UPDATE tb_kv SET value = ${this.sql.json(value)}
|
|
39597
|
+
WHERE ${predicate}
|
|
39598
|
+
`;
|
|
39599
|
+
return result.count > 0;
|
|
39600
|
+
}
|
|
38590
39601
|
async putIfAbsent(key, value) {
|
|
38591
39602
|
const result = await this.sql`
|
|
38592
39603
|
INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
|
|
@@ -38769,6 +39780,20 @@ var SqliteStateStore = class {
|
|
|
38769
39780
|
async put(key, value) {
|
|
38770
39781
|
this.stmtPut.run(key, JSON.stringify(value));
|
|
38771
39782
|
}
|
|
39783
|
+
async compareAndSwap(key, expectedRevision, value) {
|
|
39784
|
+
if (expectedRevision === null) {
|
|
39785
|
+
if (value === null) return false;
|
|
39786
|
+
const info2 = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
39787
|
+
return info2.changes > 0;
|
|
39788
|
+
}
|
|
39789
|
+
if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return false;
|
|
39790
|
+
const predicate = `key = ?
|
|
39791
|
+
AND json_type(value) = 'object'
|
|
39792
|
+
AND json_type(value, '$.revision') = 'integer'
|
|
39793
|
+
AND json_extract(value, '$.revision') = ?`;
|
|
39794
|
+
const info = value === null ? this.db.prepare(`DELETE FROM kv WHERE ${predicate}`).run(key, expectedRevision) : this.db.prepare(`UPDATE kv SET value = ? WHERE ${predicate}`).run(JSON.stringify(value), key, expectedRevision);
|
|
39795
|
+
return info.changes > 0;
|
|
39796
|
+
}
|
|
38772
39797
|
async putIfAbsent(key, value) {
|
|
38773
39798
|
const info = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
38774
39799
|
return info.changes > 0;
|
|
@@ -38816,7 +39841,7 @@ import { join as join4 } from "path";
|
|
|
38816
39841
|
// package.json
|
|
38817
39842
|
var package_default = {
|
|
38818
39843
|
name: "@tool-bridge/server",
|
|
38819
|
-
version: "0.
|
|
39844
|
+
version: "0.16.0",
|
|
38820
39845
|
description: "tool-bridge self-hosted server: run the HTBP gateway on Node with SQLite or PostgreSQL persistence, filesystem object storage and WebSocket device channel \u2014 single container via docker run",
|
|
38821
39846
|
type: "module",
|
|
38822
39847
|
license: "MIT",
|
|
@@ -38889,7 +39914,7 @@ var package_default = {
|
|
|
38889
39914
|
|
|
38890
39915
|
// src/assets.ts
|
|
38891
39916
|
import { brotliCompressSync, gzipSync, constants as zlibConstants } from "zlib";
|
|
38892
|
-
import { dirname as
|
|
39917
|
+
import { dirname as dirname3, join as join3, resolve as resolve2, sep as sep3 } from "path";
|
|
38893
39918
|
import { readFile as readFile2 } from "fs/promises";
|
|
38894
39919
|
import { createRequire } from "module";
|
|
38895
39920
|
import { existsSync } from "fs";
|
|
@@ -38922,7 +39947,7 @@ function resolveUiDir(uiDirOverride) {
|
|
|
38922
39947
|
try {
|
|
38923
39948
|
const require2 = createRequire(import.meta.url);
|
|
38924
39949
|
const pkgPath = require2.resolve("@tool-bridge/dashboard/package.json");
|
|
38925
|
-
const dist = join3(
|
|
39950
|
+
const dist = join3(dirname3(pkgPath), "dist");
|
|
38926
39951
|
return existsSync(join3(dist, "index.html")) ? dist : void 0;
|
|
38927
39952
|
} catch {
|
|
38928
39953
|
return void 0;
|
|
@@ -38949,7 +39974,7 @@ function uiAssetsFetcher(uiDir) {
|
|
|
38949
39974
|
let pathname = decodeURIComponent(url2.pathname);
|
|
38950
39975
|
if (pathname.endsWith("/")) pathname += "index.html";
|
|
38951
39976
|
const full = resolve2(join3(root, pathname));
|
|
38952
|
-
if (full !== root && !full.startsWith(root +
|
|
39977
|
+
if (full !== root && !full.startsWith(root + sep3)) {
|
|
38953
39978
|
return new Response("not found", { status: 404 });
|
|
38954
39979
|
}
|
|
38955
39980
|
let asset = cache.get(full);
|
|
@@ -39128,8 +40153,52 @@ function createTbServer(config) {
|
|
|
39128
40153
|
if (config.toolCacheTtlSec !== void 0) deps.toolCacheTtlSec = config.toolCacheTtlSec;
|
|
39129
40154
|
if (config.refThresholdBytes !== void 0) deps.refThresholdBytes = config.refThresholdBytes;
|
|
39130
40155
|
if (config.refTtlSec !== void 0) deps.refTtlSec = config.refTtlSec;
|
|
40156
|
+
if (config.uploadGrantTtlSec !== void 0) {
|
|
40157
|
+
deps.uploadGrantTtlSec = config.uploadGrantTtlSec;
|
|
40158
|
+
}
|
|
40159
|
+
if (config.storeTokenSecret !== void 0) deps.storeTokenSecret = config.storeTokenSecret;
|
|
40160
|
+
if (config.storeMaxObjectBytes !== void 0) {
|
|
40161
|
+
deps.storeMaxObjectBytes = config.storeMaxObjectBytes;
|
|
40162
|
+
}
|
|
40163
|
+
if (config.storeRelayMaxBytes !== void 0) {
|
|
40164
|
+
deps.storeRelayMaxBytes = config.storeRelayMaxBytes;
|
|
40165
|
+
}
|
|
40166
|
+
if (config.storeUploadTtlSec !== void 0) deps.storeUploadTtlSec = config.storeUploadTtlSec;
|
|
40167
|
+
if (config.storeShareTtlSec !== void 0) deps.storeShareTtlSec = config.storeShareTtlSec;
|
|
40168
|
+
if (config.storeReadTtlSec !== void 0) deps.storeReadTtlSec = config.storeReadTtlSec;
|
|
40169
|
+
if (config.storeCallMaxBytes !== void 0) deps.storeCallMaxBytes = config.storeCallMaxBytes;
|
|
40170
|
+
if (config.storeCallMaxObjectBytes !== void 0) {
|
|
40171
|
+
deps.storeCallMaxObjectBytes = config.storeCallMaxObjectBytes;
|
|
40172
|
+
}
|
|
40173
|
+
if (config.storeCallMaxObjects !== void 0) {
|
|
40174
|
+
deps.storeCallMaxObjects = config.storeCallMaxObjects;
|
|
40175
|
+
}
|
|
40176
|
+
if (config.storeCallAllowedContentTypes !== void 0) {
|
|
40177
|
+
deps.storeCallAllowedContentTypes = config.storeCallAllowedContentTypes;
|
|
40178
|
+
}
|
|
39131
40179
|
const app = createTbApp(deps);
|
|
39132
40180
|
let server;
|
|
40181
|
+
let storeCleanupTimer;
|
|
40182
|
+
let storeCleanupInFlight = false;
|
|
40183
|
+
const runStoreCleanup = async () => {
|
|
40184
|
+
if (storeCleanupInFlight) return;
|
|
40185
|
+
storeCleanupInFlight = true;
|
|
40186
|
+
try {
|
|
40187
|
+
await cleanupDefaultStore(deps);
|
|
40188
|
+
} finally {
|
|
40189
|
+
storeCleanupInFlight = false;
|
|
40190
|
+
}
|
|
40191
|
+
};
|
|
40192
|
+
const reportStoreCleanupFailure = () => {
|
|
40193
|
+
console.warn(JSON.stringify({ event: "tool_bridge_store_cleanup_failed" }));
|
|
40194
|
+
};
|
|
40195
|
+
const scheduleStoreCleanup = () => {
|
|
40196
|
+
void runStoreCleanup().catch(reportStoreCleanupFailure);
|
|
40197
|
+
storeCleanupTimer = setInterval(() => {
|
|
40198
|
+
void runStoreCleanup().catch(reportStoreCleanupFailure);
|
|
40199
|
+
}, config.storeCleanupIntervalSec * 1e3);
|
|
40200
|
+
storeCleanupTimer.unref?.();
|
|
40201
|
+
};
|
|
39133
40202
|
return {
|
|
39134
40203
|
app,
|
|
39135
40204
|
search,
|
|
@@ -39146,6 +40215,7 @@ function createTbServer(config) {
|
|
|
39146
40215
|
await hub.sweepOrphans();
|
|
39147
40216
|
return await new Promise((resolve3) => {
|
|
39148
40217
|
server = serve({ fetch: app.fetch, port: config.port, hostname: config.host }, (info) => {
|
|
40218
|
+
scheduleStoreCleanup();
|
|
39149
40219
|
resolve3({ port: info.port });
|
|
39150
40220
|
});
|
|
39151
40221
|
hub.attach(server);
|
|
@@ -39156,6 +40226,10 @@ function createTbServer(config) {
|
|
|
39156
40226
|
},
|
|
39157
40227
|
async close() {
|
|
39158
40228
|
draining = true;
|
|
40229
|
+
if (storeCleanupTimer !== void 0) {
|
|
40230
|
+
clearInterval(storeCleanupTimer);
|
|
40231
|
+
storeCleanupTimer = void 0;
|
|
40232
|
+
}
|
|
39159
40233
|
let closed;
|
|
39160
40234
|
if (server !== void 0) {
|
|
39161
40235
|
const s = server;
|