@namzu/sdk 39.0.0 → 41.0.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 (289) hide show
  1. package/CHANGELOG.md +328 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +8 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +11 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/connector/index.d.ts +2 -2
  9. package/dist/connector/index.d.ts.map +1 -1
  10. package/dist/connector/index.js +1 -1
  11. package/dist/connector/index.js.map +1 -1
  12. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  13. package/dist/connector/mcp/adapter.js +112 -10
  14. package/dist/connector/mcp/adapter.js.map +1 -1
  15. package/dist/connector/mcp/audio-admission.d.ts +17 -0
  16. package/dist/connector/mcp/audio-admission.d.ts.map +1 -0
  17. package/dist/connector/mcp/audio-admission.js +171 -0
  18. package/dist/connector/mcp/audio-admission.js.map +1 -0
  19. package/dist/connector/mcp/client.d.ts +252 -1
  20. package/dist/connector/mcp/client.d.ts.map +1 -1
  21. package/dist/connector/mcp/client.js +611 -39
  22. package/dist/connector/mcp/client.js.map +1 -1
  23. package/dist/connector/mcp/envelope.d.ts +91 -0
  24. package/dist/connector/mcp/envelope.d.ts.map +1 -0
  25. package/dist/connector/mcp/envelope.js +173 -0
  26. package/dist/connector/mcp/envelope.js.map +1 -0
  27. package/dist/connector/mcp/era.d.ts +130 -0
  28. package/dist/connector/mcp/era.d.ts.map +1 -0
  29. package/dist/connector/mcp/era.js +304 -0
  30. package/dist/connector/mcp/era.js.map +1 -0
  31. package/dist/connector/mcp/errors.d.ts +106 -0
  32. package/dist/connector/mcp/errors.d.ts.map +1 -0
  33. package/dist/connector/mcp/errors.js +154 -0
  34. package/dist/connector/mcp/errors.js.map +1 -0
  35. package/dist/connector/mcp/http-sse.d.ts +11 -0
  36. package/dist/connector/mcp/http-sse.d.ts.map +1 -1
  37. package/dist/connector/mcp/http-sse.js +21 -6
  38. package/dist/connector/mcp/http-sse.js.map +1 -1
  39. package/dist/connector/mcp/index.d.ts +7 -0
  40. package/dist/connector/mcp/index.d.ts.map +1 -1
  41. package/dist/connector/mcp/index.js +10 -0
  42. package/dist/connector/mcp/index.js.map +1 -1
  43. package/dist/connector/mcp/streamable-http.d.ts +83 -0
  44. package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
  45. package/dist/connector/mcp/streamable-http.js +177 -11
  46. package/dist/connector/mcp/streamable-http.js.map +1 -1
  47. package/dist/connector/mcp/x-mcp-header.d.ts +56 -0
  48. package/dist/connector/mcp/x-mcp-header.d.ts.map +1 -0
  49. package/dist/connector/mcp/x-mcp-header.js +254 -0
  50. package/dist/connector/mcp/x-mcp-header.js.map +1 -0
  51. package/dist/constants/mcp/index.d.ts +123 -15
  52. package/dist/constants/mcp/index.d.ts.map +1 -1
  53. package/dist/constants/mcp/index.js +135 -16
  54. package/dist/constants/mcp/index.js.map +1 -1
  55. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  56. package/dist/manager/agent/lifecycle.js +23 -0
  57. package/dist/manager/agent/lifecycle.js.map +1 -1
  58. package/dist/manager/run/persistence.d.ts +8 -0
  59. package/dist/manager/run/persistence.d.ts.map +1 -1
  60. package/dist/manager/run/persistence.js +12 -0
  61. package/dist/manager/run/persistence.js.map +1 -1
  62. package/dist/prompt/coding-agent-doctrine.d.ts +20 -0
  63. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  64. package/dist/prompt/coding-agent-doctrine.js +19 -3
  65. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  66. package/dist/prompt/index.d.ts +1 -1
  67. package/dist/prompt/index.d.ts.map +1 -1
  68. package/dist/prompt/index.js +1 -1
  69. package/dist/prompt/index.js.map +1 -1
  70. package/dist/public-runtime.d.ts +6 -4
  71. package/dist/public-runtime.d.ts.map +1 -1
  72. package/dist/public-runtime.js +8 -4
  73. package/dist/public-runtime.js.map +1 -1
  74. package/dist/public-tools.d.ts +11 -0
  75. package/dist/public-tools.d.ts.map +1 -1
  76. package/dist/public-tools.js +14 -0
  77. package/dist/public-tools.js.map +1 -1
  78. package/dist/registry/tool/execute.d.ts.map +1 -1
  79. package/dist/registry/tool/execute.js +2 -3
  80. package/dist/registry/tool/execute.js.map +1 -1
  81. package/dist/registry/tool/portable.d.ts +65 -0
  82. package/dist/registry/tool/portable.d.ts.map +1 -0
  83. package/dist/registry/tool/portable.js +244 -0
  84. package/dist/registry/tool/portable.js.map +1 -0
  85. package/dist/registry/tool/schema.d.ts +32 -5
  86. package/dist/registry/tool/schema.d.ts.map +1 -1
  87. package/dist/registry/tool/schema.js +35 -9
  88. package/dist/registry/tool/schema.js.map +1 -1
  89. package/dist/registry/toolset/catalog.js +8 -8
  90. package/dist/registry/toolset/catalog.js.map +1 -1
  91. package/dist/runtime/jobs/awaited-jobs.d.ts +215 -0
  92. package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -0
  93. package/dist/runtime/jobs/awaited-jobs.js +259 -0
  94. package/dist/runtime/jobs/awaited-jobs.js.map +1 -0
  95. package/dist/runtime/jobs/registry.d.ts +33 -2
  96. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  97. package/dist/runtime/jobs/registry.js +37 -0
  98. package/dist/runtime/jobs/registry.js.map +1 -1
  99. package/dist/runtime/query/executor.d.ts +28 -0
  100. package/dist/runtime/query/executor.d.ts.map +1 -1
  101. package/dist/runtime/query/executor.js +39 -1
  102. package/dist/runtime/query/executor.js.map +1 -1
  103. package/dist/runtime/query/file-evidence-context.d.ts.map +1 -1
  104. package/dist/runtime/query/file-evidence-context.js +159 -43
  105. package/dist/runtime/query/file-evidence-context.js.map +1 -1
  106. package/dist/runtime/query/file-evidence-replay.d.ts +260 -0
  107. package/dist/runtime/query/file-evidence-replay.d.ts.map +1 -0
  108. package/dist/runtime/query/file-evidence-replay.js +647 -0
  109. package/dist/runtime/query/file-evidence-replay.js.map +1 -0
  110. package/dist/runtime/query/file-evidence-seed.d.ts +50 -0
  111. package/dist/runtime/query/file-evidence-seed.d.ts.map +1 -0
  112. package/dist/runtime/query/file-evidence-seed.js +100 -0
  113. package/dist/runtime/query/file-evidence-seed.js.map +1 -0
  114. package/dist/runtime/query/index.d.ts.map +1 -1
  115. package/dist/runtime/query/index.js +94 -2
  116. package/dist/runtime/query/index.js.map +1 -1
  117. package/dist/runtime/query/iteration/index.d.ts +87 -9
  118. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  119. package/dist/runtime/query/iteration/index.js +193 -28
  120. package/dist/runtime/query/iteration/index.js.map +1 -1
  121. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  122. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  124. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/tool-review.js +5 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  127. package/dist/runtime/query/plugin-hooks.d.ts +14 -0
  128. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  129. package/dist/runtime/query/plugin-hooks.js +18 -0
  130. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  131. package/dist/runtime/query/repeat-call.d.ts +17 -4
  132. package/dist/runtime/query/repeat-call.d.ts.map +1 -1
  133. package/dist/runtime/query/repeat-call.js +26 -19
  134. package/dist/runtime/query/repeat-call.js.map +1 -1
  135. package/dist/runtime/query/steering.d.ts +11 -1
  136. package/dist/runtime/query/steering.d.ts.map +1 -1
  137. package/dist/runtime/query/steering.js +12 -1
  138. package/dist/runtime/query/steering.js.map +1 -1
  139. package/dist/runtime/query/tooling.d.ts +2 -0
  140. package/dist/runtime/query/tooling.d.ts.map +1 -1
  141. package/dist/runtime/query/tooling.js +1 -0
  142. package/dist/runtime/query/tooling.js.map +1 -1
  143. package/dist/sandbox/provider/local.d.ts.map +1 -1
  144. package/dist/sandbox/provider/local.js +46 -3
  145. package/dist/sandbox/provider/local.js.map +1 -1
  146. package/dist/scheduler/completion-inbox.d.ts +48 -2
  147. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  148. package/dist/scheduler/completion-inbox.js +102 -10
  149. package/dist/scheduler/completion-inbox.js.map +1 -1
  150. package/dist/scheduler/local.d.ts.map +1 -1
  151. package/dist/scheduler/local.js +8 -0
  152. package/dist/scheduler/local.js.map +1 -1
  153. package/dist/store/run/disk.d.ts +35 -1
  154. package/dist/store/run/disk.d.ts.map +1 -1
  155. package/dist/store/run/disk.js +100 -0
  156. package/dist/store/run/disk.js.map +1 -1
  157. package/dist/tools/builtins/bash.d.ts.map +1 -1
  158. package/dist/tools/builtins/bash.js +4 -10
  159. package/dist/tools/builtins/bash.js.map +1 -1
  160. package/dist/tools/builtins/edit-apply.d.ts +126 -0
  161. package/dist/tools/builtins/edit-apply.d.ts.map +1 -0
  162. package/dist/tools/builtins/edit-apply.js +360 -0
  163. package/dist/tools/builtins/edit-apply.js.map +1 -0
  164. package/dist/tools/builtins/edit.d.ts +143 -1
  165. package/dist/tools/builtins/edit.d.ts.map +1 -1
  166. package/dist/tools/builtins/edit.js +37 -219
  167. package/dist/tools/builtins/edit.js.map +1 -1
  168. package/dist/tools/builtins/index.d.ts +1 -0
  169. package/dist/tools/builtins/index.d.ts.map +1 -1
  170. package/dist/tools/builtins/index.js +9 -3
  171. package/dist/tools/builtins/index.js.map +1 -1
  172. package/dist/tools/builtins/job.js +1 -1
  173. package/dist/tools/builtins/job.js.map +1 -1
  174. package/dist/tools/builtins/read-file.d.ts +2 -2
  175. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  176. package/dist/tools/builtins/read-file.js +50 -65
  177. package/dist/tools/builtins/read-file.js.map +1 -1
  178. package/dist/tools/builtins/read-render.d.ts +56 -0
  179. package/dist/tools/builtins/read-render.d.ts.map +1 -0
  180. package/dist/tools/builtins/read-render.js +73 -0
  181. package/dist/tools/builtins/read-render.js.map +1 -0
  182. package/dist/tools/builtins/wait-for-job-bounds.d.ts +67 -0
  183. package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -0
  184. package/dist/tools/builtins/wait-for-job-bounds.js +108 -0
  185. package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -0
  186. package/dist/tools/builtins/wait-for-job.d.ts +6 -0
  187. package/dist/tools/builtins/wait-for-job.d.ts.map +1 -0
  188. package/dist/tools/builtins/wait-for-job.js +162 -0
  189. package/dist/tools/builtins/wait-for-job.js.map +1 -0
  190. package/dist/tools/builtins/write-file.js +5 -0
  191. package/dist/tools/builtins/write-file.js.map +1 -1
  192. package/dist/tools/coordinator/index.d.ts.map +1 -1
  193. package/dist/tools/coordinator/index.js +1 -7
  194. package/dist/tools/coordinator/index.js.map +1 -1
  195. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  196. package/dist/tools/file-read-tracker.js +88 -10
  197. package/dist/tools/file-read-tracker.js.map +1 -1
  198. package/dist/types/agent/scheduler.d.ts +20 -0
  199. package/dist/types/agent/scheduler.d.ts.map +1 -1
  200. package/dist/types/agent/task.d.ts +20 -0
  201. package/dist/types/agent/task.d.ts.map +1 -1
  202. package/dist/types/connector/mcp.d.ts +205 -0
  203. package/dist/types/connector/mcp.d.ts.map +1 -1
  204. package/dist/types/message/index.d.ts +1 -1
  205. package/dist/types/message/index.d.ts.map +1 -1
  206. package/dist/types/message/index.js +2 -0
  207. package/dist/types/message/index.js.map +1 -1
  208. package/dist/types/run/entity.d.ts +13 -0
  209. package/dist/types/run/entity.d.ts.map +1 -1
  210. package/dist/types/run/events.d.ts +56 -0
  211. package/dist/types/run/events.d.ts.map +1 -1
  212. package/dist/types/run/events.js.map +1 -1
  213. package/dist/types/run/store.d.ts +41 -0
  214. package/dist/types/run/store.d.ts.map +1 -1
  215. package/dist/types/sandbox/index.d.ts +83 -15
  216. package/dist/types/sandbox/index.d.ts.map +1 -1
  217. package/dist/types/sandbox/index.js.map +1 -1
  218. package/dist/types/tool/index.d.ts +109 -0
  219. package/dist/types/tool/index.d.ts.map +1 -1
  220. package/dist/types/tool/index.js.map +1 -1
  221. package/dist/utils/env.d.ts +19 -0
  222. package/dist/utils/env.d.ts.map +1 -0
  223. package/dist/utils/env.js +25 -0
  224. package/dist/utils/env.js.map +1 -0
  225. package/package.json +1 -1
  226. package/src/bridge/a2a/mapper.ts +8 -0
  227. package/src/bridge/sse/mapper.ts +11 -0
  228. package/src/connector/index.ts +27 -0
  229. package/src/connector/mcp/adapter.ts +123 -10
  230. package/src/connector/mcp/audio-admission.ts +173 -0
  231. package/src/connector/mcp/client.ts +694 -45
  232. package/src/connector/mcp/envelope.ts +235 -0
  233. package/src/connector/mcp/era.ts +400 -0
  234. package/src/connector/mcp/errors.ts +171 -0
  235. package/src/connector/mcp/http-sse.ts +23 -6
  236. package/src/connector/mcp/index.ts +37 -0
  237. package/src/connector/mcp/streamable-http.ts +199 -11
  238. package/src/connector/mcp/x-mcp-header.ts +322 -0
  239. package/src/constants/mcp/index.ts +145 -16
  240. package/src/manager/agent/lifecycle.ts +29 -0
  241. package/src/manager/run/persistence.ts +12 -0
  242. package/src/prompt/coding-agent-doctrine.ts +31 -4
  243. package/src/prompt/index.ts +1 -0
  244. package/src/public-runtime.ts +37 -1
  245. package/src/public-tools.ts +18 -0
  246. package/src/registry/tool/execute.ts +2 -4
  247. package/src/registry/tool/portable.ts +264 -0
  248. package/src/registry/tool/schema.ts +38 -8
  249. package/src/registry/toolset/catalog.ts +8 -9
  250. package/src/runtime/jobs/awaited-jobs.ts +271 -0
  251. package/src/runtime/jobs/registry.ts +50 -0
  252. package/src/runtime/query/executor.ts +49 -1
  253. package/src/runtime/query/file-evidence-context.ts +190 -46
  254. package/src/runtime/query/file-evidence-replay.ts +776 -0
  255. package/src/runtime/query/file-evidence-seed.ts +126 -0
  256. package/src/runtime/query/index.ts +104 -2
  257. package/src/runtime/query/iteration/index.ts +202 -28
  258. package/src/runtime/query/iteration/phases/context.ts +10 -0
  259. package/src/runtime/query/iteration/phases/tool-review.ts +4 -0
  260. package/src/runtime/query/plugin-hooks.ts +20 -0
  261. package/src/runtime/query/repeat-call.ts +28 -18
  262. package/src/runtime/query/steering.ts +11 -0
  263. package/src/runtime/query/tooling.ts +3 -0
  264. package/src/sandbox/provider/local.ts +45 -2
  265. package/src/scheduler/completion-inbox.ts +105 -9
  266. package/src/scheduler/local.ts +8 -0
  267. package/src/store/run/disk.ts +108 -0
  268. package/src/tools/builtins/bash.ts +4 -10
  269. package/src/tools/builtins/edit-apply.ts +456 -0
  270. package/src/tools/builtins/edit.ts +39 -270
  271. package/src/tools/builtins/index.ts +9 -3
  272. package/src/tools/builtins/job.ts +1 -1
  273. package/src/tools/builtins/read-file.ts +56 -77
  274. package/src/tools/builtins/read-render.ts +104 -0
  275. package/src/tools/builtins/wait-for-job-bounds.ts +179 -0
  276. package/src/tools/builtins/wait-for-job.ts +184 -0
  277. package/src/tools/builtins/write-file.ts +5 -0
  278. package/src/tools/coordinator/index.ts +1 -7
  279. package/src/tools/file-read-tracker.ts +85 -7
  280. package/src/types/agent/scheduler.ts +21 -0
  281. package/src/types/agent/task.ts +21 -0
  282. package/src/types/connector/mcp.ts +205 -1
  283. package/src/types/message/index.ts +2 -0
  284. package/src/types/run/entity.ts +14 -0
  285. package/src/types/run/events.ts +56 -0
  286. package/src/types/run/store.ts +42 -0
  287. package/src/types/sandbox/index.ts +84 -15
  288. package/src/types/tool/index.ts +104 -0
  289. package/src/utils/env.ts +23 -0
