@nyte-ai/host 0.0.0-stage → 0.0.19

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 (250) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -2
  3. package/THIRD-PARTY-NOTICES.md +866 -0
  4. package/dist/chunks/canvas-preview-0cmbkp2j.js +65 -0
  5. package/dist/chunks/canvas-preview-0nq75h9k.js +7 -0
  6. package/dist/chunks/canvas-preview-11nadadc.js +241 -0
  7. package/dist/chunks/canvas-preview-1sq7z9jq.js +17 -0
  8. package/dist/chunks/canvas-preview-2jwbe14k.js +130 -0
  9. package/dist/chunks/canvas-preview-2zdyh9ny.js +26 -0
  10. package/dist/chunks/canvas-preview-31w7xm8z.js +34 -0
  11. package/dist/chunks/canvas-preview-4b6sayhg.js +295 -0
  12. package/dist/chunks/canvas-preview-4e0pxa32.js +63 -0
  13. package/dist/chunks/canvas-preview-518dh8by.js +169 -0
  14. package/dist/chunks/canvas-preview-55s0y68g.js +387 -0
  15. package/dist/chunks/canvas-preview-5ekjx6sw.js +613 -0
  16. package/dist/chunks/canvas-preview-5mex725c.js +14 -0
  17. package/dist/chunks/canvas-preview-5z63am63.js +295 -0
  18. package/dist/chunks/canvas-preview-65xqjw7f.js +20 -0
  19. package/dist/chunks/canvas-preview-69fkk9yn.js +95 -0
  20. package/dist/chunks/canvas-preview-8xc0vn7v.js +458 -0
  21. package/dist/chunks/canvas-preview-9aqczacs.js +73 -0
  22. package/dist/chunks/canvas-preview-af86jz8t.js +141 -0
  23. package/dist/chunks/canvas-preview-aq5j4p25.js +240 -0
  24. package/dist/chunks/canvas-preview-b2v8wfnz.js +36 -0
  25. package/dist/chunks/canvas-preview-befkjea2.js +60 -0
  26. package/dist/chunks/canvas-preview-bxnqt8cd.js +103 -0
  27. package/dist/chunks/canvas-preview-cawy6vbz.js +352 -0
  28. package/dist/chunks/canvas-preview-crmzxxt8.js +518 -0
  29. package/dist/chunks/canvas-preview-drctegz4.js +591 -0
  30. package/dist/chunks/canvas-preview-e3vnkkc5.js +134 -0
  31. package/dist/chunks/canvas-preview-ec2mt7es.js +108 -0
  32. package/dist/chunks/canvas-preview-fxa0rfb0.js +61 -0
  33. package/dist/chunks/canvas-preview-g5fkjs34.js +598 -0
  34. package/dist/chunks/canvas-preview-jeddjex8.js +166 -0
  35. package/dist/chunks/canvas-preview-k1yg4fgp.js +571 -0
  36. package/dist/chunks/canvas-preview-kdqgrstc.js +194 -0
  37. package/dist/chunks/canvas-preview-kn7kvbfj.js +12 -0
  38. package/dist/chunks/canvas-preview-kp75gb63.js +141 -0
  39. package/dist/chunks/canvas-preview-mgcx2b2p.js +271 -0
  40. package/dist/chunks/canvas-preview-n2a35zv5.js +81 -0
  41. package/dist/chunks/canvas-preview-pkzqg96v.js +63 -0
  42. package/dist/chunks/canvas-preview-rp4x6y1h.js +144 -0
  43. package/dist/chunks/canvas-preview-s94ebwf5.js +112 -0
  44. package/dist/chunks/canvas-preview-skzwvw4d.js +23 -0
  45. package/dist/chunks/canvas-preview-srgfpdmf.js +60 -0
  46. package/dist/chunks/canvas-preview-tjhabynd.js +252 -0
  47. package/dist/chunks/canvas-preview-tnqe7k0b.js +374 -0
  48. package/dist/chunks/canvas-preview-vaatfznm.js +189 -0
  49. package/dist/chunks/canvas-preview-vm9zdp2t.js +398 -0
  50. package/dist/chunks/canvas-preview-w54ymtb3.js +272 -0
  51. package/dist/chunks/canvas-preview-wg80rbc2.js +46 -0
  52. package/dist/chunks/canvas-preview-y5y6ghaw.js +125 -0
  53. package/dist/chunks/canvas-preview-ycepb05s.js +176 -0
  54. package/dist/chunks/canvas-preview-yjz0f7n4.js +50 -0
  55. package/dist/chunks/canvas-preview-zaaj99wd.js +244 -0
  56. package/dist/src/canvas-preview.d.ts +29 -0
  57. package/dist/src/canvas-preview.js +271 -0
  58. package/dist/src/credentials.d.ts +44 -0
  59. package/dist/src/credentials.js +10 -0
  60. package/dist/src/deploy/cloudflare/worker.d.ts +50 -0
  61. package/dist/src/deploy/cloudflare/worker.js +196 -0
  62. package/dist/src/deploy/vercel/nitro.config.d.ts +2 -0
  63. package/dist/src/deploy/vercel/nitro.config.js +30 -0
  64. package/dist/src/deploy/vercel/server/routes/[...].d.ts +2 -0
  65. package/dist/src/deploy/vercel/server/routes/[...].js +18 -0
  66. package/dist/src/deploy/vercel/server/utils/host.d.ts +13 -0
  67. package/dist/src/deploy/vercel/server/utils/host.js +7 -0
  68. package/dist/src/deploy/vercel/server/utils/repair.d.ts +1 -0
  69. package/dist/src/deploy/vercel/server/utils/repair.js +6 -0
  70. package/dist/src/deploy/vercel/server/utils/step.d.ts +10 -0
  71. package/dist/src/deploy/vercel/server/utils/step.js +6 -0
  72. package/dist/src/deploy/vercel/server/workflows/drive.d.ts +14 -0
  73. package/dist/src/deploy/vercel/server/workflows/drive.js +13 -0
  74. package/dist/src/durable-object.d.ts +14 -0
  75. package/dist/src/durable-object.js +231 -0
  76. package/dist/src/hash.d.ts +1 -0
  77. package/dist/src/hash.js +6 -0
  78. package/dist/src/identity-key.d.ts +11 -0
  79. package/dist/src/identity-key.js +9 -0
  80. package/dist/src/identity.d.ts +12 -0
  81. package/dist/src/identity.js +6 -0
  82. package/dist/src/images/exif.d.ts +1 -0
  83. package/dist/src/images/exif.js +6 -0
  84. package/dist/src/images/node.d.ts +37 -0
  85. package/dist/src/images/node.js +12 -0
  86. package/dist/src/images/photon.d.ts +3 -0
  87. package/dist/src/images/photon.js +7 -0
  88. package/dist/src/images/worker.d.ts +1 -0
  89. package/dist/src/images/worker.js +23 -0
  90. package/dist/src/index.d.ts +20 -0
  91. package/dist/src/index.js +121 -0
  92. package/dist/src/key-file.d.ts +1 -0
  93. package/dist/src/key-file.js +7 -0
  94. package/dist/src/listen-fetch.d.ts +8 -0
  95. package/dist/src/listen-fetch.js +9 -0
  96. package/dist/src/listen.d.ts +4 -0
  97. package/dist/src/listen.js +10 -0
  98. package/dist/src/local/command.d.ts +20 -0
  99. package/dist/src/local/command.js +12 -0
  100. package/dist/src/local/feed.d.ts +7 -0
  101. package/dist/src/local/feed.js +6 -0
  102. package/dist/src/local/files.d.ts +18 -0
  103. package/dist/src/local/files.js +13 -0
  104. package/dist/src/local/git-watch.d.ts +15 -0
  105. package/dist/src/local/git-watch.js +7 -0
  106. package/dist/src/local/git.d.ts +5 -0
  107. package/dist/src/local/git.js +13 -0
  108. package/dist/src/local/github.d.ts +30 -0
  109. package/dist/src/local/github.js +9 -0
  110. package/dist/src/local/identity.d.ts +3 -0
  111. package/dist/src/local/identity.js +7 -0
  112. package/dist/src/local/jobs.d.ts +7 -0
  113. package/dist/src/local/jobs.js +7 -0
  114. package/dist/src/local/legacy-trust.d.ts +10 -0
  115. package/dist/src/local/legacy-trust.js +6 -0
  116. package/dist/src/local/patch.d.ts +16 -0
  117. package/dist/src/local/patch.js +17 -0
  118. package/dist/src/local/provider.d.ts +11 -0
  119. package/dist/src/local/provider.js +20 -0
  120. package/dist/src/local/snapshots.d.ts +7 -0
  121. package/dist/src/local/snapshots.js +11 -0
  122. package/dist/src/local/text.d.ts +1 -0
  123. package/dist/src/local/text.js +6 -0
  124. package/dist/src/local/tree.d.ts +50 -0
  125. package/dist/src/local/tree.js +16 -0
  126. package/dist/src/local/workspace-migration.d.ts +2 -0
  127. package/dist/src/local/workspace-migration.js +7 -0
  128. package/dist/src/local/worktrees.d.ts +46 -0
  129. package/dist/src/local/worktrees.js +14 -0
  130. package/dist/src/mcp/config.d.ts +75 -0
  131. package/dist/src/mcp/config.js +12 -0
  132. package/dist/src/mcp/connect.d.ts +15 -0
  133. package/dist/src/mcp/connect.js +9 -0
  134. package/dist/src/mcp/hub.d.ts +19 -0
  135. package/dist/src/mcp/hub.js +11 -0
  136. package/dist/src/mcp/oauth.d.ts +46 -0
  137. package/dist/src/mcp/oauth.js +12 -0
  138. package/dist/src/mcp/tools.d.ts +29 -0
  139. package/dist/src/mcp/tools.js +12 -0
  140. package/dist/src/open.d.ts +14 -0
  141. package/dist/src/open.js +46 -0
  142. package/dist/src/plugins/loader.d.ts +21 -0
  143. package/dist/src/plugins/loader.js +11 -0
  144. package/dist/src/postgres.d.ts +19 -0
  145. package/dist/src/postgres.js +8 -0
  146. package/dist/src/providers.d.ts +16 -0
  147. package/dist/src/providers.js +112 -0
  148. package/dist/src/secrets.d.ts +12 -0
  149. package/dist/src/secrets.js +8 -0
  150. package/dist/src/serve/binding.d.ts +39 -0
  151. package/dist/src/serve/binding.js +6 -0
  152. package/dist/src/serve/handler.d.ts +15 -0
  153. package/dist/src/serve/handler.js +8 -0
  154. package/dist/src/serve/stream-server.d.ts +6 -0
  155. package/dist/src/serve/stream-server.js +6 -0
  156. package/dist/src/session-index.d.ts +11 -0
  157. package/dist/src/session-index.js +11 -0
  158. package/dist/src/shell-env.d.ts +14 -0
  159. package/dist/src/shell-env.js +108 -0
  160. package/dist/src/sqlite-workers.d.ts +6 -0
  161. package/dist/src/sqlite-workers.js +6 -0
  162. package/dist/src/sqlite.d.ts +8 -0
  163. package/dist/src/sqlite.js +11 -0
  164. package/dist/src/store-rows.d.ts +13 -0
  165. package/dist/src/store-rows.js +26 -0
  166. package/dist/src/store-usage.d.ts +25 -0
  167. package/dist/src/store-usage.js +19 -0
  168. package/dist/src/usage-history.d.ts +54 -0
  169. package/dist/src/usage-history.js +14 -0
  170. package/dist/src/usage-scan.d.ts +24 -0
  171. package/dist/src/usage-scan.js +65 -0
  172. package/dist/src/usage-service.d.ts +23 -0
  173. package/dist/src/usage-service.js +16 -0
  174. package/dist/src/usage-summary.d.ts +9 -0
  175. package/dist/src/usage-summary.js +16 -0
  176. package/dist/src/usage-types.d.ts +86 -0
  177. package/dist/src/usage-types.js +0 -0
  178. package/dist/src/usage-worker.d.ts +1 -0
  179. package/dist/src/usage-worker.js +29 -0
  180. package/dist/src/usage.d.ts +195 -0
  181. package/dist/src/usage.js +23 -0
  182. package/package.json +140 -3
  183. package/src/canvas-preview.ts +403 -0
  184. package/src/credentials.ts +294 -0
  185. package/src/deploy/cloudflare/README.md +3 -0
  186. package/src/deploy/cloudflare/worker.ts +306 -0
  187. package/src/deploy/cloudflare/wrangler.jsonc +12 -0
  188. package/src/deploy/vercel/README.md +3 -0
  189. package/src/deploy/vercel/nitro.config.ts +29 -0
  190. package/src/deploy/vercel/server/routes/[...].ts +10 -0
  191. package/src/deploy/vercel/server/utils/host.ts +104 -0
  192. package/src/deploy/vercel/server/utils/repair.ts +35 -0
  193. package/src/deploy/vercel/server/utils/step.ts +25 -0
  194. package/src/deploy/vercel/server/workflows/drive.ts +46 -0
  195. package/src/deploy/vercel/tsconfig.json +9 -0
  196. package/src/deploy/vercel/types/bun.d.ts +2 -0
  197. package/src/durable-object.ts +386 -0
  198. package/src/hash.ts +5 -0
  199. package/src/identity-key.ts +20 -0
  200. package/src/identity.ts +66 -0
  201. package/src/images/exif.ts +69 -0
  202. package/src/images/node.ts +163 -0
  203. package/src/images/photon.ts +141 -0
  204. package/src/images/worker.ts +29 -0
  205. package/src/index.ts +39 -0
  206. package/src/key-file.ts +53 -0
  207. package/src/listen-fetch.ts +409 -0
  208. package/src/listen.ts +228 -0
  209. package/src/local/command.ts +161 -0
  210. package/src/local/feed.ts +70 -0
  211. package/src/local/files.ts +817 -0
  212. package/src/local/git-watch.ts +199 -0
  213. package/src/local/git.ts +919 -0
  214. package/src/local/github.ts +691 -0
  215. package/src/local/identity.ts +21 -0
  216. package/src/local/jobs.ts +573 -0
  217. package/src/local/legacy-trust.ts +105 -0
  218. package/src/local/patch.ts +334 -0
  219. package/src/local/provider.ts +184 -0
  220. package/src/local/snapshots.ts +778 -0
  221. package/src/local/text.ts +9 -0
  222. package/src/local/tree.ts +398 -0
  223. package/src/local/workspace-migration.ts +95 -0
  224. package/src/local/worktrees.ts +239 -0
  225. package/src/mcp/config.ts +330 -0
  226. package/src/mcp/connect.ts +92 -0
  227. package/src/mcp/hub.ts +836 -0
  228. package/src/mcp/oauth.ts +436 -0
  229. package/src/mcp/tools.ts +333 -0
  230. package/src/open.ts +449 -0
  231. package/src/plugins/loader.ts +186 -0
  232. package/src/postgres.ts +492 -0
  233. package/src/providers.ts +135 -0
  234. package/src/secrets.ts +31 -0
  235. package/src/serve/binding.ts +629 -0
  236. package/src/serve/handler.ts +362 -0
  237. package/src/serve/stream-server.ts +176 -0
  238. package/src/session-index.ts +99 -0
  239. package/src/shell-env.ts +153 -0
  240. package/src/sqlite-workers.ts +283 -0
  241. package/src/sqlite.ts +553 -0
  242. package/src/store-rows.ts +110 -0
  243. package/src/store-usage.ts +347 -0
  244. package/src/usage-history.ts +219 -0
  245. package/src/usage-scan.ts +81 -0
  246. package/src/usage-service.ts +82 -0
  247. package/src/usage-summary.ts +79 -0
  248. package/src/usage-types.ts +87 -0
  249. package/src/usage-worker.ts +24 -0
  250. package/src/usage.ts +776 -0
