@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.
Files changed (115) hide show
  1. package/dist/{airtable-S3T4TAN7.js → airtable-23JBIM6X.js} +106 -114
  2. package/dist/{alt_text_generator_ai-4DIRIDKL.js → alt_text_generator_ai-ADWBTCIH.js} +25 -16
  3. package/dist/{amap-LGVGNPML.js → amap-CYQCYW2Z.js} +114 -123
  4. package/dist/{anthropic-27RFZO35.js → anthropic-5P62HDNR.js} +34 -56
  5. package/dist/{apify-6WLYAIAS.js → apify-54RZUAGW.js} +31 -49
  6. package/dist/{brave_search-K2SVJ66F.js → brave_search-AHJ6FTD5.js} +52 -77
  7. package/dist/{chunk-VK3V2WTY.js → chunk-B4P6YZFS.js} +4 -6
  8. package/dist/{chunk-BB6ZLMTU.js → chunk-CLEZ6YQ6.js} +2600 -481
  9. package/dist/chunk-JXHMDBAY.js +45 -0
  10. package/dist/chunk-LMLX3DJF.js +50 -0
  11. package/dist/{chunk-YB2FLNWI.js → chunk-OKRZPQ3R.js} +2558 -1926
  12. package/dist/chunk-SWBNHIO4.js +977 -0
  13. package/dist/chunk-U67V476Y.js +35 -0
  14. package/dist/chunk-VLOIDU5U.js +283 -0
  15. package/dist/{circleci-WV3ITU7Q.js → circleci-UM3NSQJ3.js} +36 -55
  16. package/dist/{clerk-NFQEFKS4.js → clerk-FE5LQ2CZ.js} +60 -80
  17. package/dist/{cloudflare_dns-2RK5ASBJ.js → cloudflare_dns-KEZABKUT.js} +90 -113
  18. package/dist/{cohere-5KEW7LWT.js → cohere-4IPJADXP.js} +33 -49
  19. package/dist/{confluence-SZE63S46.js → confluence-56QEKNQI.js} +79 -115
  20. package/dist/{convertapi-QPG2JC5K.js → convertapi-3QRM5K5T.js} +13 -16
  21. package/dist/{currencyapi-UO2APQRY.js → currencyapi-L6G4OND5.js} +30 -53
  22. package/dist/{deepseek-LJELIMM3.js → deepseek-LGC6HBFG.js} +30 -38
  23. package/dist/{docker_hub-2MFXSBOF.js → docker_hub-XDMK3BIU.js} +141 -157
  24. package/dist/{dropbox-GQYNTTVA.js → dropbox-4QARMFZU.js} +134 -141
  25. package/dist/{dub-KI2LHXST.js → dub-C6JSJKOT.js} +61 -93
  26. package/dist/{e2b-WO6ISY3C.js → e2b-6A6TVACJ.js} +39 -69
  27. package/dist/{exa-IZ7B3BPT.js → exa-SIWWW6AA.js} +49 -77
  28. package/dist/{fal_ai-LTMF36GJ.js → fal_ai-3IJXOKSP.js} +37 -44
  29. package/dist/{feishu-WM7SLSFD.js → feishu-L4KDA4DC.js} +4 -6
  30. package/dist/{feishu_custom_bot-66W3MXH2.js → feishu_custom_bot-SDM75H4P.js} +4 -5
  31. package/dist/{figma-2Z274VQZ.js → figma-6DD436DN.js} +56 -81
  32. package/dist/{firehydrant-IFI4R65V.js → firehydrant-SYSGYB3R.js} +40 -53
  33. package/dist/{fixer-BCCZEB3D.js → fixer-FZG7ASTF.js} +31 -43
  34. package/dist/{fly-755FXYB5.js → fly-HMRCZBUC.js} +50 -66
  35. package/dist/{front-4FZKKXXK.js → front-FAFSD7I3.js} +43 -66
  36. package/dist/{geocodio-WYKDJ3DP.js → geocodio-4D3TWENM.js} +37 -63
  37. package/dist/{ghost-5BNQTCOH.js → ghost-OSO2FOZS.js} +51 -57
  38. package/dist/{github-ULELF5VX.js → github-OR4I4RHE.js} +48 -41
  39. package/dist/{gmail-G34WT64J.js → gmail-5AOPJ63E.js} +26 -36
  40. package/dist/{googlecalendar-BQU255SC.js → googlecalendar-SOJIJUWH.js} +57 -78
  41. package/dist/{googledocs-HPCNQXYJ.js → googledocs-QSKN5TLN.js} +14 -22
  42. package/dist/{grafana-VOV24FIC.js → grafana-R3AIZM7M.js} +95 -122
  43. package/dist/{graphhopper-D5RXW6AP.js → graphhopper-4CR3QOAX.js} +32 -46
  44. package/dist/index.d.ts +108 -56
  45. package/dist/index.js +4 -3
  46. package/dist/{ip2proxy-DAYO7EKZ.js → ip2proxy-OBCG6X6J.js} +35 -42
  47. package/dist/{ipgeolocation_io-XD4PSLII.js → ipgeolocation_io-NHH3DTQW.js} +40 -60
  48. package/dist/{ipqualityscore-K27QEAWK.js → ipqualityscore-7POMCBEC.js} +30 -42
  49. package/dist/{jina_ai-E6UXFV2P.js → jina_ai-YXVKROV4.js} +32 -58
  50. package/dist/{jira-ZDSXGQ6W.js → jira-LADO3VJU.js} +116 -126
  51. package/dist/{langsmith-TIFJRREW.js → langsmith-XZ6UAQFU.js} +101 -121
  52. package/dist/{lemlist-DASEMJMA.js → lemlist-P74GLNE7.js} +57 -68
  53. package/dist/{linear-KE6UP4SP.js → linear-CXJ7D37A.js} +109 -118
  54. package/dist/{logsnag-IMNDN4R5.js → logsnag-EVVHWFLK.js} +31 -48
  55. package/dist/main.js +103 -102
  56. package/dist/{mattermost-HHHMUXQA.js → mattermost-WG2MRGBD.js} +45 -62
  57. package/dist/{mem0-D2FGGOIY.js → mem0-HQWUKG6U.js} +72 -98
  58. package/dist/{memos-4OVIGFPH.js → memos-SQMJIFOE.js} +68 -122
  59. package/dist/{metabase-33WCX5YD.js → metabase-E27G2DZI.js} +47 -60
  60. package/dist/{mistral_ai-QIMZ2JYL.js → mistral_ai-MIL63NIT.js} +35 -40
  61. package/dist/{mother_duck-M6VWS2VH.js → mother_duck-AIZVCBNR.js} +42 -62
  62. package/dist/{ngrok-Z2PKQBHH.js → ngrok-XS4DVIQS.js} +42 -58
  63. package/dist/{notes-SS4RPC4K.js → notes-JEOUSL2Y.js} +3 -5
  64. package/dist/{notion-FJCNQ6I7.js → notion-U4EZVDW2.js} +83 -97
  65. package/dist/{open_exchange_rates-WU57WTT5.js → open_exchange_rates-AZSQEYEJ.js} +35 -39
  66. package/dist/{openai-JODZ4GKT.js → openai-HW3SYMTQ.js} +51 -55
  67. package/dist/{opengraph_io-SQRAUSV5.js → opengraph_io-QX74MAEL.js} +61 -79
  68. package/dist/{openrouter-Q5XXEJZ7.js → openrouter-OXSKYL6N.js} +41 -55
  69. package/dist/{outline-RT2EHJ2X.js → outline-CCQAPDSW.js} +89 -104
  70. package/dist/{paddle-OF2V7GAC.js → paddle-6KQPSSCK.js} +34 -49
  71. package/dist/{perplexity-LNZEWLHG.js → perplexity-2TXO7IQE.js} +33 -50
  72. package/dist/{pinecone-JSJIXZYN.js → pinecone-YOO7NULI.js} +65 -91
  73. package/dist/{pipedrive-D4CKVUXR.js → pipedrive-Q6RIFIFP.js} +37 -59
  74. package/dist/{polar-M7ZYP6UR.js → polar-KUMGCES3.js} +43 -53
  75. package/dist/{posthog-YV7CZOH4.js → posthog-VXQHBSMB.js} +58 -77
  76. package/dist/{postmark-KYHLECRG.js → postmark-YLIPF52S.js} +52 -71
  77. package/dist/{prerender-HCMHBKV2.js → prerender-CRY7UJN5.js} +32 -50
  78. package/dist/{pubmed-WYVLCCN3.js → pubmed-3Z4T5AM7.js} +28 -60
  79. package/dist/{railway-TWIRD5KR.js → railway-BP37IGFL.js} +32 -39
  80. package/dist/{readwise-UZ467IQ7.js → readwise-HTKICF4K.js} +62 -78
  81. package/dist/{render-MECUC5CC.js → render-WIVTUXJH.js} +48 -64
  82. package/dist/{replicate-A5MEK3SW.js → replicate-JJYPA43D.js} +41 -58
  83. package/dist/{resend-BC6EAWJQ.js → resend-TKHZVG7A.js} +27 -37
  84. package/dist/{rootly-ZM3QDUIS.js → rootly-FRSZLG5U.js} +64 -76
  85. package/dist/{scrapfly-WAML42QJ.js → scrapfly-M5WXGQWE.js} +45 -51
  86. package/dist/{scrapingbee-DG7PET3V.js → scrapingbee-PMXNLLBK.js} +25 -33
  87. package/dist/{screenshot_fyi-HY6WTHIO.js → screenshot_fyi-HFW4TAOH.js} +29 -47
  88. package/dist/{semantic_scholar-C5IZA7IP.js → semantic_scholar-KXVH2VZQ.js} +37 -56
  89. package/dist/{sentry-OKBPSTDM.js → sentry-WWOT2LRX.js} +66 -85
  90. package/dist/{shodan-PPC6DYU4.js → shodan-FY635H35.js} +42 -53
  91. package/dist/{shopify-EWGUHBXO.js → shopify-HQLBVSA5.js} +32 -49
  92. package/dist/{stripe-6RPDPC5W.js → stripe-GJN2GOHB.js} +4 -5
  93. package/dist/{supabase-SK7DQ42N.js → supabase-BBNZGQU6.js} +69 -88
  94. package/dist/{tavily-YCG5VQMH.js → tavily-DPENIBOG.js} +37 -62
  95. package/dist/{telegram-2OES5FYB.js → telegram-L4NONG6H.js} +168 -177
  96. package/dist/{telnyx-POOSBBR4.js → telnyx-H67MAEZS.js} +30 -50
  97. package/dist/{todoist-M7YD6YSS.js → todoist-G2BSP4DX.js} +77 -89
  98. package/dist/{trello-IS6V5W3E.js → trello-7WMVFDG2.js} +108 -122
  99. package/dist/{turso-RCFHUFVH.js → turso-ZUAESFC2.js} +52 -64
  100. package/dist/{twilio-OS2RXFGO.js → twilio-EYFYIX3B.js} +48 -55
  101. package/dist/{umami-PQ2SJ2LN.js → umami-AXAUDUVF.js} +49 -69
  102. package/dist/{unsplash-C6BPZO4E.js → unsplash-UCQAWPLR.js} +35 -52
  103. package/dist/{upstash_redis-WH6UER7H.js → upstash_redis-ZPZ4ASU6.js} +28 -40
  104. package/dist/{uptimerobot-M3YZGS6Z.js → uptimerobot-4QVKKUD5.js} +11 -12
  105. package/dist/{v2ex-R4JWTEHN.js → v2ex-VGEFJTYC.js} +36 -61
  106. package/dist/{vercel-FZVYUOQD.js → vercel-JME4TSCN.js} +124 -150
  107. package/dist/{webapi-VLM4IKVB.js → webapi-GW5QXWXE.js} +1 -1
  108. package/dist/{wolfram_alpha_api-2KOLO5DC.js → wolfram_alpha_api-TRGU2MDG.js} +26 -34
  109. package/dist/{wordpress-XRWGO6LG.js → wordpress-WRUKLCZR.js} +87 -104
  110. package/dist/{workos-WR4NKOYL.js → workos-ZDW4EHS4.js} +37 -56
  111. package/dist/{zhihu-BKZIB3WA.js → zhihu-4VI67CWU.js} +35 -57
  112. package/package.json +3 -3
  113. package/dist/chunk-2O27H4Y2.js +0 -183
  114. package/dist/chunk-GKX72JMJ.js +0 -378
  115. 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