@@ -9,7 +9,9 @@ import type {
9
9
  } from '../../types/connector/index.js'
10
10
  import type { ToolResultBlock, ToolResultContent } from '../../types/message/index.js'
11
11
  import type { ToolContext, ToolDefinition, ToolResult } from '../../types/tool/index.js'
12
+ import { admitMcpAudioBatch } from './audio-admission.js'
12
13
  import type { MCPClient } from './client.js'
14
+ import { MCPInputRequiredError, isMissingRequiredClientCapabilityError } from './errors.js'
13
15
  import { MCPHttpRedirectError } from './http-redirect.js'
14
16
  import { admitMcpImageBatch } from './image-admission.js'
15
17
 
@@ -338,11 +340,20 @@ const MAX_TUPLE_ARITY = 32
338
340
  * down every run that offered the toolset. A faithful conversion that cannot
339
341
  * be sent is strictly worse than a lossy one that can.
340
342
  *
341
- * Hence the narrow gate. A tuple is emitted only where the server itself
342
- * pinned the arity and closed the tail, because that renders as bounded
343
- * `prefixItems` — the one positional shape measured as accepted, and the same
344
- * shape a first-party builtin already ships. Everything else keeps the
345
- * permissive array and gains the positional shape in its description.
343
+ * NO positional shape is a construct every wire accepts. That was measured the
344
+ * expensive way: `items: [a, b]` is refused by a wire validating against JSON
345
+ * Schema 2020-12, `prefixItems: [a, b]` is unknown to a draft-07 one — which
346
+ * DROPS it silently rather than complaining — and only three of the ten
347
+ * drivers convert between them. So the rendering boundary now collapses every
348
+ * tuple to a uniform array (`registry/tool/portable.ts`), and this function's
349
+ * job changed with it: the tuple it returns is the PARSER, which still refuses
350
+ * a wrongly-typed or wrongly-ordered call, while the positions reach the model
351
+ * as prose in the description — the one channel every wire carries intact.
352
+ *
353
+ * Hence the narrow gate, which still matters: a tuple is a parser strict
354
+ * enough to reject a shorter array, so it is built only where the server
355
+ * itself pinned the arity and closed the tail. Everything else keeps the
356
+ * permissive array. Either way the positional shape is described.
346
357
  *
