@tool-bridge/server 0.16.0 → 0.19.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-S3T4TAN7.js → airtable-23JBIM6X.js} +106 -114
- package/dist/{alt_text_generator_ai-4DIRIDKL.js → alt_text_generator_ai-ADWBTCIH.js} +25 -16
- package/dist/{amap-LGVGNPML.js → amap-CYQCYW2Z.js} +114 -123
- package/dist/{anthropic-27RFZO35.js → anthropic-5P62HDNR.js} +34 -56
- package/dist/{apify-6WLYAIAS.js → apify-54RZUAGW.js} +31 -49
- package/dist/{brave_search-K2SVJ66F.js → brave_search-AHJ6FTD5.js} +52 -77
- package/dist/{chunk-VK3V2WTY.js → chunk-B4P6YZFS.js} +4 -6
- package/dist/{chunk-BB6ZLMTU.js → chunk-CLEZ6YQ6.js} +2600 -481
- package/dist/chunk-JXHMDBAY.js +45 -0
- package/dist/chunk-LMLX3DJF.js +50 -0
- package/dist/{chunk-YB2FLNWI.js → chunk-OKRZPQ3R.js} +2558 -1926
- package/dist/chunk-SWBNHIO4.js +977 -0
- package/dist/chunk-U67V476Y.js +35 -0
- package/dist/chunk-VLOIDU5U.js +283 -0
- package/dist/{circleci-WV3ITU7Q.js → circleci-UM3NSQJ3.js} +36 -55
- package/dist/{clerk-NFQEFKS4.js → clerk-FE5LQ2CZ.js} +60 -80
- package/dist/{cloudflare_dns-2RK5ASBJ.js → cloudflare_dns-KEZABKUT.js} +90 -113
- package/dist/{cohere-5KEW7LWT.js → cohere-4IPJADXP.js} +33 -49
- package/dist/{confluence-SZE63S46.js → confluence-56QEKNQI.js} +79 -115
- package/dist/{convertapi-QPG2JC5K.js → convertapi-3QRM5K5T.js} +13 -16
- package/dist/{currencyapi-UO2APQRY.js → currencyapi-L6G4OND5.js} +30 -53
- package/dist/{deepseek-LJELIMM3.js → deepseek-LGC6HBFG.js} +30 -38
- package/dist/{docker_hub-2MFXSBOF.js → docker_hub-XDMK3BIU.js} +141 -157
- package/dist/{dropbox-GQYNTTVA.js → dropbox-4QARMFZU.js} +134 -141
- package/dist/{dub-KI2LHXST.js → dub-C6JSJKOT.js} +61 -93
- package/dist/{e2b-WO6ISY3C.js → e2b-6A6TVACJ.js} +39 -69
- package/dist/{exa-IZ7B3BPT.js → exa-SIWWW6AA.js} +49 -77
- package/dist/{fal_ai-LTMF36GJ.js → fal_ai-3IJXOKSP.js} +37 -44
- package/dist/{feishu-WM7SLSFD.js → feishu-L4KDA4DC.js} +4 -6
- package/dist/{feishu_custom_bot-66W3MXH2.js → feishu_custom_bot-SDM75H4P.js} +4 -5
- package/dist/{figma-2Z274VQZ.js → figma-6DD436DN.js} +56 -81
- package/dist/{firehydrant-IFI4R65V.js → firehydrant-SYSGYB3R.js} +40 -53
- package/dist/{fixer-BCCZEB3D.js → fixer-FZG7ASTF.js} +31 -43
- package/dist/{fly-755FXYB5.js → fly-HMRCZBUC.js} +50 -66
- package/dist/{front-4FZKKXXK.js → front-FAFSD7I3.js} +43 -66
- package/dist/{geocodio-WYKDJ3DP.js → geocodio-4D3TWENM.js} +37 -63
- package/dist/{ghost-5BNQTCOH.js → ghost-OSO2FOZS.js} +51 -57
- package/dist/{github-ULELF5VX.js → github-OR4I4RHE.js} +48 -41
- package/dist/{gmail-G34WT64J.js → gmail-5AOPJ63E.js} +26 -36
- package/dist/{googlecalendar-BQU255SC.js → googlecalendar-SOJIJUWH.js} +57 -78
- package/dist/{googledocs-HPCNQXYJ.js → googledocs-QSKN5TLN.js} +14 -22
- package/dist/{grafana-VOV24FIC.js → grafana-R3AIZM7M.js} +95 -122
- package/dist/{graphhopper-D5RXW6AP.js → graphhopper-4CR3QOAX.js} +32 -46
- package/dist/index.d.ts +108 -56
- package/dist/index.js +4 -3
- package/dist/{ip2proxy-DAYO7EKZ.js → ip2proxy-OBCG6X6J.js} +35 -42
- package/dist/{ipgeolocation_io-XD4PSLII.js → ipgeolocation_io-NHH3DTQW.js} +40 -60
- package/dist/{ipqualityscore-K27QEAWK.js → ipqualityscore-7POMCBEC.js} +30 -42
- package/dist/{jina_ai-E6UXFV2P.js → jina_ai-YXVKROV4.js} +32 -58
- package/dist/{jira-ZDSXGQ6W.js → jira-LADO3VJU.js} +116 -126
- package/dist/{langsmith-TIFJRREW.js → langsmith-XZ6UAQFU.js} +101 -121
- package/dist/{lemlist-DASEMJMA.js → lemlist-P74GLNE7.js} +57 -68
- package/dist/{linear-KE6UP4SP.js → linear-CXJ7D37A.js} +109 -118
- package/dist/{logsnag-IMNDN4R5.js → logsnag-EVVHWFLK.js} +31 -48
- package/dist/main.js +103 -102
- package/dist/{mattermost-HHHMUXQA.js → mattermost-WG2MRGBD.js} +45 -62
- package/dist/{mem0-D2FGGOIY.js → mem0-HQWUKG6U.js} +72 -98
- package/dist/{memos-4OVIGFPH.js → memos-SQMJIFOE.js} +68 -122
- package/dist/{metabase-33WCX5YD.js → metabase-E27G2DZI.js} +47 -60
- package/dist/{mistral_ai-QIMZ2JYL.js → mistral_ai-MIL63NIT.js} +35 -40
- package/dist/{mother_duck-M6VWS2VH.js → mother_duck-AIZVCBNR.js} +42 -62
- package/dist/{ngrok-Z2PKQBHH.js → ngrok-XS4DVIQS.js} +42 -58
- package/dist/{notes-SS4RPC4K.js → notes-JEOUSL2Y.js} +3 -5
- package/dist/{notion-FJCNQ6I7.js → notion-U4EZVDW2.js} +83 -97
- package/dist/{open_exchange_rates-WU57WTT5.js → open_exchange_rates-AZSQEYEJ.js} +35 -39
- package/dist/{openai-JODZ4GKT.js → openai-HW3SYMTQ.js} +51 -55
- package/dist/{opengraph_io-SQRAUSV5.js → opengraph_io-QX74MAEL.js} +61 -79
- package/dist/{openrouter-Q5XXEJZ7.js → openrouter-OXSKYL6N.js} +41 -55
- package/dist/{outline-RT2EHJ2X.js → outline-CCQAPDSW.js} +89 -104
- package/dist/{paddle-OF2V7GAC.js → paddle-6KQPSSCK.js} +34 -49
- package/dist/{perplexity-LNZEWLHG.js → perplexity-2TXO7IQE.js} +33 -50
- package/dist/{pinecone-JSJIXZYN.js → pinecone-YOO7NULI.js} +65 -91
- package/dist/{pipedrive-D4CKVUXR.js → pipedrive-Q6RIFIFP.js} +37 -59
- package/dist/{polar-M7ZYP6UR.js → polar-KUMGCES3.js} +43 -53
- package/dist/{posthog-YV7CZOH4.js → posthog-VXQHBSMB.js} +58 -77
- package/dist/{postmark-KYHLECRG.js → postmark-YLIPF52S.js} +52 -71
- package/dist/{prerender-HCMHBKV2.js → prerender-CRY7UJN5.js} +32 -50
- package/dist/{pubmed-WYVLCCN3.js → pubmed-3Z4T5AM7.js} +28 -60
- package/dist/{railway-TWIRD5KR.js → railway-BP37IGFL.js} +32 -39
- package/dist/{readwise-UZ467IQ7.js → readwise-HTKICF4K.js} +62 -78
- package/dist/{render-MECUC5CC.js → render-WIVTUXJH.js} +48 -64
- package/dist/{replicate-A5MEK3SW.js → replicate-JJYPA43D.js} +41 -58
- package/dist/{resend-BC6EAWJQ.js → resend-TKHZVG7A.js} +27 -37
- package/dist/{rootly-ZM3QDUIS.js → rootly-FRSZLG5U.js} +64 -76
- package/dist/{scrapfly-WAML42QJ.js → scrapfly-M5WXGQWE.js} +45 -51
- package/dist/{scrapingbee-DG7PET3V.js → scrapingbee-PMXNLLBK.js} +25 -33
- package/dist/{screenshot_fyi-HY6WTHIO.js → screenshot_fyi-HFW4TAOH.js} +29 -47
- package/dist/{semantic_scholar-C5IZA7IP.js → semantic_scholar-KXVH2VZQ.js} +37 -56
- package/dist/{sentry-OKBPSTDM.js → sentry-WWOT2LRX.js} +66 -85
- package/dist/{shodan-PPC6DYU4.js → shodan-FY635H35.js} +42 -53
- package/dist/{shopify-EWGUHBXO.js → shopify-HQLBVSA5.js} +32 -49
- package/dist/{stripe-6RPDPC5W.js → stripe-GJN2GOHB.js} +4 -5
- package/dist/{supabase-SK7DQ42N.js → supabase-BBNZGQU6.js} +69 -88
- package/dist/{tavily-YCG5VQMH.js → tavily-DPENIBOG.js} +37 -62
- package/dist/{telegram-2OES5FYB.js → telegram-L4NONG6H.js} +168 -177
- package/dist/{telnyx-POOSBBR4.js → telnyx-H67MAEZS.js} +30 -50
- package/dist/{todoist-M7YD6YSS.js → todoist-G2BSP4DX.js} +77 -89
- package/dist/{trello-IS6V5W3E.js → trello-7WMVFDG2.js} +108 -122
- package/dist/{turso-RCFHUFVH.js → turso-ZUAESFC2.js} +52 -64
- package/dist/{twilio-OS2RXFGO.js → twilio-EYFYIX3B.js} +48 -55
- package/dist/{umami-PQ2SJ2LN.js → umami-AXAUDUVF.js} +49 -69
- package/dist/{unsplash-C6BPZO4E.js → unsplash-UCQAWPLR.js} +35 -52
- package/dist/{upstash_redis-WH6UER7H.js → upstash_redis-ZPZ4ASU6.js} +28 -40
- package/dist/{uptimerobot-M3YZGS6Z.js → uptimerobot-4QVKKUD5.js} +11 -12
- package/dist/{v2ex-R4JWTEHN.js → v2ex-VGEFJTYC.js} +36 -61
- package/dist/{vercel-FZVYUOQD.js → vercel-JME4TSCN.js} +124 -150
- package/dist/{webapi-VLM4IKVB.js → webapi-GW5QXWXE.js} +1 -1
- package/dist/{wolfram_alpha_api-2KOLO5DC.js → wolfram_alpha_api-TRGU2MDG.js} +26 -34
- package/dist/{wordpress-XRWGO6LG.js → wordpress-WRUKLCZR.js} +87 -104
- package/dist/{workos-WR4NKOYL.js → workos-ZDW4EHS4.js} +37 -56
- package/dist/{zhihu-BKZIB3WA.js → zhihu-4VI67CWU.js} +35 -57
- package/package.json +3 -3
- package/dist/chunk-2O27H4Y2.js +0 -183
- package/dist/chunk-GKX72JMJ.js +0 -378
- package/dist/chunk-MLKGABMK.js +0 -9
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGuardedFetch
|
|
3
|
+
} from "./chunk-SWBNHIO4.js";
|
|
1
4
|
import {
|
|
2
5
|
AnnotationStore,
|
|
3
6
|
CONTEXT_CAPABILITIES,
|
|
@@ -27,7 +30,6 @@ import {
|
|
|
27
30
|
PG_SEARCH_TOOLS_TABLE,
|
|
28
31
|
PG_SEARCH_WRITE_LOCK_KEY,
|
|
29
32
|
PRESIGN_TTL_SEC_DEFAULT,
|
|
30
|
-
PRESIGN_TTL_SEC_MAX,
|
|
31
33
|
RemoteAllowlistStore,
|
|
32
34
|
SKILLHUB_CAPABILITIES,
|
|
33
35
|
SKRegistryStore,
|
|
@@ -38,9 +40,11 @@ import {
|
|
|
38
40
|
TB_ERROR_CODES,
|
|
39
41
|
TOOL_SEARCH_AUDIT_NODE_LIMIT,
|
|
40
42
|
TOOL_SEARCH_BATCH_LIMIT,
|
|
43
|
+
TOOL_SEARCH_DESCRIPTION_BYTES_MAX,
|
|
41
44
|
TOOL_SEARCH_INSERT_SNAPSHOT_SQL,
|
|
42
45
|
TOOL_SEARCH_INSERT_SQL,
|
|
43
46
|
TOOL_SEARCH_PAGE_BYTES,
|
|
47
|
+
TOOL_SEARCH_RANKING_VERSION,
|
|
44
48
|
TOOL_SEARCH_SCHEMA_STATEMENTS,
|
|
45
49
|
TOOL_SEARCH_WORK_LIMIT,
|
|
46
50
|
ZodIssueCode,
|
|
@@ -88,11 +92,24 @@ import {
|
|
|
88
92
|
deviceFsHelpModel,
|
|
89
93
|
deviceShellHelpModel,
|
|
90
94
|
discriminatedUnion,
|
|
95
|
+
dispatchContextCmd,
|
|
96
|
+
dispatchContextUploadCmd,
|
|
97
|
+
dispatchSkillhubCmd,
|
|
91
98
|
effectFor,
|
|
92
99
|
email,
|
|
93
100
|
encodeCallContext,
|
|
94
101
|
encodeDeviceFrame,
|
|
95
102
|
encodePluginCall,
|
|
103
|
+
external_exports,
|
|
104
|
+
feedbackDetailSchema,
|
|
105
|
+
feedbackListSchema,
|
|
106
|
+
feedbackRemoveResponseSchema,
|
|
107
|
+
feedbackSubmitRequestSchema,
|
|
108
|
+
feedbackSubmitResponseSchema,
|
|
109
|
+
feedbackViewSchema,
|
|
110
|
+
feedbackVoteRequestSchema,
|
|
111
|
+
healthResponseSchema,
|
|
112
|
+
helpJsonSchema,
|
|
96
113
|
identify,
|
|
97
114
|
intersection,
|
|
98
115
|
isContextExpired,
|
|
@@ -101,31 +118,42 @@ import {
|
|
|
101
118
|
iso_exports,
|
|
102
119
|
lazy,
|
|
103
120
|
literal,
|
|
121
|
+
livenessResponseSchema,
|
|
104
122
|
looseObject,
|
|
105
123
|
mintKey,
|
|
106
124
|
negotiate,
|
|
107
|
-
|
|
125
|
+
nodeInputSchema,
|
|
108
126
|
normalizeEntryPath,
|
|
109
127
|
normalizePath,
|
|
110
128
|
normalizeToolSearchLimit,
|
|
129
|
+
normalizeToolSearchOptions,
|
|
111
130
|
normalizeToolSearchPath,
|
|
112
131
|
normalizeUpstreamError,
|
|
113
132
|
number,
|
|
133
|
+
oauthAuthorizeRequestSchema,
|
|
134
|
+
oauthAuthorizeResponseSchema,
|
|
114
135
|
object,
|
|
115
136
|
objectBodyToBytes,
|
|
116
137
|
optional,
|
|
117
138
|
optionalMethodsForCapabilities,
|
|
139
|
+
parseContextCmdArgs,
|
|
118
140
|
parseCredentialValues,
|
|
119
141
|
parseFeedbackEntries,
|
|
120
142
|
parseNodeInput,
|
|
143
|
+
parseNonNegativeIntEnv,
|
|
144
|
+
parsePortEnv,
|
|
145
|
+
parseRuntimeEnv,
|
|
121
146
|
parseTokenResponse,
|
|
122
147
|
parseVia,
|
|
123
148
|
pgBulkInsertSql,
|
|
124
149
|
pgSearchDialect,
|
|
125
150
|
pluginTokenSecretName,
|
|
126
151
|
prefixUpperBound,
|
|
152
|
+
prepareToolSearchQuery,
|
|
127
153
|
preprocess,
|
|
154
|
+
readinessResponseSchema,
|
|
128
155
|
record,
|
|
156
|
+
registryNodeSchema,
|
|
129
157
|
renderHelpDsl,
|
|
130
158
|
renderHelpJson,
|
|
131
159
|
renderHelpMarkdown,
|
|
@@ -141,59 +169,42 @@ import {
|
|
|
141
169
|
skillhubScopeForCmd,
|
|
142
170
|
storeUri,
|
|
143
171
|
string,
|
|
172
|
+
tbErrorBodySchema,
|
|
144
173
|
toJSONSchema,
|
|
145
174
|
toolHelpModel,
|
|
175
|
+
toolSearchPageSchema,
|
|
176
|
+
toolSearchRelevanceSchema,
|
|
177
|
+
toolSearchRequestSchema,
|
|
146
178
|
toolSearchSnapshotDigest,
|
|
179
|
+
toolSearchSourceResultSchema,
|
|
180
|
+
toolSearchSourceSchema,
|
|
147
181
|
toolsToHelpModel,
|
|
182
|
+
treeJsonSchema,
|
|
148
183
|
union,
|
|
149
184
|
unknown,
|
|
150
185
|
url,
|
|
151
186
|
validatePath,
|
|
152
187
|
virtualizeTools
|
|
153
|
-
} from "./chunk-
|
|
188
|
+
} from "./chunk-OKRZPQ3R.js";
|
|
154
189
|
|
|
155
190
|
// src/config.ts
|
|
156
191
|
var DEFAULT_PORT = 8787;
|
|
157
|
-
var DEFAULT_MAX_HOPS = 4;
|
|
158
192
|
var DEFAULT_DEVICE_RECLAIM_SEC = 24 * 60 * 60;
|
|
159
193
|
var DEFAULT_STORE_CLEANUP_INTERVAL_SEC = 15 * 60;
|
|
160
|
-
function positiveIntEnv(value) {
|
|
161
|
-
const n = Number(value);
|
|
162
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
|
|
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
|
-
}
|
|
168
|
-
function portEnv(value) {
|
|
169
|
-
if (value === void 0 || value === "") return void 0;
|
|
170
|
-
const n = Number(value);
|
|
171
|
-
return Number.isInteger(n) && n >= 0 && n <= 65535 ? n : void 0;
|
|
172
|
-
}
|
|
173
194
|
function configFromEnv(env = process.env) {
|
|
174
|
-
const
|
|
175
|
-
const allowlist = (env.TB_REMOTE_ALLOWLIST ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
195
|
+
const runtime = parseRuntimeEnv(env);
|
|
176
196
|
const config = {
|
|
197
|
+
...runtime,
|
|
177
198
|
// TB_PORT 优先,PORT 兜底:Railway / Fly / Cloud Run / CF Container 等 PaaS 只注入
|
|
178
199
|
// PORT,不认识 TB_PORT —— 不兜底的话容器会监听 8787 而平台探活另一个端口,部署直接失败。
|
|
179
|
-
port:
|
|
200
|
+
port: parsePortEnv(env.TB_PORT) ?? parsePortEnv(env.PORT) ?? DEFAULT_PORT,
|
|
180
201
|
host: env.TB_HOST !== void 0 && env.TB_HOST.length > 0 ? env.TB_HOST : "0.0.0.0",
|
|
181
202
|
dataDir: env.TB_DATA_DIR !== void 0 && env.TB_DATA_DIR.length > 0 ? env.TB_DATA_DIR : "./data",
|
|
182
|
-
allowInsecureHttp: allowInsecure,
|
|
183
203
|
allowInsecureBootstrap: env.TB_ALLOW_INSECURE_BOOTSTRAP === "true",
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
allowInsecure
|
|
189
|
-
},
|
|
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,
|
|
192
|
-
// 0 合法(立即关停,本地/单机默认);positiveIntEnv 拒 0,故单独解析。
|
|
193
|
-
shutdownDrainSec: (() => {
|
|
194
|
-
const n = Number(env.TB_SHUTDOWN_DRAIN_SEC);
|
|
195
|
-
return Number.isInteger(n) && n >= 0 ? n : 0;
|
|
196
|
-
})()
|
|
204
|
+
deviceReclaimSec: runtime.deviceReclaimSec ?? DEFAULT_DEVICE_RECLAIM_SEC,
|
|
205
|
+
storeCleanupIntervalSec: runtime.storeCleanupIntervalSec ?? DEFAULT_STORE_CLEANUP_INTERVAL_SEC,
|
|
206
|
+
// 0 合法(立即关停,本地/单机默认)。
|
|
207
|
+
shutdownDrainSec: parseNonNegativeIntEnv(env.TB_SHUTDOWN_DRAIN_SEC) ?? 0
|
|
197
208
|
};
|
|
198
209
|
if (env.TB_UI_DIR !== void 0 && env.TB_UI_DIR.length > 0) config.uiDir = env.TB_UI_DIR;
|
|
199
210
|
if (env.TB_DATABASE_URL !== void 0 && env.TB_DATABASE_URL.length > 0) {
|
|
@@ -230,47 +241,9 @@ function configFromEnv(env = process.env) {
|
|
|
230
241
|
if (env.TB_BOOTSTRAP_ADMIN_SK !== void 0 && env.TB_BOOTSTRAP_ADMIN_SK.length > 0) {
|
|
231
242
|
config.adminSk = env.TB_BOOTSTRAP_ADMIN_SK;
|
|
232
243
|
}
|
|
233
|
-
const canonicalOrigin = normalizeCanonicalOrigin(env.TB_CANONICAL_ORIGIN);
|
|
234
|
-
if (canonicalOrigin !== void 0) config.canonicalOrigin = canonicalOrigin;
|
|
235
244
|
if (env.TB_SECRET_ENCRYPTION_KEY !== void 0 && env.TB_SECRET_ENCRYPTION_KEY.length > 0) {
|
|
236
245
|
config.encryptionKey = env.TB_SECRET_ENCRYPTION_KEY;
|
|
237
246
|
}
|
|
238
|
-
const ttl = positiveIntEnv(env.TB_TOOL_CACHE_TTL);
|
|
239
|
-
if (ttl !== void 0) config.toolCacheTtlSec = ttl;
|
|
240
|
-
const refThreshold = positiveIntEnv(env.TB_REF_THRESHOLD_BYTES);
|
|
241
|
-
if (refThreshold !== void 0) config.refThresholdBytes = refThreshold;
|
|
242
|
-
const refTtl = presignTtlEnv(env.TB_REF_TTL_SEC);
|
|
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
|
-
}
|
|
274
247
|
return config;
|
|
275
248
|
}
|
|
276
249
|
|
|
@@ -607,6 +580,48 @@ async function presignS3Put(client, url2, ttlSec, opts) {
|
|
|
607
580
|
return { method: "PUT", url: signed.url, headers };
|
|
608
581
|
}
|
|
609
582
|
|
|
583
|
+
// ../app/src/objectBodyStream.ts
|
|
584
|
+
function toWebObjectBodyStream(source, options = {}) {
|
|
585
|
+
const reader = source.getReader();
|
|
586
|
+
let released = false;
|
|
587
|
+
const release = () => {
|
|
588
|
+
if (released) return;
|
|
589
|
+
released = true;
|
|
590
|
+
reader.releaseLock();
|
|
591
|
+
};
|
|
592
|
+
const strategy = options.highWaterMark === void 0 ? void 0 : { highWaterMark: options.highWaterMark };
|
|
593
|
+
return new ReadableStream({
|
|
594
|
+
async pull(controller) {
|
|
595
|
+
try {
|
|
596
|
+
const { done, value } = await reader.read();
|
|
597
|
+
if (done) {
|
|
598
|
+
release();
|
|
599
|
+
controller.close();
|
|
600
|
+
} else if (value !== void 0) {
|
|
601
|
+
controller.enqueue(value);
|
|
602
|
+
}
|
|
603
|
+
} catch (error) {
|
|
604
|
+
try {
|
|
605
|
+
release();
|
|
606
|
+
} catch {
|
|
607
|
+
}
|
|
608
|
+
controller.error(error);
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
async cancel(reason) {
|
|
612
|
+
try {
|
|
613
|
+
if (reader.cancel !== void 0) await reader.cancel(reason);
|
|
614
|
+
else await source.cancel?.(reason);
|
|
615
|
+
} finally {
|
|
616
|
+
try {
|
|
617
|
+
release();
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}, strategy);
|
|
623
|
+
}
|
|
624
|
+
|
|
610
625
|
// ../app/src/providers/s3Object.ts
|
|
611
626
|
var META_HEADER_PREFIX = "x-amz-meta-";
|
|
612
627
|
function stripEtagQuotes(etag) {
|
|
@@ -637,16 +652,12 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
637
652
|
const secErr = assertSecureUrl(cfg.endpoint, opts.allowInsecure);
|
|
638
653
|
if (secErr) throw secErr;
|
|
639
654
|
const client = new AwsClient({
|
|
640
|
-
accessKeyId: cfg.accessKeyId,
|
|
641
|
-
secretAccessKey: cfg.secretAccessKey,
|
|
642
|
-
service: "s3",
|
|
643
|
-
region: cfg.region ?? "auto"
|
|
644
|
-
});
|
|
645
|
-
const putClient = new AwsClient({
|
|
646
655
|
accessKeyId: cfg.accessKeyId,
|
|
647
656
|
secretAccessKey: cfg.secretAccessKey,
|
|
648
657
|
service: "s3",
|
|
649
658
|
region: cfg.region ?? "auto",
|
|
659
|
+
// Store 的重试由上层按操作幂等性统一决定。传输层不得自动重放写入,
|
|
660
|
+
// 也不能让读/删路径悄悄继承 aws4fetch 的默认 10 次重试。
|
|
650
661
|
retries: 0
|
|
651
662
|
});
|
|
652
663
|
const base = cfg.endpoint.replace(/\/+$/, "");
|
|
@@ -670,38 +681,7 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
670
681
|
if (body instanceof Uint8Array) {
|
|
671
682
|
return body.buffer instanceof ArrayBuffer ? new Uint8Array(body.buffer, body.byteOffset, body.byteLength) : new Uint8Array(body);
|
|
672
683
|
}
|
|
673
|
-
|
|
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 });
|
|
684
|
+
return toWebObjectBodyStream(body, { highWaterMark: 0 });
|
|
705
685
|
};
|
|
706
686
|
const metaFromHeaders = (key, headers) => {
|
|
707
687
|
const metadata = {};
|
|
@@ -750,7 +730,7 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
750
730
|
}
|
|
751
731
|
if (putOpts?.ifMatchEtag !== void 0) headers["if-match"] = `"${putOpts.ifMatchEtag}"`;
|
|
752
732
|
if (putOpts?.ifNoneMatch !== void 0) headers["if-none-match"] = putOpts.ifNoneMatch;
|
|
753
|
-
const resp = await
|
|
733
|
+
const resp = await s3Fetch(urlFor(key), {
|
|
754
734
|
method: "PUT",
|
|
755
735
|
headers,
|
|
756
736
|
body: fetchBody(body)
|
|
@@ -1256,8 +1236,8 @@ async function defaultStoreRuntime(deps) {
|
|
|
1256
1236
|
service: new StoreService(deps.state, objects, serviceOptions(deps, tokenSecret))
|
|
1257
1237
|
};
|
|
1258
1238
|
}
|
|
1259
|
-
function
|
|
1260
|
-
const raw =
|
|
1239
|
+
function resolveStoreRequestOrigin(rawRequestOrigin, canonicalOrigin) {
|
|
1240
|
+
const raw = rawRequestOrigin ?? canonicalOrigin;
|
|
1261
1241
|
if (raw === void 0) {
|
|
1262
1242
|
throw new TBError("unavailable", "request origin is unavailable", { retryable: false });
|
|
1263
1243
|
}
|
|
@@ -1270,11 +1250,26 @@ function requestOrigin(runtime, deps) {
|
|
|
1270
1250
|
if (url2.protocol !== "https:" && url2.protocol !== "http:" || url2.username !== "" || url2.password !== "") {
|
|
1271
1251
|
throw new TBError("unavailable", "request origin is invalid", { retryable: false });
|
|
1272
1252
|
}
|
|
1253
|
+
if (rawRequestOrigin !== void 0 && canonicalOrigin !== void 0) {
|
|
1254
|
+
let canonical;
|
|
1255
|
+
try {
|
|
1256
|
+
canonical = new URL(canonicalOrigin);
|
|
1257
|
+
} catch {
|
|
1258
|
+
throw new TBError("unavailable", "canonical origin is invalid", { retryable: false });
|
|
1259
|
+
}
|
|
1260
|
+
if (canonical.protocol !== "https:" && canonical.protocol !== "http:" || canonical.username !== "" || canonical.password !== "") {
|
|
1261
|
+
throw new TBError("unavailable", "canonical origin is invalid", { retryable: false });
|
|
1262
|
+
}
|
|
1263
|
+
url2.protocol = canonical.protocol;
|
|
1264
|
+
}
|
|
1273
1265
|
return url2.origin;
|
|
1274
1266
|
}
|
|
1275
|
-
function
|
|
1267
|
+
function requestOrigin(runtime, deps) {
|
|
1268
|
+
return resolveStoreRequestOrigin(runtime?.requestOrigin, deps.canonicalOrigin);
|
|
1269
|
+
}
|
|
1270
|
+
function storeUploadGrant(start, origin) {
|
|
1276
1271
|
const useRelay = start.alreadyCompleted || start.signedRequest === void 0;
|
|
1277
|
-
const url2 = useRelay ? `${
|
|
1272
|
+
const url2 = useRelay ? `${origin}/~store/uploads/${encodeURIComponent(start.uploadId)}` : start.signedRequest.url;
|
|
1278
1273
|
const headers = useRelay ? { [STORE_UPLOAD_HEADER]: start.uploadToken } : start.signedRequest.headers;
|
|
1279
1274
|
return {
|
|
1280
1275
|
uploadId: start.uploadId,
|
|
@@ -1369,10 +1364,10 @@ function lazyDefaultStoreModule(deps) {
|
|
|
1369
1364
|
}
|
|
1370
1365
|
};
|
|
1371
1366
|
}
|
|
1372
|
-
async function beginCapabilityUpload(deps, input, capabilityToken,
|
|
1367
|
+
async function beginCapabilityUpload(deps, input, capabilityToken, origin) {
|
|
1373
1368
|
const store = await defaultStoreRuntime(deps);
|
|
1374
1369
|
const start = await store.service.beginCallUpload(input, capabilityToken);
|
|
1375
|
-
return storeUploadGrant(start,
|
|
1370
|
+
return storeUploadGrant(start, origin);
|
|
1376
1371
|
}
|
|
1377
1372
|
async function completeCapabilityUpload(deps, uploadId2, uploadToken) {
|
|
1378
1373
|
return await (await defaultStoreRuntime(deps)).service.completeUploadWithToken(
|
|
@@ -1400,37 +1395,7 @@ async function relayStoreUpload(deps, uploadId2, uploadToken, body) {
|
|
|
1400
1395
|
async function storeObjectResponse(objects, object2) {
|
|
1401
1396
|
const got = await objects.get(object2.driverKey);
|
|
1402
1397
|
if (got === null) throw TBError.notFound("not found");
|
|
1403
|
-
const
|
|
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
|
-
});
|
|
1398
|
+
const body = toWebObjectBodyStream(got.body);
|
|
1434
1399
|
return new Response(body, {
|
|
1435
1400
|
headers: {
|
|
1436
1401
|
"content-type": object2.contentType,
|
|
@@ -1502,7 +1467,6 @@ function cleanupProgress(value) {
|
|
|
1502
1467
|
"objects",
|
|
1503
1468
|
"shares",
|
|
1504
1469
|
"callCapabilities",
|
|
1505
|
-
"driverObjects",
|
|
1506
1470
|
"idempotencyBindings"
|
|
1507
1471
|
]) {
|
|
1508
1472
|
const cursor = cursors[field];
|
|
@@ -1516,7 +1480,6 @@ function emptyCleanupResult() {
|
|
|
1516
1480
|
return {
|
|
1517
1481
|
abandonedObjects: 0,
|
|
1518
1482
|
deletedBytes: 0,
|
|
1519
|
-
deletedOrphans: 0,
|
|
1520
1483
|
deletedStaging: 0,
|
|
1521
1484
|
expiredCallCapabilities: 0,
|
|
1522
1485
|
expiredIdempotencyBindings: 0,
|
|
@@ -1527,7 +1490,6 @@ function emptyCleanupResult() {
|
|
|
1527
1490
|
function addCleanupResult(target, page) {
|
|
1528
1491
|
target.abandonedObjects += page.abandonedObjects;
|
|
1529
1492
|
target.deletedBytes += page.deletedBytes;
|
|
1530
|
-
target.deletedOrphans += page.deletedOrphans;
|
|
1531
1493
|
target.deletedStaging += page.deletedStaging;
|
|
1532
1494
|
target.expiredCallCapabilities += page.expiredCallCapabilities;
|
|
1533
1495
|
target.expiredIdempotencyBindings += page.expiredIdempotencyBindings;
|
|
@@ -5804,8 +5766,8 @@ var LADDER_ERROR_HTTP_STATUS = {
|
|
|
5804
5766
|
function parseSchema(schema, data) {
|
|
5805
5767
|
return safeParse(schema, data);
|
|
5806
5768
|
}
|
|
5807
|
-
function shapeKeys(
|
|
5808
|
-
return new Set(
|
|
5769
|
+
function shapeKeys(schemas2) {
|
|
5770
|
+
return new Set(schemas2.flatMap((schema) => Object.keys(schema.shape)));
|
|
5809
5771
|
}
|
|
5810
5772
|
function isStandardSchema(schema) {
|
|
5811
5773
|
if (schema == null) return false;
|
|
@@ -10068,13 +10030,13 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
10068
10030
|
const buffer = [];
|
|
10069
10031
|
let endipv6Encountered = false;
|
|
10070
10032
|
let endIpv6 = false;
|
|
10071
|
-
let
|
|
10033
|
+
let consume2 = consumeHextets;
|
|
10072
10034
|
for (let i = 0; i < input.length; i++) {
|
|
10073
10035
|
const cursor = input[i];
|
|
10074
10036
|
if (cursor === "[" || cursor === "]") continue;
|
|
10075
10037
|
if (cursor === ":") {
|
|
10076
10038
|
if (endipv6Encountered === true) endIpv6 = true;
|
|
10077
|
-
if (!
|
|
10039
|
+
if (!consume2(buffer, address, output)) break;
|
|
10078
10040
|
if (++tokenCount > 7) {
|
|
10079
10041
|
output.error = true;
|
|
10080
10042
|
break;
|
|
@@ -10083,14 +10045,14 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
10083
10045
|
address.push(":");
|
|
10084
10046
|
continue;
|
|
10085
10047
|
} else if (cursor === "%") {
|
|
10086
|
-
if (!
|
|
10087
|
-
|
|
10048
|
+
if (!consume2(buffer, address, output)) break;
|
|
10049
|
+
consume2 = consumeIsZone;
|
|
10088
10050
|
} else {
|
|
10089
10051
|
buffer.push(cursor);
|
|
10090
10052
|
continue;
|
|
10091
10053
|
}
|
|
10092
10054
|
}
|
|
10093
|
-
if (buffer.length) if (
|
|
10055
|
+
if (buffer.length) if (consume2 === consumeIsZone) output.zone = buffer.join("");
|
|
10094
10056
|
else if (endIpv6) address.push(buffer.join(""));
|
|
10095
10057
|
else address.push(stringArrayToHexStripped(buffer));
|
|
10096
10058
|
output.address = address.join("");
|
|
@@ -10980,14 +10942,14 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
10980
10942
|
}
|
|
10981
10943
|
return metaSchema;
|
|
10982
10944
|
}
|
|
10983
|
-
_removeAllSchemas(
|
|
10984
|
-
for (const keyRef in
|
|
10985
|
-
const sch =
|
|
10945
|
+
_removeAllSchemas(schemas2, regex3) {
|
|
10946
|
+
for (const keyRef in schemas2) {
|
|
10947
|
+
const sch = schemas2[keyRef];
|
|
10986
10948
|
if (!regex3 || regex3.test(keyRef)) {
|
|
10987
|
-
if (typeof sch == "string") delete
|
|
10949
|
+
if (typeof sch == "string") delete schemas2[keyRef];
|
|
10988
10950
|
else if (sch && !sch.meta) {
|
|
10989
10951
|
this._cache.delete(sch.schema);
|
|
10990
|
-
delete
|
|
10952
|
+
delete schemas2[keyRef];
|
|
10991
10953
|
}
|
|
10992
10954
|
}
|
|
10993
10955
|
}
|
|
@@ -19233,12 +19195,51 @@ async function invalidateMcpOAuth(store, nodePath) {
|
|
|
19233
19195
|
await store.delete(KEY_OAUTH_TOKENS + nodePath);
|
|
19234
19196
|
await store.delete(KEY_OAUTH_DISCOVERY + nodePath);
|
|
19235
19197
|
}
|
|
19198
|
+
async function assertMcpOAuthConfig(config, secrets) {
|
|
19199
|
+
if (config === null || typeof config !== "object") return;
|
|
19200
|
+
const candidate = config;
|
|
19201
|
+
if (candidate.kind !== "mcp" || candidate.oauthClient === void 0) return;
|
|
19202
|
+
if (candidate.auth !== "oauth") {
|
|
19203
|
+
throw new TBError("invalid_argument", "mcp oauthClient \u53EA\u80FD\u4E0E auth:'oauth' \u4E00\u8D77\u4F7F\u7528");
|
|
19204
|
+
}
|
|
19205
|
+
if (candidate.oauthClient === null || typeof candidate.oauthClient !== "object") {
|
|
19206
|
+
throw new TBError("invalid_argument", "mcp oauthClient \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
19207
|
+
}
|
|
19208
|
+
const client = candidate.oauthClient;
|
|
19209
|
+
const unknown2 = Object.keys(client).filter((key) => !["clientId", "clientSecretRef"].includes(key));
|
|
19210
|
+
if (unknown2.length > 0) {
|
|
19211
|
+
throw new TBError(
|
|
19212
|
+
"invalid_argument",
|
|
19213
|
+
`mcp oauthClient \u4E0D\u63A5\u53D7\u5B57\u6BB5:${unknown2.join(", ")};client secret \u5FC5\u987B\u8D70 clientSecretRef`
|
|
19214
|
+
);
|
|
19215
|
+
}
|
|
19216
|
+
if (typeof client.clientId !== "string" || client.clientId.trim() === "") {
|
|
19217
|
+
throw new TBError("invalid_argument", "mcp oauthClient.clientId \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
19218
|
+
}
|
|
19219
|
+
if (client.clientSecretRef === void 0) return;
|
|
19220
|
+
if (typeof client.clientSecretRef !== "string" || client.clientSecretRef.trim() === "") {
|
|
19221
|
+
throw new TBError("invalid_argument", "mcp oauthClient.clientSecretRef \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
19222
|
+
}
|
|
19223
|
+
if (await secrets.resolve(client.clientSecretRef) === void 0) {
|
|
19224
|
+
throw new TBError(
|
|
19225
|
+
"invalid_argument",
|
|
19226
|
+
`OAuth client secret ref '${client.clientSecretRef}' \u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u89E3\u5BC6`
|
|
19227
|
+
);
|
|
19228
|
+
}
|
|
19229
|
+
}
|
|
19236
19230
|
function reauthorizeRequired(nodePath) {
|
|
19237
19231
|
return new TBError(
|
|
19238
19232
|
"permission_denied",
|
|
19239
19233
|
`mcp upstream '${nodePath}' requires (re)authorization: run \`tb tool auth ${nodePath}\``
|
|
19240
19234
|
);
|
|
19241
19235
|
}
|
|
19236
|
+
var mcpOAuthFetch = (input, init) => {
|
|
19237
|
+
const headers = new Headers(
|
|
19238
|
+
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
19239
|
+
);
|
|
19240
|
+
const tokenRequest = headers.get("content-type")?.toLowerCase().startsWith("application/x-www-form-urlencoded") === true;
|
|
19241
|
+
return fetch(input, tokenRequest ? { ...init, redirect: "error" } : init);
|
|
19242
|
+
};
|
|
19242
19243
|
async function stateCryptoKey(secret) {
|
|
19243
19244
|
const digest = await crypto.subtle.digest(
|
|
19244
19245
|
"SHA-256",
|
|
@@ -19270,7 +19271,7 @@ async function openOAuthState(state, secret) {
|
|
|
19270
19271
|
ciphertext
|
|
19271
19272
|
);
|
|
19272
19273
|
const payload = JSON.parse(new TextDecoder().decode(plain));
|
|
19273
|
-
if (typeof payload.p !== "string" || typeof payload.v !== "string" || typeof payload.exp !== "number" || payload.r !== void 0 && typeof payload.r !== "string") {
|
|
19274
|
+
if (typeof payload.p !== "string" || typeof payload.v !== "string" || typeof payload.exp !== "number" || payload.r !== void 0 && typeof payload.r !== "string" || payload.o !== void 0 && (typeof payload.o !== "object" || payload.o === null || typeof payload.o.i !== "string" || payload.o.r !== void 0 && typeof payload.o.r !== "string")) {
|
|
19274
19275
|
return null;
|
|
19275
19276
|
}
|
|
19276
19277
|
return payload;
|
|
@@ -19288,8 +19289,8 @@ var GatewayMcpOAuthProvider = class {
|
|
|
19288
19289
|
}
|
|
19289
19290
|
get redirectUrl() {
|
|
19290
19291
|
if (this.opts.redirectUri !== void 0) return this.opts.redirectUri;
|
|
19291
|
-
const
|
|
19292
|
-
return `${
|
|
19292
|
+
const origin = this.opts.origin ?? "https://tool-bridge.invalid";
|
|
19293
|
+
return `${origin}${OAUTH_CALLBACK_PATH}`;
|
|
19293
19294
|
}
|
|
19294
19295
|
get clientMetadata() {
|
|
19295
19296
|
return {
|
|
@@ -19297,11 +19298,31 @@ var GatewayMcpOAuthProvider = class {
|
|
|
19297
19298
|
redirect_uris: [this.redirectUrl],
|
|
19298
19299
|
grant_types: ["authorization_code", "refresh_token"],
|
|
19299
19300
|
response_types: ["code"],
|
|
19300
|
-
|
|
19301
|
-
//
|
|
19301
|
+
// DCR 与 static public client 都是 PKCE public client;confidential client 由 AS
|
|
19302
|
+
// metadata + client_secret 自动协商 basic/post,不在 metadata 里误声明 none。
|
|
19303
|
+
...this.opts.oauthClient?.clientSecretRef === void 0 ? { token_endpoint_auth_method: "none" } : {}
|
|
19302
19304
|
};
|
|
19303
19305
|
}
|
|
19304
|
-
async clientInformation() {
|
|
19306
|
+
async clientInformation(ctx) {
|
|
19307
|
+
const configured = this.opts.oauthClient;
|
|
19308
|
+
if (configured !== void 0) {
|
|
19309
|
+
let clientSecret;
|
|
19310
|
+
if (configured.clientSecretRef !== void 0) {
|
|
19311
|
+
clientSecret = await this.opts.secrets.resolve(configured.clientSecretRef);
|
|
19312
|
+
if (clientSecret === void 0) {
|
|
19313
|
+
throw new TBError(
|
|
19314
|
+
"unavailable",
|
|
19315
|
+
`OAuth client secret ref '${configured.clientSecretRef}' \u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u89E3\u5BC6`,
|
|
19316
|
+
{ retryable: false }
|
|
19317
|
+
);
|
|
19318
|
+
}
|
|
19319
|
+
}
|
|
19320
|
+
return {
|
|
19321
|
+
client_id: configured.clientId,
|
|
19322
|
+
...clientSecret !== void 0 ? { client_secret: clientSecret } : {},
|
|
19323
|
+
...ctx?.issuer !== void 0 ? { issuer: ctx.issuer } : {}
|
|
19324
|
+
};
|
|
19325
|
+
}
|
|
19305
19326
|
const raw = await this.opts.store.get(KEY_OAUTH_CLIENT + this.opts.nodePath);
|
|
19306
19327
|
if (raw === null) {
|
|
19307
19328
|
if (this.opts.mode === "deny") throw reauthorizeRequired(this.opts.nodePath);
|
|
@@ -19310,14 +19331,47 @@ var GatewayMcpOAuthProvider = class {
|
|
|
19310
19331
|
return raw;
|
|
19311
19332
|
}
|
|
19312
19333
|
async saveClientInformation(info) {
|
|
19334
|
+
if (this.opts.oauthClient !== void 0) return;
|
|
19313
19335
|
await this.opts.store.put(KEY_OAUTH_CLIENT + this.opts.nodePath, info);
|
|
19314
19336
|
}
|
|
19315
|
-
async tokens() {
|
|
19337
|
+
async tokens(ctx) {
|
|
19316
19338
|
const raw = await this.opts.store.get(KEY_OAUTH_TOKENS + this.opts.nodePath);
|
|
19317
|
-
|
|
19339
|
+
if (raw === null) return void 0;
|
|
19340
|
+
const tokens = raw;
|
|
19341
|
+
const discovery = await this.opts.store.get(KEY_OAUTH_DISCOVERY + this.opts.nodePath);
|
|
19342
|
+
const discoveredIssuer = discovery?.authorizationServerMetadata?.issuer;
|
|
19343
|
+
if (discoveredIssuer !== void 0 && tokens.issuer !== void 0 && tokens.issuer !== discoveredIssuer) {
|
|
19344
|
+
await this.opts.store.delete(KEY_OAUTH_TOKENS + this.opts.nodePath);
|
|
19345
|
+
return void 0;
|
|
19346
|
+
}
|
|
19347
|
+
let binding = tokens.toolBridgeBinding;
|
|
19348
|
+
const client = await this.clientInformation(ctx);
|
|
19349
|
+
if (binding === void 0 && this.opts.oauthClient === void 0 && client !== void 0 && "redirect_uris" in client && client.redirect_uris?.length === 1) {
|
|
19350
|
+
binding = { clientId: client.client_id, redirectUri: client.redirect_uris[0] };
|
|
19351
|
+
await this.opts.store.put(KEY_OAUTH_TOKENS + this.opts.nodePath, {
|
|
19352
|
+
...tokens,
|
|
19353
|
+
toolBridgeBinding: binding
|
|
19354
|
+
});
|
|
19355
|
+
}
|
|
19356
|
+
const bindingMismatch = binding === void 0 || client === void 0 || binding.clientId !== client.client_id || this.opts.mode === "interactive" && binding.redirectUri !== this.redirectUrl;
|
|
19357
|
+
if (bindingMismatch) {
|
|
19358
|
+
await this.opts.store.delete(KEY_OAUTH_TOKENS + this.opts.nodePath);
|
|
19359
|
+
return void 0;
|
|
19360
|
+
}
|
|
19361
|
+
return tokens;
|
|
19318
19362
|
}
|
|
19319
|
-
async saveTokens(tokens) {
|
|
19320
|
-
await this.
|
|
19363
|
+
async saveTokens(tokens, ctx) {
|
|
19364
|
+
const client = await this.clientInformation(ctx);
|
|
19365
|
+
if (client === void 0) throw new TBError("internal", "OAuth client information missing");
|
|
19366
|
+
const previous = await this.opts.store.get(KEY_OAUTH_TOKENS + this.opts.nodePath);
|
|
19367
|
+
const redirectUri = this.opts.mode === "interactive" ? this.redirectUrl : previous?.toolBridgeBinding?.redirectUri ?? ("redirect_uris" in client && client.redirect_uris?.length === 1 ? client.redirect_uris[0] : void 0);
|
|
19368
|
+
if (redirectUri === void 0) {
|
|
19369
|
+
throw new TBError("permission_denied", "OAuth grant redirect binding missing; reauthorize");
|
|
19370
|
+
}
|
|
19371
|
+
await this.opts.store.put(KEY_OAUTH_TOKENS + this.opts.nodePath, {
|
|
19372
|
+
...tokens,
|
|
19373
|
+
toolBridgeBinding: { clientId: client.client_id, redirectUri }
|
|
19374
|
+
});
|
|
19321
19375
|
}
|
|
19322
19376
|
async saveCodeVerifier(codeVerifier) {
|
|
19323
19377
|
this.verifier = codeVerifier;
|
|
@@ -19339,7 +19393,13 @@ var GatewayMcpOAuthProvider = class {
|
|
|
19339
19393
|
p: this.opts.nodePath,
|
|
19340
19394
|
v: this.codeVerifier(),
|
|
19341
19395
|
exp: Math.floor(Date.now() / 1e3) + STATE_TTL_SEC,
|
|
19342
|
-
|
|
19396
|
+
r: this.redirectUrl,
|
|
19397
|
+
...this.opts.oauthClient !== void 0 ? {
|
|
19398
|
+
o: {
|
|
19399
|
+
i: this.opts.oauthClient.clientId,
|
|
19400
|
+
...this.opts.oauthClient.clientSecretRef !== void 0 ? { r: this.opts.oauthClient.clientSecretRef } : {}
|
|
19401
|
+
}
|
|
19402
|
+
} : {}
|
|
19343
19403
|
},
|
|
19344
19404
|
this.opts.encryptionKey
|
|
19345
19405
|
);
|
|
@@ -19356,7 +19416,9 @@ var GatewayMcpOAuthProvider = class {
|
|
|
19356
19416
|
/** SDK 对 InvalidClient/InvalidGrant 等可恢复错误的重试钩子:清对应缓存后重走。 */
|
|
19357
19417
|
async invalidateCredentials(scope) {
|
|
19358
19418
|
const { store, nodePath } = this.opts;
|
|
19359
|
-
if (scope === "all" || scope === "client")
|
|
19419
|
+
if ((scope === "all" || scope === "client") && this.opts.oauthClient === void 0) {
|
|
19420
|
+
await store.delete(KEY_OAUTH_CLIENT + nodePath);
|
|
19421
|
+
}
|
|
19360
19422
|
if (scope === "all" || scope === "tokens") await store.delete(KEY_OAUTH_TOKENS + nodePath);
|
|
19361
19423
|
if (scope === "all" || scope === "discovery") await store.delete(KEY_OAUTH_DISCOVERY + nodePath);
|
|
19362
19424
|
if (scope === "all" || scope === "verifier") this.verifier = void 0;
|
|
@@ -19383,6 +19445,13 @@ async function guardOAuth(fn) {
|
|
|
19383
19445
|
return await fn();
|
|
19384
19446
|
} catch (err) {
|
|
19385
19447
|
if (err instanceof TBError) throw err;
|
|
19448
|
+
if (err instanceof Error && /does not support dynamic client registration/i.test(err.message)) {
|
|
19449
|
+
throw new TBError(
|
|
19450
|
+
"invalid_argument",
|
|
19451
|
+
"authorization server \u672A\u63D0\u4F9B\u52A8\u6001\u5BA2\u6237\u7AEF\u6CE8\u518C\uFF1B\u8BF7\u914D\u7F6E\u9884\u6CE8\u518C OAuth client(CLI: --oauth-client-id\uFF0C\u53EF\u9009 --oauth-client-secret-ref)",
|
|
19452
|
+
{ retryable: false }
|
|
19453
|
+
);
|
|
19454
|
+
}
|
|
19386
19455
|
throw new TBError(
|
|
19387
19456
|
"unavailable",
|
|
19388
19457
|
`OAuth flow failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -19391,11 +19460,13 @@ async function guardOAuth(fn) {
|
|
|
19391
19460
|
}
|
|
19392
19461
|
}
|
|
19393
19462
|
async function startMcpAuthorization(opts) {
|
|
19394
|
-
if (opts.redirectUri !== void 0)
|
|
19395
|
-
|
|
19463
|
+
if (opts.redirectUri !== void 0) assertLocalRedirectUri(opts.redirectUri);
|
|
19464
|
+
const redirectUri = opts.redirectUri ?? `${opts.origin}${OAUTH_CALLBACK_PATH}`;
|
|
19465
|
+
if (opts.oauthClient === void 0) {
|
|
19396
19466
|
const cached = await opts.store.get(KEY_OAUTH_CLIENT + opts.nodePath);
|
|
19397
|
-
if (cached !== null && !(cached.redirect_uris ?? []).includes(
|
|
19467
|
+
if (cached !== null && !(cached.redirect_uris ?? []).includes(redirectUri)) {
|
|
19398
19468
|
await opts.store.delete(KEY_OAUTH_CLIENT + opts.nodePath);
|
|
19469
|
+
await opts.store.delete(KEY_OAUTH_TOKENS + opts.nodePath);
|
|
19399
19470
|
}
|
|
19400
19471
|
}
|
|
19401
19472
|
const provider = new GatewayMcpOAuthProvider({
|
|
@@ -19404,10 +19475,12 @@ async function startMcpAuthorization(opts) {
|
|
|
19404
19475
|
encryptionKey: opts.encryptionKey,
|
|
19405
19476
|
mode: "interactive",
|
|
19406
19477
|
origin: opts.origin,
|
|
19478
|
+
secrets: opts.secrets,
|
|
19479
|
+
...opts.oauthClient !== void 0 ? { oauthClient: opts.oauthClient } : {},
|
|
19407
19480
|
...opts.redirectUri !== void 0 ? { redirectUri: opts.redirectUri } : {}
|
|
19408
19481
|
});
|
|
19409
19482
|
return await guardOAuth(async () => {
|
|
19410
|
-
const result = await auth(provider, { serverUrl: opts.serverUrl });
|
|
19483
|
+
const result = await auth(provider, { serverUrl: opts.serverUrl, fetchFn: mcpOAuthFetch });
|
|
19411
19484
|
if (result === "AUTHORIZED") return { status: "authorized" };
|
|
19412
19485
|
const url2 = provider.capturedAuthorizationUrl;
|
|
19413
19486
|
if (url2 === void 0) {
|
|
@@ -19423,13 +19496,17 @@ async function finishMcpAuthorization(opts) {
|
|
|
19423
19496
|
encryptionKey: opts.encryptionKey,
|
|
19424
19497
|
mode: "interactive",
|
|
19425
19498
|
origin: opts.origin,
|
|
19499
|
+
secrets: opts.secrets,
|
|
19500
|
+
...opts.oauthClient !== void 0 ? { oauthClient: opts.oauthClient } : {},
|
|
19426
19501
|
...opts.redirectUri !== void 0 ? { redirectUri: opts.redirectUri } : {}
|
|
19427
19502
|
});
|
|
19428
19503
|
provider.setCodeVerifier(opts.codeVerifier);
|
|
19429
19504
|
await guardOAuth(async () => {
|
|
19430
19505
|
const result = await auth(provider, {
|
|
19431
19506
|
serverUrl: opts.serverUrl,
|
|
19432
|
-
authorizationCode: opts.code
|
|
19507
|
+
authorizationCode: opts.code,
|
|
19508
|
+
fetchFn: mcpOAuthFetch,
|
|
19509
|
+
...opts.authorizationResponseIssuer !== void 0 ? { iss: opts.authorizationResponseIssuer } : {}
|
|
19433
19510
|
});
|
|
19434
19511
|
if (result !== "AUTHORIZED") {
|
|
19435
19512
|
throw new TBError("internal", "authorization did not complete");
|
|
@@ -19496,7 +19573,7 @@ var noStandaloneSseFetch = (input, init) => {
|
|
|
19496
19573
|
if (String(method).toUpperCase() === "GET" && accept.includes("text/event-stream")) {
|
|
19497
19574
|
return Promise.resolve(new Response(null, { status: 405, statusText: "Method Not Allowed" }));
|
|
19498
19575
|
}
|
|
19499
|
-
return
|
|
19576
|
+
return mcpOAuthFetch(input, init);
|
|
19500
19577
|
};
|
|
19501
19578
|
var ERA_KEY_PREFIX = "mcpera:";
|
|
19502
19579
|
var LEGACY_ERA_HORIZON_SEC = 300;
|
|
@@ -19636,7 +19713,9 @@ function createMcpProvider(config, secrets, opts) {
|
|
|
19636
19713
|
store: opts.oauth.store,
|
|
19637
19714
|
nodePath,
|
|
19638
19715
|
encryptionKey: opts.oauth.encryptionKey,
|
|
19639
|
-
mode: "deny"
|
|
19716
|
+
mode: "deny",
|
|
19717
|
+
secrets,
|
|
19718
|
+
...config.oauthClient !== void 0 ? { oauthClient: config.oauthClient } : {}
|
|
19640
19719
|
})
|
|
19641
19720
|
};
|
|
19642
19721
|
}
|
|
@@ -19738,15 +19817,6 @@ async function clientCredential(secrets, authRef) {
|
|
|
19738
19817
|
...values.clientSecret === void 0 ? {} : { clientSecret: values.clientSecret }
|
|
19739
19818
|
};
|
|
19740
19819
|
}
|
|
19741
|
-
function parseTokenErrorMessage(payload) {
|
|
19742
|
-
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return void 0;
|
|
19743
|
-
const record2 = payload;
|
|
19744
|
-
for (const key of ["error_description", "error", "msg", "message"]) {
|
|
19745
|
-
const value = record2[key];
|
|
19746
|
-
if (typeof value === "string" && value !== "") return value;
|
|
19747
|
-
}
|
|
19748
|
-
return void 0;
|
|
19749
|
-
}
|
|
19750
19820
|
async function writeTokens(store, nodePath, next, previous) {
|
|
19751
19821
|
const merged = {
|
|
19752
19822
|
...next,
|
|
@@ -19770,10 +19840,10 @@ async function requestTokens(opts) {
|
|
|
19770
19840
|
let response;
|
|
19771
19841
|
try {
|
|
19772
19842
|
response = await opts.fetcher(endpoint, { method: "POST", headers, body });
|
|
19773
|
-
} catch
|
|
19843
|
+
} catch {
|
|
19774
19844
|
throw new TBError(
|
|
19775
19845
|
"unavailable",
|
|
19776
|
-
|
|
19846
|
+
"\u4EE4\u724C\u7AEF\u70B9\u4E0D\u53EF\u8FBE",
|
|
19777
19847
|
{ retryable: true }
|
|
19778
19848
|
);
|
|
19779
19849
|
}
|
|
@@ -19785,14 +19855,20 @@ async function requestTokens(opts) {
|
|
|
19785
19855
|
payload = null;
|
|
19786
19856
|
}
|
|
19787
19857
|
if (!response.ok) {
|
|
19788
|
-
const
|
|
19789
|
-
const detail = parsed ?? `HTTP ${response.status}`;
|
|
19858
|
+
const detail = `HTTP ${response.status}`;
|
|
19790
19859
|
if (response.status >= 500) {
|
|
19791
19860
|
throw new TBError("unavailable", `\u4EE4\u724C\u7AEF\u70B9\u8FD4\u56DE ${detail}`, { retryable: true });
|
|
19792
19861
|
}
|
|
19793
19862
|
throw new TBError("invalid_argument", `\u4EE4\u724C\u7AEF\u70B9\u62D2\u7EDD\u4E86\u8BF7\u6C42:${detail}`);
|
|
19794
19863
|
}
|
|
19795
|
-
|
|
19864
|
+
try {
|
|
19865
|
+
return parseTokenResponse(payload, opts.config, opts.now);
|
|
19866
|
+
} catch (err) {
|
|
19867
|
+
if (isTBError(err) && err.code === "invalid_argument") {
|
|
19868
|
+
throw new TBError("invalid_argument", "\u4EE4\u724C\u7AEF\u70B9\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94");
|
|
19869
|
+
}
|
|
19870
|
+
throw err;
|
|
19871
|
+
}
|
|
19796
19872
|
}
|
|
19797
19873
|
function base64url(bytes) {
|
|
19798
19874
|
let binary = "";
|
|
@@ -19804,7 +19880,14 @@ async function createPkce() {
|
|
|
19804
19880
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
19805
19881
|
return { verifier, challenge: base64url(new Uint8Array(digest)) };
|
|
19806
19882
|
}
|
|
19883
|
+
function requireProviderOAuthFetch(fetcher) {
|
|
19884
|
+
if (fetcher === void 0) {
|
|
19885
|
+
throw new TBError("unavailable", "Provider OAuth \u51FA\u7AD9\u901A\u9053\u672A\u914D\u7F6E", { retryable: false });
|
|
19886
|
+
}
|
|
19887
|
+
return fetcher;
|
|
19888
|
+
}
|
|
19807
19889
|
async function startProviderAuthorization(opts) {
|
|
19890
|
+
const fetcher = requireProviderOAuthFetch(opts.fetcher);
|
|
19808
19891
|
const existing = await readTokens(opts.store, opts.nodePath);
|
|
19809
19892
|
if (existing !== void 0 && !shouldRefresh(existing, opts.now)) {
|
|
19810
19893
|
return { status: "authorized" };
|
|
@@ -19814,7 +19897,7 @@ async function startProviderAuthorization(opts) {
|
|
|
19814
19897
|
const client2 = await clientCredential(opts.secrets, opts.authRef);
|
|
19815
19898
|
const refreshed = await requestTokens({
|
|
19816
19899
|
config: opts.config,
|
|
19817
|
-
fetcher
|
|
19900
|
+
fetcher,
|
|
19818
19901
|
now: opts.now,
|
|
19819
19902
|
grant: { refreshToken: existing.refreshToken },
|
|
19820
19903
|
...client2
|
|
@@ -19847,10 +19930,11 @@ async function startProviderAuthorization(opts) {
|
|
|
19847
19930
|
};
|
|
19848
19931
|
}
|
|
19849
19932
|
async function finishProviderAuthorization(opts) {
|
|
19933
|
+
const fetcher = requireProviderOAuthFetch(opts.fetcher);
|
|
19850
19934
|
const client = await clientCredential(opts.secrets, opts.authRef);
|
|
19851
19935
|
const tokens = await requestTokens({
|
|
19852
19936
|
config: opts.config,
|
|
19853
|
-
fetcher
|
|
19937
|
+
fetcher,
|
|
19854
19938
|
now: opts.now,
|
|
19855
19939
|
grant: { code: opts.code, redirectUri: opts.origin + PROVIDER_OAUTH_CALLBACK_PATH },
|
|
19856
19940
|
...opts.codeVerifier === "" ? {} : { codeVerifier: opts.codeVerifier },
|
|
@@ -19859,6 +19943,7 @@ async function finishProviderAuthorization(opts) {
|
|
|
19859
19943
|
await writeTokens(opts.store, opts.nodePath, tokens);
|
|
19860
19944
|
}
|
|
19861
19945
|
async function resolveProviderAccessToken(opts) {
|
|
19946
|
+
const fetcher = requireProviderOAuthFetch(opts.fetcher);
|
|
19862
19947
|
const tokens = await readTokens(opts.store, opts.nodePath);
|
|
19863
19948
|
if (tokens === void 0) throw providerReauthorizeRequired(opts.nodePath);
|
|
19864
19949
|
if (opts.force !== true && !shouldRefresh(tokens, opts.now)) return tokens.accessToken;
|
|
@@ -19868,7 +19953,7 @@ async function resolveProviderAccessToken(opts) {
|
|
|
19868
19953
|
try {
|
|
19869
19954
|
refreshed = await requestTokens({
|
|
19870
19955
|
config: opts.config,
|
|
19871
|
-
fetcher
|
|
19956
|
+
fetcher,
|
|
19872
19957
|
now: opts.now,
|
|
19873
19958
|
grant: { refreshToken: tokens.refreshToken },
|
|
19874
19959
|
...client
|
|
@@ -19972,7 +20057,7 @@ async function providerAccessTokenFor(opts) {
|
|
|
19972
20057
|
config: opts.config,
|
|
19973
20058
|
encryptionKey,
|
|
19974
20059
|
...opts.force === true ? { force: true } : {},
|
|
19975
|
-
fetcher:
|
|
20060
|
+
fetcher: opts.deps.providerOAuthFetch,
|
|
19976
20061
|
nodePath: opts.nodePath,
|
|
19977
20062
|
now: /* @__PURE__ */ new Date(),
|
|
19978
20063
|
// 调用期不需要 origin(只在构造授权 URL 时用);传空串会被 buildAuthorizationUrl 用到,
|
|
@@ -20313,11 +20398,11 @@ async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
|
20313
20398
|
if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
|
|
20314
20399
|
const encKey = deps.encryptionKey;
|
|
20315
20400
|
if (encKey !== void 0) {
|
|
20316
|
-
const
|
|
20401
|
+
const origin = new URL(requestUrl).origin;
|
|
20317
20402
|
const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
|
|
20318
20403
|
opts.relayRefUrl = async (key) => {
|
|
20319
20404
|
const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
|
|
20320
|
-
return `${
|
|
20405
|
+
return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
20321
20406
|
};
|
|
20322
20407
|
}
|
|
20323
20408
|
return createObjectContextProvider(objects, opts);
|
|
@@ -20338,69 +20423,20 @@ async function skillhubProviderFor(node, cfg, deps, requestUrl) {
|
|
|
20338
20423
|
if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
|
|
20339
20424
|
const encKey = deps.encryptionKey;
|
|
20340
20425
|
if (encKey !== void 0) {
|
|
20341
|
-
const
|
|
20426
|
+
const origin = new URL(requestUrl).origin;
|
|
20342
20427
|
const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
|
|
20343
20428
|
opts.relayRefUrl = async (key) => {
|
|
20344
20429
|
const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
|
|
20345
|
-
return `${
|
|
20430
|
+
return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
20346
20431
|
};
|
|
20347
20432
|
}
|
|
20348
20433
|
return createSkillhubProvider(objects, opts);
|
|
20349
20434
|
}
|
|
20350
|
-
async function
|
|
20351
|
-
|
|
20352
|
-
|
|
20353
|
-
|
|
20354
|
-
|
|
20355
|
-
return typeof args.file === "string" ? await provider.get_file(args.id, args.file) : await provider.get(args.id);
|
|
20356
|
-
case "search":
|
|
20357
|
-
return await provider.search(args.query, args.opts);
|
|
20358
|
-
case "publish":
|
|
20359
|
-
if (!Array.isArray(args.files)) {
|
|
20360
|
-
throw new TBError("invalid_argument", "publish \u9700\u8981\u6570\u7EC4 'files'");
|
|
20361
|
-
}
|
|
20362
|
-
return await provider.publish({
|
|
20363
|
-
...typeof args.id === "string" ? { id: args.id } : {},
|
|
20364
|
-
files: args.files
|
|
20365
|
-
});
|
|
20366
|
-
case "remove":
|
|
20367
|
-
return await provider.remove(args.id);
|
|
20368
|
-
default:
|
|
20369
|
-
throw new TBError("invalid_argument", `unknown cmd '${command}'`);
|
|
20370
|
-
}
|
|
20371
|
-
}
|
|
20372
|
-
async function dispatchContextCmd(provider, command, args) {
|
|
20373
|
-
const unimplemented = () => {
|
|
20374
|
-
throw new TBError("invalid_argument", `unknown cmd '${command}'(provider \u672A\u5B9E\u73B0)`);
|
|
20375
|
-
};
|
|
20376
|
-
switch (command.toLowerCase()) {
|
|
20377
|
-
case "list":
|
|
20378
|
-
if (provider.list === void 0) return unimplemented();
|
|
20379
|
-
return await provider.list(args.path ?? "", args.opts);
|
|
20380
|
-
case "get":
|
|
20381
|
-
if (provider.get === void 0) return unimplemented();
|
|
20382
|
-
return await provider.get(args.path);
|
|
20383
|
-
case "write":
|
|
20384
|
-
if (provider.write === void 0) return unimplemented();
|
|
20385
|
-
if (typeof args.entry !== "object" || args.entry === null) {
|
|
20386
|
-
throw new TBError("invalid_argument", "write \u9700\u8981\u5BF9\u8C61 'entry'");
|
|
20387
|
-
}
|
|
20388
|
-
return await provider.write(args.path, args.entry);
|
|
20389
|
-
case "update":
|
|
20390
|
-
if (provider.update === void 0) return unimplemented();
|
|
20391
|
-
if (typeof args.patch !== "object" || args.patch === null) {
|
|
20392
|
-
throw new TBError("invalid_argument", "update \u9700\u8981\u5BF9\u8C61 'patch'");
|
|
20393
|
-
}
|
|
20394
|
-
return await provider.update(args.path, args.patch);
|
|
20395
|
-
case "delete":
|
|
20396
|
-
if (provider.delete === void 0) return unimplemented();
|
|
20397
|
-
return await provider.delete(args.path);
|
|
20398
|
-
case "search":
|
|
20399
|
-
if (provider.search === void 0) return unimplemented();
|
|
20400
|
-
return await provider.search(args.query, args.opts);
|
|
20401
|
-
default:
|
|
20402
|
-
throw new TBError("invalid_argument", `unknown cmd '${command}'`);
|
|
20403
|
-
}
|
|
20435
|
+
async function dispatchSkillhubCmd2(provider, command, args) {
|
|
20436
|
+
return await dispatchSkillhubCmd(provider, command, args);
|
|
20437
|
+
}
|
|
20438
|
+
async function dispatchContextCmd2(provider, command, args) {
|
|
20439
|
+
return await dispatchContextCmd(provider, command, args);
|
|
20404
20440
|
}
|
|
20405
20441
|
async function assertContextAlive(node, cfg, registry) {
|
|
20406
20442
|
if (!isContextExpired(node.createdAt, cfg.ttl, Date.now())) return;
|
|
@@ -23845,8 +23881,8 @@ var LADDER_ERROR_HTTP_STATUS2 = {
|
|
|
23845
23881
|
[ProtocolErrorCode2.MissingRequiredClientCapability]: 400,
|
|
23846
23882
|
[HEADER_MISMATCH_ERROR_CODE2]: 400
|
|
23847
23883
|
};
|
|
23848
|
-
function httpStatusForErrorCode(code,
|
|
23849
|
-
if (
|
|
23884
|
+
function httpStatusForErrorCode(code, origin) {
|
|
23885
|
+
if (origin === "in-band") return code === ProtocolErrorCode2.MissingRequiredClientCapability ? 400 : 200;
|
|
23850
23886
|
return LADDER_ERROR_HTTP_STATUS2[code] ?? 400;
|
|
23851
23887
|
}
|
|
23852
23888
|
function rejection(rung, cell, httpStatus, error, settled) {
|
|
@@ -24059,8 +24095,8 @@ function modernOnlyStrictRejection(route, supportedVersions) {
|
|
|
24059
24095
|
function parseSchema2(schema, data) {
|
|
24060
24096
|
return safeParse(schema, data);
|
|
24061
24097
|
}
|
|
24062
|
-
function shapeKeys2(
|
|
24063
|
-
return new Set(
|
|
24098
|
+
function shapeKeys2(schemas2) {
|
|
24099
|
+
return new Set(schemas2.flatMap((schema) => Object.keys(schema.shape)));
|
|
24064
24100
|
}
|
|
24065
24101
|
function isStandardSchema2(schema) {
|
|
24066
24102
|
if (schema == null) return false;
|
|
@@ -28245,13 +28281,13 @@ var require_utils2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
|
|
|
28245
28281
|
const buffer = [];
|
|
28246
28282
|
let endipv6Encountered = false;
|
|
28247
28283
|
let endIpv6 = false;
|
|
28248
|
-
let
|
|
28284
|
+
let consume2 = consumeHextets;
|
|
28249
28285
|
for (let i = 0; i < input.length; i++) {
|
|
28250
28286
|
const cursor = input[i];
|
|
28251
28287
|
if (cursor === "[" || cursor === "]") continue;
|
|
28252
28288
|
if (cursor === ":") {
|
|
28253
28289
|
if (endipv6Encountered === true) endIpv6 = true;
|
|
28254
|
-
if (!
|
|
28290
|
+
if (!consume2(buffer, address, output)) break;
|
|
28255
28291
|
if (++tokenCount > 7) {
|
|
28256
28292
|
output.error = true;
|
|
28257
28293
|
break;
|
|
@@ -28260,14 +28296,14 @@ var require_utils2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
|
|
|
28260
28296
|
address.push(":");
|
|
28261
28297
|
continue;
|
|
28262
28298
|
} else if (cursor === "%") {
|
|
28263
|
-
if (!
|
|
28264
|
-
|
|
28299
|
+
if (!consume2(buffer, address, output)) break;
|
|
28300
|
+
consume2 = consumeIsZone;
|
|
28265
28301
|
} else {
|
|
28266
28302
|
buffer.push(cursor);
|
|
28267
28303
|
continue;
|
|
28268
28304
|
}
|
|
28269
28305
|
}
|
|
28270
|
-
if (buffer.length) if (
|
|
28306
|
+
if (buffer.length) if (consume2 === consumeIsZone) output.zone = buffer.join("");
|
|
28271
28307
|
else if (endIpv6) address.push(buffer.join(""));
|
|
28272
28308
|
else address.push(stringArrayToHexStripped(buffer));
|
|
28273
28309
|
output.address = address.join("");
|
|
@@ -29157,14 +29193,14 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
29157
29193
|
}
|
|
29158
29194
|
return metaSchema;
|
|
29159
29195
|
}
|
|
29160
|
-
_removeAllSchemas(
|
|
29161
|
-
for (const keyRef in
|
|
29162
|
-
const sch =
|
|
29196
|
+
_removeAllSchemas(schemas2, regex3) {
|
|
29197
|
+
for (const keyRef in schemas2) {
|
|
29198
|
+
const sch = schemas2[keyRef];
|
|
29163
29199
|
if (!regex3 || regex3.test(keyRef)) {
|
|
29164
|
-
if (typeof sch == "string") delete
|
|
29200
|
+
if (typeof sch == "string") delete schemas2[keyRef];
|
|
29165
29201
|
else if (sch && !sch.meta) {
|
|
29166
29202
|
this._cache.delete(sch.schema);
|
|
29167
|
-
delete
|
|
29203
|
+
delete schemas2[keyRef];
|
|
29168
29204
|
}
|
|
29169
29205
|
}
|
|
29170
29206
|
}
|
|
@@ -36438,6 +36474,121 @@ function createPluginContextProvider(opts) {
|
|
|
36438
36474
|
|
|
36439
36475
|
// ../app/src/providers/remote.ts
|
|
36440
36476
|
var VIA_HEADER = "x-tb-via";
|
|
36477
|
+
function remoteAbortError() {
|
|
36478
|
+
return new TBError("unavailable", "remote request aborted", { retryable: true });
|
|
36479
|
+
}
|
|
36480
|
+
function remoteResponseTooLarge(maxBytes) {
|
|
36481
|
+
return new TBError(
|
|
36482
|
+
"unavailable",
|
|
36483
|
+
`remote response body exceeds ${maxBytes} bytes`,
|
|
36484
|
+
{ retryable: false }
|
|
36485
|
+
);
|
|
36486
|
+
}
|
|
36487
|
+
function isAbortError(error, signal) {
|
|
36488
|
+
return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
|
|
36489
|
+
}
|
|
36490
|
+
function signalAborted(signal) {
|
|
36491
|
+
return signal?.aborted === true;
|
|
36492
|
+
}
|
|
36493
|
+
function declaredContentLength(response) {
|
|
36494
|
+
const value = response.headers.get("content-length");
|
|
36495
|
+
if (value === null || !/^\d+$/.test(value)) return void 0;
|
|
36496
|
+
const parsed = Number(value);
|
|
36497
|
+
return Number.isSafeInteger(parsed) ? parsed : Number.POSITIVE_INFINITY;
|
|
36498
|
+
}
|
|
36499
|
+
async function readRemoteResponseBody(response, maxBytes, signal) {
|
|
36500
|
+
if (signal?.aborted === true) throw remoteAbortError();
|
|
36501
|
+
const declared = declaredContentLength(response);
|
|
36502
|
+
if (maxBytes !== void 0 && declared !== void 0 && declared > maxBytes) {
|
|
36503
|
+
void response.body?.cancel().catch(() => {
|
|
36504
|
+
});
|
|
36505
|
+
throw remoteResponseTooLarge(maxBytes);
|
|
36506
|
+
}
|
|
36507
|
+
if (response.body === null) return new Uint8Array();
|
|
36508
|
+
const reader = response.body.getReader();
|
|
36509
|
+
const chunks = [];
|
|
36510
|
+
let total = 0;
|
|
36511
|
+
let abortListener;
|
|
36512
|
+
const abortPromise = signal === void 0 ? void 0 : new Promise((_resolve, reject) => {
|
|
36513
|
+
abortListener = () => {
|
|
36514
|
+
reject(remoteAbortError());
|
|
36515
|
+
void reader.cancel().catch(() => {
|
|
36516
|
+
});
|
|
36517
|
+
};
|
|
36518
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
36519
|
+
if (signal.aborted) abortListener();
|
|
36520
|
+
});
|
|
36521
|
+
try {
|
|
36522
|
+
while (true) {
|
|
36523
|
+
const result = await (abortPromise === void 0 ? reader.read() : Promise.race([reader.read(), abortPromise]));
|
|
36524
|
+
if (signalAborted(signal)) throw remoteAbortError();
|
|
36525
|
+
if (result.done) break;
|
|
36526
|
+
total += result.value.byteLength;
|
|
36527
|
+
if (maxBytes !== void 0 && total > maxBytes) {
|
|
36528
|
+
void reader.cancel().catch(() => {
|
|
36529
|
+
});
|
|
36530
|
+
throw remoteResponseTooLarge(maxBytes);
|
|
36531
|
+
}
|
|
36532
|
+
chunks.push(result.value);
|
|
36533
|
+
}
|
|
36534
|
+
} catch (error) {
|
|
36535
|
+
if (error instanceof TBError) throw error;
|
|
36536
|
+
if (isAbortError(error, signal)) throw remoteAbortError();
|
|
36537
|
+
throw normalizeUpstreamError({
|
|
36538
|
+
kind: "network",
|
|
36539
|
+
message: "failed to read remote response body"
|
|
36540
|
+
});
|
|
36541
|
+
} finally {
|
|
36542
|
+
if (abortListener !== void 0) signal?.removeEventListener("abort", abortListener);
|
|
36543
|
+
}
|
|
36544
|
+
const body = new Uint8Array(total);
|
|
36545
|
+
let offset = 0;
|
|
36546
|
+
for (const chunk of chunks) {
|
|
36547
|
+
body.set(chunk, offset);
|
|
36548
|
+
offset += chunk.byteLength;
|
|
36549
|
+
}
|
|
36550
|
+
return body;
|
|
36551
|
+
}
|
|
36552
|
+
var DEFAULT_FEDERATED_SEARCH_SETTINGS = {
|
|
36553
|
+
maxConcurrency: 4,
|
|
36554
|
+
maxResponseBodyBytes: 512 * 1024,
|
|
36555
|
+
maxSources: 16,
|
|
36556
|
+
minChildWorkMs: 200,
|
|
36557
|
+
perHopReturnReserveMs: 100,
|
|
36558
|
+
sessionTtlMs: 5 * 60 * 1e3,
|
|
36559
|
+
totalDeadlineMs: 2500
|
|
36560
|
+
};
|
|
36561
|
+
function federatedSearchSettings(settings) {
|
|
36562
|
+
const configured = settings.federatedSearch ?? {};
|
|
36563
|
+
const positive = (value, fallback) => value !== void 0 && Number.isFinite(value) && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
36564
|
+
return {
|
|
36565
|
+
maxConcurrency: positive(
|
|
36566
|
+
configured.maxConcurrency,
|
|
36567
|
+
DEFAULT_FEDERATED_SEARCH_SETTINGS.maxConcurrency
|
|
36568
|
+
),
|
|
36569
|
+
maxResponseBodyBytes: positive(
|
|
36570
|
+
configured.maxResponseBodyBytes,
|
|
36571
|
+
DEFAULT_FEDERATED_SEARCH_SETTINGS.maxResponseBodyBytes
|
|
36572
|
+
),
|
|
36573
|
+
maxSources: positive(configured.maxSources, DEFAULT_FEDERATED_SEARCH_SETTINGS.maxSources),
|
|
36574
|
+
minChildWorkMs: positive(
|
|
36575
|
+
configured.minChildWorkMs,
|
|
36576
|
+
DEFAULT_FEDERATED_SEARCH_SETTINGS.minChildWorkMs
|
|
36577
|
+
),
|
|
36578
|
+
perHopReturnReserveMs: positive(
|
|
36579
|
+
configured.perHopReturnReserveMs,
|
|
36580
|
+
DEFAULT_FEDERATED_SEARCH_SETTINGS.perHopReturnReserveMs
|
|
36581
|
+
),
|
|
36582
|
+
sessionTtlMs: positive(
|
|
36583
|
+
configured.sessionTtlMs,
|
|
36584
|
+
DEFAULT_FEDERATED_SEARCH_SETTINGS.sessionTtlMs
|
|
36585
|
+
),
|
|
36586
|
+
totalDeadlineMs: positive(
|
|
36587
|
+
configured.totalDeadlineMs,
|
|
36588
|
+
DEFAULT_FEDERATED_SEARCH_SETTINGS.totalDeadlineMs
|
|
36589
|
+
)
|
|
36590
|
+
};
|
|
36591
|
+
}
|
|
36441
36592
|
function selfInstanceId(settings, requestUrl) {
|
|
36442
36593
|
if (settings.instanceId !== void 0 && settings.instanceId.length > 0) {
|
|
36443
36594
|
return settings.instanceId;
|
|
@@ -36456,6 +36607,13 @@ function assertRemoteAllowed(baseUrl, settings) {
|
|
|
36456
36607
|
}
|
|
36457
36608
|
}
|
|
36458
36609
|
async function passthroughRemote(opts) {
|
|
36610
|
+
if (opts.maxResponseBodyBytes !== void 0 && (!Number.isSafeInteger(opts.maxResponseBodyBytes) || opts.maxResponseBodyBytes < 0)) {
|
|
36611
|
+
throw new TBError(
|
|
36612
|
+
"invalid_argument",
|
|
36613
|
+
"maxResponseBodyBytes must be a non-negative safe integer"
|
|
36614
|
+
);
|
|
36615
|
+
}
|
|
36616
|
+
if (opts.signal?.aborted === true) throw remoteAbortError();
|
|
36459
36617
|
const secErr = assertSecureUrl(opts.config.baseUrl, opts.settings.allowInsecure);
|
|
36460
36618
|
if (secErr) throw secErr;
|
|
36461
36619
|
if (!checkAllowlist(opts.config.baseUrl, opts.settings.allowlist)) {
|
|
@@ -36480,6 +36638,16 @@ async function passthroughRemote(opts) {
|
|
|
36480
36638
|
if (accept !== null) outHeaders.accept = accept;
|
|
36481
36639
|
const contentType = opts.headers.get("content-type");
|
|
36482
36640
|
if (contentType !== null) outHeaders["content-type"] = contentType;
|
|
36641
|
+
for (const name of [
|
|
36642
|
+
"x-tb-search-remaining-ms",
|
|
36643
|
+
"x-tb-search-session-ttl-ms",
|
|
36644
|
+
"x-tb-search-source-budget",
|
|
36645
|
+
"x-tb-search-validate-snapshot",
|
|
36646
|
+
"x-tb-search-want-snapshot"
|
|
36647
|
+
]) {
|
|
36648
|
+
const value = opts.headers.get(name);
|
|
36649
|
+
if (value !== null) outHeaders[name] = value;
|
|
36650
|
+
}
|
|
36483
36651
|
if (opts.config.skRef !== void 0) {
|
|
36484
36652
|
const cred = await opts.secrets.resolve(opts.config.skRef);
|
|
36485
36653
|
if (cred === void 0) {
|
|
@@ -36507,22 +36675,304 @@ async function passthroughRemote(opts) {
|
|
|
36507
36675
|
resp = await fetch(target, {
|
|
36508
36676
|
method: opts.method,
|
|
36509
36677
|
headers: outHeaders,
|
|
36678
|
+
// remote 请求可能携带高权 service credential 与敏感 arguments。禁止自动跟随任何
|
|
36679
|
+
// 30x,避免 redirect 绕过初始 baseUrl 的 allowlist/HTTPS 校验或把 body 发往另一目标。
|
|
36680
|
+
redirect: "error",
|
|
36681
|
+
...opts.signal === void 0 ? {} : { signal: opts.signal },
|
|
36510
36682
|
...opts.body !== void 0 && opts.body.length > 0 ? { body: opts.body } : {}
|
|
36511
36683
|
});
|
|
36512
36684
|
} catch (err) {
|
|
36685
|
+
if (isAbortError(err, opts.signal)) throw remoteAbortError();
|
|
36513
36686
|
throw normalizeUpstreamError({
|
|
36514
36687
|
kind: "network",
|
|
36515
36688
|
message: err instanceof Error ? err.message : String(err)
|
|
36516
36689
|
});
|
|
36517
36690
|
}
|
|
36518
|
-
const respBody = await
|
|
36691
|
+
const respBody = await readRemoteResponseBody(
|
|
36692
|
+
resp,
|
|
36693
|
+
opts.maxResponseBodyBytes,
|
|
36694
|
+
opts.signal
|
|
36695
|
+
);
|
|
36519
36696
|
const respCt = resp.headers.get("content-type") ?? "application/octet-stream";
|
|
36520
|
-
return new Response(respBody
|
|
36697
|
+
return new Response(respBody.buffer, {
|
|
36698
|
+
status: resp.status,
|
|
36699
|
+
headers: { "content-type": respCt }
|
|
36700
|
+
});
|
|
36521
36701
|
}
|
|
36522
36702
|
|
|
36523
36703
|
// ../app/src/tbApp.ts
|
|
36524
36704
|
import { Hono } from "hono";
|
|
36525
36705
|
|
|
36706
|
+
// ../core/src/protocol/openapi.ts
|
|
36707
|
+
var schemaRegistry = external_exports.registry();
|
|
36708
|
+
var schemas = {
|
|
36709
|
+
Error: tbErrorBodySchema,
|
|
36710
|
+
FeedbackDetail: feedbackDetailSchema,
|
|
36711
|
+
FeedbackList: feedbackListSchema,
|
|
36712
|
+
FeedbackRemoveResponse: feedbackRemoveResponseSchema,
|
|
36713
|
+
FeedbackSubmitRequest: feedbackSubmitRequestSchema,
|
|
36714
|
+
FeedbackSubmitResponse: feedbackSubmitResponseSchema,
|
|
36715
|
+
FeedbackView: feedbackViewSchema,
|
|
36716
|
+
FeedbackVoteRequest: feedbackVoteRequestSchema,
|
|
36717
|
+
Health: healthResponseSchema,
|
|
36718
|
+
Help: helpJsonSchema,
|
|
36719
|
+
Liveness: livenessResponseSchema,
|
|
36720
|
+
NodeInput: nodeInputSchema,
|
|
36721
|
+
OAuthAuthorizeRequest: oauthAuthorizeRequestSchema,
|
|
36722
|
+
OAuthAuthorizeResponse: oauthAuthorizeResponseSchema,
|
|
36723
|
+
Readiness: readinessResponseSchema,
|
|
36724
|
+
RegistryNode: registryNodeSchema,
|
|
36725
|
+
SearchRelevance: toolSearchRelevanceSchema,
|
|
36726
|
+
SearchRequest: toolSearchRequestSchema,
|
|
36727
|
+
SearchResponse: toolSearchPageSchema,
|
|
36728
|
+
SearchSource: toolSearchSourceSchema,
|
|
36729
|
+
SearchSourceResult: toolSearchSourceResultSchema,
|
|
36730
|
+
Tree: treeJsonSchema
|
|
36731
|
+
};
|
|
36732
|
+
for (const [id, schema] of Object.entries(schemas)) schemaRegistry.add(schema, { id });
|
|
36733
|
+
function asOpenApiSchema(value) {
|
|
36734
|
+
if (Array.isArray(value)) return value.map(asOpenApiSchema);
|
|
36735
|
+
if (value === null || typeof value !== "object") return value;
|
|
36736
|
+
const out = {};
|
|
36737
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
36738
|
+
if (key === "$schema") continue;
|
|
36739
|
+
if (key === "$ref" && typeof entry === "string" && !entry.startsWith("#/")) {
|
|
36740
|
+
out[key] = `#/components/schemas/${entry}`;
|
|
36741
|
+
} else {
|
|
36742
|
+
out[key] = asOpenApiSchema(entry);
|
|
36743
|
+
}
|
|
36744
|
+
}
|
|
36745
|
+
return out;
|
|
36746
|
+
}
|
|
36747
|
+
var generated = external_exports.toJSONSchema(schemaRegistry);
|
|
36748
|
+
var componentSchemas = Object.fromEntries(
|
|
36749
|
+
Object.entries(generated.schemas).map(([name, schema]) => [name, asOpenApiSchema(schema)])
|
|
36750
|
+
);
|
|
36751
|
+
var jsonContent = (schema) => ({
|
|
36752
|
+
"application/json": { schema: { $ref: `#/components/schemas/${schema}` } }
|
|
36753
|
+
});
|
|
36754
|
+
var jsonResponse = (description, schema) => ({
|
|
36755
|
+
description,
|
|
36756
|
+
content: jsonContent(schema)
|
|
36757
|
+
});
|
|
36758
|
+
var errorResponses = {
|
|
36759
|
+
400: jsonResponse("Invalid request", "Error"),
|
|
36760
|
+
401: jsonResponse("Unauthenticated", "Error"),
|
|
36761
|
+
403: jsonResponse("Permission denied", "Error"),
|
|
36762
|
+
404: jsonResponse("Not found or not visible", "Error"),
|
|
36763
|
+
409: jsonResponse("Conflict", "Error"),
|
|
36764
|
+
429: jsonResponse("Rate limited", "Error"),
|
|
36765
|
+
500: jsonResponse("Internal error", "Error"),
|
|
36766
|
+
503: jsonResponse("Unavailable", "Error")
|
|
36767
|
+
};
|
|
36768
|
+
var bearerSecurity = [{ bearerAuth: [] }];
|
|
36769
|
+
var pathParameter = {
|
|
36770
|
+
in: "path",
|
|
36771
|
+
name: "path",
|
|
36772
|
+
required: true,
|
|
36773
|
+
description: "Slash-delimited Tool Bridge tree path; each segment is URL encoded.",
|
|
36774
|
+
schema: { type: "string" }
|
|
36775
|
+
};
|
|
36776
|
+
var fixedControlPlaneOpenApi = {
|
|
36777
|
+
openapi: "3.1.0",
|
|
36778
|
+
info: {
|
|
36779
|
+
title: "Tool Bridge fixed control plane",
|
|
36780
|
+
version: "1.0.0"
|
|
36781
|
+
},
|
|
36782
|
+
components: {
|
|
36783
|
+
securitySchemes: {
|
|
36784
|
+
bearerAuth: { type: "http", scheme: "bearer" }
|
|
36785
|
+
},
|
|
36786
|
+
schemas: componentSchemas
|
|
36787
|
+
},
|
|
36788
|
+
paths: {
|
|
36789
|
+
"/healthz": {
|
|
36790
|
+
get: {
|
|
36791
|
+
operationId: "getHealth",
|
|
36792
|
+
security: [],
|
|
36793
|
+
responses: { 200: jsonResponse("Deployment health", "Health") }
|
|
36794
|
+
}
|
|
36795
|
+
},
|
|
36796
|
+
"/livez": {
|
|
36797
|
+
get: {
|
|
36798
|
+
operationId: "getLiveness",
|
|
36799
|
+
security: [],
|
|
36800
|
+
responses: { 200: jsonResponse("Process liveness", "Liveness") }
|
|
36801
|
+
}
|
|
36802
|
+
},
|
|
36803
|
+
"/readyz": {
|
|
36804
|
+
get: {
|
|
36805
|
+
operationId: "getReadiness",
|
|
36806
|
+
security: [],
|
|
36807
|
+
responses: {
|
|
36808
|
+
200: jsonResponse("Ready", "Readiness"),
|
|
36809
|
+
503: jsonResponse("Not ready", "Readiness")
|
|
36810
|
+
}
|
|
36811
|
+
}
|
|
36812
|
+
},
|
|
36813
|
+
"/~help": {
|
|
36814
|
+
get: {
|
|
36815
|
+
operationId: "getRootHelp",
|
|
36816
|
+
security: bearerSecurity,
|
|
36817
|
+
parameters: [{
|
|
36818
|
+
in: "query",
|
|
36819
|
+
name: "schemas",
|
|
36820
|
+
schema: { type: "string", enum: ["1"] }
|
|
36821
|
+
}],
|
|
36822
|
+
responses: { 200: jsonResponse("Root HTBP help", "Help"), ...errorResponses }
|
|
36823
|
+
}
|
|
36824
|
+
},
|
|
36825
|
+
"/{path}/~help": {
|
|
36826
|
+
get: {
|
|
36827
|
+
"operationId": "getHelp",
|
|
36828
|
+
"security": bearerSecurity,
|
|
36829
|
+
"parameters": [pathParameter, {
|
|
36830
|
+
in: "query",
|
|
36831
|
+
name: "schemas",
|
|
36832
|
+
schema: { type: "string", enum: ["1"] }
|
|
36833
|
+
}],
|
|
36834
|
+
"responses": { 200: jsonResponse("HTBP help", "Help"), ...errorResponses },
|
|
36835
|
+
"x-tool-bridge-greedy-path": true
|
|
36836
|
+
}
|
|
36837
|
+
},
|
|
36838
|
+
"/~tree": {
|
|
36839
|
+
get: {
|
|
36840
|
+
operationId: "getRootTree",
|
|
36841
|
+
security: bearerSecurity,
|
|
36842
|
+
parameters: [{ in: "query", name: "depth", schema: { type: "integer", minimum: 1, maximum: 8 } }],
|
|
36843
|
+
responses: { 200: jsonResponse("Root tree", "Tree"), ...errorResponses }
|
|
36844
|
+
}
|
|
36845
|
+
},
|
|
36846
|
+
"/{path}/~tree": {
|
|
36847
|
+
get: {
|
|
36848
|
+
"operationId": "getTree",
|
|
36849
|
+
"security": bearerSecurity,
|
|
36850
|
+
"parameters": [pathParameter, {
|
|
36851
|
+
in: "query",
|
|
36852
|
+
name: "depth",
|
|
36853
|
+
schema: { type: "integer", minimum: 1, maximum: 8 }
|
|
36854
|
+
}],
|
|
36855
|
+
"responses": { 200: jsonResponse("Subtree", "Tree"), ...errorResponses },
|
|
36856
|
+
"x-tool-bridge-greedy-path": true
|
|
36857
|
+
}
|
|
36858
|
+
},
|
|
36859
|
+
"/~search": {
|
|
36860
|
+
post: {
|
|
36861
|
+
operationId: "searchTools",
|
|
36862
|
+
security: bearerSecurity,
|
|
36863
|
+
requestBody: { required: true, content: jsonContent("SearchRequest") },
|
|
36864
|
+
responses: { 200: jsonResponse("Visible tool page", "SearchResponse"), ...errorResponses }
|
|
36865
|
+
}
|
|
36866
|
+
},
|
|
36867
|
+
"/{path}/~authorize": {
|
|
36868
|
+
post: {
|
|
36869
|
+
"operationId": "startOAuthAuthorization",
|
|
36870
|
+
"security": bearerSecurity,
|
|
36871
|
+
"parameters": [pathParameter],
|
|
36872
|
+
"requestBody": { required: false, content: jsonContent("OAuthAuthorizeRequest") },
|
|
36873
|
+
"responses": {
|
|
36874
|
+
200: jsonResponse("Authorization status or redirect", "OAuthAuthorizeResponse"),
|
|
36875
|
+
...errorResponses
|
|
36876
|
+
},
|
|
36877
|
+
"x-tool-bridge-greedy-path": true
|
|
36878
|
+
}
|
|
36879
|
+
},
|
|
36880
|
+
"/{path}/~register": {
|
|
36881
|
+
post: {
|
|
36882
|
+
"operationId": "registerNode",
|
|
36883
|
+
"security": bearerSecurity,
|
|
36884
|
+
"parameters": [pathParameter],
|
|
36885
|
+
"requestBody": { required: true, content: jsonContent("NodeInput") },
|
|
36886
|
+
"responses": {
|
|
36887
|
+
200: jsonResponse("Registered node", "RegistryNode"),
|
|
36888
|
+
...errorResponses
|
|
36889
|
+
},
|
|
36890
|
+
"x-tool-bridge-greedy-path": true
|
|
36891
|
+
}
|
|
36892
|
+
},
|
|
36893
|
+
"/{path}/~feedback": {
|
|
36894
|
+
get: {
|
|
36895
|
+
"operationId": "listFeedback",
|
|
36896
|
+
"security": bearerSecurity,
|
|
36897
|
+
"parameters": [pathParameter, {
|
|
36898
|
+
in: "query",
|
|
36899
|
+
name: "hidden",
|
|
36900
|
+
schema: { type: "string", enum: ["1"] }
|
|
36901
|
+
}],
|
|
36902
|
+
"responses": { 200: jsonResponse("Feedback list", "FeedbackList"), ...errorResponses },
|
|
36903
|
+
"x-tool-bridge-greedy-path": true
|
|
36904
|
+
},
|
|
36905
|
+
post: {
|
|
36906
|
+
"operationId": "submitFeedback",
|
|
36907
|
+
"security": bearerSecurity,
|
|
36908
|
+
"parameters": [pathParameter],
|
|
36909
|
+
"requestBody": { required: true, content: jsonContent("FeedbackSubmitRequest") },
|
|
36910
|
+
"responses": {
|
|
36911
|
+
200: jsonResponse("Submitted feedback", "FeedbackSubmitResponse"),
|
|
36912
|
+
...errorResponses
|
|
36913
|
+
},
|
|
36914
|
+
"x-tool-bridge-greedy-path": true
|
|
36915
|
+
}
|
|
36916
|
+
},
|
|
36917
|
+
"/{path}/~feedback/{id}": {
|
|
36918
|
+
get: {
|
|
36919
|
+
"operationId": "getFeedback",
|
|
36920
|
+
"security": bearerSecurity,
|
|
36921
|
+
"parameters": [pathParameter, { in: "path", name: "id", required: true, schema: { type: "string" } }],
|
|
36922
|
+
"responses": { 200: jsonResponse("Feedback detail", "FeedbackDetail"), ...errorResponses },
|
|
36923
|
+
"x-tool-bridge-greedy-path": true
|
|
36924
|
+
},
|
|
36925
|
+
post: {
|
|
36926
|
+
"operationId": "voteFeedback",
|
|
36927
|
+
"security": bearerSecurity,
|
|
36928
|
+
"parameters": [pathParameter, { in: "path", name: "id", required: true, schema: { type: "string" } }],
|
|
36929
|
+
"requestBody": { required: true, content: jsonContent("FeedbackVoteRequest") },
|
|
36930
|
+
"responses": { 200: jsonResponse("Updated feedback", "FeedbackView"), ...errorResponses },
|
|
36931
|
+
"x-tool-bridge-greedy-path": true
|
|
36932
|
+
},
|
|
36933
|
+
delete: {
|
|
36934
|
+
"operationId": "removeFeedback",
|
|
36935
|
+
"security": bearerSecurity,
|
|
36936
|
+
"parameters": [pathParameter, { in: "path", name: "id", required: true, schema: { type: "string" } }],
|
|
36937
|
+
"responses": {
|
|
36938
|
+
200: jsonResponse("Feedback removed", "FeedbackRemoveResponse"),
|
|
36939
|
+
...errorResponses
|
|
36940
|
+
},
|
|
36941
|
+
"x-tool-bridge-greedy-path": true
|
|
36942
|
+
}
|
|
36943
|
+
},
|
|
36944
|
+
"/{commandPath}": {
|
|
36945
|
+
post: {
|
|
36946
|
+
"operationId": "invoke",
|
|
36947
|
+
"security": bearerSecurity,
|
|
36948
|
+
"parameters": [{
|
|
36949
|
+
in: "path",
|
|
36950
|
+
name: "commandPath",
|
|
36951
|
+
required: true,
|
|
36952
|
+
schema: { type: "string" },
|
|
36953
|
+
description: "Complete HTBP command path, including the command/tool leaf."
|
|
36954
|
+
}],
|
|
36955
|
+
"requestBody": {
|
|
36956
|
+
required: true,
|
|
36957
|
+
description: "Raw command arguments; there is no {tool, arguments} envelope.",
|
|
36958
|
+
content: { "application/json": { schema: {} } }
|
|
36959
|
+
},
|
|
36960
|
+
"responses": {
|
|
36961
|
+
200: {
|
|
36962
|
+
description: "Raw JSON or Markdown according to Accept.",
|
|
36963
|
+
content: {
|
|
36964
|
+
"application/json": { schema: {} },
|
|
36965
|
+
"text/markdown": { schema: { type: "string" } }
|
|
36966
|
+
}
|
|
36967
|
+
},
|
|
36968
|
+
...errorResponses
|
|
36969
|
+
},
|
|
36970
|
+
"x-tool-bridge-greedy-path": true
|
|
36971
|
+
}
|
|
36972
|
+
}
|
|
36973
|
+
}
|
|
36974
|
+
};
|
|
36975
|
+
|
|
36526
36976
|
// ../app/src/paths.ts
|
|
36527
36977
|
function scopeForCmd(mod, nodePath, cmd) {
|
|
36528
36978
|
return mod.help(nodePath).cmds.find((c) => c.name === cmd);
|
|
@@ -36606,7 +37056,9 @@ function indexByParent(nodes) {
|
|
|
36606
37056
|
}
|
|
36607
37057
|
|
|
36608
37058
|
// ../app/src/routes/feedback.ts
|
|
36609
|
-
var feedbackJson = (value) => new Response(JSON.stringify(value), {
|
|
37059
|
+
var feedbackJson = (schema, value) => new Response(JSON.stringify(schema.parse(value)), {
|
|
37060
|
+
headers: { "content-type": contentTypeFor("json") }
|
|
37061
|
+
});
|
|
36610
37062
|
async function handleFeedbackGet(c) {
|
|
36611
37063
|
const target = splitFeedback(new URL(c.req.url).pathname);
|
|
36612
37064
|
if (target === null || target.path === "") throw TBError.notFound("no such path");
|
|
@@ -36615,7 +37067,7 @@ async function handleFeedbackGet(c) {
|
|
|
36615
37067
|
const fb = new FeedbackStore(c.get("store"));
|
|
36616
37068
|
if (target.id !== void 0) {
|
|
36617
37069
|
const e = await fb.get(target.path, target.id);
|
|
36618
|
-
return feedbackJson({
|
|
37070
|
+
return feedbackJson(feedbackDetailSchema, {
|
|
36619
37071
|
id: e.id,
|
|
36620
37072
|
path: target.path,
|
|
36621
37073
|
title: e.title,
|
|
@@ -36629,7 +37081,7 @@ async function handleFeedbackGet(c) {
|
|
|
36629
37081
|
}
|
|
36630
37082
|
const views = await fb.listViews(target.path);
|
|
36631
37083
|
const items = c.req.query("hidden") === "1" ? views : views.filter((v) => v.score > FEEDBACK_HIDE_SCORE);
|
|
36632
|
-
return feedbackJson({ items });
|
|
37084
|
+
return feedbackJson(feedbackListSchema, { items });
|
|
36633
37085
|
}
|
|
36634
37086
|
async function handleFeedbackPost(c, env) {
|
|
36635
37087
|
const { searchSync } = env;
|
|
@@ -36651,23 +37103,32 @@ async function handleFeedbackPost(c, env) {
|
|
|
36651
37103
|
await searchSync?.reconcileNodeQuietly(path, { feedback: entries, marker });
|
|
36652
37104
|
});
|
|
36653
37105
|
if (target.id !== void 0) {
|
|
36654
|
-
const
|
|
36655
|
-
if (
|
|
37106
|
+
const input2 = feedbackVoteRequestSchema.safeParse(body);
|
|
37107
|
+
if (!input2.success) {
|
|
36656
37108
|
throw new TBError("invalid_argument", `body.vote must be 'up' | 'down' | 'clear'`);
|
|
36657
37109
|
}
|
|
36658
|
-
return feedbackJson(
|
|
37110
|
+
return feedbackJson(
|
|
37111
|
+
feedbackViewSchema,
|
|
37112
|
+
await fb.vote(target.path, target.id, ctx.owner, input2.data.vote)
|
|
37113
|
+
);
|
|
36659
37114
|
}
|
|
36660
|
-
|
|
37115
|
+
const input = feedbackSubmitRequestSchema.safeParse(body);
|
|
37116
|
+
if (!input.success) {
|
|
36661
37117
|
throw new TBError("invalid_argument", "body must be { title: string, detail: string }");
|
|
36662
37118
|
}
|
|
36663
37119
|
await new NodeRegistryStore(store).resolve(target.path);
|
|
36664
37120
|
const entry = await fb.submit(
|
|
36665
37121
|
target.path,
|
|
36666
|
-
|
|
37122
|
+
input.data,
|
|
36667
37123
|
ctx.owner,
|
|
36668
37124
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
36669
37125
|
);
|
|
36670
|
-
return feedbackJson(
|
|
37126
|
+
return feedbackJson(feedbackSubmitResponseSchema, {
|
|
37127
|
+
id: entry.id,
|
|
37128
|
+
path: target.path,
|
|
37129
|
+
title: entry.title,
|
|
37130
|
+
at: entry.at
|
|
37131
|
+
});
|
|
36671
37132
|
} catch (error) {
|
|
36672
37133
|
await searchSync?.abort(marker);
|
|
36673
37134
|
throw error;
|
|
@@ -36693,12 +37154,12 @@ async function handleFeedbackDelete(c, env) {
|
|
|
36693
37154
|
await searchSync?.abort(marker);
|
|
36694
37155
|
throw error;
|
|
36695
37156
|
}
|
|
36696
|
-
return feedbackJson({ ok: true });
|
|
37157
|
+
return feedbackJson(feedbackRemoveResponseSchema, { ok: true });
|
|
36697
37158
|
}
|
|
36698
37159
|
|
|
36699
37160
|
// ../app/src/responses.ts
|
|
36700
37161
|
function tbErrorResponse(err) {
|
|
36701
|
-
return new Response(JSON.stringify(err.toJSON()), {
|
|
37162
|
+
return new Response(JSON.stringify(tbErrorBodySchema.parse(err.toJSON())), {
|
|
36702
37163
|
status: err.httpStatus,
|
|
36703
37164
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
36704
37165
|
});
|
|
@@ -36735,7 +37196,7 @@ async function runHandler(fn) {
|
|
|
36735
37196
|
}
|
|
36736
37197
|
function renderHelp(model, rep) {
|
|
36737
37198
|
if (rep === "json") {
|
|
36738
|
-
return new Response(JSON.stringify(renderHelpJson(model)), {
|
|
37199
|
+
return new Response(JSON.stringify(helpJsonSchema.parse(renderHelpJson(model))), {
|
|
36739
37200
|
headers: { "content-type": contentTypeFor("json") }
|
|
36740
37201
|
});
|
|
36741
37202
|
}
|
|
@@ -36790,6 +37251,7 @@ function renderTreeDsl(tree) {
|
|
|
36790
37251
|
}
|
|
36791
37252
|
|
|
36792
37253
|
// ../app/src/federation.ts
|
|
37254
|
+
var SEARCH_REMAINING_MS_HEADER = "x-tb-search-remaining-ms";
|
|
36793
37255
|
function remoteProtocolError(message) {
|
|
36794
37256
|
return new TBError("unavailable", message, { retryable: false });
|
|
36795
37257
|
}
|
|
@@ -36797,28 +37259,26 @@ function canonicalRemotePath(path, allowRoot) {
|
|
|
36797
37259
|
if (path !== path.replace(/^\/+|\/+$/g, "")) {
|
|
36798
37260
|
throw remoteProtocolError(`remote returned non-canonical path '${path}'`);
|
|
36799
37261
|
}
|
|
37262
|
+
if (path !== path.toLowerCase()) {
|
|
37263
|
+
throw remoteProtocolError(`remote returned non-canonical path '${path}'`);
|
|
37264
|
+
}
|
|
36800
37265
|
const invalid = validatePath(path, { allowRoot });
|
|
36801
37266
|
if (invalid !== null) throw remoteProtocolError(`remote returned invalid path '${path}'`);
|
|
36802
37267
|
for (const segment of path === "" ? [] : path.split("/")) {
|
|
36803
|
-
|
|
36804
|
-
|
|
36805
|
-
|
|
36806
|
-
|
|
36807
|
-
|
|
36808
|
-
|
|
36809
|
-
|
|
36810
|
-
|
|
36811
|
-
|
|
36812
|
-
|
|
36813
|
-
|
|
36814
|
-
|
|
36815
|
-
|
|
36816
|
-
}
|
|
36817
|
-
if (next === decoded) break;
|
|
36818
|
-
if (pass === 3) {
|
|
36819
|
-
throw remoteProtocolError(`remote returned over-encoded path '${path}'`);
|
|
36820
|
-
}
|
|
36821
|
-
decoded = next;
|
|
37268
|
+
if (segment === "." || segment === ".." || segment.startsWith("~") || segment.includes("\\") || segment.includes("?") || segment.includes("#") || [...segment].some((char) => {
|
|
37269
|
+
const code = char.charCodeAt(0);
|
|
37270
|
+
return code <= 31 || code === 127;
|
|
37271
|
+
})) {
|
|
37272
|
+
throw remoteProtocolError(`remote returned unsafe path segment '${segment}'`);
|
|
37273
|
+
}
|
|
37274
|
+
let decoded;
|
|
37275
|
+
try {
|
|
37276
|
+
decoded = decodeURIComponent(segment);
|
|
37277
|
+
} catch {
|
|
37278
|
+
throw remoteProtocolError(`remote returned malformed encoded path '${path}'`);
|
|
37279
|
+
}
|
|
37280
|
+
if (decoded !== segment) {
|
|
37281
|
+
throw remoteProtocolError(`remote returned non-canonical encoded path '${path}'`);
|
|
36822
37282
|
}
|
|
36823
37283
|
}
|
|
36824
37284
|
return path;
|
|
@@ -36826,19 +37286,104 @@ function canonicalRemotePath(path, allowRoot) {
|
|
|
36826
37286
|
function remotePathWithin(nodePath, commandPath) {
|
|
36827
37287
|
return nodePath === "" || commandPath === nodePath || commandPath.startsWith(`${nodePath}/`);
|
|
36828
37288
|
}
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
|
|
36833
|
-
|
|
37289
|
+
var CALLABLE_TREE_KINDS = /* @__PURE__ */ new Set(["device", "http", "mcp", "remote", "tool"]);
|
|
37290
|
+
function visibleRemotePath(ctx, path, kind) {
|
|
37291
|
+
if (!check(ctx, path, "read").allow) return false;
|
|
37292
|
+
return !CALLABLE_TREE_KINDS.has(kind) || check(ctx, path, "call").allow;
|
|
37293
|
+
}
|
|
37294
|
+
var RemotePathProjector = class {
|
|
37295
|
+
constructor(mountPath) {
|
|
37296
|
+
this.mountPath = mountPath;
|
|
37297
|
+
const invalid = validatePath(mountPath);
|
|
37298
|
+
if (invalid !== null) throw remoteProtocolError(`invalid remote mount path '${mountPath}'`);
|
|
37299
|
+
}
|
|
37300
|
+
mountPath;
|
|
37301
|
+
childPath(localPath) {
|
|
37302
|
+
if (localPath === this.mountPath) return "";
|
|
37303
|
+
if (!localPath.startsWith(`${this.mountPath}/`)) {
|
|
37304
|
+
throw remoteProtocolError(`local path '${localPath}' escapes remote mount '${this.mountPath}'`);
|
|
37305
|
+
}
|
|
37306
|
+
return localPath.slice(this.mountPath.length + 1);
|
|
37307
|
+
}
|
|
37308
|
+
localPath(childPath, allowRoot = true) {
|
|
37309
|
+
const canonical = canonicalRemotePath(childPath, allowRoot);
|
|
37310
|
+
return canonical === "" ? this.mountPath : `${this.mountPath}/${canonical}`;
|
|
37311
|
+
}
|
|
37312
|
+
childCommandPath(path) {
|
|
37313
|
+
if (!path.startsWith("/") || path.startsWith("//")) {
|
|
37314
|
+
throw remoteProtocolError(`remote ~help returned invalid command path '${path}'`);
|
|
37315
|
+
}
|
|
37316
|
+
return canonicalRemotePath(path.slice(1), false);
|
|
37317
|
+
}
|
|
37318
|
+
projectHelp(help, requestedLocalPath, ctx) {
|
|
37319
|
+
const expectedChildPath = this.childPath(requestedLocalPath);
|
|
37320
|
+
const nodeChildPath = canonicalRemotePath(help.node.path, expectedChildPath === "");
|
|
37321
|
+
if (nodeChildPath !== expectedChildPath) {
|
|
37322
|
+
throw remoteProtocolError(
|
|
37323
|
+
`remote ~help path '${nodeChildPath}' does not match request '${expectedChildPath}'`
|
|
37324
|
+
);
|
|
37325
|
+
}
|
|
37326
|
+
const nodePath = this.localPath(nodeChildPath, true);
|
|
37327
|
+
const cmds = help.cmds.map((command) => {
|
|
37328
|
+
const childCommandPath = this.childCommandPath(command.path);
|
|
37329
|
+
if (!remotePathWithin(nodeChildPath, childCommandPath)) {
|
|
37330
|
+
throw remoteProtocolError(`remote ~help command '${command.path}' escapes its node`);
|
|
37331
|
+
}
|
|
37332
|
+
return { ...command, path: `/${this.localPath(childCommandPath, false)}` };
|
|
37333
|
+
});
|
|
37334
|
+
const children = help.children?.map((child) => {
|
|
37335
|
+
const childPath = canonicalRemotePath(child.path, false);
|
|
37336
|
+
const parent = childPath.split("/").slice(0, -1).join("/");
|
|
37337
|
+
if (parent !== nodeChildPath) {
|
|
37338
|
+
throw remoteProtocolError(`remote ~help child '${childPath}' is not a direct descendant`);
|
|
37339
|
+
}
|
|
37340
|
+
return { ...child, path: this.localPath(childPath, false) };
|
|
37341
|
+
}).filter((child) => ctx === void 0 || visibleRemotePath(ctx, child.path, child.kind));
|
|
37342
|
+
return {
|
|
37343
|
+
node: { ...help.node, path: nodePath },
|
|
37344
|
+
cmds,
|
|
37345
|
+
...children === void 0 ? {} : { children },
|
|
37346
|
+
...help.feedback === void 0 ? {} : { feedback: help.feedback },
|
|
37347
|
+
...help.hint === void 0 ? {} : { hint: help.hint },
|
|
37348
|
+
...help.note === void 0 ? {} : { note: help.note }
|
|
37349
|
+
};
|
|
36834
37350
|
}
|
|
36835
|
-
|
|
36836
|
-
|
|
36837
|
-
|
|
36838
|
-
|
|
36839
|
-
|
|
36840
|
-
|
|
36841
|
-
|
|
37351
|
+
projectTree(tree, requestedLocalPath, ctx) {
|
|
37352
|
+
const expectedChildPath = this.childPath(requestedLocalPath);
|
|
37353
|
+
const rootChildPath = canonicalRemotePath(tree.path, expectedChildPath === "");
|
|
37354
|
+
if (rootChildPath !== expectedChildPath) {
|
|
37355
|
+
throw remoteProtocolError(
|
|
37356
|
+
`remote ~tree path '${rootChildPath}' does not match request '${expectedChildPath}'`
|
|
37357
|
+
);
|
|
37358
|
+
}
|
|
37359
|
+
const walk = (node, childPath) => {
|
|
37360
|
+
const localPath = this.localPath(childPath, childPath === "");
|
|
37361
|
+
const children = (node.children ?? []).map((child) => {
|
|
37362
|
+
const canonical = canonicalRemotePath(child.path, false);
|
|
37363
|
+
const parent = canonical.split("/").slice(0, -1).join("/");
|
|
37364
|
+
if (parent !== childPath) {
|
|
37365
|
+
throw remoteProtocolError(`remote ~tree child '${canonical}' is not a direct descendant`);
|
|
37366
|
+
}
|
|
37367
|
+
return walk(child, canonical);
|
|
37368
|
+
}).filter((child) => visibleRemotePath(ctx, child.path, child.kind));
|
|
37369
|
+
return {
|
|
37370
|
+
path: localPath,
|
|
37371
|
+
kind: node.kind,
|
|
37372
|
+
description: node.description,
|
|
37373
|
+
...children.length === 0 ? {} : { children },
|
|
37374
|
+
...node.presence === void 0 ? {} : { presence: node.presence },
|
|
37375
|
+
...node.truncated === void 0 ? {} : { truncated: node.truncated }
|
|
37376
|
+
};
|
|
37377
|
+
};
|
|
37378
|
+
return walk(tree, rootChildPath);
|
|
37379
|
+
}
|
|
37380
|
+
};
|
|
37381
|
+
async function remotePathProjectorIfMatch(registry, treePath) {
|
|
37382
|
+
const resolved = await registry.resolve(treePath).catch(() => null);
|
|
37383
|
+
if (resolved === null || resolved.node.kind !== "remote" || resolved.node.config?.kind !== "remote") {
|
|
37384
|
+
return null;
|
|
37385
|
+
}
|
|
37386
|
+
return new RemotePathProjector(resolved.node.path);
|
|
36842
37387
|
}
|
|
36843
37388
|
async function resolveRemoteSettings(state, base) {
|
|
36844
37389
|
const runtime = await new RemoteAllowlistStore(state).hosts();
|
|
@@ -36861,6 +37406,17 @@ async function remotePassthroughIfMatch(c, ctx, registry, treePath, reservedTail
|
|
|
36861
37406
|
}
|
|
36862
37407
|
const requestPath = reservedTail === null ? treePath : `${treePath}/${reservedTail}`;
|
|
36863
37408
|
const body = method === "POST" ? await c.req.text() : void 0;
|
|
37409
|
+
const outboundHeaders = new Headers(headers);
|
|
37410
|
+
const searchSettings = federatedSearchSettings(deps.remote);
|
|
37411
|
+
const rawRemaining = outboundHeaders.get(SEARCH_REMAINING_MS_HEADER);
|
|
37412
|
+
const requestedRemaining = rawRemaining !== null && /^\d+$/u.test(rawRemaining) ? Number(rawRemaining) : void 0;
|
|
37413
|
+
const remainingMs = requestedRemaining !== void 0 && Number.isSafeInteger(requestedRemaining) && requestedRemaining > 0 ? Math.min(requestedRemaining, searchSettings.totalDeadlineMs) : void 0;
|
|
37414
|
+
if (remainingMs !== void 0) {
|
|
37415
|
+
outboundHeaders.set(
|
|
37416
|
+
SEARCH_REMAINING_MS_HEADER,
|
|
37417
|
+
String(Math.max(1, remainingMs - searchSettings.perHopReturnReserveMs))
|
|
37418
|
+
);
|
|
37419
|
+
}
|
|
36864
37420
|
return await passthroughRemote({
|
|
36865
37421
|
actor: { keyId: ctx.keyId, owner: ctx.owner, traceId: ctx.traceId },
|
|
36866
37422
|
config: node.config,
|
|
@@ -36868,7 +37424,11 @@ async function remotePassthroughIfMatch(c, ctx, registry, treePath, reservedTail
|
|
|
36868
37424
|
requestPath,
|
|
36869
37425
|
method,
|
|
36870
37426
|
...body !== void 0 ? { body } : {},
|
|
36871
|
-
headers,
|
|
37427
|
+
headers: outboundHeaders,
|
|
37428
|
+
...remainingMs === void 0 ? {} : {
|
|
37429
|
+
maxResponseBodyBytes: searchSettings.maxResponseBodyBytes,
|
|
37430
|
+
signal: AbortSignal.timeout(remainingMs)
|
|
37431
|
+
},
|
|
36872
37432
|
secrets: deps.secrets,
|
|
36873
37433
|
settings: await resolveRemoteSettings(deps.state, deps.remote),
|
|
36874
37434
|
requestUrl: c.req.url
|
|
@@ -36892,15 +37452,14 @@ async function remoteTreeChildren(c, ctx, registry, treePath, deps) {
|
|
|
36892
37452
|
retryable: resp.status >= 500
|
|
36893
37453
|
});
|
|
36894
37454
|
}
|
|
36895
|
-
const
|
|
36896
|
-
|
|
37455
|
+
const rawRemoteTree = await resp.json().catch(() => null);
|
|
37456
|
+
const parsedRemoteTree = treeJsonSchema.safeParse(rawRemoteTree);
|
|
37457
|
+
if (!parsedRemoteTree.success) {
|
|
36897
37458
|
throw new TBError("unavailable", "remote ~tree returned invalid JSON", { retryable: false });
|
|
36898
37459
|
}
|
|
36899
|
-
const
|
|
36900
|
-
|
|
36901
|
-
|
|
36902
|
-
}
|
|
36903
|
-
return (remoteTree.children ?? []).map((child) => localizeRemoteEntry(resolved.node.path, remotePath, child));
|
|
37460
|
+
const projector = new RemotePathProjector(resolved.node.path);
|
|
37461
|
+
const projected = projector.projectTree(parsedRemoteTree.data, treePath, ctx);
|
|
37462
|
+
return projected.children ?? [];
|
|
36904
37463
|
}
|
|
36905
37464
|
function assertRemoteConfigAllowed(config, settings) {
|
|
36906
37465
|
if (config === null || typeof config !== "object") return;
|
|
@@ -36939,14 +37498,14 @@ async function authorizeToolNode(c, env, node, encryptionKey) {
|
|
|
36939
37498
|
authRef: config.authRef,
|
|
36940
37499
|
config: exported.oauth,
|
|
36941
37500
|
encryptionKey,
|
|
36942
|
-
fetcher:
|
|
37501
|
+
fetcher: deps.providerOAuthFetch,
|
|
36943
37502
|
nodePath: node.path,
|
|
36944
37503
|
now: /* @__PURE__ */ new Date(),
|
|
36945
37504
|
origin: deps.canonicalOrigin ?? new URL(c.req.url).origin,
|
|
36946
37505
|
secrets: deps.secrets,
|
|
36947
37506
|
store: deps.state
|
|
36948
37507
|
});
|
|
36949
|
-
return new Response(JSON.stringify(result), {
|
|
37508
|
+
return new Response(JSON.stringify(oauthAuthorizeResponseSchema.parse(result)), {
|
|
36950
37509
|
headers: { "content-type": contentTypeFor("json") }
|
|
36951
37510
|
});
|
|
36952
37511
|
}
|
|
@@ -36974,6 +37533,7 @@ async function handleRegister(c, env) {
|
|
|
36974
37533
|
}
|
|
36975
37534
|
await assertRegisterPath2(registry, ctx, body.path, "write", deps);
|
|
36976
37535
|
assertSecretRefUse(ctx.scopes, body.config);
|
|
37536
|
+
await assertMcpOAuthConfig(body.config, deps.secrets);
|
|
36977
37537
|
await assertContextConfig(body.config, deps);
|
|
36978
37538
|
await assertSkillhubConfig(body.config, deps);
|
|
36979
37539
|
await assertToolConfig(body.config, deps, ctx, body.path);
|
|
@@ -36993,7 +37553,7 @@ async function handleRegister(c, env) {
|
|
|
36993
37553
|
await invalidateProviderOAuth(store, body.path);
|
|
36994
37554
|
await searchSync?.reconcileNodeQuietly(body.path, { marker });
|
|
36995
37555
|
if (await refreshDynamicSearchNode(node, ctx, deps)) await searchSync?.abort(marker);
|
|
36996
|
-
return new Response(JSON.stringify(node), {
|
|
37556
|
+
return new Response(JSON.stringify(registryNodeSchema.parse(node)), {
|
|
36997
37557
|
headers: { "content-type": contentTypeFor("json") }
|
|
36998
37558
|
});
|
|
36999
37559
|
}
|
|
@@ -37020,6 +37580,11 @@ async function handleAuthorize(c, env) {
|
|
|
37020
37580
|
} catch {
|
|
37021
37581
|
throw TBError.notFound("not found");
|
|
37022
37582
|
}
|
|
37583
|
+
const rawBody = await c.req.json().catch(() => ({}));
|
|
37584
|
+
const body = oauthAuthorizeRequestSchema.safeParse(rawBody);
|
|
37585
|
+
if (!body.success) {
|
|
37586
|
+
throw new TBError("invalid_argument", "body only accepts optional redirectUri string");
|
|
37587
|
+
}
|
|
37023
37588
|
if (node.kind === "tool" && node.config?.kind === "tool") {
|
|
37024
37589
|
return await authorizeToolNode(c, env, node, encKey);
|
|
37025
37590
|
}
|
|
@@ -37029,17 +37594,18 @@ async function handleAuthorize(c, env) {
|
|
|
37029
37594
|
`'${path}' \u65E2\u4E0D\u662F auth:'oauth' \u7684 mcp \u6302\u8F7D,\u4E5F\u4E0D\u662F\u58F0\u660E\u4E86 oauth \u7684 tool \u6302\u8F7D`
|
|
37030
37595
|
);
|
|
37031
37596
|
}
|
|
37032
|
-
const
|
|
37033
|
-
const redirectUri = body !== null && typeof body.redirectUri === "string" ? body.redirectUri : void 0;
|
|
37597
|
+
const redirectUri = body.data.redirectUri;
|
|
37034
37598
|
const result = await startMcpAuthorization({
|
|
37035
37599
|
store,
|
|
37036
37600
|
encryptionKey: encKey,
|
|
37037
37601
|
nodePath: path,
|
|
37038
37602
|
serverUrl: node.config.url,
|
|
37603
|
+
secrets: deps.secrets,
|
|
37039
37604
|
origin: deps.canonicalOrigin ?? new URL(c.req.url).origin,
|
|
37605
|
+
...node.config.oauthClient !== void 0 ? { oauthClient: node.config.oauthClient } : {},
|
|
37040
37606
|
...redirectUri !== void 0 ? { redirectUri } : {}
|
|
37041
37607
|
});
|
|
37042
|
-
return new Response(JSON.stringify(result), {
|
|
37608
|
+
return new Response(JSON.stringify(oauthAuthorizeResponseSchema.parse(result)), {
|
|
37043
37609
|
headers: { "content-type": contentTypeFor("json") }
|
|
37044
37610
|
});
|
|
37045
37611
|
}
|
|
@@ -37141,16 +37707,6 @@ function uploadId(body) {
|
|
|
37141
37707
|
}
|
|
37142
37708
|
return body.uploadId;
|
|
37143
37709
|
}
|
|
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
37710
|
function registerStoreCapabilityRoutes(app, deps) {
|
|
37155
37711
|
app.post("/system/store/create_upload", async (c, next) => {
|
|
37156
37712
|
if (c.req.header(STORE_CALL_CAPABILITY_HEADER) === void 0) return await next();
|
|
@@ -37162,7 +37718,7 @@ function registerStoreCapabilityRoutes(app, deps) {
|
|
|
37162
37718
|
deps,
|
|
37163
37719
|
body,
|
|
37164
37720
|
capability,
|
|
37165
|
-
|
|
37721
|
+
resolveStoreRequestOrigin(c.req.url, deps.canonicalOrigin)
|
|
37166
37722
|
);
|
|
37167
37723
|
return c.json(result);
|
|
37168
37724
|
});
|
|
@@ -37262,7 +37818,7 @@ async function finishToolAuthorization(opts) {
|
|
|
37262
37818
|
codeVerifier: opts.verifier,
|
|
37263
37819
|
config: exported.oauth,
|
|
37264
37820
|
encryptionKey: opts.encryptionKey,
|
|
37265
|
-
fetcher:
|
|
37821
|
+
fetcher: opts.deps.providerOAuthFetch,
|
|
37266
37822
|
nodePath: opts.node.path,
|
|
37267
37823
|
now: /* @__PURE__ */ new Date(),
|
|
37268
37824
|
origin: opts.origin,
|
|
@@ -37279,21 +37835,23 @@ function registerPublicRoutes(app, env) {
|
|
|
37279
37835
|
app.get("/healthz", async (c) => {
|
|
37280
37836
|
const catalog = deps.pluginCatalog;
|
|
37281
37837
|
const digest = await env.pluginCatalogDigest();
|
|
37282
|
-
return c.json({
|
|
37838
|
+
return c.json(healthResponseSchema.parse({
|
|
37283
37839
|
healthy: true,
|
|
37284
37840
|
version: deps.version,
|
|
37285
37841
|
...catalog !== void 0 && digest !== void 0 ? { catalog: { count: Object.keys(catalog).length, digest } } : {}
|
|
37286
|
-
});
|
|
37842
|
+
}));
|
|
37287
37843
|
});
|
|
37288
|
-
app.get("/livez", (c) => c.json({ live: true }));
|
|
37844
|
+
app.get("/livez", (c) => c.json(livenessResponseSchema.parse({ live: true })));
|
|
37289
37845
|
app.get("/readyz", async (c) => {
|
|
37290
37846
|
const probe = deps.readiness;
|
|
37291
|
-
if (probe === void 0)
|
|
37847
|
+
if (probe === void 0) {
|
|
37848
|
+
return c.json(readinessResponseSchema.parse({ checks: {}, ready: true }));
|
|
37849
|
+
}
|
|
37292
37850
|
try {
|
|
37293
|
-
const report = await probe();
|
|
37851
|
+
const report = readinessResponseSchema.parse(await probe());
|
|
37294
37852
|
return c.json(report, report.ready ? 200 : 503);
|
|
37295
37853
|
} catch {
|
|
37296
|
-
return c.json({ checks: {}, ready: false }, 503);
|
|
37854
|
+
return c.json(readinessResponseSchema.parse({ checks: {}, ready: false }), 503);
|
|
37297
37855
|
}
|
|
37298
37856
|
});
|
|
37299
37857
|
app.get(
|
|
@@ -37388,15 +37946,25 @@ function registerPublicRoutes(app, env) {
|
|
|
37388
37946
|
if (node.kind !== "mcp" || node.config?.kind !== "mcp" || node.config.auth !== "oauth") {
|
|
37389
37947
|
return renderOAuthCallbackHtml(false, "target node is not an OAuth-backed mount");
|
|
37390
37948
|
}
|
|
37949
|
+
const currentClient = node.config.oauthClient;
|
|
37950
|
+
if (payload.o === void 0 !== (currentClient === void 0) || payload.o !== void 0 && (currentClient?.clientId !== payload.o.i || currentClient.clientSecretRef !== payload.o.r)) {
|
|
37951
|
+
return renderOAuthCallbackHtml(
|
|
37952
|
+
false,
|
|
37953
|
+
"OAuth client configuration changed; restart authorization"
|
|
37954
|
+
);
|
|
37955
|
+
}
|
|
37391
37956
|
try {
|
|
37392
37957
|
await finishMcpAuthorization({
|
|
37393
37958
|
store: deps.state,
|
|
37394
37959
|
encryptionKey: encKey,
|
|
37395
37960
|
nodePath: payload.p,
|
|
37396
37961
|
serverUrl: node.config.url,
|
|
37962
|
+
secrets: deps.secrets,
|
|
37397
37963
|
origin: deps.canonicalOrigin ?? new URL(c.req.url).origin,
|
|
37398
37964
|
code,
|
|
37399
37965
|
codeVerifier: payload.v,
|
|
37966
|
+
...q.iss !== void 0 ? { authorizationResponseIssuer: q.iss } : {},
|
|
37967
|
+
...node.config.oauthClient !== void 0 ? { oauthClient: node.config.oauthClient } : {},
|
|
37400
37968
|
// 本地回调通道(CLI --local):兑换必须复用授权时的 redirect_uri。
|
|
37401
37969
|
...payload.r !== void 0 ? { redirectUri: payload.r } : {}
|
|
37402
37970
|
});
|
|
@@ -37409,7 +37977,1505 @@ function registerPublicRoutes(app, env) {
|
|
|
37409
37977
|
);
|
|
37410
37978
|
}
|
|
37411
37979
|
|
|
37980
|
+
// ../app/src/search/federatedSession.ts
|
|
37981
|
+
var FEDERATED_SEARCH_HANDLE_PREFIX = "fsc1_";
|
|
37982
|
+
var FEDERATED_SEARCH_HANDLE_RANDOM_BYTES = 24;
|
|
37983
|
+
var FEDERATED_SEARCH_HANDLE_LENGTH = 37;
|
|
37984
|
+
var FEDERATED_SEARCH_SESSION_KEY_PREFIX = "fsearch:session:v1:";
|
|
37985
|
+
var FEDERATED_SEARCH_QUOTA_KEY = "fsearch:quota:v1";
|
|
37986
|
+
var HANDLE_PATTERN = /^fsc1_[A-Za-z0-9_-]{32}$/;
|
|
37987
|
+
var MAX_SAFE_JSON_DEPTH = 64;
|
|
37988
|
+
var MAX_SAFE_JSON_NODES = 1e4;
|
|
37989
|
+
var DEFAULT_MAX_RECORD_BYTES = 1024 * 1024;
|
|
37990
|
+
var DEFAULT_MAX_BYTES_GLOBAL = 128 * 1024 * 1024;
|
|
37991
|
+
var DEFAULT_MAX_BYTES_PER_ACTOR = 32 * 1024 * 1024;
|
|
37992
|
+
var DEFAULT_MAX_BYTES_PER_SESSION = 8 * 1024 * 1024;
|
|
37993
|
+
var DEFAULT_MAX_GENERATIONS_PER_SESSION = 1024;
|
|
37994
|
+
var DEFAULT_MAX_SESSIONS_GLOBAL = 256;
|
|
37995
|
+
var DEFAULT_MAX_SESSIONS_PER_ACTOR = 64;
|
|
37996
|
+
var QUOTA_CAS_ATTEMPTS = 32;
|
|
37997
|
+
var FORBIDDEN_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
37998
|
+
"$schema",
|
|
37999
|
+
"authorization",
|
|
38000
|
+
"baseurl",
|
|
38001
|
+
"credential",
|
|
38002
|
+
"inputschema",
|
|
38003
|
+
"outputschema",
|
|
38004
|
+
"schema",
|
|
38005
|
+
"secret",
|
|
38006
|
+
"sk",
|
|
38007
|
+
"token"
|
|
38008
|
+
]);
|
|
38009
|
+
function sessionUnavailable(message) {
|
|
38010
|
+
return new TBError("unavailable", message, { retryable: false });
|
|
38011
|
+
}
|
|
38012
|
+
function invalidSession() {
|
|
38013
|
+
return new TBError("invalid_argument", "federated search session is invalid or expired", {
|
|
38014
|
+
retryable: false
|
|
38015
|
+
});
|
|
38016
|
+
}
|
|
38017
|
+
function corruptSession() {
|
|
38018
|
+
return new TBError("internal", "federated search session state is corrupt", {
|
|
38019
|
+
retryable: false
|
|
38020
|
+
});
|
|
38021
|
+
}
|
|
38022
|
+
function isPlainObject3(value) {
|
|
38023
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
38024
|
+
const prototype = Object.getPrototypeOf(value);
|
|
38025
|
+
return prototype === Object.prototype || prototype === null;
|
|
38026
|
+
}
|
|
38027
|
+
function hasExactKeys(value, expected) {
|
|
38028
|
+
const actual = Object.keys(value).sort();
|
|
38029
|
+
const sortedExpected = [...expected].sort();
|
|
38030
|
+
return actual.length === sortedExpected.length && actual.every((key, index) => key === sortedExpected[index]);
|
|
38031
|
+
}
|
|
38032
|
+
function validOpaque(value) {
|
|
38033
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256 && !/[\0\r\n]/u.test(value);
|
|
38034
|
+
}
|
|
38035
|
+
function validExpiry(value) {
|
|
38036
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
38037
|
+
}
|
|
38038
|
+
function normalizedStateKey(key) {
|
|
38039
|
+
return key.replaceAll("-", "").replaceAll("_", "").toLowerCase();
|
|
38040
|
+
}
|
|
38041
|
+
function safeStateValue(value, state, depth = 0) {
|
|
38042
|
+
state.nodes++;
|
|
38043
|
+
if (state.nodes > MAX_SAFE_JSON_NODES || depth > MAX_SAFE_JSON_DEPTH) {
|
|
38044
|
+
throw new Error("federated search session value exceeds structural limits");
|
|
38045
|
+
}
|
|
38046
|
+
if (value === null || typeof value === "boolean") return value;
|
|
38047
|
+
if (typeof value === "number") {
|
|
38048
|
+
if (!Number.isFinite(value)) throw new Error("non-finite number");
|
|
38049
|
+
return value;
|
|
38050
|
+
}
|
|
38051
|
+
if (typeof value === "string") return value;
|
|
38052
|
+
if (Array.isArray(value)) {
|
|
38053
|
+
return Object.freeze(value.map((item) => safeStateValue(item, state, depth + 1)));
|
|
38054
|
+
}
|
|
38055
|
+
if (!isPlainObject3(value)) throw new Error("non-JSON value");
|
|
38056
|
+
const clone = {};
|
|
38057
|
+
for (const [key, item] of Object.entries(value)) {
|
|
38058
|
+
if (FORBIDDEN_STATE_KEYS.has(normalizedStateKey(key))) {
|
|
38059
|
+
throw new Error(`forbidden state field '${key}'`);
|
|
38060
|
+
}
|
|
38061
|
+
clone[key] = safeStateValue(item, state, depth + 1);
|
|
38062
|
+
}
|
|
38063
|
+
return Object.freeze(clone);
|
|
38064
|
+
}
|
|
38065
|
+
function parseRecord(value) {
|
|
38066
|
+
if (!isPlainObject3(value) || !hasExactKeys(value, [
|
|
38067
|
+
"actorKeyId",
|
|
38068
|
+
"excludedStatuses",
|
|
38069
|
+
"expiresAt",
|
|
38070
|
+
"federationPolicy",
|
|
38071
|
+
"generation",
|
|
38072
|
+
"nextHandle",
|
|
38073
|
+
"page",
|
|
38074
|
+
"rankingVersion",
|
|
38075
|
+
"requestDigest",
|
|
38076
|
+
"revision",
|
|
38077
|
+
"sessionId",
|
|
38078
|
+
"sourceContinuations",
|
|
38079
|
+
"topologyDigest"
|
|
38080
|
+
])) {
|
|
38081
|
+
throw corruptSession();
|
|
38082
|
+
}
|
|
38083
|
+
if (value.revision !== 0 || !validOpaque(value.sessionId) || !validOpaque(value.actorKeyId) || !validOpaque(value.requestDigest) || !validOpaque(value.rankingVersion) || !validOpaque(value.topologyDigest) || !validExpiry(value.expiresAt) || !Number.isSafeInteger(value.generation) || value.generation < 0 || value.nextHandle !== null && (typeof value.nextHandle !== "string" || !HANDLE_PATTERN.test(value.nextHandle))) {
|
|
38084
|
+
throw corruptSession();
|
|
38085
|
+
}
|
|
38086
|
+
try {
|
|
38087
|
+
const state = { nodes: 0 };
|
|
38088
|
+
return Object.freeze({
|
|
38089
|
+
actorKeyId: value.actorKeyId,
|
|
38090
|
+
excludedStatuses: safeStateValue(value.excludedStatuses, state),
|
|
38091
|
+
expiresAt: value.expiresAt,
|
|
38092
|
+
federationPolicy: safeStateValue(value.federationPolicy, state),
|
|
38093
|
+
generation: value.generation,
|
|
38094
|
+
nextHandle: value.nextHandle,
|
|
38095
|
+
page: safeStateValue(value.page, state),
|
|
38096
|
+
rankingVersion: value.rankingVersion,
|
|
38097
|
+
requestDigest: value.requestDigest,
|
|
38098
|
+
revision: 0,
|
|
38099
|
+
sessionId: value.sessionId,
|
|
38100
|
+
sourceContinuations: safeStateValue(value.sourceContinuations, state),
|
|
38101
|
+
topologyDigest: value.topologyDigest
|
|
38102
|
+
});
|
|
38103
|
+
} catch (error) {
|
|
38104
|
+
if (error instanceof TBError) throw error;
|
|
38105
|
+
throw corruptSession();
|
|
38106
|
+
}
|
|
38107
|
+
}
|
|
38108
|
+
function parseCreate(handle, input) {
|
|
38109
|
+
try {
|
|
38110
|
+
return parseRecord({
|
|
38111
|
+
...input,
|
|
38112
|
+
revision: 0,
|
|
38113
|
+
sessionId: input.sessionId ?? handle
|
|
38114
|
+
});
|
|
38115
|
+
} catch {
|
|
38116
|
+
throw new TBError("invalid_argument", "federated search session input is invalid", {
|
|
38117
|
+
retryable: false
|
|
38118
|
+
});
|
|
38119
|
+
}
|
|
38120
|
+
}
|
|
38121
|
+
function parseQuotaRecord(value) {
|
|
38122
|
+
if (!isPlainObject3(value) || !hasExactKeys(value, ["revision", "sessions"])) {
|
|
38123
|
+
throw corruptSession();
|
|
38124
|
+
}
|
|
38125
|
+
if (!Number.isSafeInteger(value.revision) || value.revision < 0 || !Array.isArray(value.sessions)) {
|
|
38126
|
+
throw corruptSession();
|
|
38127
|
+
}
|
|
38128
|
+
const sessions = value.sessions.map((item) => {
|
|
38129
|
+
if (!isPlainObject3(item) || !hasExactKeys(item, [
|
|
38130
|
+
"actorKeyId",
|
|
38131
|
+
"bytes",
|
|
38132
|
+
"expiresAt",
|
|
38133
|
+
"generation",
|
|
38134
|
+
"generationBytes",
|
|
38135
|
+
"generationId",
|
|
38136
|
+
"id"
|
|
38137
|
+
]) || !validOpaque(item.actorKeyId) || !validExpiry(item.expiresAt) || !Number.isSafeInteger(item.bytes) || item.bytes < 1 || !Number.isSafeInteger(item.generation) || item.generation < 0 || !Number.isSafeInteger(item.generationBytes) || item.generationBytes < 1 || typeof item.generationId !== "string" || !/^[0-9a-f]{64}$/u.test(item.generationId) || typeof item.id !== "string" || !/^[0-9a-f]{64}$/u.test(item.id)) {
|
|
38138
|
+
throw corruptSession();
|
|
38139
|
+
}
|
|
38140
|
+
return {
|
|
38141
|
+
actorKeyId: item.actorKeyId,
|
|
38142
|
+
bytes: item.bytes,
|
|
38143
|
+
expiresAt: item.expiresAt,
|
|
38144
|
+
generation: item.generation,
|
|
38145
|
+
generationBytes: item.generationBytes,
|
|
38146
|
+
generationId: item.generationId,
|
|
38147
|
+
id: item.id
|
|
38148
|
+
};
|
|
38149
|
+
});
|
|
38150
|
+
return { revision: value.revision, sessions };
|
|
38151
|
+
}
|
|
38152
|
+
function isFederatedSearchSessionHandle(value) {
|
|
38153
|
+
return typeof value === "string" && value.length === FEDERATED_SEARCH_HANDLE_LENGTH && HANDLE_PATTERN.test(value);
|
|
38154
|
+
}
|
|
38155
|
+
async function federatedSearchSessionStateKey(handle) {
|
|
38156
|
+
if (!isFederatedSearchSessionHandle(handle)) throw invalidSession();
|
|
38157
|
+
return FEDERATED_SEARCH_SESSION_KEY_PREFIX + await sha256Hex(handle);
|
|
38158
|
+
}
|
|
38159
|
+
var FederatedSearchSessionStore = class {
|
|
38160
|
+
constructor(state, opts = {}) {
|
|
38161
|
+
this.state = state;
|
|
38162
|
+
if (state.compareAndSwap === void 0) {
|
|
38163
|
+
throw sessionUnavailable("federated search sessions require StateStore.compareAndSwap");
|
|
38164
|
+
}
|
|
38165
|
+
this.cas = state.compareAndSwap.bind(state);
|
|
38166
|
+
this.maxBytesGlobal = opts.maxBytesGlobal ?? DEFAULT_MAX_BYTES_GLOBAL;
|
|
38167
|
+
this.maxBytesPerActor = opts.maxBytesPerActor ?? DEFAULT_MAX_BYTES_PER_ACTOR;
|
|
38168
|
+
this.maxBytesPerSession = opts.maxBytesPerSession ?? DEFAULT_MAX_BYTES_PER_SESSION;
|
|
38169
|
+
this.maxGenerationsPerSession = opts.maxGenerationsPerSession ?? DEFAULT_MAX_GENERATIONS_PER_SESSION;
|
|
38170
|
+
this.maxRecordBytes = opts.maxRecordBytes ?? DEFAULT_MAX_RECORD_BYTES;
|
|
38171
|
+
this.maxSessionsGlobal = opts.maxSessionsGlobal ?? DEFAULT_MAX_SESSIONS_GLOBAL;
|
|
38172
|
+
this.maxSessionsPerActor = opts.maxSessionsPerActor ?? DEFAULT_MAX_SESSIONS_PER_ACTOR;
|
|
38173
|
+
this.now = opts.now ?? (() => Date.now());
|
|
38174
|
+
this.randomBytes = opts.randomBytes ?? ((length) => crypto.getRandomValues(new Uint8Array(length)));
|
|
38175
|
+
}
|
|
38176
|
+
state;
|
|
38177
|
+
cas;
|
|
38178
|
+
now;
|
|
38179
|
+
randomBytes;
|
|
38180
|
+
maxBytesGlobal;
|
|
38181
|
+
maxBytesPerActor;
|
|
38182
|
+
maxBytesPerSession;
|
|
38183
|
+
maxGenerationsPerSession;
|
|
38184
|
+
maxRecordBytes;
|
|
38185
|
+
maxSessionsGlobal;
|
|
38186
|
+
maxSessionsPerActor;
|
|
38187
|
+
async cleanupExpiredSessionRows() {
|
|
38188
|
+
const page = await this.state.list(FEDERATED_SEARCH_SESSION_KEY_PREFIX, { limit: 32 });
|
|
38189
|
+
for (const item of page.items) {
|
|
38190
|
+
let record2;
|
|
38191
|
+
try {
|
|
38192
|
+
record2 = parseRecord(item.value);
|
|
38193
|
+
} catch {
|
|
38194
|
+
continue;
|
|
38195
|
+
}
|
|
38196
|
+
if (Date.parse(record2.expiresAt) <= this.now()) {
|
|
38197
|
+
await this.state.delete(item.key).catch(() => {
|
|
38198
|
+
});
|
|
38199
|
+
}
|
|
38200
|
+
}
|
|
38201
|
+
}
|
|
38202
|
+
async reserveQuota(handle, record2, recordBytes) {
|
|
38203
|
+
const id = await sha256Hex(record2.sessionId);
|
|
38204
|
+
const generationId = await sha256Hex(handle);
|
|
38205
|
+
for (let attempt2 = 0; attempt2 < QUOTA_CAS_ATTEMPTS; attempt2++) {
|
|
38206
|
+
const raw = await this.state.get(FEDERATED_SEARCH_QUOTA_KEY);
|
|
38207
|
+
const current = raw === null ? { revision: 0, sessions: [] } : parseQuotaRecord(raw);
|
|
38208
|
+
const active = current.sessions.filter((entry) => Date.parse(entry.expiresAt) > this.now());
|
|
38209
|
+
const existing = active.find((entry) => entry.id === id);
|
|
38210
|
+
let nextEntry;
|
|
38211
|
+
if (existing !== void 0) {
|
|
38212
|
+
if (existing.actorKeyId !== record2.actorKeyId || existing.expiresAt !== record2.expiresAt) throw invalidSession();
|
|
38213
|
+
if (record2.generation === existing.generation) {
|
|
38214
|
+
if (existing.generationId !== generationId || existing.generationBytes !== recordBytes) throw invalidSession();
|
|
38215
|
+
return;
|
|
38216
|
+
}
|
|
38217
|
+
if (record2.generation !== existing.generation + 1 || generationId === existing.generationId) throw invalidSession();
|
|
38218
|
+
nextEntry = {
|
|
38219
|
+
...existing,
|
|
38220
|
+
bytes: existing.bytes + recordBytes,
|
|
38221
|
+
generation: record2.generation,
|
|
38222
|
+
generationBytes: recordBytes,
|
|
38223
|
+
generationId
|
|
38224
|
+
};
|
|
38225
|
+
} else {
|
|
38226
|
+
if (record2.generation !== 0) throw invalidSession();
|
|
38227
|
+
nextEntry = {
|
|
38228
|
+
actorKeyId: record2.actorKeyId,
|
|
38229
|
+
bytes: recordBytes,
|
|
38230
|
+
expiresAt: record2.expiresAt,
|
|
38231
|
+
generation: 0,
|
|
38232
|
+
generationBytes: recordBytes,
|
|
38233
|
+
generationId,
|
|
38234
|
+
id
|
|
38235
|
+
};
|
|
38236
|
+
}
|
|
38237
|
+
const actorCount = active.filter((entry) => entry.actorKeyId === record2.actorKeyId).length;
|
|
38238
|
+
const globalBytes = active.reduce((total, entry) => total + entry.bytes, 0);
|
|
38239
|
+
const actorBytes = active.filter((entry) => entry.actorKeyId === record2.actorKeyId).reduce((total, entry) => total + entry.bytes, 0);
|
|
38240
|
+
const priorBytes = existing?.bytes ?? 0;
|
|
38241
|
+
if (record2.generation >= this.maxGenerationsPerSession || nextEntry.bytes > this.maxBytesPerSession) {
|
|
38242
|
+
throw new TBError("rate_limited", "federated search session chain quota exceeded", {
|
|
38243
|
+
retryable: false
|
|
38244
|
+
});
|
|
38245
|
+
}
|
|
38246
|
+
if (existing === void 0 && active.length >= this.maxSessionsGlobal || existing === void 0 && actorCount >= this.maxSessionsPerActor || globalBytes - priorBytes + nextEntry.bytes > this.maxBytesGlobal || actorBytes - priorBytes + nextEntry.bytes > this.maxBytesPerActor) {
|
|
38247
|
+
throw new TBError("rate_limited", "federated search session quota exceeded", {
|
|
38248
|
+
retryable: true
|
|
38249
|
+
});
|
|
38250
|
+
}
|
|
38251
|
+
const next = {
|
|
38252
|
+
revision: raw === null ? 0 : current.revision + 1,
|
|
38253
|
+
sessions: [
|
|
38254
|
+
...active.filter((entry) => entry.id !== id),
|
|
38255
|
+
nextEntry
|
|
38256
|
+
]
|
|
38257
|
+
};
|
|
38258
|
+
if (await this.cas(
|
|
38259
|
+
FEDERATED_SEARCH_QUOTA_KEY,
|
|
38260
|
+
raw === null ? null : current.revision,
|
|
38261
|
+
next
|
|
38262
|
+
)) return;
|
|
38263
|
+
}
|
|
38264
|
+
throw new TBError("unavailable", "federated search session quota is busy", {
|
|
38265
|
+
retryable: true
|
|
38266
|
+
});
|
|
38267
|
+
}
|
|
38268
|
+
issueHandle() {
|
|
38269
|
+
const bytes = this.randomBytes(FEDERATED_SEARCH_HANDLE_RANDOM_BYTES);
|
|
38270
|
+
if (!(bytes instanceof Uint8Array) || bytes.length !== FEDERATED_SEARCH_HANDLE_RANDOM_BYTES) {
|
|
38271
|
+
throw sessionUnavailable("federated search session randomness is unavailable");
|
|
38272
|
+
}
|
|
38273
|
+
const handle = FEDERATED_SEARCH_HANDLE_PREFIX + base64urlEncode(bytes);
|
|
38274
|
+
if (!isFederatedSearchSessionHandle(handle)) {
|
|
38275
|
+
throw sessionUnavailable("federated search session randomness is invalid");
|
|
38276
|
+
}
|
|
38277
|
+
return handle;
|
|
38278
|
+
}
|
|
38279
|
+
async create(handle, input) {
|
|
38280
|
+
const key = await federatedSearchSessionStateKey(handle);
|
|
38281
|
+
const record2 = parseCreate(handle, input);
|
|
38282
|
+
if (Date.parse(record2.expiresAt) <= this.now()) throw invalidSession();
|
|
38283
|
+
const recordBytes = new TextEncoder().encode(JSON.stringify(record2)).length;
|
|
38284
|
+
if (recordBytes > this.maxRecordBytes) {
|
|
38285
|
+
throw new TBError("rate_limited", "federated search session record is too large", {
|
|
38286
|
+
retryable: false
|
|
38287
|
+
});
|
|
38288
|
+
}
|
|
38289
|
+
if (await this.state.get(key) !== null) return await this.read(handle, input);
|
|
38290
|
+
if (record2.generation === 0) await this.cleanupExpiredSessionRows();
|
|
38291
|
+
await this.reserveQuota(handle, record2, recordBytes);
|
|
38292
|
+
if (await this.cas(key, null, record2)) {
|
|
38293
|
+
return record2;
|
|
38294
|
+
}
|
|
38295
|
+
return await this.read(handle, input);
|
|
38296
|
+
}
|
|
38297
|
+
async createNew(input) {
|
|
38298
|
+
const handle = this.issueHandle();
|
|
38299
|
+
return { handle, record: await this.create(handle, input) };
|
|
38300
|
+
}
|
|
38301
|
+
async read(handle, binding) {
|
|
38302
|
+
if (!validOpaque(binding.actorKeyId) || !validOpaque(binding.requestDigest)) {
|
|
38303
|
+
throw invalidSession();
|
|
38304
|
+
}
|
|
38305
|
+
const raw = await this.state.get(await federatedSearchSessionStateKey(handle));
|
|
38306
|
+
if (raw === null) throw invalidSession();
|
|
38307
|
+
const record2 = parseRecord(raw);
|
|
38308
|
+
if (Date.parse(record2.expiresAt) <= this.now()) {
|
|
38309
|
+
await this.state.delete(await federatedSearchSessionStateKey(handle)).catch(() => {
|
|
38310
|
+
});
|
|
38311
|
+
throw invalidSession();
|
|
38312
|
+
}
|
|
38313
|
+
if (record2.actorKeyId !== binding.actorKeyId || record2.requestDigest !== binding.requestDigest) {
|
|
38314
|
+
throw invalidSession();
|
|
38315
|
+
}
|
|
38316
|
+
return record2;
|
|
38317
|
+
}
|
|
38318
|
+
};
|
|
38319
|
+
|
|
38320
|
+
// ../app/src/search/localSearch.ts
|
|
38321
|
+
function normalizedEffect(effect) {
|
|
38322
|
+
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
38323
|
+
}
|
|
38324
|
+
function projectSearchTool(tool, detail) {
|
|
38325
|
+
if (detail === "full") return tool;
|
|
38326
|
+
const compact = { ...tool };
|
|
38327
|
+
delete compact.inputSchema;
|
|
38328
|
+
delete compact.outputSchema;
|
|
38329
|
+
if (compact.description !== void 0) {
|
|
38330
|
+
const encoder = new TextEncoder();
|
|
38331
|
+
if (encoder.encode(compact.description).length > TOOL_SEARCH_DESCRIPTION_BYTES_MAX) {
|
|
38332
|
+
let description = "";
|
|
38333
|
+
let bytes = 0;
|
|
38334
|
+
for (const char of compact.description) {
|
|
38335
|
+
const size = encoder.encode(char).length;
|
|
38336
|
+
if (bytes + size > TOOL_SEARCH_DESCRIPTION_BYTES_MAX) break;
|
|
38337
|
+
description += char;
|
|
38338
|
+
bytes += size;
|
|
38339
|
+
}
|
|
38340
|
+
compact.description = description;
|
|
38341
|
+
}
|
|
38342
|
+
}
|
|
38343
|
+
return compact;
|
|
38344
|
+
}
|
|
38345
|
+
async function executeLocalSearch(deps, request) {
|
|
38346
|
+
const { ctx, search, searchSync, state } = deps;
|
|
38347
|
+
const query = request.query;
|
|
38348
|
+
const wireOpts = request.opts ?? {};
|
|
38349
|
+
const detail = wireOpts.detail ?? "compact";
|
|
38350
|
+
const matching = wireOpts.matching ?? "best";
|
|
38351
|
+
const mode = wireOpts.mode ?? "keyword";
|
|
38352
|
+
const limit = normalizeToolSearchLimit(wireOpts.limit);
|
|
38353
|
+
const constraints = normalizeToolSearchOptions({
|
|
38354
|
+
...wireOpts.effects === void 0 ? {} : { effects: wireOpts.effects },
|
|
38355
|
+
matching,
|
|
38356
|
+
...wireOpts.minCoverage === void 0 ? {} : { minCoverage: wireOpts.minCoverage },
|
|
38357
|
+
...wireOpts.pathPrefix === void 0 ? {} : { pathPrefix: wireOpts.pathPrefix }
|
|
38358
|
+
});
|
|
38359
|
+
const indexOptions = { mode, ...constraints };
|
|
38360
|
+
await searchSync?.ensureReady();
|
|
38361
|
+
const registry = new NodeRegistryStore(state);
|
|
38362
|
+
const selected = [];
|
|
38363
|
+
const workLimit = Math.min(
|
|
38364
|
+
TOOL_SEARCH_WORK_LIMIT,
|
|
38365
|
+
Math.max(TOOL_SEARCH_BATCH_LIMIT, limit * 2)
|
|
38366
|
+
);
|
|
38367
|
+
let scanCursor = wireOpts.cursor;
|
|
38368
|
+
let responseCursor;
|
|
38369
|
+
let responseCandidate;
|
|
38370
|
+
let visibleCoverage;
|
|
38371
|
+
let scanned = 0;
|
|
38372
|
+
let stopped = false;
|
|
38373
|
+
while (!stopped && selected.length < limit && scanned < workLimit) {
|
|
38374
|
+
const batchLimit = Math.min(TOOL_SEARCH_BATCH_LIMIT, workLimit - scanned);
|
|
38375
|
+
const page = await search.search(query, {
|
|
38376
|
+
...indexOptions,
|
|
38377
|
+
limit: batchLimit,
|
|
38378
|
+
...scanCursor === void 0 ? {} : { cursor: scanCursor }
|
|
38379
|
+
});
|
|
38380
|
+
if (page.items.length === 0) {
|
|
38381
|
+
responseCursor = page.cursor;
|
|
38382
|
+
break;
|
|
38383
|
+
}
|
|
38384
|
+
const nodes = await registry.getMany(page.items.map((candidate) => candidate.path));
|
|
38385
|
+
let lastCandidateInVisibleBand;
|
|
38386
|
+
for (const [index, candidate] of page.items.entries()) {
|
|
38387
|
+
if (matching === "best" && visibleCoverage !== void 0 && candidate.coverage !== visibleCoverage) {
|
|
38388
|
+
responseCandidate = lastCandidateInVisibleBand;
|
|
38389
|
+
responseCursor = responseCandidate === void 0 ? scanCursor : void 0;
|
|
38390
|
+
stopped = true;
|
|
38391
|
+
break;
|
|
38392
|
+
}
|
|
38393
|
+
scanned++;
|
|
38394
|
+
lastCandidateInVisibleBand = candidate;
|
|
38395
|
+
if (!check(ctx, candidate.path, "read").allow || !check(ctx, candidate.path, "call").allow) {
|
|
38396
|
+
continue;
|
|
38397
|
+
}
|
|
38398
|
+
const node = nodes.get(candidate.path);
|
|
38399
|
+
if (node === void 0 || node.kind !== "mcp" && node.kind !== "http" && node.kind !== "tool" || node.config?.kind !== node.kind || virtualizeTools(node.virtualize, [{ name: candidate.name }]).exposed.length === 0) {
|
|
38400
|
+
continue;
|
|
38401
|
+
}
|
|
38402
|
+
visibleCoverage ??= candidate.coverage;
|
|
38403
|
+
selected.push({ candidate, node });
|
|
38404
|
+
if (selected.length === limit) {
|
|
38405
|
+
const hasUnscanned = index < page.items.length - 1 || page.cursor !== void 0;
|
|
38406
|
+
responseCandidate = hasUnscanned ? candidate : void 0;
|
|
38407
|
+
responseCursor = void 0;
|
|
38408
|
+
stopped = true;
|
|
38409
|
+
break;
|
|
38410
|
+
}
|
|
38411
|
+
}
|
|
38412
|
+
if (stopped) break;
|
|
38413
|
+
responseCursor = page.cursor;
|
|
38414
|
+
if (page.cursor === void 0) break;
|
|
38415
|
+
scanCursor = page.cursor;
|
|
38416
|
+
}
|
|
38417
|
+
const canonicalTools = await canonicalSearchTools(state, selected.map((item) => item.node));
|
|
38418
|
+
const requestedEffects = constraints.effects === void 0 ? void 0 : new Set(constraints.effects);
|
|
38419
|
+
const items = [];
|
|
38420
|
+
let pageBytes = 0;
|
|
38421
|
+
for (const [index, selectedItem] of selected.entries()) {
|
|
38422
|
+
const raw = canonicalTools.get(selectedItem.candidate.path)?.find((tool2) => tool2.name === selectedItem.candidate.name);
|
|
38423
|
+
if (raw === void 0) continue;
|
|
38424
|
+
const tool = virtualizeTools(selectedItem.node.virtualize, [raw]).exposed[0];
|
|
38425
|
+
if (tool === void 0) continue;
|
|
38426
|
+
if (requestedEffects !== void 0 && !requestedEffects.has(normalizedEffect(tool.effect))) {
|
|
38427
|
+
continue;
|
|
38428
|
+
}
|
|
38429
|
+
const item = {
|
|
38430
|
+
path: selectedItem.candidate.path,
|
|
38431
|
+
relevance: {
|
|
38432
|
+
coverage: selectedItem.candidate.coverage,
|
|
38433
|
+
matchedTermCount: selectedItem.candidate.matchedTermCount,
|
|
38434
|
+
rankingVersion: "keyword-v2",
|
|
38435
|
+
totalTermCount: selectedItem.candidate.totalTermCount
|
|
38436
|
+
},
|
|
38437
|
+
tool: projectSearchTool(tool, detail)
|
|
38438
|
+
};
|
|
38439
|
+
const itemBytes = new TextEncoder().encode(JSON.stringify(item)).length;
|
|
38440
|
+
if (pageBytes + itemBytes > TOOL_SEARCH_PAGE_BYTES) {
|
|
38441
|
+
if (items.length === 0) {
|
|
38442
|
+
throw new TBError("internal", "\u5DE5\u5177\u641C\u7D22\u9875\u9762\u5B57\u8282\u9884\u7B97\u65E0\u6CD5\u5BB9\u7EB3\u9996\u4E2A\u7ED3\u679C");
|
|
38443
|
+
}
|
|
38444
|
+
responseCandidate = selected[index - 1]?.candidate;
|
|
38445
|
+
responseCursor = void 0;
|
|
38446
|
+
break;
|
|
38447
|
+
}
|
|
38448
|
+
items.push(item);
|
|
38449
|
+
pageBytes += itemBytes;
|
|
38450
|
+
}
|
|
38451
|
+
if (responseCandidate !== void 0) {
|
|
38452
|
+
responseCursor = await search.cursorFor(query, responseCandidate, mode);
|
|
38453
|
+
}
|
|
38454
|
+
if (items.length === 0) responseCursor = void 0;
|
|
38455
|
+
return responseCursor === void 0 ? { items } : { items, cursor: responseCursor };
|
|
38456
|
+
}
|
|
38457
|
+
|
|
38458
|
+
// ../app/src/search/federatedSearch.ts
|
|
38459
|
+
var HEADER_REMAINING_MS = "x-tb-search-remaining-ms";
|
|
38460
|
+
var HEADER_SESSION_TTL_MS = "x-tb-search-session-ttl-ms";
|
|
38461
|
+
var HEADER_SOURCE_BUDGET = "x-tb-search-source-budget";
|
|
38462
|
+
var HEADER_VALIDATE_SNAPSHOT = "x-tb-search-validate-snapshot";
|
|
38463
|
+
var HEADER_VIA = "x-tb-via";
|
|
38464
|
+
var HEADER_WANT_SNAPSHOT = "x-tb-search-want-snapshot";
|
|
38465
|
+
function clampHeader(value, local) {
|
|
38466
|
+
if (value === null || !/^\d+$/u.test(value)) return local;
|
|
38467
|
+
const parsed = Number(value);
|
|
38468
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? Math.min(parsed, local) : local;
|
|
38469
|
+
}
|
|
38470
|
+
function isPrefix(prefix, path) {
|
|
38471
|
+
return prefix === "" || prefix === path || path.startsWith(`${prefix}/`);
|
|
38472
|
+
}
|
|
38473
|
+
function controlledStatus(error) {
|
|
38474
|
+
if (error instanceof TBError) {
|
|
38475
|
+
if (error.message.includes("\u73AF")) return "cycle";
|
|
38476
|
+
if (error.message.includes("\u8DF3\u6570")) return "hop_limit";
|
|
38477
|
+
if (error.message.includes("aborted")) return "timed_out";
|
|
38478
|
+
if (error.message.includes("work budget")) return "budget_exhausted";
|
|
38479
|
+
}
|
|
38480
|
+
return "unavailable";
|
|
38481
|
+
}
|
|
38482
|
+
function retryableContinuationError() {
|
|
38483
|
+
return new TBError("unavailable", "federated search continuation source unavailable", {
|
|
38484
|
+
retryable: true
|
|
38485
|
+
});
|
|
38486
|
+
}
|
|
38487
|
+
function invalidContinuation() {
|
|
38488
|
+
return new TBError("invalid_argument", "federated search cursor no longer matches topology", {
|
|
38489
|
+
retryable: false
|
|
38490
|
+
});
|
|
38491
|
+
}
|
|
38492
|
+
function continuationSourceError(error) {
|
|
38493
|
+
if (error instanceof TBError) {
|
|
38494
|
+
if (error.code === "invalid_argument") return error;
|
|
38495
|
+
if (error.message.includes("source work budget exhausted")) return invalidContinuation();
|
|
38496
|
+
}
|
|
38497
|
+
return retryableContinuationError();
|
|
38498
|
+
}
|
|
38499
|
+
function compactItem(item) {
|
|
38500
|
+
const tool = { ...item.tool };
|
|
38501
|
+
delete tool.inputSchema;
|
|
38502
|
+
delete tool.outputSchema;
|
|
38503
|
+
if (tool.description !== void 0) {
|
|
38504
|
+
const encoder = new TextEncoder();
|
|
38505
|
+
if (encoder.encode(tool.description).length > TOOL_SEARCH_DESCRIPTION_BYTES_MAX) {
|
|
38506
|
+
let description = "";
|
|
38507
|
+
let bytes = 0;
|
|
38508
|
+
for (const char of tool.description) {
|
|
38509
|
+
const size = encoder.encode(char).length;
|
|
38510
|
+
if (bytes + size > TOOL_SEARCH_DESCRIPTION_BYTES_MAX) break;
|
|
38511
|
+
description += char;
|
|
38512
|
+
bytes += size;
|
|
38513
|
+
}
|
|
38514
|
+
tool.description = description;
|
|
38515
|
+
}
|
|
38516
|
+
}
|
|
38517
|
+
return { ...item, tool };
|
|
38518
|
+
}
|
|
38519
|
+
function normalizedEffect2(effect) {
|
|
38520
|
+
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
38521
|
+
}
|
|
38522
|
+
function requestConstraints(request) {
|
|
38523
|
+
return normalizeToolSearchOptions({
|
|
38524
|
+
...request.opts?.effects === void 0 ? {} : { effects: request.opts.effects },
|
|
38525
|
+
matching: request.opts?.matching ?? "best",
|
|
38526
|
+
...request.opts?.minCoverage === void 0 ? {} : { minCoverage: request.opts.minCoverage },
|
|
38527
|
+
...request.opts?.pathPrefix === void 0 ? {} : { pathPrefix: request.opts.pathPrefix }
|
|
38528
|
+
});
|
|
38529
|
+
}
|
|
38530
|
+
function assertHardResultConstraints(item, constraints) {
|
|
38531
|
+
const effects = constraints.effects === void 0 ? void 0 : new Set(constraints.effects);
|
|
38532
|
+
if (constraints.pathPrefix !== void 0 && !isPrefix(constraints.pathPrefix, item.path) || constraints.minCoverage !== void 0 && item.relevance.coverage < constraints.minCoverage || effects !== void 0 && !effects.has(normalizedEffect2(item.tool.effect))) {
|
|
38533
|
+
throw new TBError("unavailable", "remote search violated a hard result constraint", {
|
|
38534
|
+
retryable: false
|
|
38535
|
+
});
|
|
38536
|
+
}
|
|
38537
|
+
}
|
|
38538
|
+
function sourceStatusKey(value) {
|
|
38539
|
+
return `${value.path}\0${value.status}`;
|
|
38540
|
+
}
|
|
38541
|
+
function mergeStatuses(...groups) {
|
|
38542
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38543
|
+
const out = [];
|
|
38544
|
+
for (const item of groups.flat()) {
|
|
38545
|
+
const key = sourceStatusKey(item);
|
|
38546
|
+
if (seen.has(key)) continue;
|
|
38547
|
+
seen.add(key);
|
|
38548
|
+
out.push(item);
|
|
38549
|
+
}
|
|
38550
|
+
return out.sort((left, right) => left.path.localeCompare(right.path) || left.status.localeCompare(right.status));
|
|
38551
|
+
}
|
|
38552
|
+
function responseHasMore(sources) {
|
|
38553
|
+
return sources.some((source) => source.pending !== void 0 || !source.done);
|
|
38554
|
+
}
|
|
38555
|
+
function serializableSources(sources) {
|
|
38556
|
+
return sources.map((source) => ({
|
|
38557
|
+
balanceCount: source.balanceCount,
|
|
38558
|
+
...source.balanceCoverage === void 0 ? {} : { balanceCoverage: source.balanceCoverage },
|
|
38559
|
+
budget: source.budget,
|
|
38560
|
+
done: source.done,
|
|
38561
|
+
...source.excluded === void 0 ? {} : { excluded: source.excluded },
|
|
38562
|
+
kind: source.kind,
|
|
38563
|
+
path: source.path,
|
|
38564
|
+
...source.cursor === void 0 ? {} : { cursor: source.cursor },
|
|
38565
|
+
...source.nextCursor === void 0 ? {} : { nextCursor: source.nextCursor },
|
|
38566
|
+
...source.pending === void 0 ? {} : { pending: compactItem(source.pending) },
|
|
38567
|
+
...source.validationCursor === void 0 ? {} : { validationCursor: source.validationCursor }
|
|
38568
|
+
}));
|
|
38569
|
+
}
|
|
38570
|
+
function canonicalJson(value) {
|
|
38571
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
38572
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
38573
|
+
const object2 = value;
|
|
38574
|
+
return `{${Object.keys(object2).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object2[key])}`).join(",")}}`;
|
|
38575
|
+
}
|
|
38576
|
+
function sameJson(left, right) {
|
|
38577
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
38578
|
+
}
|
|
38579
|
+
function assertNextRecord(current, next, topology) {
|
|
38580
|
+
if (next.topologyDigest !== topology || next.rankingVersion !== TOOL_SEARCH_RANKING_VERSION || next.sessionId !== current.sessionId || next.generation !== current.generation + 1 || next.expiresAt !== current.expiresAt || !sameJson(next.federationPolicy, current.federationPolicy)) {
|
|
38581
|
+
throw invalidContinuation();
|
|
38582
|
+
}
|
|
38583
|
+
}
|
|
38584
|
+
function assertCachedPage(computed, cached) {
|
|
38585
|
+
if (!sameJson(computed.page, cached.page) || !sameJson(computed.excluded, cached.excludedStatuses) || !sameJson(serializableSources(computed.sources), cached.sourceContinuations)) {
|
|
38586
|
+
throw invalidContinuation();
|
|
38587
|
+
}
|
|
38588
|
+
}
|
|
38589
|
+
function requestForDigest(request, limit) {
|
|
38590
|
+
const opts = request.opts ?? {};
|
|
38591
|
+
return {
|
|
38592
|
+
query: request.query,
|
|
38593
|
+
opts: {
|
|
38594
|
+
effects: opts.effects ?? null,
|
|
38595
|
+
federation: "recursive",
|
|
38596
|
+
limit,
|
|
38597
|
+
matching: opts.matching ?? "best",
|
|
38598
|
+
minCoverage: opts.minCoverage ?? null,
|
|
38599
|
+
mode: opts.mode ?? "keyword",
|
|
38600
|
+
pathPrefix: opts.pathPrefix ?? null
|
|
38601
|
+
}
|
|
38602
|
+
};
|
|
38603
|
+
}
|
|
38604
|
+
async function topologyDigest(nodes, includeLocal, localRevision, remotePolicy) {
|
|
38605
|
+
return await sha256Hex(canonicalJson({
|
|
38606
|
+
local: includeLocal,
|
|
38607
|
+
localRevision,
|
|
38608
|
+
nodes: nodes.map((node) => ({
|
|
38609
|
+
kind: node.kind,
|
|
38610
|
+
path: node.path,
|
|
38611
|
+
remote: node.config?.kind === "remote" ? { baseUrl: node.config.baseUrl, skRef: node.config.skRef ?? null } : null,
|
|
38612
|
+
updatedAt: node.updatedAt ?? "",
|
|
38613
|
+
virtualize: node.virtualize ?? null
|
|
38614
|
+
})),
|
|
38615
|
+
remotePolicy
|
|
38616
|
+
}));
|
|
38617
|
+
}
|
|
38618
|
+
function directRemoteNodes(nodes) {
|
|
38619
|
+
const remotes = nodes.filter((node) => node.kind === "remote" && node.config?.kind === "remote").sort((left, right) => left.path.localeCompare(right.path));
|
|
38620
|
+
return remotes.filter((node) => !remotes.some((parent) => parent.path !== node.path && isPrefix(parent.path, node.path)));
|
|
38621
|
+
}
|
|
38622
|
+
function childPrefixFor(mount, pathPrefix) {
|
|
38623
|
+
if (pathPrefix === void 0) return { eligible: true };
|
|
38624
|
+
if (isPrefix(mount, pathPrefix)) {
|
|
38625
|
+
const child = new RemotePathProjector(mount).childPath(pathPrefix);
|
|
38626
|
+
return { eligible: true, ...child === "" ? {} : { pathPrefix: child } };
|
|
38627
|
+
}
|
|
38628
|
+
return { eligible: isPrefix(pathPrefix, mount) };
|
|
38629
|
+
}
|
|
38630
|
+
function localEligible(remotes, pathPrefix) {
|
|
38631
|
+
return pathPrefix === void 0 || !remotes.some((node) => isPrefix(node.path, pathPrefix));
|
|
38632
|
+
}
|
|
38633
|
+
async function mapLimit(values, concurrency, fn) {
|
|
38634
|
+
const results = new Array(values.length);
|
|
38635
|
+
let next = 0;
|
|
38636
|
+
const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
38637
|
+
while (next < values.length) {
|
|
38638
|
+
const index = next++;
|
|
38639
|
+
results[index] = await fn(values[index]);
|
|
38640
|
+
}
|
|
38641
|
+
});
|
|
38642
|
+
await Promise.all(workers);
|
|
38643
|
+
return results;
|
|
38644
|
+
}
|
|
38645
|
+
function deadlineSignal(deadline) {
|
|
38646
|
+
return AbortSignal.timeout(Math.max(1, Math.floor(deadline - Date.now())));
|
|
38647
|
+
}
|
|
38648
|
+
function federatedHeaders(inbound, remainingMs, sourceBudget, sessionTtlMs) {
|
|
38649
|
+
const headers = new Headers();
|
|
38650
|
+
headers.set("accept", "application/json");
|
|
38651
|
+
headers.set("content-type", "application/json");
|
|
38652
|
+
const via = inbound.get(HEADER_VIA);
|
|
38653
|
+
if (via !== null) headers.set(HEADER_VIA, via);
|
|
38654
|
+
headers.set(HEADER_REMAINING_MS, String(Math.max(1, Math.floor(remainingMs))));
|
|
38655
|
+
headers.set(HEADER_SESSION_TTL_MS, String(Math.max(1, Math.floor(sessionTtlMs))));
|
|
38656
|
+
headers.set(HEADER_SOURCE_BUDGET, String(Math.max(1, Math.floor(sourceBudget))));
|
|
38657
|
+
headers.set(HEADER_WANT_SNAPSHOT, "1");
|
|
38658
|
+
return headers;
|
|
38659
|
+
}
|
|
38660
|
+
function projectChildPage(mount, page, totalTermCount, ctx, request) {
|
|
38661
|
+
const projector = new RemotePathProjector(mount);
|
|
38662
|
+
const constraints = requestConstraints(request);
|
|
38663
|
+
const items = page.items.map((item) => {
|
|
38664
|
+
if (item.relevance.rankingVersion !== TOOL_SEARCH_RANKING_VERSION || item.relevance.totalTermCount !== totalTermCount || item.relevance.matchedTermCount < 1 || item.relevance.matchedTermCount > totalTermCount || item.relevance.coverage !== item.relevance.matchedTermCount / totalTermCount) {
|
|
38665
|
+
throw new TBError("unavailable", "remote search returned incompatible relevance evidence", {
|
|
38666
|
+
retryable: false
|
|
38667
|
+
});
|
|
38668
|
+
}
|
|
38669
|
+
const path = projector.localPath(item.path, false);
|
|
38670
|
+
const sourcePath = projector.localPath(item.source?.path ?? "", true);
|
|
38671
|
+
if (item.tool.name.includes("/")) {
|
|
38672
|
+
throw new TBError("unavailable", "remote search returned an unsafe tool name", {
|
|
38673
|
+
retryable: false
|
|
38674
|
+
});
|
|
38675
|
+
}
|
|
38676
|
+
try {
|
|
38677
|
+
if (canonicalRemotePath(item.tool.name, false) !== item.tool.name.toLowerCase()) {
|
|
38678
|
+
throw new Error("non-canonical");
|
|
38679
|
+
}
|
|
38680
|
+
} catch {
|
|
38681
|
+
throw new TBError("unavailable", "remote search returned an unsafe tool name", {
|
|
38682
|
+
retryable: false
|
|
38683
|
+
});
|
|
38684
|
+
}
|
|
38685
|
+
if (!isPrefix(sourcePath, path)) {
|
|
38686
|
+
throw new TBError("unavailable", "remote search source does not own its result", {
|
|
38687
|
+
retryable: false
|
|
38688
|
+
});
|
|
38689
|
+
}
|
|
38690
|
+
const projected = compactItem({ ...item, path, source: { path: sourcePath } });
|
|
38691
|
+
assertHardResultConstraints(projected, constraints);
|
|
38692
|
+
return projected;
|
|
38693
|
+
}).filter((item) => check(ctx, item.path, "read").allow && check(ctx, item.path, "call").allow);
|
|
38694
|
+
const projectedSources = page.sources?.map((source) => ({
|
|
38695
|
+
path: projector.localPath(source.path, true),
|
|
38696
|
+
status: source.status
|
|
38697
|
+
}));
|
|
38698
|
+
const sourceStatuses = /* @__PURE__ */ new Map();
|
|
38699
|
+
for (const source of projectedSources ?? []) {
|
|
38700
|
+
const previous = sourceStatuses.get(source.path);
|
|
38701
|
+
if (previous !== void 0 && previous !== source.status) {
|
|
38702
|
+
throw new TBError("unavailable", "remote search returned conflicting source statuses", {
|
|
38703
|
+
retryable: false
|
|
38704
|
+
});
|
|
38705
|
+
}
|
|
38706
|
+
sourceStatuses.set(source.path, source.status);
|
|
38707
|
+
}
|
|
38708
|
+
const hasFailure = [...sourceStatuses.values()].some((status) => status !== "ok");
|
|
38709
|
+
if (page.partial === true !== hasFailure || page.cursor !== void 0 && !isFederatedSearchSessionHandle(page.cursor) || page.snapshot !== void 0 && !isFederatedSearchSessionHandle(page.snapshot)) {
|
|
38710
|
+
throw new TBError("unavailable", "remote search returned inconsistent page metadata", {
|
|
38711
|
+
retryable: false
|
|
38712
|
+
});
|
|
38713
|
+
}
|
|
38714
|
+
const sources = projectedSources?.filter((source) => check(ctx, source.path, "read").allow && check(ctx, source.path, "call").allow);
|
|
38715
|
+
const visiblePartial = sources?.some((source) => source.status !== "ok") === true;
|
|
38716
|
+
return {
|
|
38717
|
+
items,
|
|
38718
|
+
...page.cursor === void 0 ? {} : { cursor: page.cursor },
|
|
38719
|
+
...visiblePartial ? { partial: true } : {},
|
|
38720
|
+
...sources === void 0 ? {} : { sources }
|
|
38721
|
+
};
|
|
38722
|
+
}
|
|
38723
|
+
async function remoteResponse(deps, input, node, requestPath, method, headers, deadline, body) {
|
|
38724
|
+
if (node.config?.kind !== "remote") throw invalidContinuation();
|
|
38725
|
+
return await passthroughRemote({
|
|
38726
|
+
actor: {
|
|
38727
|
+
keyId: input.ctx.keyId,
|
|
38728
|
+
owner: input.ctx.owner,
|
|
38729
|
+
traceId: input.ctx.traceId
|
|
38730
|
+
},
|
|
38731
|
+
...body === void 0 ? {} : { body },
|
|
38732
|
+
config: node.config,
|
|
38733
|
+
headers,
|
|
38734
|
+
maxResponseBodyBytes: federatedSearchSettings(deps.remote).maxResponseBodyBytes,
|
|
38735
|
+
method,
|
|
38736
|
+
nodePath: node.path,
|
|
38737
|
+
requestPath,
|
|
38738
|
+
requestUrl: input.requestUrl,
|
|
38739
|
+
secrets: deps.secrets,
|
|
38740
|
+
settings: await resolveRemoteSettings(input.state, deps.remote),
|
|
38741
|
+
signal: deadlineSignal(deadline)
|
|
38742
|
+
});
|
|
38743
|
+
}
|
|
38744
|
+
async function assertFederatedCapability(deps, input, node, headers, deadline) {
|
|
38745
|
+
if (node.config?.kind !== "remote" || node.config.skRef === void 0) {
|
|
38746
|
+
throw new TBError("unavailable", "remote search source is unsupported", { retryable: false });
|
|
38747
|
+
}
|
|
38748
|
+
const response = await remoteResponse(
|
|
38749
|
+
deps,
|
|
38750
|
+
input,
|
|
38751
|
+
node,
|
|
38752
|
+
`${node.path}/~describe`,
|
|
38753
|
+
"GET",
|
|
38754
|
+
headers,
|
|
38755
|
+
deadline
|
|
38756
|
+
);
|
|
38757
|
+
if (!response.ok) {
|
|
38758
|
+
throw new TBError("unavailable", "remote search capability is unavailable", {
|
|
38759
|
+
retryable: response.status >= 500
|
|
38760
|
+
});
|
|
38761
|
+
}
|
|
38762
|
+
const value = await response.json().catch(() => null);
|
|
38763
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.capabilities) || !value.capabilities.includes("search:federated")) {
|
|
38764
|
+
throw new TBError("unavailable", "remote search source is unsupported", { retryable: false });
|
|
38765
|
+
}
|
|
38766
|
+
}
|
|
38767
|
+
function childSearchRequest(input, source, cursor) {
|
|
38768
|
+
const opts = input.request.opts ?? {};
|
|
38769
|
+
const childPrefix = childPrefixFor(source.path, opts.pathPrefix).pathPrefix;
|
|
38770
|
+
return {
|
|
38771
|
+
query: input.request.query,
|
|
38772
|
+
opts: {
|
|
38773
|
+
...opts.effects === void 0 ? {} : { effects: opts.effects },
|
|
38774
|
+
...cursor === void 0 ? {} : { cursor },
|
|
38775
|
+
detail: "compact",
|
|
38776
|
+
federation: "recursive",
|
|
38777
|
+
limit: 1,
|
|
38778
|
+
matching: opts.matching ?? "best",
|
|
38779
|
+
...opts.minCoverage === void 0 ? {} : { minCoverage: opts.minCoverage },
|
|
38780
|
+
mode: "keyword",
|
|
38781
|
+
...childPrefix === void 0 ? {} : { pathPrefix: childPrefix }
|
|
38782
|
+
}
|
|
38783
|
+
};
|
|
38784
|
+
}
|
|
38785
|
+
async function throwChildSearchError(response) {
|
|
38786
|
+
const error = await response.json().catch(() => null);
|
|
38787
|
+
const message = typeof error?.message === "string" ? error.message : "";
|
|
38788
|
+
if (message.includes("\u73AF")) {
|
|
38789
|
+
throw new TBError("unavailable", "\u68C0\u6D4B\u5230\u900F\u4F20\u73AF", { retryable: false });
|
|
38790
|
+
}
|
|
38791
|
+
if (message.includes("\u8DF3\u6570")) {
|
|
38792
|
+
throw new TBError("unavailable", "\u900F\u4F20\u8DF3\u6570\u8D85\u9650", { retryable: false });
|
|
38793
|
+
}
|
|
38794
|
+
if (response.status >= 400 && response.status < 500) throw invalidContinuation();
|
|
38795
|
+
throw new TBError("unavailable", "remote search unavailable", {
|
|
38796
|
+
retryable: response.status >= 500
|
|
38797
|
+
});
|
|
38798
|
+
}
|
|
38799
|
+
async function fetchHead(deps, input, source, remoteNodes, deadline, expiresAt, totalTermCount, skipBudget = 20) {
|
|
38800
|
+
if (source.done || source.pending !== void 0) return { statuses: [] };
|
|
38801
|
+
const opts = input.request.opts ?? {};
|
|
38802
|
+
if (source.kind === "local") {
|
|
38803
|
+
const page2 = await executeLocalSearch(
|
|
38804
|
+
{
|
|
38805
|
+
ctx: input.ctx,
|
|
38806
|
+
search: input.search,
|
|
38807
|
+
state: input.state
|
|
38808
|
+
},
|
|
38809
|
+
{
|
|
38810
|
+
query: input.request.query,
|
|
38811
|
+
opts: {
|
|
38812
|
+
...opts.effects === void 0 ? {} : { effects: opts.effects },
|
|
38813
|
+
...source.cursor === void 0 ? {} : { cursor: source.cursor },
|
|
38814
|
+
detail: "compact",
|
|
38815
|
+
federation: "local",
|
|
38816
|
+
limit: 1,
|
|
38817
|
+
matching: opts.matching ?? "best",
|
|
38818
|
+
...opts.minCoverage === void 0 ? {} : { minCoverage: opts.minCoverage },
|
|
38819
|
+
mode: "keyword",
|
|
38820
|
+
...opts.pathPrefix === void 0 ? {} : { pathPrefix: opts.pathPrefix }
|
|
38821
|
+
}
|
|
38822
|
+
}
|
|
38823
|
+
);
|
|
38824
|
+
const item2 = page2.items[0];
|
|
38825
|
+
if (item2 === void 0) {
|
|
38826
|
+
source.done = page2.cursor === void 0;
|
|
38827
|
+
source.cursor = page2.cursor;
|
|
38828
|
+
return { statuses: [] };
|
|
38829
|
+
}
|
|
38830
|
+
source.pending = compactItem({ ...item2, source: { path: "" } });
|
|
38831
|
+
source.nextCursor = page2.cursor;
|
|
38832
|
+
return { statuses: [] };
|
|
38833
|
+
}
|
|
38834
|
+
const node = remoteNodes.get(source.path);
|
|
38835
|
+
if (node === void 0 || node.config?.kind !== "remote" || !check(input.ctx, node.path, "read").allow || !check(input.ctx, node.path, "call").allow) {
|
|
38836
|
+
throw invalidContinuation();
|
|
38837
|
+
}
|
|
38838
|
+
const settings = federatedSearchSettings(deps.remote);
|
|
38839
|
+
const remainingMs = deadline - Date.now() - settings.perHopReturnReserveMs;
|
|
38840
|
+
if (remainingMs < settings.minChildWorkMs) {
|
|
38841
|
+
throw new TBError("unavailable", "federated search child has no remaining work budget", {
|
|
38842
|
+
retryable: false
|
|
38843
|
+
});
|
|
38844
|
+
}
|
|
38845
|
+
const headers = federatedHeaders(
|
|
38846
|
+
input.headers,
|
|
38847
|
+
remainingMs,
|
|
38848
|
+
source.budget,
|
|
38849
|
+
expiresAt - Date.now()
|
|
38850
|
+
);
|
|
38851
|
+
const childRequest = childSearchRequest(input, source, source.cursor);
|
|
38852
|
+
const response = await remoteResponse(
|
|
38853
|
+
deps,
|
|
38854
|
+
input,
|
|
38855
|
+
node,
|
|
38856
|
+
`${node.path}/~search`,
|
|
38857
|
+
"POST",
|
|
38858
|
+
headers,
|
|
38859
|
+
deadline,
|
|
38860
|
+
JSON.stringify(childRequest)
|
|
38861
|
+
);
|
|
38862
|
+
if (!response.ok) await throwChildSearchError(response);
|
|
38863
|
+
const parsed = toolSearchPageSchema.safeParse(await response.json().catch(() => null));
|
|
38864
|
+
if (!parsed.success) {
|
|
38865
|
+
throw new TBError("unavailable", "remote search returned invalid JSON", { retryable: false });
|
|
38866
|
+
}
|
|
38867
|
+
if (parsed.data.items.length > 1) {
|
|
38868
|
+
throw new TBError("unavailable", "remote search exceeded the requested source batch size", {
|
|
38869
|
+
retryable: false
|
|
38870
|
+
});
|
|
38871
|
+
}
|
|
38872
|
+
if (!isFederatedSearchSessionHandle(parsed.data.snapshot)) {
|
|
38873
|
+
throw new TBError("unavailable", "remote search omitted its validation snapshot", {
|
|
38874
|
+
retryable: false
|
|
38875
|
+
});
|
|
38876
|
+
}
|
|
38877
|
+
source.validationCursor = parsed.data.snapshot;
|
|
38878
|
+
const page = projectChildPage(
|
|
38879
|
+
source.path,
|
|
38880
|
+
parsed.data,
|
|
38881
|
+
totalTermCount,
|
|
38882
|
+
input.ctx,
|
|
38883
|
+
input.request
|
|
38884
|
+
);
|
|
38885
|
+
const item = page.items[0];
|
|
38886
|
+
if (item === void 0) {
|
|
38887
|
+
source.done = page.cursor === void 0;
|
|
38888
|
+
source.cursor = page.cursor;
|
|
38889
|
+
if (!source.done) {
|
|
38890
|
+
if (skipBudget <= 1) {
|
|
38891
|
+
throw new TBError("unavailable", "federated search source work budget exhausted", {
|
|
38892
|
+
retryable: false
|
|
38893
|
+
});
|
|
38894
|
+
}
|
|
38895
|
+
return await fetchHead(
|
|
38896
|
+
deps,
|
|
38897
|
+
input,
|
|
38898
|
+
source,
|
|
38899
|
+
remoteNodes,
|
|
38900
|
+
deadline,
|
|
38901
|
+
expiresAt,
|
|
38902
|
+
totalTermCount,
|
|
38903
|
+
skipBudget - 1
|
|
38904
|
+
);
|
|
38905
|
+
}
|
|
38906
|
+
} else {
|
|
38907
|
+
source.pending = item;
|
|
38908
|
+
source.nextCursor = page.cursor;
|
|
38909
|
+
}
|
|
38910
|
+
return { statuses: page.sources?.filter((status) => status.status !== "ok") ?? [] };
|
|
38911
|
+
}
|
|
38912
|
+
async function validateSourceSnapshots(deps, input, sources, remoteNodes, deadline, expiresAt, maxConcurrency) {
|
|
38913
|
+
const remotes = sources.filter((source) => source.kind === "remote" && source.excluded !== true);
|
|
38914
|
+
await mapLimit(remotes, maxConcurrency, async (source) => {
|
|
38915
|
+
const node = remoteNodes.get(source.path);
|
|
38916
|
+
if (node === void 0 || source.validationCursor === void 0) throw invalidContinuation();
|
|
38917
|
+
const settings = federatedSearchSettings(deps.remote);
|
|
38918
|
+
const remainingMs = deadline - Date.now() - settings.perHopReturnReserveMs;
|
|
38919
|
+
if (remainingMs < settings.minChildWorkMs) throw retryableContinuationError();
|
|
38920
|
+
const headers = federatedHeaders(
|
|
38921
|
+
input.headers,
|
|
38922
|
+
remainingMs,
|
|
38923
|
+
source.budget,
|
|
38924
|
+
expiresAt - Date.now()
|
|
38925
|
+
);
|
|
38926
|
+
headers.set(HEADER_VALIDATE_SNAPSHOT, "1");
|
|
38927
|
+
const response = await remoteResponse(
|
|
38928
|
+
deps,
|
|
38929
|
+
input,
|
|
38930
|
+
node,
|
|
38931
|
+
`${node.path}/~search`,
|
|
38932
|
+
"POST",
|
|
38933
|
+
headers,
|
|
38934
|
+
deadline,
|
|
38935
|
+
JSON.stringify(childSearchRequest(input, source, source.validationCursor))
|
|
38936
|
+
);
|
|
38937
|
+
if (!response.ok) await throwChildSearchError(response);
|
|
38938
|
+
const parsed = toolSearchPageSchema.safeParse(await response.json().catch(() => null));
|
|
38939
|
+
if (!parsed.success || parsed.data.items.length !== 0 || parsed.data.cursor !== void 0 || parsed.data.snapshot !== void 0 || parsed.data.partial !== void 0 || parsed.data.sources !== void 0) throw invalidContinuation();
|
|
38940
|
+
});
|
|
38941
|
+
}
|
|
38942
|
+
function consume(source) {
|
|
38943
|
+
const item = source.pending;
|
|
38944
|
+
if (item === void 0) throw new Error("federated source has no pending item");
|
|
38945
|
+
source.pending = void 0;
|
|
38946
|
+
source.cursor = source.nextCursor;
|
|
38947
|
+
source.nextCursor = void 0;
|
|
38948
|
+
source.done = source.cursor === void 0;
|
|
38949
|
+
return item;
|
|
38950
|
+
}
|
|
38951
|
+
async function hydrateLocal(input, item) {
|
|
38952
|
+
if (!check(input.ctx, item.path, "read").allow || !check(input.ctx, item.path, "call").allow) {
|
|
38953
|
+
throw invalidContinuation();
|
|
38954
|
+
}
|
|
38955
|
+
const registry = new NodeRegistryStore(input.state);
|
|
38956
|
+
const node = await registry.get(item.path).catch(() => null);
|
|
38957
|
+
if (node === null || node.kind !== "mcp" && node.kind !== "http" && node.kind !== "tool" || node.config?.kind !== node.kind) {
|
|
38958
|
+
throw invalidContinuation();
|
|
38959
|
+
}
|
|
38960
|
+
const raw = (await canonicalSearchTools(input.state, [node])).get(node.path);
|
|
38961
|
+
if (raw === void 0) throw invalidContinuation();
|
|
38962
|
+
const matches = virtualizeTools(node.virtualize, raw).exposed.filter((tool2) => tool2.name === item.tool.name);
|
|
38963
|
+
if (matches.length !== 1) throw invalidContinuation();
|
|
38964
|
+
const tool = matches[0];
|
|
38965
|
+
if (tool === void 0) throw invalidContinuation();
|
|
38966
|
+
return { ...item, tool };
|
|
38967
|
+
}
|
|
38968
|
+
async function hydrateRemote(deps, input, item, source, node, deadline) {
|
|
38969
|
+
if (!check(input.ctx, source.path, "read").allow || !check(input.ctx, source.path, "call").allow || !check(input.ctx, item.path, "read").allow || !check(input.ctx, item.path, "call").allow) {
|
|
38970
|
+
throw invalidContinuation();
|
|
38971
|
+
}
|
|
38972
|
+
const response = await remoteResponse(
|
|
38973
|
+
deps,
|
|
38974
|
+
input,
|
|
38975
|
+
node,
|
|
38976
|
+
`${item.path}/${item.tool.name}/~help`,
|
|
38977
|
+
"GET",
|
|
38978
|
+
(() => {
|
|
38979
|
+
const headers = new Headers({ accept: "application/json" });
|
|
38980
|
+
const via = input.headers.get(HEADER_VIA);
|
|
38981
|
+
if (via !== null) headers.set(HEADER_VIA, via);
|
|
38982
|
+
headers.set(
|
|
38983
|
+
HEADER_REMAINING_MS,
|
|
38984
|
+
String(Math.max(1, Math.floor(deadline - Date.now())))
|
|
38985
|
+
);
|
|
38986
|
+
return headers;
|
|
38987
|
+
})(),
|
|
38988
|
+
deadline
|
|
38989
|
+
);
|
|
38990
|
+
if (!response.ok) {
|
|
38991
|
+
if (response.status >= 400 && response.status < 500) throw invalidContinuation();
|
|
38992
|
+
throw retryableContinuationError();
|
|
38993
|
+
}
|
|
38994
|
+
const parsed = helpJsonSchema.safeParse(await response.json().catch(() => null));
|
|
38995
|
+
if (!parsed.success) throw invalidContinuation();
|
|
38996
|
+
const projected = new RemotePathProjector(source.path).projectHelp(parsed.data, `${item.path}/${item.tool.name}`);
|
|
38997
|
+
const commands = projected.cmds.filter((cmd) => cmd.name === item.tool.name && cmd.path === `/${item.path}/${item.tool.name}` && cmd.scope === "call");
|
|
38998
|
+
if (commands.length !== 1) throw invalidContinuation();
|
|
38999
|
+
const command = commands[0];
|
|
39000
|
+
if (command === void 0) throw invalidContinuation();
|
|
39001
|
+
const tool = {
|
|
39002
|
+
name: command.name,
|
|
39003
|
+
...command.h === void 0 ? {} : { description: command.h },
|
|
39004
|
+
...command.effect === void 0 ? {} : { effect: command.effect },
|
|
39005
|
+
...command.confirm === void 0 ? {} : { confirm: command.confirm },
|
|
39006
|
+
...command.inputSchema === void 0 ? {} : { inputSchema: command.inputSchema },
|
|
39007
|
+
...command.outputSchema === void 0 ? {} : { outputSchema: command.outputSchema }
|
|
39008
|
+
};
|
|
39009
|
+
return { ...item, tool };
|
|
39010
|
+
}
|
|
39011
|
+
async function computePage(deps, input, sources, remoteNodes, deadline, expiresAt, totalTermCount, initial, initialExcluded, maxConcurrency) {
|
|
39012
|
+
let excluded = [...initialExcluded];
|
|
39013
|
+
if (!initial) {
|
|
39014
|
+
await validateSourceSnapshots(
|
|
39015
|
+
deps,
|
|
39016
|
+
input,
|
|
39017
|
+
sources,
|
|
39018
|
+
remoteNodes,
|
|
39019
|
+
deadline,
|
|
39020
|
+
expiresAt,
|
|
39021
|
+
maxConcurrency
|
|
39022
|
+
);
|
|
39023
|
+
}
|
|
39024
|
+
const active = sources.filter((source) => !source.done);
|
|
39025
|
+
const capabilityFailures = await mapLimit(
|
|
39026
|
+
initial ? active.filter((source) => source.kind === "remote") : [],
|
|
39027
|
+
maxConcurrency,
|
|
39028
|
+
async (source) => {
|
|
39029
|
+
const node = remoteNodes.get(source.path);
|
|
39030
|
+
if (node === void 0) return { source, status: "invalid_response" };
|
|
39031
|
+
const settings = federatedSearchSettings(deps.remote);
|
|
39032
|
+
const remainingMs = deadline - Date.now() - settings.perHopReturnReserveMs;
|
|
39033
|
+
if (remainingMs < settings.minChildWorkMs) {
|
|
39034
|
+
return { source, status: "budget_exhausted" };
|
|
39035
|
+
}
|
|
39036
|
+
const headers = federatedHeaders(
|
|
39037
|
+
input.headers,
|
|
39038
|
+
remainingMs,
|
|
39039
|
+
source.budget,
|
|
39040
|
+
expiresAt - Date.now()
|
|
39041
|
+
);
|
|
39042
|
+
try {
|
|
39043
|
+
await assertFederatedCapability(deps, input, node, headers, deadline);
|
|
39044
|
+
return null;
|
|
39045
|
+
} catch (error) {
|
|
39046
|
+
const status = error instanceof TBError && error.message.includes("unsupported") ? "unsupported" : controlledStatus(error);
|
|
39047
|
+
return { error, source, status };
|
|
39048
|
+
}
|
|
39049
|
+
}
|
|
39050
|
+
);
|
|
39051
|
+
for (const failure of capabilityFailures) {
|
|
39052
|
+
if (failure === null) continue;
|
|
39053
|
+
if (!initial) {
|
|
39054
|
+
if (failure.error instanceof TBError && failure.error.code === "invalid_argument") {
|
|
39055
|
+
throw failure.error;
|
|
39056
|
+
}
|
|
39057
|
+
throw failure.status === "unsupported" ? invalidContinuation() : retryableContinuationError();
|
|
39058
|
+
}
|
|
39059
|
+
failure.source.done = true;
|
|
39060
|
+
failure.source.excluded = true;
|
|
39061
|
+
excluded = mergeStatuses(excluded, [{ path: failure.source.path, status: failure.status }]);
|
|
39062
|
+
}
|
|
39063
|
+
const storedPending = sources.filter((source) => source.pending !== void 0);
|
|
39064
|
+
const refreshedStatuses = await mapLimit(storedPending, maxConcurrency, async (source) => {
|
|
39065
|
+
try {
|
|
39066
|
+
const pending = source.pending;
|
|
39067
|
+
if (pending === void 0) throw invalidContinuation();
|
|
39068
|
+
const hydrated = source.kind === "local" ? await hydrateLocal(input, pending) : await (async () => {
|
|
39069
|
+
const node = remoteNodes.get(source.path);
|
|
39070
|
+
if (node === void 0) throw invalidContinuation();
|
|
39071
|
+
return await hydrateRemote(deps, input, pending, source, node, deadline);
|
|
39072
|
+
})();
|
|
39073
|
+
if (!sameJson(compactItem(hydrated), pending)) throw invalidContinuation();
|
|
39074
|
+
return [];
|
|
39075
|
+
} catch (error) {
|
|
39076
|
+
throw continuationSourceError(error);
|
|
39077
|
+
}
|
|
39078
|
+
});
|
|
39079
|
+
excluded = mergeStatuses(excluded, ...refreshedStatuses);
|
|
39080
|
+
let successfulSources = storedPending.length;
|
|
39081
|
+
const headResults = await mapLimit(
|
|
39082
|
+
sources.filter((source) => !source.done && source.pending === void 0),
|
|
39083
|
+
maxConcurrency,
|
|
39084
|
+
async (source) => {
|
|
39085
|
+
try {
|
|
39086
|
+
const result = await fetchHead(
|
|
39087
|
+
deps,
|
|
39088
|
+
input,
|
|
39089
|
+
source,
|
|
39090
|
+
remoteNodes,
|
|
39091
|
+
deadline,
|
|
39092
|
+
expiresAt,
|
|
39093
|
+
totalTermCount
|
|
39094
|
+
);
|
|
39095
|
+
return { ...result, success: true };
|
|
39096
|
+
} catch (error) {
|
|
39097
|
+
if (!initial) throw continuationSourceError(error);
|
|
39098
|
+
source.done = true;
|
|
39099
|
+
source.excluded = true;
|
|
39100
|
+
return {
|
|
39101
|
+
statuses: [{ path: source.path, status: controlledStatus(error) }],
|
|
39102
|
+
success: false
|
|
39103
|
+
};
|
|
39104
|
+
}
|
|
39105
|
+
}
|
|
39106
|
+
);
|
|
39107
|
+
successfulSources += headResults.filter((result) => result.success).length;
|
|
39108
|
+
excluded = mergeStatuses(excluded, ...headResults.map((result) => result.statuses));
|
|
39109
|
+
const items = [];
|
|
39110
|
+
let selectedCoverage;
|
|
39111
|
+
const limit = input.request.opts?.limit ?? 10;
|
|
39112
|
+
while (items.length < limit) {
|
|
39113
|
+
const heads = sources.filter((source2) => source2.pending !== void 0);
|
|
39114
|
+
if (heads.length === 0) break;
|
|
39115
|
+
const highest = Math.max(...heads.map((source2) => source2.pending?.relevance.coverage ?? -1));
|
|
39116
|
+
selectedCoverage ??= highest;
|
|
39117
|
+
if ((input.request.opts?.matching ?? "best") === "best" && highest !== selectedCoverage) break;
|
|
39118
|
+
const bucket = heads.filter((source2) => source2.pending?.relevance.coverage === highest).sort((left, right) => (left.balanceCoverage === highest ? left.balanceCount : 0) - (right.balanceCoverage === highest ? right.balanceCount : 0) || left.path.localeCompare(right.path));
|
|
39119
|
+
const source = bucket[0];
|
|
39120
|
+
if (source === void 0) break;
|
|
39121
|
+
items.push(consume(source));
|
|
39122
|
+
if (source.balanceCoverage !== highest) {
|
|
39123
|
+
source.balanceCoverage = highest;
|
|
39124
|
+
source.balanceCount = 0;
|
|
39125
|
+
}
|
|
39126
|
+
source.balanceCount++;
|
|
39127
|
+
try {
|
|
39128
|
+
const result = await fetchHead(
|
|
39129
|
+
deps,
|
|
39130
|
+
input,
|
|
39131
|
+
source,
|
|
39132
|
+
remoteNodes,
|
|
39133
|
+
deadline,
|
|
39134
|
+
expiresAt,
|
|
39135
|
+
totalTermCount
|
|
39136
|
+
);
|
|
39137
|
+
excluded = mergeStatuses(excluded, result.statuses);
|
|
39138
|
+
} catch (error) {
|
|
39139
|
+
if (!initial) throw continuationSourceError(error);
|
|
39140
|
+
source.done = true;
|
|
39141
|
+
source.excluded = true;
|
|
39142
|
+
excluded = mergeStatuses(excluded, [{ path: source.path, status: controlledStatus(error) }]);
|
|
39143
|
+
}
|
|
39144
|
+
}
|
|
39145
|
+
const partial = excluded.length > 0;
|
|
39146
|
+
const page = {
|
|
39147
|
+
items,
|
|
39148
|
+
...partial ? { partial: true, sources: excluded } : {}
|
|
39149
|
+
};
|
|
39150
|
+
return { excluded, page, sources, successfulSources };
|
|
39151
|
+
}
|
|
39152
|
+
function visibleSourceStatuses(ctx, page) {
|
|
39153
|
+
const rawSources = page.sources;
|
|
39154
|
+
const rest = { ...page };
|
|
39155
|
+
delete rest.partial;
|
|
39156
|
+
delete rest.sources;
|
|
39157
|
+
const sources = rawSources?.filter((source) => check(ctx, source.path, "read").allow && check(ctx, source.path, "call").allow);
|
|
39158
|
+
const partial = sources?.some((source) => source.status !== "ok") === true;
|
|
39159
|
+
return {
|
|
39160
|
+
...rest,
|
|
39161
|
+
...partial ? { partial: true } : {},
|
|
39162
|
+
...sources === void 0 || sources.length === 0 ? {} : { sources }
|
|
39163
|
+
};
|
|
39164
|
+
}
|
|
39165
|
+
async function hydratePage(deps, input, page, sources, remoteNodes, deadline, forceRevalidate = false) {
|
|
39166
|
+
const visiblePage = visibleSourceStatuses(input.ctx, page);
|
|
39167
|
+
const detail = input.request.opts?.detail ?? "compact";
|
|
39168
|
+
if (detail !== "full" && !forceRevalidate) return visiblePage;
|
|
39169
|
+
const constraints = requestConstraints(input.request);
|
|
39170
|
+
const output = [];
|
|
39171
|
+
const encoder = new TextEncoder();
|
|
39172
|
+
let outputBytes = encoder.encode(JSON.stringify({ ...visiblePage, items: [] })).length;
|
|
39173
|
+
const concurrency = federatedSearchSettings(deps.remote).maxConcurrency;
|
|
39174
|
+
for (let offset = 0; offset < visiblePage.items.length; offset += concurrency) {
|
|
39175
|
+
const batch = visiblePage.items.slice(offset, offset + concurrency);
|
|
39176
|
+
const hydrated = await Promise.all(batch.map(async (item) => {
|
|
39177
|
+
const direct = sources.find((source) => source.kind === "remote" && isPrefix(source.path, item.path));
|
|
39178
|
+
const full = direct === void 0 ? await hydrateLocal(input, item) : await (async () => {
|
|
39179
|
+
const node = remoteNodes.get(direct.path);
|
|
39180
|
+
if (node === void 0) throw invalidContinuation();
|
|
39181
|
+
return await hydrateRemote(deps, input, item, direct, node, deadline);
|
|
39182
|
+
})();
|
|
39183
|
+
if (!sameJson(compactItem(full), compactItem(item))) throw invalidContinuation();
|
|
39184
|
+
assertHardResultConstraints(full, constraints);
|
|
39185
|
+
return full;
|
|
39186
|
+
}));
|
|
39187
|
+
if (detail !== "full") continue;
|
|
39188
|
+
for (const item of hydrated) {
|
|
39189
|
+
outputBytes += encoder.encode(JSON.stringify(item)).length;
|
|
39190
|
+
if (outputBytes > TOOL_SEARCH_PAGE_BYTES) {
|
|
39191
|
+
throw new TBError("rate_limited", "federated search page exceeds response byte budget", {
|
|
39192
|
+
retryable: false
|
|
39193
|
+
});
|
|
39194
|
+
}
|
|
39195
|
+
output.push(item);
|
|
39196
|
+
}
|
|
39197
|
+
}
|
|
39198
|
+
return detail === "full" ? { ...visiblePage, items: output } : visiblePage;
|
|
39199
|
+
}
|
|
39200
|
+
function pageWithCursor(page, cursor) {
|
|
39201
|
+
return cursor === void 0 ? page : { ...page, cursor };
|
|
39202
|
+
}
|
|
39203
|
+
function assertPageBytes(page) {
|
|
39204
|
+
if (new TextEncoder().encode(JSON.stringify(page)).length > TOOL_SEARCH_PAGE_BYTES) {
|
|
39205
|
+
throw new TBError("rate_limited", "federated search page exceeds response byte budget", {
|
|
39206
|
+
retryable: false
|
|
39207
|
+
});
|
|
39208
|
+
}
|
|
39209
|
+
}
|
|
39210
|
+
async function executeFederatedSearch(deps, input) {
|
|
39211
|
+
if (deps.remote.instanceId === void 0 || input.state.compareAndSwap === void 0) {
|
|
39212
|
+
throw new TBError(
|
|
39213
|
+
"invalid_argument",
|
|
39214
|
+
"recursive search requires explicit TB_INSTANCE_ID and atomic StateStore CAS"
|
|
39215
|
+
);
|
|
39216
|
+
}
|
|
39217
|
+
if ((input.request.opts?.mode ?? "keyword") !== "keyword") {
|
|
39218
|
+
throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
|
|
39219
|
+
}
|
|
39220
|
+
const wireOpts = input.request.opts ?? {};
|
|
39221
|
+
const constraints = normalizeToolSearchOptions({
|
|
39222
|
+
...wireOpts.effects === void 0 ? {} : { effects: wireOpts.effects },
|
|
39223
|
+
matching: wireOpts.matching ?? "best",
|
|
39224
|
+
...wireOpts.minCoverage === void 0 ? {} : { minCoverage: wireOpts.minCoverage },
|
|
39225
|
+
...wireOpts.pathPrefix === void 0 ? {} : { pathPrefix: wireOpts.pathPrefix }
|
|
39226
|
+
});
|
|
39227
|
+
input = {
|
|
39228
|
+
...input,
|
|
39229
|
+
request: {
|
|
39230
|
+
query: input.request.query,
|
|
39231
|
+
opts: {
|
|
39232
|
+
...wireOpts,
|
|
39233
|
+
...constraints.effects === void 0 ? {} : { effects: constraints.effects },
|
|
39234
|
+
matching: constraints.matching,
|
|
39235
|
+
...constraints.minCoverage === void 0 ? {} : { minCoverage: constraints.minCoverage },
|
|
39236
|
+
...constraints.pathPrefix === void 0 ? {} : { pathPrefix: constraints.pathPrefix }
|
|
39237
|
+
}
|
|
39238
|
+
}
|
|
39239
|
+
};
|
|
39240
|
+
const viaError = checkVia(
|
|
39241
|
+
parseVia(input.headers.get(HEADER_VIA) ?? void 0),
|
|
39242
|
+
deps.remote.instanceId,
|
|
39243
|
+
deps.remote.maxHops
|
|
39244
|
+
);
|
|
39245
|
+
if (viaError !== null) throw viaError;
|
|
39246
|
+
const settings = federatedSearchSettings(deps.remote);
|
|
39247
|
+
await input.searchSync?.ensureReady();
|
|
39248
|
+
if (input.search.revision === void 0) {
|
|
39249
|
+
throw new TBError("invalid_argument", "recursive search requires a revisioned search index");
|
|
39250
|
+
}
|
|
39251
|
+
const localRevision = await input.search.revision();
|
|
39252
|
+
const deadlineMs = clampHeader(input.headers.get(HEADER_REMAINING_MS), settings.totalDeadlineMs);
|
|
39253
|
+
const sessionTtlMs = clampHeader(input.headers.get(HEADER_SESSION_TTL_MS), settings.sessionTtlMs);
|
|
39254
|
+
const sourceBudget = clampHeader(input.headers.get(HEADER_SOURCE_BUDGET), settings.maxSources);
|
|
39255
|
+
const deadline = Date.now() + deadlineMs;
|
|
39256
|
+
const expiresAtMs = Date.now() + sessionTtlMs;
|
|
39257
|
+
const limit = input.request.opts?.limit ?? 10;
|
|
39258
|
+
const requestDigest = await sha256Hex(canonicalJson({
|
|
39259
|
+
authorization: input.ctx.scopes,
|
|
39260
|
+
request: requestForDigest(input.request, limit)
|
|
39261
|
+
}));
|
|
39262
|
+
const sessionStore = new FederatedSearchSessionStore(input.state);
|
|
39263
|
+
const plan = prepareToolSearchQuery(input.request.query);
|
|
39264
|
+
const registry = new NodeRegistryStore(input.state);
|
|
39265
|
+
const snapshot = await registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
39266
|
+
const allDirect = directRemoteNodes(snapshot.items);
|
|
39267
|
+
const eligibleRemotes = allDirect.filter((node) => {
|
|
39268
|
+
const relation = childPrefixFor(node.path, input.request.opts?.pathPrefix);
|
|
39269
|
+
return relation.eligible && check(input.ctx, node.path, "read").allow && check(input.ctx, node.path, "call").allow;
|
|
39270
|
+
});
|
|
39271
|
+
const includeLocal = localEligible(allDirect, input.request.opts?.pathPrefix);
|
|
39272
|
+
const configuredRemotes = eligibleRemotes.filter((node) => node.config?.kind === "remote" && node.config.skRef !== void 0);
|
|
39273
|
+
const secretAvailability = await mapLimit(
|
|
39274
|
+
configuredRemotes,
|
|
39275
|
+
settings.maxConcurrency,
|
|
39276
|
+
async (node) => {
|
|
39277
|
+
if (node.config?.kind !== "remote" || node.config.skRef === void 0) return false;
|
|
39278
|
+
try {
|
|
39279
|
+
return await deps.secrets.resolve(node.config.skRef) !== void 0;
|
|
39280
|
+
} catch {
|
|
39281
|
+
return false;
|
|
39282
|
+
}
|
|
39283
|
+
}
|
|
39284
|
+
);
|
|
39285
|
+
const participatingRemotes = configuredRemotes.filter((_node, index) => secretAvailability[index] === true);
|
|
39286
|
+
const unavailableRemotes = configuredRemotes.filter((_node, index) => secretAvailability[index] !== true);
|
|
39287
|
+
const transportSettings = await resolveRemoteSettings(input.state, deps.remote);
|
|
39288
|
+
const topologyNodes = snapshot.items.filter((node) => check(input.ctx, node.path, "read").allow && check(input.ctx, node.path, "call").allow);
|
|
39289
|
+
const digest = await topologyDigest(topologyNodes, includeLocal, localRevision, {
|
|
39290
|
+
allowInsecure: transportSettings.allowInsecure,
|
|
39291
|
+
allowlist: [...transportSettings.allowlist].sort(),
|
|
39292
|
+
availableRemotes: participatingRemotes.map((node) => node.path),
|
|
39293
|
+
instanceId: transportSettings.instanceId ?? null,
|
|
39294
|
+
maxHops: transportSettings.maxHops
|
|
39295
|
+
});
|
|
39296
|
+
const remoteNodes = new Map(participatingRemotes.map((node) => [node.path, node]));
|
|
39297
|
+
const cursor = input.request.opts?.cursor;
|
|
39298
|
+
if (cursor !== void 0) {
|
|
39299
|
+
const current = await sessionStore.read(cursor, { actorKeyId: input.ctx.keyId, requestDigest });
|
|
39300
|
+
if (current.topologyDigest !== digest || current.rankingVersion !== TOOL_SEARCH_RANKING_VERSION) {
|
|
39301
|
+
throw invalidContinuation();
|
|
39302
|
+
}
|
|
39303
|
+
if (input.headers.get(HEADER_VALIDATE_SNAPSHOT) === "1") {
|
|
39304
|
+
await validateSourceSnapshots(
|
|
39305
|
+
deps,
|
|
39306
|
+
input,
|
|
39307
|
+
current.sourceContinuations,
|
|
39308
|
+
remoteNodes,
|
|
39309
|
+
deadline,
|
|
39310
|
+
Date.parse(current.expiresAt),
|
|
39311
|
+
settings.maxConcurrency
|
|
39312
|
+
);
|
|
39313
|
+
return { items: [] };
|
|
39314
|
+
}
|
|
39315
|
+
if (current.nextHandle === null) throw invalidContinuation();
|
|
39316
|
+
let next;
|
|
39317
|
+
try {
|
|
39318
|
+
next = await sessionStore.read(current.nextHandle, {
|
|
39319
|
+
actorKeyId: input.ctx.keyId,
|
|
39320
|
+
requestDigest
|
|
39321
|
+
});
|
|
39322
|
+
} catch (error) {
|
|
39323
|
+
if (!(error instanceof TBError) || error.code !== "invalid_argument") throw error;
|
|
39324
|
+
}
|
|
39325
|
+
if (next !== void 0) assertNextRecord(current, next, digest);
|
|
39326
|
+
const cachedNext = next !== void 0;
|
|
39327
|
+
if (next === void 0) {
|
|
39328
|
+
const state2 = await computePage(
|
|
39329
|
+
deps,
|
|
39330
|
+
input,
|
|
39331
|
+
structuredClone(current.sourceContinuations),
|
|
39332
|
+
remoteNodes,
|
|
39333
|
+
deadline,
|
|
39334
|
+
Date.parse(current.expiresAt),
|
|
39335
|
+
plan.totalTermCount,
|
|
39336
|
+
false,
|
|
39337
|
+
current.excludedStatuses,
|
|
39338
|
+
settings.maxConcurrency
|
|
39339
|
+
);
|
|
39340
|
+
const hasMore2 = responseHasMore(state2.sources);
|
|
39341
|
+
const nextNextHandle = hasMore2 ? sessionStore.issueHandle() : null;
|
|
39342
|
+
next = await sessionStore.create(current.nextHandle, {
|
|
39343
|
+
actorKeyId: input.ctx.keyId,
|
|
39344
|
+
excludedStatuses: state2.excluded,
|
|
39345
|
+
expiresAt: current.expiresAt,
|
|
39346
|
+
federationPolicy: current.federationPolicy,
|
|
39347
|
+
generation: current.generation + 1,
|
|
39348
|
+
nextHandle: nextNextHandle,
|
|
39349
|
+
page: state2.page,
|
|
39350
|
+
rankingVersion: TOOL_SEARCH_RANKING_VERSION,
|
|
39351
|
+
requestDigest,
|
|
39352
|
+
sessionId: current.sessionId,
|
|
39353
|
+
sourceContinuations: serializableSources(state2.sources),
|
|
39354
|
+
topologyDigest: digest
|
|
39355
|
+
});
|
|
39356
|
+
assertNextRecord(current, next, digest);
|
|
39357
|
+
assertCachedPage(state2, next);
|
|
39358
|
+
}
|
|
39359
|
+
if (cachedNext) {
|
|
39360
|
+
await validateSourceSnapshots(
|
|
39361
|
+
deps,
|
|
39362
|
+
input,
|
|
39363
|
+
next.sourceContinuations,
|
|
39364
|
+
remoteNodes,
|
|
39365
|
+
deadline,
|
|
39366
|
+
Date.parse(next.expiresAt),
|
|
39367
|
+
settings.maxConcurrency
|
|
39368
|
+
);
|
|
39369
|
+
}
|
|
39370
|
+
const wantsSnapshot2 = input.headers.get(HEADER_WANT_SNAPSHOT) === "1";
|
|
39371
|
+
const responsePage2 = {
|
|
39372
|
+
...pageWithCursor(
|
|
39373
|
+
next.page,
|
|
39374
|
+
next.nextHandle === null ? void 0 : current.nextHandle
|
|
39375
|
+
),
|
|
39376
|
+
...wantsSnapshot2 ? { snapshot: current.nextHandle } : {}
|
|
39377
|
+
};
|
|
39378
|
+
const response2 = await hydratePage(
|
|
39379
|
+
deps,
|
|
39380
|
+
input,
|
|
39381
|
+
responsePage2,
|
|
39382
|
+
next.sourceContinuations,
|
|
39383
|
+
remoteNodes,
|
|
39384
|
+
deadline,
|
|
39385
|
+
cachedNext
|
|
39386
|
+
);
|
|
39387
|
+
assertPageBytes(response2);
|
|
39388
|
+
return response2;
|
|
39389
|
+
}
|
|
39390
|
+
const initialExcluded = eligibleRemotes.filter((node) => node.config?.kind === "remote" && node.config.skRef === void 0).map((node) => ({ path: node.path, status: "unsupported" }));
|
|
39391
|
+
initialExcluded.push(...unavailableRemotes.map((node) => ({
|
|
39392
|
+
path: node.path,
|
|
39393
|
+
status: "unavailable"
|
|
39394
|
+
})));
|
|
39395
|
+
const availableRemoteSlots = Math.max(0, sourceBudget - (includeLocal ? 1 : 0));
|
|
39396
|
+
const selectedRemotes = participatingRemotes.slice(0, availableRemoteSlots);
|
|
39397
|
+
for (const node of participatingRemotes.slice(availableRemoteSlots)) {
|
|
39398
|
+
initialExcluded.push({ path: node.path, status: "budget_exhausted" });
|
|
39399
|
+
}
|
|
39400
|
+
if (snapshot.truncated) initialExcluded.push({ path: "", status: "budget_exhausted" });
|
|
39401
|
+
const allocations = selectedRemotes.map((_node, index) => {
|
|
39402
|
+
const base = selectedRemotes.length === 0 ? 0 : Math.floor(availableRemoteSlots / selectedRemotes.length);
|
|
39403
|
+
const extra = index < availableRemoteSlots % Math.max(1, selectedRemotes.length) ? 1 : 0;
|
|
39404
|
+
return Math.max(1, base + extra);
|
|
39405
|
+
});
|
|
39406
|
+
const sources = [
|
|
39407
|
+
...includeLocal ? [{ balanceCount: 0, budget: 1, done: false, kind: "local", path: "" }] : [],
|
|
39408
|
+
...selectedRemotes.map((node, index) => ({
|
|
39409
|
+
balanceCount: 0,
|
|
39410
|
+
budget: allocations[index] ?? 1,
|
|
39411
|
+
done: false,
|
|
39412
|
+
kind: "remote",
|
|
39413
|
+
path: node.path
|
|
39414
|
+
}))
|
|
39415
|
+
];
|
|
39416
|
+
const state = await computePage(
|
|
39417
|
+
deps,
|
|
39418
|
+
input,
|
|
39419
|
+
sources,
|
|
39420
|
+
remoteNodes,
|
|
39421
|
+
deadline,
|
|
39422
|
+
expiresAtMs,
|
|
39423
|
+
plan.totalTermCount,
|
|
39424
|
+
true,
|
|
39425
|
+
initialExcluded,
|
|
39426
|
+
settings.maxConcurrency
|
|
39427
|
+
);
|
|
39428
|
+
const candidateSourceCount = (includeLocal ? 1 : 0) + eligibleRemotes.length;
|
|
39429
|
+
if (candidateSourceCount > 0 && state.successfulSources === 0 && state.page.items.length === 0) {
|
|
39430
|
+
throw new TBError("unavailable", "all federated search sources failed", { retryable: true });
|
|
39431
|
+
}
|
|
39432
|
+
const hasMore = responseHasMore(state.sources);
|
|
39433
|
+
const wantsSnapshot = input.headers.get(HEADER_WANT_SNAPSHOT) === "1";
|
|
39434
|
+
const needsSession = hasMore || wantsSnapshot;
|
|
39435
|
+
const sessionHandle = needsSession ? sessionStore.issueHandle() : void 0;
|
|
39436
|
+
const responseCursor = hasMore ? sessionHandle : void 0;
|
|
39437
|
+
const nextHandle = hasMore ? sessionStore.issueHandle() : null;
|
|
39438
|
+
const responsePage = {
|
|
39439
|
+
...pageWithCursor(state.page, responseCursor),
|
|
39440
|
+
...wantsSnapshot && sessionHandle !== void 0 ? { snapshot: sessionHandle } : {}
|
|
39441
|
+
};
|
|
39442
|
+
const response = await hydratePage(
|
|
39443
|
+
deps,
|
|
39444
|
+
input,
|
|
39445
|
+
responsePage,
|
|
39446
|
+
state.sources,
|
|
39447
|
+
remoteNodes,
|
|
39448
|
+
deadline
|
|
39449
|
+
);
|
|
39450
|
+
assertPageBytes(response);
|
|
39451
|
+
if (sessionHandle !== void 0) {
|
|
39452
|
+
await sessionStore.create(sessionHandle, {
|
|
39453
|
+
actorKeyId: input.ctx.keyId,
|
|
39454
|
+
excludedStatuses: state.excluded,
|
|
39455
|
+
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
39456
|
+
federationPolicy: { limit, matching: input.request.opts?.matching ?? "best" },
|
|
39457
|
+
generation: 0,
|
|
39458
|
+
nextHandle,
|
|
39459
|
+
page: state.page,
|
|
39460
|
+
rankingVersion: TOOL_SEARCH_RANKING_VERSION,
|
|
39461
|
+
requestDigest,
|
|
39462
|
+
sourceContinuations: serializableSources(state.sources),
|
|
39463
|
+
topologyDigest: digest
|
|
39464
|
+
});
|
|
39465
|
+
}
|
|
39466
|
+
return response;
|
|
39467
|
+
}
|
|
39468
|
+
|
|
37412
39469
|
// ../app/src/routes/search.ts
|
|
39470
|
+
function requestWithNormalizedLimit(request) {
|
|
39471
|
+
return {
|
|
39472
|
+
...request,
|
|
39473
|
+
opts: {
|
|
39474
|
+
...request.opts ?? {},
|
|
39475
|
+
limit: normalizeToolSearchLimit(request.opts?.limit)
|
|
39476
|
+
}
|
|
39477
|
+
};
|
|
39478
|
+
}
|
|
37413
39479
|
function registerSearchRoute(app, env) {
|
|
37414
39480
|
const { deps, globalSearchCapabilities, searchSync } = env;
|
|
37415
39481
|
app.post(
|
|
@@ -37424,8 +39490,7 @@ function registerSearchRoute(app, env) {
|
|
|
37424
39490
|
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
37425
39491
|
throw new TBError("invalid_argument", "body must be a JSON object");
|
|
37426
39492
|
}
|
|
37427
|
-
|
|
37428
|
-
if (bodyKeys.some((key) => key !== "query" && key !== "opts")) {
|
|
39493
|
+
if (Object.keys(body).some((key) => key !== "query" && key !== "opts")) {
|
|
37429
39494
|
throw new TBError("invalid_argument", "body only accepts query and opts");
|
|
37430
39495
|
}
|
|
37431
39496
|
if (typeof body.query !== "string" || body.query.trim().length === 0) {
|
|
@@ -37436,102 +39501,72 @@ function registerSearchRoute(app, env) {
|
|
|
37436
39501
|
throw new TBError("invalid_argument", "opts must be a JSON object");
|
|
37437
39502
|
}
|
|
37438
39503
|
const opts = rawOpts ?? {};
|
|
37439
|
-
|
|
37440
|
-
|
|
39504
|
+
const optionKeys = /* @__PURE__ */ new Set([
|
|
39505
|
+
"cursor",
|
|
39506
|
+
"detail",
|
|
39507
|
+
"effects",
|
|
39508
|
+
"federation",
|
|
39509
|
+
"limit",
|
|
39510
|
+
"matching",
|
|
39511
|
+
"minCoverage",
|
|
39512
|
+
"mode",
|
|
39513
|
+
"pathPrefix"
|
|
39514
|
+
]);
|
|
39515
|
+
if (Object.keys(opts).some((key) => !optionKeys.has(key))) {
|
|
39516
|
+
throw new TBError("invalid_argument", "opts contains an unknown search option");
|
|
37441
39517
|
}
|
|
37442
|
-
|
|
37443
|
-
|
|
37444
|
-
|
|
39518
|
+
if (opts.cursor !== void 0 && typeof opts.cursor !== "string") {
|
|
39519
|
+
throw new TBError("invalid_argument", "opts.cursor must be a string");
|
|
39520
|
+
}
|
|
39521
|
+
const parsed = toolSearchRequestSchema.safeParse({
|
|
39522
|
+
query: body.query,
|
|
39523
|
+
...rawOpts === void 0 ? {} : { opts }
|
|
39524
|
+
});
|
|
39525
|
+
if (!parsed.success) {
|
|
39526
|
+
throw new TBError(
|
|
39527
|
+
"invalid_argument",
|
|
39528
|
+
parsed.error.issues[0]?.message ?? "invalid search request"
|
|
39529
|
+
);
|
|
37445
39530
|
}
|
|
39531
|
+
const request = requestWithNormalizedLimit(parsed.data);
|
|
39532
|
+
const mode = request.opts?.mode ?? "keyword";
|
|
37446
39533
|
if (mode === "semantic" && !capabilities.includes("search:semantic")) {
|
|
37447
39534
|
throw new TBError(
|
|
37448
39535
|
"invalid_argument",
|
|
37449
39536
|
"search mode 'semantic' requires capability 'search:semantic'"
|
|
37450
39537
|
);
|
|
37451
39538
|
}
|
|
37452
|
-
const
|
|
37453
|
-
|
|
37454
|
-
|
|
37455
|
-
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37459
|
-
|
|
37460
|
-
const selected = [];
|
|
37461
|
-
const workLimit = Math.min(
|
|
37462
|
-
TOOL_SEARCH_WORK_LIMIT,
|
|
37463
|
-
Math.max(TOOL_SEARCH_BATCH_LIMIT, limit * 2)
|
|
37464
|
-
);
|
|
37465
|
-
let scanCursor = opts.cursor;
|
|
37466
|
-
let responseCursor;
|
|
37467
|
-
let responseCandidate;
|
|
37468
|
-
let scanned = 0;
|
|
37469
|
-
let stopped = false;
|
|
37470
|
-
while (!stopped && selected.length < limit && scanned < workLimit) {
|
|
37471
|
-
const batchLimit = Math.min(TOOL_SEARCH_BATCH_LIMIT, workLimit - scanned);
|
|
37472
|
-
const page = await search.search(query, {
|
|
37473
|
-
mode,
|
|
37474
|
-
limit: batchLimit,
|
|
37475
|
-
...scanCursor === void 0 ? {} : { cursor: scanCursor }
|
|
37476
|
-
});
|
|
37477
|
-
if (page.items.length === 0) {
|
|
37478
|
-
responseCursor = page.cursor;
|
|
37479
|
-
break;
|
|
37480
|
-
}
|
|
37481
|
-
const nodes = await registry.getMany(page.items.map((candidate) => candidate.path));
|
|
37482
|
-
for (const [index, candidate] of page.items.entries()) {
|
|
37483
|
-
scanned++;
|
|
37484
|
-
if (!check(ctx, candidate.path, "read").allow || !check(ctx, candidate.path, "call").allow) {
|
|
37485
|
-
continue;
|
|
37486
|
-
}
|
|
37487
|
-
const node = nodes.get(candidate.path);
|
|
37488
|
-
if (node === void 0 || node.kind !== "mcp" && node.kind !== "http" && node.kind !== "tool" || node.config?.kind !== node.kind || virtualizeTools(node.virtualize, [{ name: candidate.name }]).exposed.length === 0) {
|
|
37489
|
-
continue;
|
|
37490
|
-
}
|
|
37491
|
-
selected.push({ candidate, node });
|
|
37492
|
-
if (selected.length === limit) {
|
|
37493
|
-
const hasUnscanned = index < page.items.length - 1 || page.cursor !== void 0;
|
|
37494
|
-
responseCandidate = hasUnscanned ? candidate : void 0;
|
|
37495
|
-
responseCursor = void 0;
|
|
37496
|
-
stopped = true;
|
|
37497
|
-
break;
|
|
37498
|
-
}
|
|
37499
|
-
}
|
|
37500
|
-
if (stopped) break;
|
|
37501
|
-
responseCursor = page.cursor;
|
|
37502
|
-
if (page.cursor === void 0) break;
|
|
37503
|
-
scanCursor = page.cursor;
|
|
37504
|
-
}
|
|
37505
|
-
const canonicalTools = await canonicalSearchTools(
|
|
37506
|
-
c.get("store"),
|
|
37507
|
-
selected.map((item) => item.node)
|
|
37508
|
-
);
|
|
37509
|
-
const items = [];
|
|
37510
|
-
let pageBytes = 0;
|
|
37511
|
-
for (const [index, selectedItem] of selected.entries()) {
|
|
37512
|
-
const raw = canonicalTools.get(selectedItem.candidate.path)?.find((tool2) => tool2.name === selectedItem.candidate.name);
|
|
37513
|
-
if (raw === void 0) continue;
|
|
37514
|
-
const tool = virtualizeTools(selectedItem.node.virtualize, [raw]).exposed[0];
|
|
37515
|
-
if (tool === void 0) continue;
|
|
37516
|
-
const item = { path: selectedItem.candidate.path, tool };
|
|
37517
|
-
const itemBytes = new TextEncoder().encode(JSON.stringify(item)).length;
|
|
37518
|
-
if (pageBytes + itemBytes > TOOL_SEARCH_PAGE_BYTES) {
|
|
37519
|
-
if (items.length === 0) {
|
|
37520
|
-
throw new TBError("internal", "\u5DE5\u5177\u641C\u7D22\u9875\u9762\u5B57\u8282\u9884\u7B97\u65E0\u6CD5\u5BB9\u7EB3\u9996\u4E2A\u7ED3\u679C");
|
|
37521
|
-
}
|
|
37522
|
-
responseCandidate = selected[index - 1]?.candidate;
|
|
37523
|
-
responseCursor = void 0;
|
|
37524
|
-
break;
|
|
37525
|
-
}
|
|
37526
|
-
items.push(item);
|
|
37527
|
-
pageBytes += itemBytes;
|
|
39539
|
+
const explicitFederation = request.opts?.federation;
|
|
39540
|
+
const cursor = request.opts?.cursor;
|
|
39541
|
+
const federation = explicitFederation ?? (cursor !== void 0 && !isFederatedSearchSessionHandle(cursor) ? "local" : capabilities.includes("search:federated") ? "recursive" : "local");
|
|
39542
|
+
if (federation === "recursive" && !capabilities.includes("search:federated")) {
|
|
39543
|
+
throw new TBError(
|
|
39544
|
+
"invalid_argument",
|
|
39545
|
+
"recursive search requires capability 'search:federated'"
|
|
39546
|
+
);
|
|
37528
39547
|
}
|
|
37529
|
-
if (
|
|
37530
|
-
|
|
39548
|
+
if (federation === "recursive" && mode === "semantic") {
|
|
39549
|
+
throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
|
|
37531
39550
|
}
|
|
37532
|
-
|
|
37533
|
-
|
|
37534
|
-
|
|
39551
|
+
const normalizedRequest = {
|
|
39552
|
+
...request,
|
|
39553
|
+
opts: { ...request.opts ?? {}, federation }
|
|
39554
|
+
};
|
|
39555
|
+
const result = federation === "local" ? await executeLocalSearch({
|
|
39556
|
+
ctx: c.get("ctx"),
|
|
39557
|
+
search,
|
|
39558
|
+
...searchSync === void 0 ? {} : { searchSync },
|
|
39559
|
+
state: c.get("store")
|
|
39560
|
+
}, normalizedRequest) : await executeFederatedSearch(deps, {
|
|
39561
|
+
ctx: c.get("ctx"),
|
|
39562
|
+
headers: c.req.raw.headers,
|
|
39563
|
+
request: normalizedRequest,
|
|
39564
|
+
requestUrl: c.req.url,
|
|
39565
|
+
search,
|
|
39566
|
+
...searchSync === void 0 ? {} : { searchSync },
|
|
39567
|
+
state: c.get("store")
|
|
39568
|
+
});
|
|
39569
|
+
return new Response(JSON.stringify(toolSearchPageSchema.parse(result)), {
|
|
37535
39570
|
headers: { "content-type": contentTypeFor("json") }
|
|
37536
39571
|
});
|
|
37537
39572
|
})
|
|
@@ -37719,7 +39754,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
37719
39754
|
invokePath: "/~search",
|
|
37720
39755
|
mcpName: "tb_search",
|
|
37721
39756
|
operation: "search",
|
|
37722
|
-
description: "Search visible tools across the Tool Bridge tree.",
|
|
39757
|
+
description: "Search visible tools across the Tool Bridge tree (compact by default).",
|
|
37723
39758
|
effect: "read",
|
|
37724
39759
|
inputSchema: {
|
|
37725
39760
|
type: "object",
|
|
@@ -37728,7 +39763,20 @@ function mcpBridgeFor(c, env, app) {
|
|
|
37728
39763
|
query: { type: "string", minLength: 1 },
|
|
37729
39764
|
mode: { type: "string", enum: ["keyword", "semantic"] },
|
|
37730
39765
|
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
37731
|
-
cursor: { type: "string", minLength: 1 }
|
|
39766
|
+
cursor: { type: "string", minLength: 1 },
|
|
39767
|
+
detail: { type: "string", enum: ["compact", "full"], default: "compact" },
|
|
39768
|
+
effects: {
|
|
39769
|
+
type: "array",
|
|
39770
|
+
minItems: 1,
|
|
39771
|
+
items: {
|
|
39772
|
+
type: "string",
|
|
39773
|
+
enum: ["read", "write", "destructive", "unknown"]
|
|
39774
|
+
}
|
|
39775
|
+
},
|
|
39776
|
+
federation: { type: "string", enum: ["local", "recursive"] },
|
|
39777
|
+
matching: { type: "string", enum: ["best", "all"] },
|
|
39778
|
+
minCoverage: { type: "number", exclusiveMinimum: 0, maximum: 1 },
|
|
39779
|
+
pathPrefix: { type: "string", minLength: 1 }
|
|
37732
39780
|
},
|
|
37733
39781
|
required: ["query"]
|
|
37734
39782
|
}
|
|
@@ -37936,15 +39984,27 @@ function mcpBridgeFor(c, env, app) {
|
|
|
37936
39984
|
if (tool.operation === "search") {
|
|
37937
39985
|
headers2.set("accept", "application/json");
|
|
37938
39986
|
headers2.set("content-type", "application/json");
|
|
37939
|
-
const opts =
|
|
37940
|
-
|
|
37941
|
-
|
|
39987
|
+
const opts = {
|
|
39988
|
+
detail: args.detail ?? "compact",
|
|
39989
|
+
...Object.fromEntries(
|
|
39990
|
+
[
|
|
39991
|
+
"mode",
|
|
39992
|
+
"limit",
|
|
39993
|
+
"cursor",
|
|
39994
|
+
"effects",
|
|
39995
|
+
"federation",
|
|
39996
|
+
"matching",
|
|
39997
|
+
"minCoverage",
|
|
39998
|
+
"pathPrefix"
|
|
39999
|
+
].filter((key) => args[key] !== void 0).map((key) => [key, args[key]])
|
|
40000
|
+
)
|
|
40001
|
+
};
|
|
37942
40002
|
const response2 = await app.request(new Request(new URL("/~search", c.req.url), {
|
|
37943
40003
|
method: "POST",
|
|
37944
40004
|
headers: headers2,
|
|
37945
40005
|
body: JSON.stringify({
|
|
37946
40006
|
query: args.query,
|
|
37947
|
-
|
|
40007
|
+
opts
|
|
37948
40008
|
})
|
|
37949
40009
|
}));
|
|
37950
40010
|
return await resultFromResponse(response2);
|
|
@@ -38102,27 +40162,27 @@ async function handleInvoke(c, env) {
|
|
|
38102
40162
|
throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
|
|
38103
40163
|
}
|
|
38104
40164
|
if (directUpload) {
|
|
38105
|
-
const result3 = await
|
|
38106
|
-
|
|
38107
|
-
cfg,
|
|
38108
|
-
deps,
|
|
38109
|
-
args2
|
|
40165
|
+
const result3 = await dispatchContextUploadCmd(
|
|
40166
|
+
args2,
|
|
40167
|
+
(input) => createContextUploadGrant(node, cfg, deps, input)
|
|
38110
40168
|
);
|
|
38111
40169
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
38112
40170
|
}
|
|
38113
40171
|
if (cfg.provider === "device-fs") {
|
|
40172
|
+
const forwardedArgs = parseContextCmdArgs(command, args2);
|
|
38114
40173
|
const result3 = await invokeDevice(deps, deviceIdForDeviceFs(cfg), {
|
|
38115
40174
|
path: `fs/${command}`,
|
|
38116
|
-
arguments:
|
|
40175
|
+
arguments: forwardedArgs,
|
|
38117
40176
|
context: deviceCallContextFrom(ctx)
|
|
38118
40177
|
});
|
|
38119
40178
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
38120
40179
|
}
|
|
38121
40180
|
const contextMarker = deviceMarkerOf(cfg.providerConfig);
|
|
38122
40181
|
if (cfg.provider !== "r2" && cfg.provider !== "s3" && contextMarker !== null) {
|
|
40182
|
+
const forwardedArgs = parseContextCmdArgs(command, args2);
|
|
38123
40183
|
const result3 = await invokeDevice(deps, contextMarker.deviceId, {
|
|
38124
40184
|
path: `${relativeDevicePath(node.path, contextMarker.mountPath)}/${command}`,
|
|
38125
|
-
arguments:
|
|
40185
|
+
arguments: forwardedArgs,
|
|
38126
40186
|
context: deviceCallContextFrom(ctx)
|
|
38127
40187
|
});
|
|
38128
40188
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
@@ -38130,7 +40190,7 @@ async function handleInvoke(c, env) {
|
|
|
38130
40190
|
if (cfg.provider !== "r2" && cfg.provider !== "s3") {
|
|
38131
40191
|
const local = localContext(deps, node);
|
|
38132
40192
|
if (local !== null) {
|
|
38133
|
-
const result4 = await
|
|
40193
|
+
const result4 = await dispatchContextCmd2(local, command, args2);
|
|
38134
40194
|
return renderResult(result4, negotiate(c.req.header("accept")));
|
|
38135
40195
|
}
|
|
38136
40196
|
const { manifest, export: exported } = await requirePluginExport(
|
|
@@ -38150,11 +40210,11 @@ async function handleInvoke(c, env) {
|
|
|
38150
40210
|
...cfg.authRef !== void 0 ? { upstreamAuthRef: cfg.authRef } : {},
|
|
38151
40211
|
...deps.pluginBindings !== void 0 ? { bindings: deps.pluginBindings } : {}
|
|
38152
40212
|
});
|
|
38153
|
-
const result3 = await
|
|
40213
|
+
const result3 = await dispatchContextCmd2(provider2, command, args2);
|
|
38154
40214
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
38155
40215
|
}
|
|
38156
40216
|
const provider = await contextProviderFor(node, cfg, deps, c.req.url);
|
|
38157
|
-
const result2 = await
|
|
40217
|
+
const result2 = await dispatchContextCmd2(provider, command, args2);
|
|
38158
40218
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
38159
40219
|
}
|
|
38160
40220
|
if (node.kind === "skillhub" && node.config?.kind === "skillhub") {
|
|
@@ -38172,7 +40232,7 @@ async function handleInvoke(c, env) {
|
|
|
38172
40232
|
throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
|
|
38173
40233
|
}
|
|
38174
40234
|
const provider = await skillhubProviderFor(node, cfg, deps, c.req.url);
|
|
38175
|
-
const result2 = await
|
|
40235
|
+
const result2 = await dispatchSkillhubCmd2(provider, command, args2);
|
|
38176
40236
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
38177
40237
|
}
|
|
38178
40238
|
if (node.kind !== "builtin" || node.config?.kind !== "builtin") {
|
|
@@ -38205,6 +40265,7 @@ async function handleInvoke(c, env) {
|
|
|
38205
40265
|
deps
|
|
38206
40266
|
);
|
|
38207
40267
|
assertSecretRefUse(ctx.scopes, cfgPatch);
|
|
40268
|
+
await assertMcpOAuthConfig(cfgPatch, deps.secrets);
|
|
38208
40269
|
await assertContextConfig(cfgPatch, deps);
|
|
38209
40270
|
await assertSkillhubConfig(cfgPatch, deps);
|
|
38210
40271
|
await assertToolConfig(cfgPatch, deps, ctx, targetPath);
|
|
@@ -38228,7 +40289,7 @@ async function handleInvoke(c, env) {
|
|
|
38228
40289
|
let result;
|
|
38229
40290
|
try {
|
|
38230
40291
|
result = await mod.dispatch(cmd, args, ctx, {
|
|
38231
|
-
requestOrigin:
|
|
40292
|
+
requestOrigin: resolveStoreRequestOrigin(c.req.url, deps.canonicalOrigin)
|
|
38232
40293
|
});
|
|
38233
40294
|
} catch (error) {
|
|
38234
40295
|
await searchSync?.abort(registryMarker);
|
|
@@ -38300,6 +40361,7 @@ function createRouteEnv(deps) {
|
|
|
38300
40361
|
if (!declared.has("search")) return [];
|
|
38301
40362
|
return [
|
|
38302
40363
|
"search",
|
|
40364
|
+
...deps.remote.instanceId !== void 0 && deps.state.compareAndSwap !== void 0 && deps.search?.revision !== void 0 ? ["search:federated"] : [],
|
|
38303
40365
|
...declared.has("search:semantic") ? ["search:semantic"] : []
|
|
38304
40366
|
];
|
|
38305
40367
|
};
|
|
@@ -38336,11 +40398,20 @@ async function handleHelp(c, env) {
|
|
|
38336
40398
|
return renderHelp(await enrichHelp(model2, "", store), rep);
|
|
38337
40399
|
}
|
|
38338
40400
|
if (!check(ctx, path, "read").allow) throw TBError.notFound("not found");
|
|
38339
|
-
const
|
|
38340
|
-
if (
|
|
40401
|
+
const projector = await remotePathProjectorIfMatch(registry, path);
|
|
40402
|
+
if (projector !== null) {
|
|
40403
|
+
const headers = new Headers(c.req.raw.headers);
|
|
40404
|
+
headers.set("accept", "application/json");
|
|
40405
|
+
const remote = await remotePassthroughIfMatch(c, ctx, registry, path, "~help", deps, headers);
|
|
40406
|
+
if (remote === null) throw remoteProtocolError("remote ~help lost its mount owner");
|
|
40407
|
+
if (!remote.ok) return remote;
|
|
40408
|
+
const parsed = helpJsonSchema.safeParse(await remote.json().catch(() => null));
|
|
40409
|
+
if (!parsed.success) throw remoteProtocolError("remote ~help returned invalid JSON");
|
|
40410
|
+
return renderHelp(projector.projectHelp(parsed.data, path, ctx), rep);
|
|
40411
|
+
}
|
|
38341
40412
|
const builtins = builtinsOf(store);
|
|
38342
40413
|
const refresh = c.req.query("refresh") === "1";
|
|
38343
|
-
const
|
|
40414
|
+
const schemas2 = c.req.query("schemas") === "1";
|
|
38344
40415
|
let node;
|
|
38345
40416
|
try {
|
|
38346
40417
|
node = await registry.get(path);
|
|
@@ -38349,14 +40420,14 @@ async function handleHelp(c, env) {
|
|
|
38349
40420
|
if (toolModel !== null) return renderHelp(await enrichHelp(toolModel, path, store), rep);
|
|
38350
40421
|
const cmdModel = await commandHelpModelFor(registry, ctx, builtins, deps, path, {
|
|
38351
40422
|
refresh,
|
|
38352
|
-
schemas
|
|
40423
|
+
schemas: schemas2
|
|
38353
40424
|
});
|
|
38354
40425
|
if (cmdModel !== null) return renderHelp(await enrichHelp(cmdModel, path, store), rep);
|
|
38355
40426
|
throw TBError.notFound("not found");
|
|
38356
40427
|
}
|
|
38357
40428
|
const model = await helpModelFor(node, registry, ctx, builtins, deps, {
|
|
38358
40429
|
refresh,
|
|
38359
|
-
schemas,
|
|
40430
|
+
schemas: schemas2,
|
|
38360
40431
|
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
38361
40432
|
});
|
|
38362
40433
|
return renderHelp(await enrichHelp(model, path, store), rep);
|
|
@@ -38364,18 +40435,45 @@ async function handleHelp(c, env) {
|
|
|
38364
40435
|
|
|
38365
40436
|
// ../app/src/routes/tree.ts
|
|
38366
40437
|
var REMOTE_OPAQUE_KINDS = /* @__PURE__ */ new Set(["remote"]);
|
|
40438
|
+
function renderTree(tree, rep) {
|
|
40439
|
+
if (rep === "json") {
|
|
40440
|
+
return new Response(JSON.stringify(treeJsonSchema.parse(tree)), {
|
|
40441
|
+
headers: { "content-type": contentTypeFor("json") }
|
|
40442
|
+
});
|
|
40443
|
+
}
|
|
40444
|
+
if (rep === "dsl") {
|
|
40445
|
+
return new Response(renderTreeDsl(tree), {
|
|
40446
|
+
headers: { "content-type": contentTypeFor("dsl") }
|
|
40447
|
+
});
|
|
40448
|
+
}
|
|
40449
|
+
return new Response(`\`\`\`text
|
|
40450
|
+
${renderTreeDsl(tree)}\`\`\`
|
|
40451
|
+
`, {
|
|
40452
|
+
headers: { "content-type": contentTypeFor("markdown") }
|
|
40453
|
+
});
|
|
40454
|
+
}
|
|
38367
40455
|
async function handleTree(c, env) {
|
|
38368
40456
|
const { deps } = env;
|
|
38369
40457
|
const path = splitReserved(new URL(c.req.url).pathname, "~tree");
|
|
38370
40458
|
if (path === null) throw TBError.notFound("no such path");
|
|
38371
40459
|
const ctx = c.get("ctx");
|
|
38372
40460
|
const store = c.get("store");
|
|
40461
|
+
const rep = negotiate(c.req.header("accept"));
|
|
38373
40462
|
if (path !== "" && !check(ctx, path, "read").allow) throw TBError.notFound("not found");
|
|
38374
40463
|
const registry = new NodeRegistryStore(store);
|
|
38375
40464
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
38376
40465
|
if (path !== "") {
|
|
38377
|
-
const
|
|
38378
|
-
if (
|
|
40466
|
+
const projector = await remotePathProjectorIfMatch(registry, path);
|
|
40467
|
+
if (projector !== null) {
|
|
40468
|
+
const headers = new Headers(c.req.raw.headers);
|
|
40469
|
+
headers.set("accept", "application/json");
|
|
40470
|
+
const remote = await remotePassthroughIfMatch(c, ctx, registry, path, "~tree", deps, headers);
|
|
40471
|
+
if (remote === null) throw remoteProtocolError("remote ~tree lost its mount owner");
|
|
40472
|
+
if (!remote.ok) return remote;
|
|
40473
|
+
const parsed = treeJsonSchema.safeParse(await remote.json().catch(() => null));
|
|
40474
|
+
if (!parsed.success) throw remoteProtocolError("remote ~tree returned invalid JSON");
|
|
40475
|
+
return renderTree(projector.projectTree(parsed.data, path, ctx), rep);
|
|
40476
|
+
}
|
|
38379
40477
|
}
|
|
38380
40478
|
let rootEntry;
|
|
38381
40479
|
if (path !== "") {
|
|
@@ -38406,22 +40504,7 @@ async function handleTree(c, env) {
|
|
|
38406
40504
|
opaqueKinds: REMOTE_OPAQUE_KINDS,
|
|
38407
40505
|
...rootEntry !== void 0 ? { rootEntry } : {}
|
|
38408
40506
|
});
|
|
38409
|
-
|
|
38410
|
-
if (rep === "json") {
|
|
38411
|
-
return new Response(JSON.stringify(tree), {
|
|
38412
|
-
headers: { "content-type": contentTypeFor("json") }
|
|
38413
|
-
});
|
|
38414
|
-
}
|
|
38415
|
-
if (rep === "dsl") {
|
|
38416
|
-
return new Response(renderTreeDsl(tree), {
|
|
38417
|
-
headers: { "content-type": contentTypeFor("dsl") }
|
|
38418
|
-
});
|
|
38419
|
-
}
|
|
38420
|
-
return new Response(`\`\`\`text
|
|
38421
|
-
${renderTreeDsl(tree)}\`\`\`
|
|
38422
|
-
`, {
|
|
38423
|
-
headers: { "content-type": contentTypeFor("markdown") }
|
|
38424
|
-
});
|
|
40507
|
+
return renderTree(tree, rep);
|
|
38425
40508
|
}
|
|
38426
40509
|
|
|
38427
40510
|
// ../app/src/tbApp.ts
|
|
@@ -39224,6 +41307,40 @@ var FsObjectStore = class {
|
|
|
39224
41307
|
// ../core/src/node/shellExecutor.ts
|
|
39225
41308
|
import { spawn as nodeSpawn } from "child_process";
|
|
39226
41309
|
var SHELL_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
41310
|
+
var SHELL_EXEC_DEFAULT_TIMEOUT_MS = 55e3;
|
|
41311
|
+
|
|
41312
|
+
// ../core/src/node/structuredCommand.ts
|
|
41313
|
+
import { spawn as nodeSpawn2 } from "child_process";
|
|
41314
|
+
var STRUCTURED_COMMAND_PROFILE_VERSION = 1;
|
|
41315
|
+
var STRUCTURED_COMMAND_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
41316
|
+
var envNameSchema = external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
|
|
41317
|
+
var argumentSchema = external_exports.strictObject({
|
|
41318
|
+
input: external_exports.string().min(1),
|
|
41319
|
+
description: external_exports.string().min(1).optional(),
|
|
41320
|
+
flag: external_exports.string().min(1).optional(),
|
|
41321
|
+
choices: external_exports.array(external_exports.string()).min(1).optional(),
|
|
41322
|
+
multiple: external_exports.boolean().optional(),
|
|
41323
|
+
required: external_exports.boolean().optional(),
|
|
41324
|
+
type: external_exports.enum(["string", "number", "boolean"]).optional()
|
|
41325
|
+
});
|
|
41326
|
+
var commandSchema = external_exports.strictObject({
|
|
41327
|
+
name: external_exports.string().min(1),
|
|
41328
|
+
description: external_exports.string().min(1),
|
|
41329
|
+
executable: external_exports.string().min(1),
|
|
41330
|
+
argv: external_exports.array(external_exports.union([external_exports.string(), argumentSchema])).optional(),
|
|
41331
|
+
cwd: external_exports.string().min(1).optional(),
|
|
41332
|
+
effect: external_exports.enum(["read", "write", "destructive"]),
|
|
41333
|
+
confirm: external_exports.boolean().optional(),
|
|
41334
|
+
timeoutMs: external_exports.number().int().positive().max(SHELL_EXEC_DEFAULT_TIMEOUT_MS).optional(),
|
|
41335
|
+
maxOutputBytes: external_exports.number().int().positive().max(STRUCTURED_COMMAND_MAX_OUTPUT_BYTES).optional(),
|
|
41336
|
+
inheritEnv: external_exports.array(envNameSchema).optional()
|
|
41337
|
+
});
|
|
41338
|
+
var profileSchema = external_exports.strictObject({
|
|
41339
|
+
version: external_exports.literal(STRUCTURED_COMMAND_PROFILE_VERSION),
|
|
41340
|
+
path: external_exports.string().min(1),
|
|
41341
|
+
description: external_exports.string().min(1),
|
|
41342
|
+
commands: external_exports.array(commandSchema).min(1)
|
|
41343
|
+
});
|
|
39227
41344
|
|
|
39228
41345
|
// src/objects.ts
|
|
39229
41346
|
import { dirname as dirname2, join as join2, sep as sep2 } from "path";
|
|
@@ -39507,7 +41624,7 @@ var PgSearchDriver = class {
|
|
|
39507
41624
|
records,
|
|
39508
41625
|
PG_SEARCH_TOOLS_TABLE,
|
|
39509
41626
|
PG_SEARCH_TOOLS_COLUMNS,
|
|
39510
|
-
(record2) => [record2.path, record2.name, record2.description, record2.feedback]
|
|
41627
|
+
(record2) => [record2.path, record2.name, record2.description, record2.effect, record2.feedback]
|
|
39511
41628
|
);
|
|
39512
41629
|
}
|
|
39513
41630
|
insertSnapshots(digests) {
|
|
@@ -39697,7 +41814,7 @@ var SqliteSearchDriver = class {
|
|
|
39697
41814
|
}
|
|
39698
41815
|
insertRecords(records) {
|
|
39699
41816
|
return records.map((record2) => ({
|
|
39700
|
-
params: [record2.path, record2.name, record2.description, record2.feedback],
|
|
41817
|
+
params: [record2.path, record2.name, record2.description, record2.effect, record2.feedback],
|
|
39701
41818
|
sql: TOOL_SEARCH_INSERT_SQL
|
|
39702
41819
|
}));
|
|
39703
41820
|
}
|
|
@@ -39841,7 +41958,7 @@ import { join as join4 } from "path";
|
|
|
39841
41958
|
// package.json
|
|
39842
41959
|
var package_default = {
|
|
39843
41960
|
name: "@tool-bridge/server",
|
|
39844
|
-
version: "0.
|
|
41961
|
+
version: "0.19.0",
|
|
39845
41962
|
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",
|
|
39846
41963
|
type: "module",
|
|
39847
41964
|
license: "MIT",
|
|
@@ -40023,6 +42140,7 @@ function resolveUiAssets(uiDirOverride) {
|
|
|
40023
42140
|
}
|
|
40024
42141
|
|
|
40025
42142
|
// src/server.ts
|
|
42143
|
+
var providerOAuthFetch = createGuardedFetch({ crossOriginRedirect: "error" });
|
|
40026
42144
|
function pgBackends(databaseUrl) {
|
|
40027
42145
|
const sql = postgres(databaseUrl, { onnotice: () => {
|
|
40028
42146
|
} });
|
|
@@ -40136,6 +42254,7 @@ function createTbServer(config) {
|
|
|
40136
42254
|
const deps = {
|
|
40137
42255
|
state,
|
|
40138
42256
|
secrets,
|
|
42257
|
+
providerOAuthFetch,
|
|
40139
42258
|
version: package_default.version,
|
|
40140
42259
|
remote: config.remote,
|
|
40141
42260
|
search,
|