- normalizeCanonicalOrigin,
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-YB2FLNWI.js";
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 allowInsecure = env.TB_ALLOW_INSECURE_HTTP === "true";
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: portEnv(env.TB_PORT) ?? portEnv(env.PORT) ?? DEFAULT_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
- remote: {
185
- allowlist,
186
- maxHops: positiveIntEnv(env.TB_MAX_HOPS) ?? DEFAULT_MAX_HOPS,
187
- ...env.TB_INSTANCE_ID !== void 0 && env.TB_INSTANCE_ID.length > 0 ? { instanceId: env.TB_INSTANCE_ID } : {},
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
- const source = body;
674
- const reader = source.getReader();
675
- return new ReadableStream({
676
- async pull(controller) {
677
- try {
678
- const chunk = await reader.read();
679
- if (chunk.done) {
680
- reader.releaseLock();
681
- controller.close();
682
- } else if (chunk.value !== void 0) {
683
- controller.enqueue(chunk.value);
684
- }
685
- } catch (error) {
686
- try {
687
- reader.releaseLock();
688
- } catch {
689
- }
690
- controller.error(error);
691
- }
692
- },
693
- async cancel(reason) {
694
- try {
695
- if (reader.cancel !== void 0) await reader.cancel(reason);
696
- else await source.cancel?.(reason);
697
- } finally {
698
- try {
699
- reader.releaseLock();
700
- } catch {
701
- }
702
- }
703
- }
704
- }, { highWaterMark: 0 });
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 s3FetchWith(putClient, urlFor(key), {
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 requestOrigin(runtime, deps) {
1260
- const raw = deps.canonicalOrigin ?? runtime?.requestOrigin;
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 storeUploadGrant(start, origin2) {
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 ? `${origin2}/~store/uploads/${encodeURIComponent(start.uploadId)}` : start.signedRequest.url;
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, origin2) {
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, origin2);
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 reader = got.body.getReader();
1404
- const body = new ReadableStream({
1405
- async pull(controller) {
1406
- try {
1407
- const chunk = await reader.read();
1408
- if (chunk.done) {
1409
- reader.releaseLock();
1410
- controller.close();
1411
- } else if (chunk.value !== void 0) {
1412
- controller.enqueue(chunk.value);
1413
- }
1414
- } catch (error) {
1415
- try {
1416
- reader.releaseLock();
1417
- } catch {
1418
- }
1419
- controller.error(error);
1420
- }
1421
- },
1422
- async cancel(reason) {
1423
- try {
1424
- if (reader.cancel !== void 0) await reader.cancel(reason);
1425
- else await got.body.cancel?.(reason);
1426
- } finally {
1427
- try {
1428
- reader.releaseLock();
1429
- } catch {
1430
- }
1431
- }
1432
- }
1433
- });
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(schemas) {
5808
- return new Set(schemas.flatMap((schema) => Object.keys(schema.shape)));
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 consume = consumeHextets;
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 (!consume(buffer, address, output)) break;
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 (!consume(buffer, address, output)) break;
10087
- consume = consumeIsZone;
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 (consume === consumeIsZone) output.zone = buffer.join("");
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(schemas, regex3) {
10984
- for (const keyRef in schemas) {
10985
- const sch = schemas[keyRef];
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 schemas[keyRef];
10949
+ if (typeof sch == "string") delete schemas2[keyRef];
10988
10950
  else if (sch && !sch.meta) {
10989
10951
  this._cache.delete(sch.schema);
10990
- delete schemas[keyRef];
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 origin2 = this.opts.origin ?? "https://tool-bridge.invalid";
19292
- return `${origin2}${OAUTH_CALLBACK_PATH}`;
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
- token_endpoint_auth_method: "none"
19301
- // public client,PKCE 保护
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
- return raw === null ? void 0 : raw;
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.opts.store.put(KEY_OAUTH_TOKENS + this.opts.nodePath, tokens);
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
- ...this.opts.redirectUri !== void 0 ? { r: this.opts.redirectUri } : {}
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") await store.delete(KEY_OAUTH_CLIENT + nodePath);
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
- assertLocalRedirectUri(opts.redirectUri);
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(opts.redirectUri)) {
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 fetch(input, init);
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 (err) {
19843
+ } catch {
19774
19844
  throw new TBError(
19775
19845
  "unavailable",
19776
- `\u4EE4\u724C\u7AEF\u70B9\u4E0D\u53EF\u8FBE:${err instanceof Error ? err.message : String(err)}`,
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 parsed = payload === null ? void 0 : parseTokenErrorMessage(payload);
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
- return parseTokenResponse(payload, opts.config, opts.now);
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: opts.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: opts.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: opts.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: fetch,
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 origin2 = new URL(requestUrl).origin;
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 `${origin2}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
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 origin2 = new URL(requestUrl).origin;
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 `${origin2}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
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 dispatchSkillhubCmd(provider, command, args) {
20351
- switch (command.toLowerCase()) {
20352
- case "list":
20353
- return await provider.list(args.opts);
20354
- case "get":
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, origin2) {
23849
- if (origin2 === "in-band") return code === ProtocolErrorCode2.MissingRequiredClientCapability ? 400 : 200;
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(schemas) {
24063
- return new Set(schemas.flatMap((schema) => Object.keys(schema.shape)));
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 consume = consumeHextets;
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 (!consume(buffer, address, output)) break;
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 (!consume(buffer, address, output)) break;
28264
- consume = consumeIsZone;
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 (consume === consumeIsZone) output.zone = buffer.join("");
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(schemas, regex3) {
29161
- for (const keyRef in schemas) {
29162
- const sch = schemas[keyRef];
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 schemas[keyRef];
29200
+ if (typeof sch == "string") delete schemas2[keyRef];
29165
29201
  else if (sch && !sch.meta) {
29166
29202
  this._cache.delete(sch.schema);
29167
- delete schemas[keyRef];
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 resp.text();
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, { status: resp.status, headers: { "content-type": respCt } });
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), { headers: { "content-type": contentTypeFor("json") } });
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 vote = body.vote;
36655
- if (vote !== "up" && vote !== "down" && vote !== "clear") {
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(await fb.vote(target.path, target.id, ctx.owner, vote));
37110
+ return feedbackJson(
37111
+ feedbackViewSchema,
37112
+ await fb.vote(target.path, target.id, ctx.owner, input2.data.vote)
37113
+ );
36659
37114
  }
36660
- if (typeof body.title !== "string" || typeof body.detail !== "string") {
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
- { title: body.title, detail: body.detail },
37122
+ input.data,
36667
37123
  ctx.owner,
36668
37124
  (/* @__PURE__ */ new Date()).toISOString()
36669
37125
  );
36670
- return feedbackJson({ id: entry.id, path: target.path, title: entry.title, at: entry.at });
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
- let decoded = segment;
36804
- for (let pass = 0; pass < 4; pass++) {
36805
- if (decoded === "." || decoded === ".." || decoded.startsWith("~") || decoded.includes("/") || decoded.includes("\\") || [...decoded].some((char) => {
36806
- const code = char.charCodeAt(0);
36807
- return code <= 31 || code === 127;
36808
- })) {
36809
- throw remoteProtocolError(`remote returned unsafe path segment '${segment}'`);
36810
- }
36811
- let next;
36812
- try {
36813
- next = decodeURIComponent(decoded);
36814
- } catch {
36815
- throw remoteProtocolError(`remote returned malformed encoded path '${path}'`);
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
- function localizeRemoteEntry(mountPath, remoteParentPath, entry) {
36830
- const rel = canonicalRemotePath(entry.path, false);
36831
- const parent = rel.split("/").slice(0, -1).join("/");
36832
- if (parent !== remoteParentPath) {
36833
- throw remoteProtocolError(`remote ~tree child '${rel}' is not a direct descendant`);
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
- const out = {
36836
- path: `${mountPath}/${rel}`,
36837
- kind: entry.kind,
36838
- description: entry.description
36839
- };
36840
- if (entry.presence !== void 0) out.presence = entry.presence;
36841
- return out;
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 remoteTree = await resp.json().catch(() => null);
36896
- if (remoteTree === null) {
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 remotePath = canonicalRemotePath(remoteTree.path, true);
36900
- if (remotePath !== resolved.rest) {
36901
- throw remoteProtocolError(`remote ~tree path '${remotePath}' does not match request`);
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: fetch,
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 body = await c.req.json().catch(() => null);
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
- origin(c, deps.canonicalOrigin)
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: fetch,
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) return c.json({ checks: {}, ready: true });
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
- const bodyKeys = Object.keys(body);
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
- if (Object.keys(opts).some((key) => key !== "mode" && key !== "limit" && key !== "cursor")) {
37440
- throw new TBError("invalid_argument", "opts only accepts mode, limit and cursor");
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
- const mode = opts.mode ?? "keyword";
37443
- if (mode !== "keyword" && mode !== "semantic") {
37444
- throw new TBError("invalid_argument", "opts.mode must be 'keyword' or 'semantic'");
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 limit = normalizeToolSearchLimit(opts.limit);
37453
- if (opts.cursor !== void 0 && typeof opts.cursor !== "string") {
37454
- throw new TBError("invalid_argument", "opts.cursor must be a string");
37455
- }
37456
- const query = body.query.trim();
37457
- await searchSync?.ensureReady();
37458
- const ctx = c.get("ctx");
37459
- const registry = new NodeRegistryStore(c.get("store"));
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 (responseCandidate !== void 0) {
37530
- responseCursor = await search.cursorFor(query, responseCandidate, mode);
39548
+ if (federation === "recursive" && mode === "semantic") {
39549
+ throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
37531
39550
  }
37532
- if (items.length === 0) responseCursor = void 0;
37533
- const result = responseCursor === void 0 ? { items } : { items, cursor: responseCursor };
37534
- return new Response(JSON.stringify(result), {
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 = Object.fromEntries(
37940
- ["mode", "limit", "cursor"].filter((key) => args[key] !== void 0).map((key) => [key, args[key]])
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
- ...Object.keys(opts).length === 0 ? {} : { opts }
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 createContextUploadGrant(
38106
- node,
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: args2,
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: args2,
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 dispatchContextCmd(local, command, args2);
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 dispatchContextCmd(provider2, command, args2);
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 dispatchContextCmd(provider, command, args2);
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 dispatchSkillhubCmd(provider, command, args2);
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: deps.canonicalOrigin ?? new URL(c.req.url).origin
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 remote = await remotePassthroughIfMatch(c, ctx, registry, path, "~help", deps);
38340
- if (remote) return remote;
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 schemas = c.req.query("schemas") === "1";
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 remote = await remotePassthroughIfMatch(c, ctx, registry, path, "~tree", deps);
38378
- if (remote) return remote;
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
- const rep = negotiate(c.req.header("accept"));
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.16.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,