347
358
  * The subtlety worth stating, because it inverts the intuition: positional
348
359
  * `items`/`prefixItems` does not constrain LENGTH. Without `minItems` the
@@ -372,7 +383,12 @@ function positionalToZod(
372
383
  const members = positional.map((member) => jsonSchemaPropertyToZod(member, depth + 1))
373
384
  // Never `.rest()`. It renders a tail schema this wire has not been measured
374
385
  // against, and the gate above has already established there is no tail.
375
- return z.tuple(members as [z.ZodType, ...z.ZodType[]])
386
+ //
387
+ // Described as well as parsed: the rendering collapses the tuple to a
388
+ // uniform array, so without this sentence the model would be shown "two of
389
+ // string-or-number" for a schema the parser reads as "a string then a
390
+ // number" — and would learn the difference only from a rejected call.
391
+ return z.tuple(members as [z.ZodType, ...z.ZodType[]]).describe(describePositional(positional))
376
392
  }
377
393
 
378
394
  /** The positional shape in prose, for the cases a tuple cannot carry. */
@@ -502,12 +518,68 @@ export function mcpToolToToolDefinition(
502
518
  },
503
519
  }
504
520
  }
521
+ // MRTR: a conforming server never sends this, since namzu declares
522
+ // `clientCapabilities: {}` — but a non-conforming one, or a second
523
+ // `input_required` after the one automatic retry `MCPClient`
524
+ // already made, is named here rather than reaching the caller as
525
+ // an unexplained rejection. No side effect is known to have
526
+ // happened on this path (the spec's model is that a call ending
527
+ // in `input_required` has not truly run yet), so a retry — of
528
+ // this tool, or a different approach entirely — is safe.
529
+ if (error instanceof MCPInputRequiredError) {
530
+ const requested = error.inputRequests.map((request) => request.method)
531
+ return frameServerResult(
532
+ {
533
+ success: false,
534
+ output: '',
535
+ error: `MCP tool "${tool.name}" on server "${serverName}" asked for input this client has no way to supply${requested.length > 0 ? ` (${requested.join(', ')})` : ''}.`,
536
+ data: {
537
+ code: 'mcp_tool_input_required',
538
+ server: serverName,
539
+ tool: tool.name,
540
+ requested,
541
+ retrySafety: 'safe',
542
+ },
543
+ },
544
+ serverName,
545
+ tool.name,
546
+ )
547
+ }
548
+ // Same principle, for the other outcome a no-capability host is
549
+ // genuinely likely to see: the server refused before running the
550
+ // tool because this client never declared a capability it needs.
551
+ if (isMissingRequiredClientCapabilityError(error)) {
552
+ const requiredCapabilities = requiredCapabilitiesFrom(error.data)
553
+ return frameServerResult(
554
+ {
555
+ success: false,
556
+ output: '',
557
+ error: `MCP tool "${tool.name}" on server "${serverName}" requires client capabilities this client did not declare${requiredCapabilities.length > 0 ? ` (${requiredCapabilities.join(', ')})` : ''}.`,
558
+ data: {
559
+ code: 'mcp_tool_missing_client_capability',
560
+ server: serverName,
561
+ tool: tool.name,
562
+ requiredCapabilities,
563
+ retrySafety: 'safe',
564
+ },
565
+ },
566
+ serverName,
567
+ tool.name,
568
+ )
569
+ }
505
570
  throw error