@@ -0,0 +1,333 @@
1
+ import { createHash } from "node:crypto";
2
+ import { McpClient } from "@earendil-works/pi-mcp";
3
+ import type {
4
+ CallToolResult,
5
+ McpRequestOptions,
6
+ ProgressNotification,
7
+ Tool,
8
+ } from "@earendil-works/pi-mcp";
9
+ import { followedTrace, startSpan, traceparent } from "@nyte-ai/core";
10
+ import type { ExecutionEnv, ToolDefinition } from "@nyte-ai/core";
11
+ import { isJson, isJsonObject } from "@nyte-ai/schema";
12
+ import type {
13
+ Json,
14
+ McpExposure,
15
+ Schema,
16
+ ToolContent,
17
+ ToolProgress,
18
+ ValidationIssue,
19
+ } from "@nyte-ai/schema";
20
+ import { Errors, Meta } from "typebox/schema";
21
+ import type { McpServerEntry } from "./config.ts";
22
+
23
+ type JsonObject = { readonly [key: string]: Json };
24
+
25
+ const NAME_LIMIT = 64;
26
+
27
+ /** The most result text a session stores and the model sees; the rest is kept as an attachment. */
28
+ const RESULT_CHARS = 32_000;
29
+
30
+ /** How much of a clipped result's head is kept; the tail keeps the rest of `RESULT_CHARS`. */
31
+ const HEAD_CHARS = 8_000;
32
+
33
+ /** The longest a call may run in all, however often its progress resets the per-request timeout. */
34
+ const CALL_LIMIT_MS = 10 * 60_000;
35
+
36
+ const DRAFT_2020_12 = Meta["https://json-schema.org/draft/2020-12/schema"];
37
+
38
+ /** JSON Schema draft-04 through 2020-12, by `$schema` URI without its scheme or empty fragment; draft-03's boolean `required` is no provider's. */
39
+ const DRAFTS = new Map(
40
+ Object.entries(Meta)
41
+ .filter(([uri]) => !uri.includes("draft-03"))
42
+ .map(([uri, meta]) => [draftKey(uri), meta] as const),
43
+ );
44
+
45
+ /** The exposure `entry` gives `tool`: an exact `toolExposure` key, else the first matching pattern, else the server's. */
46
+ export function toolExposure(entry: McpServerEntry, tool: string): McpExposure {
47
+ const exact = entry.toolExposure.find(([pattern]) => pattern === tool);
48
+
49
+ if (exact !== undefined) return exact[1];
50
+ const matched = entry.toolExposure.find(([pattern]) => globMatches(pattern, tool));
51
+
52
+ return matched?.[1] ?? entry.exposure;
53
+ }
54
+
55
+ /** A server's listed tools that sessions get, and why each of the others is left out. */
56
+ export type CheckedTools = {
57
+ readonly usable: readonly Tool[];
58
+ readonly issues: readonly ValidationIssue[];
59
+ };
60
+
61
+ /**
62
+ * Splits a server's listed tools into those whose input schema is JSON Schema
63
+ * for an object, which providers accept, and an issue for each of the rest.
64
+ * A tool with a bad schema would fail every request that declares it, so no
65
+ * session gets it.
66
+ */
67
+ export function checkToolSchemas(server: string, tools: readonly Tool[]): CheckedTools {
68
+ const usable: Tool[] = [];
69
+ const issues: ValidationIssue[] = [];
70
+
71
+ for (const tool of tools) {
72
+ const problem = findSchemaProblem(tool.inputSchema);
73
+
74
+ if (problem === null) {
75
+ usable.push(tool);
76
+
77
+ continue;
78
+ }
79
+
80
+ issues.push({
81
+ severity: "error",
82
+ path: ["mcpServers", server, "tools", tool.name, "inputSchema"],
83
+ message: `${server} lists ${tool.name} with an input schema that is not valid JSON Schema (${problem}), so sessions do not get the tool.`,
84
+ });
85
+ }
86
+
87
+ return { usable, issues };
88
+ }
89
+
90
+ /**
91
+ * Checks `schema` against the draft its `$schema` names, or when it names none typebox knows,
92
+ * against every draft: servers emit draft-07 tuples and draft-04 boolean bounds without saying
93
+ * so. Errors are reported against the named draft, else 2020-12.
94
+ */
95
+ function findSchemaProblem(schema: Tool["inputSchema"]): string | null {
96
+ if (schema["type"] !== "object") return 'its type is not "object"';
97
+ const declared = schema["$schema"];
98
+ const named = typeof declared === "string" ? DRAFTS.get(draftKey(declared)) : undefined;
99
+ const drafts = named === undefined ? [...DRAFTS.values()] : [named];
100
+
101
+ if (drafts.some((meta) => Errors(Meta, meta, schema)[0])) return null;
102
+ const [, errors] = Errors(Meta, named ?? DRAFT_2020_12, schema);
103
+ const first = new Map<string, string>();
104
+
105
+ for (const error of errors)
106
+ if (!first.has(error.instancePath))
107
+ first.set(error.instancePath, `${error.instancePath || "/"} ${error.message}`);
108
+
109
+ return [...first.values()].slice(0, 3).join("; ");
110
+ }
111
+
112
+ function draftKey(uri: string): string {
113
+ return uri.replace(/^https?:\/\//, "").replace(/#$/, "");
114
+ }
115
+
116
+ /** Calls one of the server's tools over its live connection, its `traceparent` carried to the server; throws when the server is not connected. */
117
+ export type McpCall = (
118
+ name: string,
119
+ args: Record<string, unknown>,
120
+ options: McpRequestOptions & { readonly traceparent?: string },
121
+ ) => Promise<CallToolResult>;
122
+
123
+ /** An `McpClient` whose requests carry a `traceparent` given in their options to the server in `params._meta`, where MCP's trace-context convention puts it. */
124
+ export class TracingMcpClient extends McpClient {
125
+ override request<Result = unknown>(
126
+ method: string,
127
+ params?: Record<string, unknown>,
128
+ options?: McpRequestOptions,
129
+ ): Promise<Result> {
130
+ const traceparent =
131
+ options !== undefined && "traceparent" in options && typeof options.traceparent === "string"
132
+ ? options.traceparent
133
+ : null;
134
+
135
+ return super.request(
136
+ method,
137
+ traceparent === null || params?._meta !== undefined
138
+ ? params
139
+ : { ...params, _meta: { traceparent } },
140
+ options,
141
+ );
142
+ }
143
+ }
144
+
145
+ /** Turns a server's listed tools into session tools named `mcp__<server>__<tool>`. */
146
+ export function bridgeTools(
147
+ entry: McpServerEntry,
148
+ tools: readonly Tool[],
149
+ call: McpCall,
150
+ ): ToolDefinition[] {
151
+ const names = tools.map((tool) => toolName(entry.name, tool.name));
152
+ const counts = Map.groupBy(names, (name) => name);
153
+
154
+ return tools.map((tool, index) => {
155
+ const name = names[index];
156
+ const unique = (counts.get(name)?.length ?? 0) > 1 ? hashed(name, tool.name) : name;
157
+
158
+ return bridge(entry, tool, call, unique);
159
+ });
160
+ }
161
+
162
+ function toolName(server: string, tool: string): string {
163
+ const name = `mcp__${server}__${tool}`.replace(/[^A-Za-z0-9_]/g, "_");
164
+
165
+ return name.length > NAME_LIMIT ? hashed(name, tool) : name;
166
+ }
167
+
168
+ function hashed(name: string, tool: string): string {
169
+ const suffix = createHash("sha256").update(tool).digest("hex").slice(0, 8);
170
+
171
+ return `${name.slice(0, NAME_LIMIT - suffix.length - 1)}_${suffix}`;
172
+ }
173
+
174
+ function globMatches(pattern: string, value: string): boolean {
175
+ if (!pattern.includes("*")) return false;
176
+
177
+ const source = pattern
178
+ .split("*")
179
+ .map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&"))
180
+ .join(".*");
181
+
182
+ return new RegExp(`^${source}$`).test(value);
183
+ }
184
+
185
+ function bridge(
186
+ entry: McpServerEntry,
187
+ tool: Tool,
188
+ call: McpCall,
189
+ name: string,
190
+ ): ToolDefinition<JsonObject> {
191
+ const output = tool.outputSchema;
192
+
193
+ const definition: ToolDefinition<JsonObject> = {
194
+ name,
195
+ namespace: `mcp/${entry.name}`,
196
+ description: tool.description ?? tool.title ?? tool.name,
197
+ parameters: passthrough(tool.inputSchema),
198
+ exposure: toolExposure(entry, tool.name),
199
+ class: () => ({ kind: "custom", label: `${entry.name}: ${tool.title ?? tool.name}` }),
200
+ effects: tool.annotations?.readOnlyHint === true ? "none" : "workspace",
201
+ replay:
202
+ tool.annotations?.readOnlyHint === true || tool.annotations?.idempotentHint === true
203
+ ? "safe"
204
+ : "never",
205
+ async execute(args, invocation) {
206
+ const limitMs = Math.max(entry.timeoutMs, CALL_LIMIT_MS);
207
+ const limit = AbortSignal.timeout(limitMs);
208
+
209
+ let result: CallToolResult;
210
+
211
+ const trace = followedTrace(invocation.session.id);
212
+
213
+ const ended = startSpan("mcp.call", trace, {
214
+ session: invocation.session.id,
215
+ server: entry.name,
216
+ tool: tool.name,
217
+ });
218
+
219
+ try {
220
+ result = await call(tool.name, args, {
221
+ signal: AbortSignal.any([invocation.signal, limit]),
222
+ timeoutMs: entry.timeoutMs,
223
+ onProgress: (update) => invocation.progress(toolProgress(update)),
224
+ traceparent: trace === null ? undefined : traceparent(trace),
225
+ });
226
+ } catch (error) {
227
+ ended({ failed: true });
228
+
229
+ if (!limit.aborted || invocation.signal.aborted) throw error;
230
+
231
+ throw new Error(
232
+ `${tool.name} was still running after ${limitMs / 60_000} minutes, so it was cancelled.`,
233
+ { cause: error },
234
+ );
235
+ }
236
+
237
+ ended({ failed: result.isError === true });
238
+ const content = await readToolContent(result, invocation.env, tool.name);
239
+
240
+ if (result.isError === true)
241
+ throw new Error(
242
+ content.flatMap((item) => (item.kind === "text" ? [item.text] : [])).join("\n") ||
243
+ `${tool.name} failed.`,
244
+ );
245
+
246
+ const structured = result.structuredContent;
247
+
248
+ if (
249
+ output === undefined ||
250
+ !isJson(structured) ||
251
+ JSON.stringify(structured).length > RESULT_CHARS
252
+ )
253
+ return { content };
254
+
255
+ return { content, output: structured };
256
+ },
257
+ };
258
+
259
+ return output === undefined ? definition : { ...definition, output: passthrough(output) };
260
+ }
261
+
262
+ function toolProgress(update: ProgressNotification): ToolProgress {
263
+ const step =
264
+ update.total === undefined ? `${update.progress}` : `${update.progress} of ${update.total}`;
265
+
266
+ const text = update.message ?? step;
267
+
268
+ return update.total === undefined || update.total <= 0
269
+ ? { text }
270
+ : { text, fraction: Math.min(1, update.progress / update.total) };
271
+ }
272
+
273
+ /**
274
+ * The result's blocks as tool content. Text past `RESULT_CHARS` in all keeps
275
+ * its head and its longer tail around a marker, which is where logs put the
276
+ * failure, and the whole text is attached once as a file.
277
+ */
278
+ async function readToolContent(
279
+ result: CallToolResult,
280
+ env: ExecutionEnv,
281
+ tool: string,
282
+ ): Promise<readonly ToolContent[]> {
283
+ const content: ToolContent[] = [];
284
+
285
+ for (const block of result.content) {
286
+ if (block.type === "text") content.push({ kind: "text", text: block.text });
287
+ else if (block.type === "image")
288
+ content.push({
289
+ kind: "file",
290
+ blob: await env.files.upload(once(Uint8Array.fromBase64(block.data))),
291
+ mime: block.mimeType,
292
+ });
293
+ else content.push({ kind: "text", text: JSON.stringify(block) });
294
+ }
295
+
296
+ const text = content.flatMap((item) => (item.kind === "text" ? [item.text] : [])).join("\n");
297
+
298
+ if (text.length <= RESULT_CHARS) return content;
299
+ const name = `${tool}.txt`;
300
+ const elided = (text.length - RESULT_CHARS).toLocaleString("en-US");
301
+
302
+ return [
303
+ {
304
+ kind: "text",
305
+ text: `${text.slice(0, HEAD_CHARS)}\n\n[${elided} characters cut from the middle; the whole result is attached as ${name}.]\n\n${text.slice(HEAD_CHARS - RESULT_CHARS)}`,
306
+ },
307
+ {
308
+ kind: "file",
309
+ blob: await env.files.upload(once(new TextEncoder().encode(text))),
310
+ mime: "text/plain",
311
+ name,
312
+ },
313
+ ...content.filter((item) => item.kind === "file"),
314
+ ];
315
+ }
316
+
317
+ function passthrough(json: Tool["inputSchema"]): Schema<JsonObject> {
318
+ return {
319
+ "~standard": {
320
+ version: 1,
321
+ vendor: "mcp",
322
+ validate: (value) =>
323
+ isJson(value) && isJsonObject(value)
324
+ ? { value }
325
+ : { issues: [{ message: "Expected a JSON object." }] },
326
+ jsonSchema: { input: () => json, output: () => json },
327
+ },
328
+ };
329
+ }
330
+
331
+ async function* once(bytes: Uint8Array): AsyncIterable<Uint8Array> {
332
+ yield bytes;
333
+ }