506
571
  }
507
572
  },
508
573
  }
509
574
  }
510
575
 
576
+ /** Read `-32021`'s `data.requiredCapabilities` defensively — it is a server's own claim, not a validated shape. */
577
+ function requiredCapabilitiesFrom(data: unknown): string[] {
578
+ if (typeof data !== 'object' || data === null) return []
579
+ const required = (data as { requiredCapabilities?: unknown }).requiredCapabilities
580
+ return Array.isArray(required) ? required.filter((c): c is string => typeof c === 'string') : []
581
+ }
582
+
511
583
  export function toolDefinitionToMCPTool(tool: ToolDefinition): MCPToolDefinition {
512
584
  return {
513
585
  name: tool.name,
@@ -595,6 +667,11 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
595
667
  block.type === 'image',
596
668
  )
597
669
  const imagesAdmitted = admitMcpImageBatch(imageBlocks)
670
+ const audioBlocks = result.content.filter(
671
+ (block): block is Extract<(typeof result.content)[number], { type: 'audio' }> =>
672
+ block.type === 'audio',
673
+ )
674
+ const audiosAdmitted = admitMcpAudioBatch(audioBlocks)
598
675
 
599
676
  // Non-text blocks used to be filtered out and never seen again: a
600
677
  // bridged MCP server returning a chart, a screenshot or a PDF had that
@@ -603,6 +680,13 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
603
680
  // as model-visible content when any are present.
604
681
  const blocks: ToolResultBlock[] = []
605
682
  const pins: WorkingStatePin[] = []
683
+ // Set only by a block type that has no `ToolResultBlock` shape of its
684
+ // own and is rendered as a plain text pointer instead (`resource_link`
685
+ // today). Without this, a result made up entirely of such pointers
686
+ // would compute `hasRichContent` false — every pushed block LOOKS like
687
+ // ordinary text — and the whole `content` array, the pointer's only
688
+ // carrier, would be dropped by the `hasRichContent` gate below.
689
+ let hasNamedPointer = false
606
690
  for (const block of result.content) {
607
691
  if (block.type === 'resource' && block.resource?.mimeType === WORKING_STATE_MIME) {
608
692
  // A server pins facts by returning them as a resource of this type:
@@ -626,14 +710,31 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
626
710
  }
627
711
  : {}),
628
712
  })
713
+ } else if (block.type === 'resource_link') {
714
+ // A resource_link never carries content, admitted or not — it is
715
+ // a pointer the model cannot dereference, so it is named rather
716
+ // than pretended to be present, exactly like a URI-only embedded
717
+ // resource just below.
718
+ blocks.push({
719
+ type: 'text',
720
+ text: `[MCP resource link: ${block.name} (${block.uri})]`,
721
+ })
722
+ hasNamedPointer = true
629
723
  } else if (block.type === 'resource' && block.resource?.text) {
630
724
  // A resource with inline text is readable content; one that is
631
- // only a URI is a pointer the model cannot dereference, so it is
632
- // named rather than pretended to be present.
725
+ // only a URI, or only a `blob`, is a pointer the model cannot
726
+ // dereference, so it is named rather than pretended to be
727
+ // present — here, that means it is left out of `blocks`
728
+ // entirely rather than rendered as fabricated text.
633
729
  blocks.push({ type: 'text', text: block.resource.text })
634
730
  }
731
+ // `audio` has no `ToolResultBlock` shape of its own today, so it is
732
+ // summarized into `output` below rather than pushed here — the
733
+ // same reasoning that keeps a withheld image out of plain text,
734
+ // applied to a media type this adapter cannot pass through as
735
+ // bytes at all yet.
635
736
  }
636
- const hasRichContent = blocks.some((b) => b.type !== 'text')
737
+ const hasRichContent = hasNamedPointer || blocks.some((b) => b.type !== 'text')
637
738
 
638
739
  // A server may answer with a structured payload and skip the
639
740
  // compatibility text block. Serializing it is the difference between
@@ -648,7 +749,19 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
648
749
  imageBlocks.length > 0 && !imagesAdmitted
649
750
  ? '[MCP image batch withheld from model input: one or more blocks are not complete supported raster containers matching their declared media types.]'
650
751
  : ''
651
- const output = [visibleText, invalidImageNotice].filter((part) => part.length > 0).join('\n')
752
+ // Audio has no `ToolResultBlock` carrier, so an admitted clip is named
753
+ // in `output` by its media type instead of being dumped into `content`
754
+ // as base64 text — the same mistake this adapter already fixed once for
755
+ // screenshots. A withheld batch gets the same notice shape as images.
756
+ const audioNotice =
757
+ audioBlocks.length === 0
758
+ ? ''
759
+ : audiosAdmitted
760
+ ? `[MCP audio: ${audioBlocks.map((block) => block.mimeType).join(', ')}]`
761
+ : '[MCP audio batch withheld from model input: one or more blocks are not complete supported audio containers matching their declared media types.]'
762
+ const output = [visibleText, invalidImageNotice, audioNotice]
763
+ .filter((part) => part.length > 0)
764
+ .join('\n')
652
765
 
653
766
  return {
654
767
  ...(pins.length > 0 ? { workingState: pins } : {}),
@@ -0,0 +1,173 @@
1
+ import type { MCPContentBlock } from '../../types/connector/index.js'
2
+
3
+ const MAX_ENCODED_BYTES = 24 * 1024 * 1024
4
+
5
+ type MCPAudioBlock = Extract<MCPContentBlock, { type: 'audio' }>
6
+ type AudioMediaType = 'audio/wav' | 'audio/mpeg' | 'audio/ogg'
7
+
8
+ const AUDIO_MEDIA_TYPES = new Set<AudioMediaType>(['audio/wav', 'audio/mpeg', 'audio/ogg'])
9
+
10
+ function isAudioMediaType(value: string): value is AudioMediaType {
11
+ return AUDIO_MEDIA_TYPES.has(value as AudioMediaType)
12
+ }
13
+
14
+ function decodeCanonicalBase64(value: string): Uint8Array | null {
15
+ if (value.length === 0 || value.length > MAX_ENCODED_BYTES) return null
16
+ if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
17
+ return null
18
+ }
19
+ const decoded = Buffer.from(value, 'base64')
20
+ return decoded.toString('base64') === value ? decoded : null
21
+ }
22
+
23
+ /**
24
+ * A canonical RIFF/WAVE container: the size field agrees with the buffer
25
+ * length, and walking its chunks (word-aligned, as RIFF requires) lands
26
+ * exactly on the end of the buffer having seen both a `fmt ` and a `data`
27
+ * chunk. Mirrors the WebP walker in `image-admission.ts`, which is the same
28
+ * RIFF container format wearing a different fourCC.
29
+ */
30
+ function isWav(bytes: Uint8Array): boolean {
31
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
32
+ if (
33
+ bytes.length < 12 ||
34
+ buffer.toString('ascii', 0, 4) !== 'RIFF' ||
35
+ buffer.readUInt32LE(4) + 8 !== bytes.length ||
36
+ buffer.toString('ascii', 8, 12) !== 'WAVE'
37
+ ) {
38
+ return false
39
+ }
40
+ let offset = 12
41
+ let sawFmt = false
42
+ let sawData = false
43
+ while (offset + 8 <= bytes.length) {
44
+ const chunkId = buffer.toString('ascii', offset, offset + 4)
45
+ const chunkSize = buffer.readUInt32LE(offset + 4)
46
+ const dataStart = offset + 8
47
+ const dataEnd = dataStart + chunkSize
48
+ if (dataEnd < dataStart || dataEnd > bytes.length) return false
49
+ if (chunkId === 'fmt ') {
50
+ if (chunkSize < 16) return false
51
+ sawFmt = true
52
+ }
53
+ if (chunkId === 'data') sawData = true
54
+ // RIFF chunks are padded to an even byte count; the pad byte is not
55
+ // counted in chunkSize but is present in the stream.
56
+ offset = dataEnd + (chunkSize % 2)
57
+ }
58
+ return sawFmt && sawData && offset === bytes.length
59
+ }
60
+
61
+ /**
62
+ * A sequence of Ogg pages, each framed by its `OggS` capture pattern and a
63
+ * segment table whose declared sizes account for every byte of payload.
64
+ * Page checksums are not verified — the framing itself, consumed exactly to
65
+ * the end of the buffer, is what this admits or refuses.
66
+ */
67
+ function isOgg(bytes: Uint8Array): boolean {
68
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
69
+ let offset = 0
70
+ let sawPage = false
71
+ while (offset + 27 <= bytes.length) {
72
+ if (buffer.toString('ascii', offset, offset + 4) !== 'OggS') return false
73
+ if (bytes[offset + 4] !== 0) return false // stream_structure_version
74
+ const segmentCount = bytes[offset + 26] ?? 0
75
+ const segmentTableStart = offset + 27
76
+ if (segmentTableStart + segmentCount > bytes.length) return false
77
+ let payloadLength = 0
78
+ for (let i = 0; i < segmentCount; i += 1) {
79
+ payloadLength += bytes[segmentTableStart + i] ?? 0
80
+ }
81
+ const payloadEnd = segmentTableStart + segmentCount + payloadLength
82
+ if (payloadEnd > bytes.length) return false
83
+ sawPage = true
84
+ offset = payloadEnd
85
+ }
86
+ return sawPage && offset === bytes.length
87
+ }
88
+
89
+ // MPEG-1 Layer III bitrates in kbps, indexed by the header's 4-bit field.
90
+ // Index 0 (free bitrate) and 15 (reserved) are both treated as unsupported.
91
+ const MPEG1_L3_BITRATES: readonly number[] = [
92
+ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0,
93
+ ]
94
+ const MPEG1_SAMPLE_RATES: readonly number[] = [44100, 48000, 32000, 0]
95
+
96
+ /** Frame length in bytes for an MPEG-1 Layer III frame, or null if the header is malformed/unsupported. */
97
+ function mp3FrameLength(headerByte1: number, headerByte2: number): number | null {
98
+ const versionBits = (headerByte1 >> 3) & 0x3
99
+ const layerBits = (headerByte1 >> 1) & 0x3
100
+ if (versionBits !== 0x3 || layerBits !== 0x1) return null // MPEG-1, Layer III only
101
+ const bitrateIndex = (headerByte2 >> 4) & 0xf
102
+ const sampleRateIndex = (headerByte2 >> 2) & 0x3
103
+ const padding = (headerByte2 >> 1) & 0x1
104
+ if (bitrateIndex === 0 || bitrateIndex === 0xf || sampleRateIndex === 0x3) return null
105
+ const bitrate = MPEG1_L3_BITRATES[bitrateIndex]
106
+ const sampleRate = MPEG1_SAMPLE_RATES[sampleRateIndex]
107
+ if (!bitrate || !sampleRate) return null
108
+ return Math.floor((144 * bitrate * 1000) / sampleRate) + padding
109
+ }
110
+
111
+ function id3v2TagLength(bytes: Uint8Array, buffer: Buffer): number {
112
+ if (bytes.length < 10 || buffer.toString('ascii', 0, 3) !== 'ID3') return 0
113
+ // Synchsafe integer: 7 significant bits per byte, big-endian.
114
+ const size =
115
+ ((bytes[6] ?? 0) & 0x7f) * 0x200000 +
116
+ ((bytes[7] ?? 0) & 0x7f) * 0x4000 +
117
+ ((bytes[8] ?? 0) & 0x7f) * 0x80 +
118
+ ((bytes[9] ?? 0) & 0x7f)
119
+ return 10 + size
120
+ }
121
+
122
+ /**
123
+ * MPEG-1 Layer III frames, one after another with no trailing bytes: an
124
+ * optional ID3v2 tag is skipped, then every remaining byte must belong to a
125
+ * syntactically valid frame header whose declared length reaches exactly
126
+ * the next frame (or the end of the buffer). Other MPEG versions and layers
127
+ * are refused rather than approximated.
128
+ */
129
+ function isMp3(bytes: Uint8Array): boolean {
130
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
131
+ const offsetAfterTag = id3v2TagLength(bytes, buffer)
132
+ if (offsetAfterTag + 4 > bytes.length) return false
133
+ let offset = offsetAfterTag
134
+ let sawFrame = false
135
+ while (offset + 4 <= bytes.length) {
136
+ if (bytes[offset] !== 0xff || ((bytes[offset + 1] ?? 0) & 0xe0) !== 0xe0) return false
137
+ const frameLength = mp3FrameLength(bytes[offset + 1] ?? 0, bytes[offset + 2] ?? 0)
138
+ if (frameLength === null || frameLength < 4 || offset + frameLength > bytes.length) {
139
+ return false
140
+ }
141
+ sawFrame = true
142
+ offset += frameLength
143
+ }
144
+ return sawFrame && offset === bytes.length
145
+ }
146
+
147
+ function audioIsAdmitted(block: MCPAudioBlock): boolean {
148
+ if (!isAudioMediaType(block.mimeType)) return false
149
+ const bytes = decodeCanonicalBase64(block.data)
150
+ if (!bytes) return false
151
+ switch (block.mimeType) {
152
+ case 'audio/wav':
153
+ return isWav(bytes)
154
+ case 'audio/ogg':
155
+ return isOgg(bytes)
156
+ case 'audio/mpeg':
157
+ return isMp3(bytes)
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Admit an MCP audio batch atomically before any member becomes model input.
163
+ *
164
+ * Mirrors `admitMcpImageBatch`: a remote server controls the declared MIME
165
+ * and bytes, canonical base64 is only an encoding property, and the decoded
166
+ * value must also be a complete, bounded container whose framing agrees
167
+ * with the declared media type. One bad member withholds the complete batch
168
+ * so a model is never shown a partial result while the host retains every
169
+ * raw block for inspection.
170
+ */
171
+ export function admitMcpAudioBatch(audios: readonly MCPAudioBlock[]): boolean {
172
+ return audios.length === 0 || audios.every(audioIsAdmitted)
173
+ }