@namzu/sdk 32.0.0 → 33.1.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 (305) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/dist/authorization/gate.d.ts +1 -1
  3. package/dist/authorization/gate.js +1 -1
  4. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  5. package/dist/bridge/a2a/mapper.js +13 -0
  6. package/dist/bridge/a2a/mapper.js.map +1 -1
  7. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  8. package/dist/bridge/sse/mapper.js +3 -0
  9. package/dist/bridge/sse/mapper.js.map +1 -1
  10. package/dist/connector/index.d.ts +1 -1
  11. package/dist/connector/index.d.ts.map +1 -1
  12. package/dist/connector/index.js +1 -1
  13. package/dist/connector/index.js.map +1 -1
  14. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  15. package/dist/connector/mcp/adapter.js +71 -16
  16. package/dist/connector/mcp/adapter.js.map +1 -1
  17. package/dist/connector/mcp/client.d.ts.map +1 -1
  18. package/dist/connector/mcp/client.js +3 -2
  19. package/dist/connector/mcp/client.js.map +1 -1
  20. package/dist/connector/mcp/http-redirect.d.ts +17 -0
  21. package/dist/connector/mcp/http-redirect.d.ts.map +1 -0
  22. package/dist/connector/mcp/http-redirect.js +31 -0
  23. package/dist/connector/mcp/http-redirect.js.map +1 -0
  24. package/dist/connector/mcp/http-sse.d.ts.map +1 -1
  25. package/dist/connector/mcp/http-sse.js +5 -0
  26. package/dist/connector/mcp/http-sse.js.map +1 -1
  27. package/dist/connector/mcp/image-admission.d.ts +16 -0
  28. package/dist/connector/mcp/image-admission.d.ts.map +1 -0
  29. package/dist/connector/mcp/image-admission.js +288 -0
  30. package/dist/connector/mcp/image-admission.js.map +1 -0
  31. package/dist/connector/mcp/stdio.d.ts +13 -0
  32. package/dist/connector/mcp/stdio.d.ts.map +1 -1
  33. package/dist/connector/mcp/stdio.js +66 -23
  34. package/dist/connector/mcp/stdio.js.map +1 -1
  35. package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
  36. package/dist/connector/mcp/streamable-http.js +3 -0
  37. package/dist/connector/mcp/streamable-http.js.map +1 -1
  38. package/dist/constants/tools/index.js +1 -1
  39. package/dist/constants/tools/index.js.map +1 -1
  40. package/dist/execution/base.d.ts +13 -0
  41. package/dist/execution/base.d.ts.map +1 -1
  42. package/dist/execution/base.js +103 -8
  43. package/dist/execution/base.js.map +1 -1
  44. package/dist/execution/errors.d.ts +12 -0
  45. package/dist/execution/errors.d.ts.map +1 -0
  46. package/dist/execution/errors.js +21 -0
  47. package/dist/execution/errors.js.map +1 -0
  48. package/dist/execution/factory.d.ts.map +1 -1
  49. package/dist/execution/factory.js +1 -0
  50. package/dist/execution/factory.js.map +1 -1
  51. package/dist/execution/hybrid.d.ts +5 -0
  52. package/dist/execution/hybrid.d.ts.map +1 -1
  53. package/dist/execution/hybrid.js +36 -16
  54. package/dist/execution/hybrid.js.map +1 -1
  55. package/dist/execution/index.d.ts +1 -0
  56. package/dist/execution/index.d.ts.map +1 -1
  57. package/dist/execution/index.js +1 -0
  58. package/dist/execution/index.js.map +1 -1
  59. package/dist/execution/local.d.ts +13 -0
  60. package/dist/execution/local.d.ts.map +1 -1
  61. package/dist/execution/local.js +145 -19
  62. package/dist/execution/local.js.map +1 -1
  63. package/dist/execution/output.d.ts +27 -0
  64. package/dist/execution/output.d.ts.map +1 -0
  65. package/dist/execution/output.js +105 -0
  66. package/dist/execution/output.js.map +1 -0
  67. package/dist/execution/remote.d.ts +26 -2
  68. package/dist/execution/remote.d.ts.map +1 -1
  69. package/dist/execution/remote.js +86 -15
  70. package/dist/execution/remote.js.map +1 -1
  71. package/dist/plugin/lifecycle.d.ts.map +1 -1
  72. package/dist/plugin/lifecycle.js +9 -3
  73. package/dist/plugin/lifecycle.js.map +1 -1
  74. package/dist/process/kill-tree.d.ts +11 -13
  75. package/dist/process/kill-tree.d.ts.map +1 -1
  76. package/dist/process/kill-tree.js +11 -13
  77. package/dist/process/kill-tree.js.map +1 -1
  78. package/dist/provider/capabilities.d.ts +2 -0
  79. package/dist/provider/capabilities.d.ts.map +1 -1
  80. package/dist/provider/capabilities.js +6 -0
  81. package/dist/provider/capabilities.js.map +1 -1
  82. package/dist/provider/fallback.d.ts.map +1 -1
  83. package/dist/provider/fallback.js +31 -0
  84. package/dist/provider/fallback.js.map +1 -1
  85. package/dist/provider/idle-timeout.d.ts.map +1 -1
  86. package/dist/provider/idle-timeout.js +5 -0
  87. package/dist/provider/idle-timeout.js.map +1 -1
  88. package/dist/provider/retry.d.ts.map +1 -1
  89. package/dist/provider/retry.js +5 -0
  90. package/dist/provider/retry.js.map +1 -1
  91. package/dist/public-runtime.d.ts +2 -2
  92. package/dist/public-runtime.d.ts.map +1 -1
  93. package/dist/public-runtime.js +2 -2
  94. package/dist/public-runtime.js.map +1 -1
  95. package/dist/registry/tool/execute.d.ts +7 -1
  96. package/dist/registry/tool/execute.d.ts.map +1 -1
  97. package/dist/registry/tool/execute.js +201 -54
  98. package/dist/registry/tool/execute.js.map +1 -1
  99. package/dist/run/command-gate.d.ts.map +1 -1
  100. package/dist/run/command-gate.js +11 -0
  101. package/dist/run/command-gate.js.map +1 -1
  102. package/dist/run/workspace-fingerprint.d.ts +4 -4
  103. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  104. package/dist/run/workspace-fingerprint.js +6 -4
  105. package/dist/run/workspace-fingerprint.js.map +1 -1
  106. package/dist/runtime/bidi/index.d.ts +1 -1
  107. package/dist/runtime/bidi/index.d.ts.map +1 -1
  108. package/dist/runtime/bidi/index.js +1 -1
  109. package/dist/runtime/bidi/index.js.map +1 -1
  110. package/dist/runtime/bidi/session.d.ts +19 -0
  111. package/dist/runtime/bidi/session.d.ts.map +1 -1
  112. package/dist/runtime/bidi/session.js +150 -26
  113. package/dist/runtime/bidi/session.js.map +1 -1
  114. package/dist/runtime/query/executor.d.ts +43 -1
  115. package/dist/runtime/query/executor.d.ts.map +1 -1
  116. package/dist/runtime/query/executor.js +626 -161
  117. package/dist/runtime/query/executor.js.map +1 -1
  118. package/dist/runtime/query/index.d.ts +3 -1
  119. package/dist/runtime/query/index.d.ts.map +1 -1
  120. package/dist/runtime/query/index.js +48 -4
  121. package/dist/runtime/query/index.js.map +1 -1
  122. package/dist/runtime/query/iteration/index.d.ts +10 -0
  123. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/index.js +67 -0
  125. package/dist/runtime/query/iteration/index.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
  127. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  128. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  130. package/dist/runtime/query/iteration/phases/tool-review.js +84 -22
  131. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  132. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  133. package/dist/runtime/query/request-rich-content.js +33 -13
  134. package/dist/runtime/query/request-rich-content.js.map +1 -1
  135. package/dist/runtime/query/result.d.ts.map +1 -1
  136. package/dist/runtime/query/result.js +25 -22
  137. package/dist/runtime/query/result.js.map +1 -1
  138. package/dist/runtime/query/resume-pending.d.ts +5 -1
  139. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  140. package/dist/runtime/query/resume-pending.js +66 -1
  141. package/dist/runtime/query/resume-pending.js.map +1 -1
  142. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  143. package/dist/runtime/query/tool-output-budget.js +45 -16
  144. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  145. package/dist/runtime/query/tooling.d.ts +6 -0
  146. package/dist/runtime/query/tooling.d.ts.map +1 -1
  147. package/dist/runtime/query/tooling.js +4 -0
  148. package/dist/runtime/query/tooling.js.map +1 -1
  149. package/dist/sandbox/provider/local.d.ts.map +1 -1
  150. package/dist/sandbox/provider/local.js +193 -41
  151. package/dist/sandbox/provider/local.js.map +1 -1
  152. package/dist/scheduler/delegating.d.ts +2 -1
  153. package/dist/scheduler/delegating.d.ts.map +1 -1
  154. package/dist/scheduler/delegating.js +6 -5
  155. package/dist/scheduler/delegating.js.map +1 -1
  156. package/dist/scheduler/local.d.ts +2 -1
  157. package/dist/scheduler/local.d.ts.map +1 -1
  158. package/dist/scheduler/local.js +2 -2
  159. package/dist/scheduler/local.js.map +1 -1
  160. package/dist/session/workspace/driver.d.ts +12 -4
  161. package/dist/session/workspace/driver.d.ts.map +1 -1
  162. package/dist/session/workspace/git-worktree.d.ts +17 -3
  163. package/dist/session/workspace/git-worktree.d.ts.map +1 -1
  164. package/dist/session/workspace/git-worktree.js +153 -34
  165. package/dist/session/workspace/git-worktree.js.map +1 -1
  166. package/dist/skills/registry.d.ts +15 -0
  167. package/dist/skills/registry.d.ts.map +1 -1
  168. package/dist/skills/registry.js +33 -0
  169. package/dist/skills/registry.js.map +1 -1
  170. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  171. package/dist/tools/builtins/computer-use.js +76 -2
  172. package/dist/tools/builtins/computer-use.js.map +1 -1
  173. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  174. package/dist/tools/builtins/run-code.js +9 -11
  175. package/dist/tools/builtins/run-code.js.map +1 -1
  176. package/dist/tools/builtins/skill.d.ts +2 -1
  177. package/dist/tools/builtins/skill.d.ts.map +1 -1
  178. package/dist/tools/builtins/skill.js +250 -6
  179. package/dist/tools/builtins/skill.js.map +1 -1
  180. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  181. package/dist/tools/coordinator/agent.js +100 -30
  182. package/dist/tools/coordinator/agent.js.map +1 -1
  183. package/dist/types/agent/manager.d.ts +1 -1
  184. package/dist/types/agent/manager.d.ts.map +1 -1
  185. package/dist/types/agent/scheduler.d.ts +10 -1
  186. package/dist/types/agent/scheduler.d.ts.map +1 -1
  187. package/dist/types/authorization/index.d.ts +12 -12
  188. package/dist/types/bidi/index.d.ts +19 -2
  189. package/dist/types/bidi/index.d.ts.map +1 -1
  190. package/dist/types/computer-use/index.d.ts +18 -0
  191. package/dist/types/computer-use/index.d.ts.map +1 -1
  192. package/dist/types/connector/execution-context.d.ts +3 -1
  193. package/dist/types/connector/execution-context.d.ts.map +1 -1
  194. package/dist/types/errors/catalog.d.ts.map +1 -1
  195. package/dist/types/errors/catalog.js +13 -6
  196. package/dist/types/errors/catalog.js.map +1 -1
  197. package/dist/types/errors/index.d.ts.map +1 -1
  198. package/dist/types/errors/index.js +21 -8
  199. package/dist/types/errors/index.js.map +1 -1
  200. package/dist/types/execution/index.d.ts +43 -1
  201. package/dist/types/execution/index.d.ts.map +1 -1
  202. package/dist/types/hitl/index.d.ts +11 -0
  203. package/dist/types/hitl/index.d.ts.map +1 -1
  204. package/dist/types/hitl/index.js.map +1 -1
  205. package/dist/types/message/index.d.ts +1 -1
  206. package/dist/types/message/index.d.ts.map +1 -1
  207. package/dist/types/message/index.js +2 -1
  208. package/dist/types/message/index.js.map +1 -1
  209. package/dist/types/plugin/index.d.ts +14 -1
  210. package/dist/types/plugin/index.d.ts.map +1 -1
  211. package/dist/types/plugin/index.js +1 -0
  212. package/dist/types/plugin/index.js.map +1 -1
  213. package/dist/types/provider/config.d.ts +9 -0
  214. package/dist/types/provider/config.d.ts.map +1 -1
  215. package/dist/types/provider/interface.d.ts +14 -0
  216. package/dist/types/provider/interface.d.ts.map +1 -1
  217. package/dist/types/run/events.d.ts +17 -0
  218. package/dist/types/run/events.d.ts.map +1 -1
  219. package/dist/types/run/events.js.map +1 -1
  220. package/dist/types/sandbox/index.d.ts +15 -10
  221. package/dist/types/sandbox/index.d.ts.map +1 -1
  222. package/dist/types/sandbox/index.js.map +1 -1
  223. package/dist/types/tool/index.d.ts +77 -6
  224. package/dist/types/tool/index.d.ts.map +1 -1
  225. package/dist/types/tool/index.js.map +1 -1
  226. package/dist/types/tool/presentation.d.ts +6 -2
  227. package/dist/types/tool/presentation.d.ts.map +1 -1
  228. package/dist/types/workspace/ref.d.ts +5 -0
  229. package/dist/types/workspace/ref.d.ts.map +1 -1
  230. package/dist/utils/abort.d.ts +8 -0
  231. package/dist/utils/abort.d.ts.map +1 -1
  232. package/dist/utils/abort.js +16 -0
  233. package/dist/utils/abort.js.map +1 -1
  234. package/package.json +4 -4
  235. package/src/authorization/gate.ts +1 -1
  236. package/src/bridge/a2a/mapper.ts +23 -4
  237. package/src/bridge/sse/mapper.ts +3 -0
  238. package/src/connector/index.ts +2 -0
  239. package/src/connector/mcp/adapter.ts +82 -21
  240. package/src/connector/mcp/client.ts +2 -1
  241. package/src/connector/mcp/http-redirect.ts +32 -0
  242. package/src/connector/mcp/http-sse.ts +5 -0
  243. package/src/connector/mcp/image-admission.ts +272 -0
  244. package/src/connector/mcp/stdio.ts +63 -21
  245. package/src/connector/mcp/streamable-http.ts +3 -0
  246. package/src/constants/tools/index.ts +1 -1
  247. package/src/execution/base.ts +124 -7
  248. package/src/execution/errors.ts +24 -0
  249. package/src/execution/factory.ts +1 -0
  250. package/src/execution/hybrid.ts +47 -16
  251. package/src/execution/index.ts +1 -0
  252. package/src/execution/local.ts +164 -19
  253. package/src/execution/output.ts +104 -0
  254. package/src/execution/remote.ts +122 -17
  255. package/src/plugin/lifecycle.ts +9 -3
  256. package/src/process/kill-tree.ts +11 -13
  257. package/src/provider/capabilities.ts +10 -0
  258. package/src/provider/fallback.ts +39 -0
  259. package/src/provider/idle-timeout.ts +8 -0
  260. package/src/provider/retry.ts +8 -0
  261. package/src/public-runtime.ts +7 -1
  262. package/src/registry/tool/execute.ts +230 -64
  263. package/src/run/command-gate.ts +12 -0
  264. package/src/run/workspace-fingerprint.ts +5 -4
  265. package/src/runtime/bidi/index.ts +1 -1
  266. package/src/runtime/bidi/session.ts +177 -28
  267. package/src/runtime/query/executor.ts +769 -163
  268. package/src/runtime/query/index.ts +58 -7
  269. package/src/runtime/query/iteration/index.ts +72 -0
  270. package/src/runtime/query/iteration/phases/context.ts +5 -0
  271. package/src/runtime/query/iteration/phases/tool-review.ts +87 -23
  272. package/src/runtime/query/request-rich-content.ts +39 -14
  273. package/src/runtime/query/result.ts +25 -23
  274. package/src/runtime/query/resume-pending.ts +83 -1
  275. package/src/runtime/query/tool-output-budget.ts +52 -17
  276. package/src/runtime/query/tooling.ts +10 -0
  277. package/src/sandbox/provider/local.ts +213 -45
  278. package/src/scheduler/delegating.ts +6 -5
  279. package/src/scheduler/local.ts +3 -2
  280. package/src/session/workspace/driver.ts +12 -4
  281. package/src/session/workspace/git-worktree.ts +173 -34
  282. package/src/skills/registry.ts +47 -0
  283. package/src/tools/builtins/computer-use.ts +86 -2
  284. package/src/tools/builtins/run-code.ts +9 -11
  285. package/src/tools/builtins/skill.ts +325 -7
  286. package/src/tools/coordinator/agent.ts +108 -33
  287. package/src/types/agent/manager.ts +1 -1
  288. package/src/types/agent/scheduler.ts +10 -1
  289. package/src/types/bidi/index.ts +19 -2
  290. package/src/types/computer-use/index.ts +19 -0
  291. package/src/types/connector/execution-context.ts +3 -0
  292. package/src/types/errors/catalog.ts +14 -6
  293. package/src/types/errors/index.ts +22 -8
  294. package/src/types/execution/index.ts +46 -1
  295. package/src/types/hitl/index.ts +11 -0
  296. package/src/types/message/index.ts +3 -2
  297. package/src/types/plugin/index.ts +15 -0
  298. package/src/types/provider/config.ts +9 -0
  299. package/src/types/provider/interface.ts +18 -0
  300. package/src/types/run/events.ts +17 -3
  301. package/src/types/sandbox/index.ts +15 -10
  302. package/src/types/tool/index.ts +85 -6
  303. package/src/types/tool/presentation.ts +10 -3
  304. package/src/types/workspace/ref.ts +5 -0
  305. package/src/utils/abort.ts +18 -0
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from 'node:events';
1
2
  import { toErrorMessage } from './error.js';
2
3
  /**
3
4
  * The words a caller attached to a stop, or nothing when there were none.
@@ -44,6 +45,21 @@ export function abortReasonText(reason) {
44
45
  const text = toErrorMessage(reason).trim();
45
46
  return text.length > 0 ? text : undefined;
46
47
  }
48
+ /**
49
+ * Subscribe to caller cancellation without raising the Node 20.0 runtime floor.
50
+ *
51
+ * `addAbortListener` cannot be hidden by another listener calling
52
+ * `stopImmediatePropagation`. Both branches return an explicit disposer so a
53
+ * completed operation does not remain attached to a long-lived caller signal.
54
+ */
55
+ export function subscribeToAbort(signal, listener) {
56
+ if (typeof EventEmitter.addAbortListener === 'function') {
57
+ const subscription = EventEmitter.addAbortListener(signal, listener);
58
+ return () => subscription[Symbol.dispose]();
59
+ }
60
+ signal.addEventListener('abort', listener, { once: true });
61
+ return () => signal.removeEventListener('abort', listener);
62
+ }
47
63
  export function createChildAbortController(parent) {
48
64
  const child = new AbortController();
49
65
  if (parent.signal.aborted) {
@@ -1 +1 @@
1
- {"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC9C,IAAI,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAChD,uEAAuE;IACvE,wEAAwE;IACxE,oEAAoE;IACpE,mEAAmE;IACnE,EAAE;IACF,yEAAyE;IACzE,qEAAqE;IACrE,sEAAsE;IACtE,qCAAqC;IACrC,IACC,MAAM,CAAC,IAAI,KAAK,YAAY;QAC5B,MAAM,CAAC,IAAI,KAAK,cAAc;QAC9B,MAAM,CAAC,IAAI,KAAK,cAAc,EAC7B,CAAC;QACF,OAAO,SAAS,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1C,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAuB;IACjE,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAA;IAEnC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,OAAO,KAAK,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IAEpC,MAAM,aAAa,GAAG,GAAS,EAAE;QAChC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAA;QAC7B,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChC,CAAC;IACF,CAAC,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAEtE,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAC5B,OAAO,EACP,GAAG,EAAE;QACJ,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAC1D,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACd,CAAA;IAED,OAAO,KAAK,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC9C,IAAI,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAChD,uEAAuE;IACvE,wEAAwE;IACxE,oEAAoE;IACpE,mEAAmE;IACnE,EAAE;IACF,yEAAyE;IACzE,qEAAqE;IACrE,sEAAsE;IACtE,qCAAqC;IACrC,IACC,MAAM,CAAC,IAAI,KAAK,YAAY;QAC5B,MAAM,CAAC,IAAI,KAAK,cAAc;QAC9B,MAAM,CAAC,IAAI,KAAK,cAAc,EAC7B,CAAC;QACF,OAAO,SAAS,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1C,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAmB,EAAE,QAAoB;IACzE,IAAI,OAAO,YAAY,CAAC,gBAAgB,KAAK,UAAU,EAAE,CAAC;QACzD,MAAM,YAAY,GAAG,YAAY,CAAC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QACpE,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;IAC5C,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1D,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;AAC3D,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAuB;IACjE,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAA;IAEnC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjC,OAAO,KAAK,CAAA;IACb,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IAEpC,MAAM,aAAa,GAAG,GAAS,EAAE;QAChC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAA;QAC7B,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChC,CAAC;IACF,CAAC,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAEtE,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAC5B,OAAO,EACP,GAAG,EAAE;QACJ,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAC1D,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,CACd,CAAA;IAED,OAAO,KAAK,CAAA;AACb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namzu/sdk",
3
- "version": "32.0.0",
3
+ "version": "33.1.0",
4
4
  "description": "Open-source AI agent SDK with a built-in runtime. Nothing between you and your agents.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "engines": {
@@ -77,11 +77,11 @@
77
77
  "lint": "biome check src/",
78
78
  "lint:fix": "biome check --write src/",
79
79
  "format": "biome format --write src/",
80
- "test": "vitest run --passWithNoTests",
81
- "test:coverage": "vitest run --passWithNoTests --coverage",
80
+ "test": "node ../../scripts/run-sdk-tests.mjs unit --passWithNoTests",
81
+ "test:coverage": "node ../../scripts/run-sdk-tests.mjs unit --passWithNoTests --coverage",
82
82
  "typecheck": "tsc --noEmit",
83
83
  "knip": "knip",
84
84
  "knip:production": "knip --production",
85
- "test:proc": "tsc --build && vitest run --config vitest.proc.config.ts"
85
+ "test:proc": "tsc --build && node ../../scripts/run-sdk-tests.mjs proc"
86
86
  }
87
87
  }
@@ -21,7 +21,7 @@ export interface ToolCallContext {
21
21
  * What the rule actually said, in words a model can act on.
22
22
  *
23
23
  * This used to be the rule TYPE and nothing else, so a denial reached the
24
- * model as "Blocked by the verification gate: Matched rule: deny_by_name" —
24
+ * model as "Blocked by the authorization gate: Matched rule: deny_by_name" —
25
25
  * naming the kind of rule and nothing about it. Not which tool, not which
26
26
  * pattern, not whether a different input would fare better.
27
27
  *
@@ -206,10 +206,29 @@ const MAPPING: {
206
206
  }),
207
207
 
208
208
  run_paused: (e, ctx) =>
209
- statusEvent(e.runId, 'input-required', false, ctx, {
210
- role: 'agent',
211
- parts: [{ kind: 'text', text: `Run paused: ${e.reason}` }],
212
- }),
209
+ statusEvent(
210
+ e.runId,
211
+ 'input-required',
212
+ false,
213
+ ctx,
214
+ {
215
+ role: 'agent',
216
+ parts: [{ kind: 'text', text: `Run paused: ${e.reason}` }],
217
+ },
218
+ // A remote host has the same recovery decision as a local one. The
219
+ // checkpoint is the address of that recovery; the classification tells
220
+ // it whether and when a retry is justified.
221
+ {
222
+ checkpointId: e.checkpointId,
223
+ ...(e.failure
224
+ ? {
225
+ code: e.failure.code,
226
+ retryable: e.failure.retryable,
227
+ ...(e.failure.details ? { details: e.failure.details } : {}),
228
+ }
229
+ : {}),
230
+ },
231
+ ),
213
232
 
214
233
  iteration_completed: null,
215
234
  // Context management is kernel-internal bookkeeping; A2A peers model a
@@ -276,6 +276,9 @@ const MAPPING: {
276
276
  run_id: runId,
277
277
  checkpoint_id: e.checkpointId,
278
278
  reason: e.reason,
279
+ ...(e.failure ? { failure: e.failure } : {}),
280
+ ...(e.providerError ? { provider_error: e.providerError } : {}),
281
+ ...(e.explanation ? { explanation: e.explanation } : {}),
279
282
  }),
280
283
  },
281
284
 
@@ -24,9 +24,11 @@ export { WebhookConnector } from './builtins/webhook.js'
24
24
  // thing that defines one.
25
25
  export {
26
26
  BaseExecutionContext,
27
+ CommandCancellationUnsupportedError,
27
28
  ExecutionContextFactory,
28
29
  HybridExecutionContext,
29
30
  LocalExecutionContext,
31
+ RemoteExecutionBusyError,
30
32
  RemoteExecutionContext,
31
33
  } from '../execution/index.js'
32
34
  export type {
@@ -1,14 +1,16 @@
1
1
  import { z } from 'zod'
2
2
  import { zodToJsonSchema } from 'zod-to-json-schema'
3
- import { wrapUntrusted } from '../../tools/untrusted-envelope.js'
3
+ import { type UntrustedEnvelope, wrapUntrusted } from '../../tools/untrusted-envelope.js'
4
4
  import type {
5
5
  MCPJsonSchema,
6
6
  MCPToolDefinition,
7
7
  MCPToolResult,
8
8
  } from '../../types/connector/index.js'
9
- import type { ToolResultBlock } from '../../types/message/index.js'
9
+ import type { ToolResultBlock, ToolResultContent } from '../../types/message/index.js'
10
10
  import type { ToolContext, ToolDefinition, ToolResult } from '../../types/tool/index.js'
11
11
  import type { MCPClient } from './client.js'
12
+ import { MCPHttpRedirectError } from './http-redirect.js'
13
+ import { admitMcpImageBatch } from './image-admission.js'
12
14
  import { inlineSchemaRefs } from './schema-refs.js'
13
15
 
14
16
  /**
@@ -376,7 +378,9 @@ function positionalTypeName(member: unknown): string {
376
378
  * property collision the later member wins, matching the reading order.
377
379
  */
378
380
  function mergeAllOf(members: readonly unknown[], parent: Record<string, unknown>): JsonRecord {
379
- const properties: JsonRecord = { ...((parent.properties as JsonRecord | undefined) ?? {}) }
381
+ const properties: JsonRecord = {
382
+ ...((parent.properties as JsonRecord | undefined) ?? {}),
383
+ }
380
384
  const required = new Set((parent.required as string[] | undefined) ?? [])
381
385
  let additionalProperties = parent.additionalProperties
382
386
 
@@ -439,7 +443,9 @@ export function mcpToolToToolDefinition(
439
443
  // Carried as the server wrote it. It is shown to the model, never
440
444
  // validated here, so rebuilding it would only lose fidelity.
441
445
  ...(tool.outputSchema
442
- ? { outputSchema: inlineSchemaRefs(tool.outputSchema as unknown as Record<string, unknown>) }
446
+ ? {
447
+ outputSchema: inlineSchemaRefs(tool.outputSchema as unknown as Record<string, unknown>),
448
+ }
443
449
  : {}),
444
450
  category: 'network',
445
451
  permissions: ['network_access'],
@@ -453,10 +459,28 @@ export function mcpToolToToolDefinition(
453
459
  provenance: { server: serverName, readOnlyHintTrusted },
454
460
 
455
461
  async execute(input: unknown, context: ToolContext): Promise<ToolResult> {
456
- const result = await client.callTool(tool.name, input as Record<string, unknown>, {
457
- signal: context.abortSignal,
458
- })
459
- return frameServerResult(mcpToolResultToToolResult(result), serverName, tool.name)
462
+ try {
463
+ const result = await client.callTool(tool.name, input as Record<string, unknown>, {
464
+ signal: context.abortSignal,
465
+ })
466
+ return frameServerResult(mcpToolResultToToolResult(result), serverName, tool.name)
467
+ } catch (error) {
468
+ if (error instanceof MCPHttpRedirectError && error.remoteOutcomeUnknown) {
469
+ return {
470
+ success: false,
471
+ output: '',
472
+ error: error.message,
473
+ data: {
474
+ code: 'mcp_tool_outcome_unknown',
475
+ server: serverName,
476
+ tool: tool.name,
477
+ outcome: 'unknown',
478
+ retrySafety: 'unsafe',
479
+ },
480
+ }
481
+ }
482
+ throw error
483
+ }
460
484
  },
461
485
  }
462
486
  }
@@ -510,18 +534,31 @@ export function frameServerResult(
510
534
  serverName: string,
511
535
  toolName: string,
512
536
  ): ToolResult {
513
- if (result.output.length === 0) return result
537
+ const envelope: UntrustedEnvelope = {
538
+ kind: 'connector-tool-result',
539
+ attributes: { server: serverName, tool: toolName },
540
+ provenance: 'This is output the named server returned, not this agent.',
541
+ }
542
+ const frameText = (text: string): string =>
543
+ text.length === 0 ? text : wrapUntrusted(envelope, text)
544
+ const frameContent = (content: ToolResultContent): ToolResultContent =>
545
+ typeof content === 'string'
546
+ ? frameText(content)
547
+ : content.map((block) =>
548
+ block.type === 'text' ? { ...block, text: frameText(block.text) } : block,
549
+ )
550
+
551
+ const output = frameText(result.output)
552
+ const error = result.error === undefined ? undefined : frameText(result.error)
553
+ const content = result.content === undefined ? undefined : frameContent(result.content)
554
+ if (output === result.output && error === result.error && content === result.content)
555
+ return result
514
556
 
515
557
  return {
516
558
  ...result,
517
- output: wrapUntrusted(
518
- {
519
- kind: 'connector-tool-result',
520
- attributes: { server: serverName, tool: toolName },
521
- provenance: 'This is output the named server returned, not this agent.',
522
- },
523
- result.output,
524
- ),
559
+ output,
560
+ ...(error !== undefined ? { error } : {}),
561
+ ...(content !== undefined ? { content } : {}),
525
562
  }
526
563
  }
527
564
 
@@ -530,6 +567,11 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
530
567
  .filter((block): block is { type: 'text'; text: string } => block.type === 'text')
531
568
  .map((block) => block.text)
532
569
  .join('\n')
570
+ const imageBlocks = result.content.filter(
571
+ (block): block is Extract<(typeof result.content)[number], { type: 'image' }> =>
572
+ block.type === 'image',
573
+ )
574
+ const imagesAdmitted = admitMcpImageBatch(imageBlocks)
533
575
 
534
576
  // Non-text blocks used to be filtered out and never seen again: a
535
577
  // bridged MCP server returning a chart, a screenshot or a PDF had that
@@ -540,8 +582,19 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
540
582
  for (const block of result.content) {
541
583
  if (block.type === 'text') {
542
584
  blocks.push({ type: 'text', text: block.text })
543
- } else if (block.type === 'image' && block.data && block.mimeType) {
544
- blocks.push({ type: 'image', data: block.data, mediaType: block.mimeType })
585
+ } else if (block.type === 'image') {
586
+ blocks.push({
587
+ type: 'image',
588
+ data: block.data,
589
+ mediaType: block.mimeType,
590
+ ...(!imagesAdmitted
591
+ ? {
592
+ modelOmission: {
593
+ reason: 'invalid-image' as const,
594
+ },
595
+ }
596
+ : {}),
597
+ })
545
598
  } else if (block.type === 'resource' && block.resource?.text) {
546
599
  // A resource with inline text is readable content; one that is
547
600
  // only a URI is a pointer the model cannot dereference, so it is
@@ -556,10 +609,15 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
556
609
  // the model reading the answer and reading nothing at all — the call
557
610
  // succeeded, so no error path would have caught it either. Text wins
558
611
  // when both are present: the server wrote it for the model.
559
- const output =
612
+ const visibleText =
560
613
  textContent.length > 0 || result.structuredContent === undefined
561
614
  ? textContent
562
615
  : stringifyStructured(result.structuredContent)
616
+ const invalidImageNotice =
617
+ imageBlocks.length > 0 && !imagesAdmitted
618
+ ? '[MCP image batch withheld from model input: one or more blocks are not complete supported raster containers matching their declared media types.]'
619
+ : ''
620
+ const output = [visibleText, invalidImageNotice].filter((part) => part.length > 0).join('\n')
563
621
 
564
622
  return {
565
623
  success: !result.isError,
@@ -570,7 +628,10 @@ export function mcpToolResultToToolResult(result: MCPToolResult): ToolResult {
570
628
  data:
571
629
  result.structuredContent === undefined
572
630
  ? result.content
573
- : { content: result.content, structuredContent: result.structuredContent },
631
+ : {
632
+ content: result.content,
633
+ structuredContent: result.structuredContent,
634
+ },
574
635
  error: result.isError ? output : undefined,
575
636
  }
576
637
  }
@@ -170,13 +170,14 @@ export class MCPClient {
170
170
  async disconnect(): Promise<void> {
171
171
  const reason = new Error('MCPClient disconnecting')
172
172
  this.abortCancellations(reason)
173
- if (this.status === 'disconnected') return
173
+ const alreadyDisconnected = this.status === 'disconnected'
174
174
 
175
175
  this.rejectAllPending('MCPClient disconnecting')
176
176
 
177
177
  await this.transport.close()
178
178
  this.status = 'disconnected'
179
179
  this.connectedAt = undefined
180
+ if (alreadyDisconnected) return
180
181
  this.log.info('MCP client disconnected')
181
182
  this.emitLifecycle({ type: 'mcp_client_disconnected', clientId: this.id })
182
183
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A request reached the configured MCP server, but no response from the
3
+ * requested operation was received because that server redirected it.
4
+ *
5
+ * This stays an internal transport-to-adapter signal. The public tool result
6
+ * carries the stable, serializable outcome fields instead of exposing an
7
+ * Error subclass as API.
8
+ */
9
+ export class MCPHttpRedirectError extends Error {
10
+ readonly code = 'mcp_http_redirect_refused'
11
+ readonly remoteOutcomeUnknown: boolean
12
+
13
+ constructor(
14
+ readonly status: number,
15
+ readonly requestMethod?: string,
16
+ ) {
17
+ const base = `MCP HTTP ${status} redirect was not followed because authenticated requests must remain at their configured endpoint. Configure the final MCP endpoint directly.`
18
+ const outcome =
19
+ requestMethod === 'tools/call'
20
+ ? ' The configured MCP server received the tool call, so the remote outcome is unknown; do not automatically retry.'
21
+ : ''
22
+ super(base + outcome)
23
+ this.name = 'MCPHttpRedirectError'
24
+ this.remoteOutcomeUnknown = requestMethod === 'tools/call'
25
+ }
26
+ }
27
+
28
+ export function refuseMcpHttpRedirect(response: Response, requestMethod?: string): void {
29
+ if (response.status >= 300 && response.status < 400) {
30
+ throw new MCPHttpRedirectError(response.status, requestMethod)
31
+ }
32
+ }
@@ -7,6 +7,7 @@ import type {
7
7
  import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
8
8
  import { type Logger, resolveLogger } from '../../utils/logger.js'
9
9
  import { ConnectorHttpOperation, validateConnectorTimeoutMs } from '../http-operation.js'
10
+ import { refuseMcpHttpRedirect } from './http-redirect.js'
10
11
 
11
12
  const DEFAULT_TIMEOUT_MS = 30_000
12
13
 
@@ -111,10 +112,12 @@ export class HttpSseTransport implements MCPTransport {
111
112
  ...this.config.headers,
112
113
  },
113
114
  body: JSON.stringify(message),
115
+ redirect: 'manual',
114
116
  signal: operation.signal,
115
117
  }),
116
118
  )
117
119
 
120
+ refuseMcpHttpRedirect(response, message.method)
118
121
  if (!response.ok) {
119
122
  throw new Error(`HTTP ${response.status}: ${response.statusText}`)
120
123
  }
@@ -202,9 +205,11 @@ export class HttpSseTransport implements MCPTransport {
202
205
  Accept: 'text/event-stream',
203
206
  ...this.config.headers,
204
207
  },
208
+ redirect: 'manual',
205
209
  signal,
206
210
  })
207
211
 
212
+ refuseMcpHttpRedirect(response)
208
213
  if (!response.ok || !response.body) {
209
214
  throw new Error(`SSE connection failed: HTTP ${response.status}`)
210
215
  }
@@ -0,0 +1,272 @@
1
+ import type { MCPContentBlock } from '../../types/connector/index.js'
2
+
3
+ const MAX_ENCODED_BYTES = 24 * 1024 * 1024
4
+ const MAX_DIMENSION = 16_384
5
+ const MAX_PIXELS = 40_000_000
6
+
7
+ type MCPImageBlock = Extract<MCPContentBlock, { type: 'image' }>
8
+ type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'
9
+
10
+ const IMAGE_MEDIA_TYPES = new Set<ImageMediaType>([
11
+ 'image/png',
12
+ 'image/jpeg',
13
+ 'image/webp',
14
+ 'image/gif',
15
+ ])
16
+
17
+ function isImageMediaType(value: string): value is ImageMediaType {
18
+ return IMAGE_MEDIA_TYPES.has(value as ImageMediaType)
19
+ }
20
+
21
+ function decodeCanonicalBase64(value: string): Uint8Array | null {
22
+ if (value.length === 0 || value.length > MAX_ENCODED_BYTES) return null
23
+ if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
24
+ return null
25
+ }
26
+ const decoded = Buffer.from(value, 'base64')
27
+ return decoded.toString('base64') === value ? decoded : null
28
+ }
29
+
30
+ function dimensionsAreSafe(width: number, height: number): boolean {
31
+ return (
32
+ Number.isSafeInteger(width) &&
33
+ Number.isSafeInteger(height) &&
34
+ width > 0 &&
35
+ height > 0 &&
36
+ width <= MAX_DIMENSION &&
37
+ height <= MAX_DIMENSION &&
38
+ width * height <= MAX_PIXELS
39
+ )
40
+ }
41
+
42
+ const crcTable = (() => {
43
+ const table = new Uint32Array(256)
44
+ for (let n = 0; n < 256; n += 1) {
45
+ let value = n
46
+ for (let bit = 0; bit < 8; bit += 1) {
47
+ value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1
48
+ }
49
+ table[n] = value >>> 0
50
+ }
51
+ return table
52
+ })()
53
+
54
+ function crc32(bytes: Uint8Array, start: number, end: number): number {
55
+ let crc = 0xffffffff
56
+ for (let offset = start; offset < end; offset += 1) {
57
+ crc = (crcTable[(crc ^ (bytes[offset] ?? 0)) & 0xff] ?? 0) ^ (crc >>> 8)
58
+ }
59
+ return (crc ^ 0xffffffff) >>> 0
60
+ }
61
+
62
+ function isPng(bytes: Uint8Array): boolean {
63
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
64
+ if (bytes.length < 45 || !buffer.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) {
65
+ return false
66
+ }
67
+ let offset = 8
68
+ let sawHeader = false
69
+ let sawData = false
70
+ while (offset + 12 <= bytes.length) {
71
+ const length = buffer.readUInt32BE(offset)
72
+ const typeStart = offset + 4
73
+ const dataStart = typeStart + 4
74
+ const dataEnd = dataStart + length
75
+ const chunkEnd = dataEnd + 4
76
+ if (dataEnd < dataStart || chunkEnd > bytes.length) return false
77
+ const type = buffer.toString('ascii', typeStart, dataStart)
78
+ if (buffer.readUInt32BE(dataEnd) !== crc32(bytes, typeStart, dataEnd)) return false
79
+ if (!sawHeader) {
80
+ if (type !== 'IHDR' || length !== 13) return false
81
+ const width = buffer.readUInt32BE(dataStart)
82
+ const height = buffer.readUInt32BE(dataStart + 4)
83
+ if (!dimensionsAreSafe(width, height)) return false
84
+ sawHeader = true
85
+ } else if (type === 'IHDR') return false
86
+ if (type === 'IDAT') sawData = true
87
+ if (type === 'IEND') return length === 0 && sawHeader && sawData && chunkEnd === bytes.length
88
+ offset = chunkEnd
89
+ }
90
+ return false
91
+ }
92
+
93
+ const JPEG_SOF_MARKERS = new Set([
94
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
95
+ ])
96
+
97
+ function isJpeg(bytes: Uint8Array): boolean {
98
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
99
+ if (bytes.length < 11 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return false
100
+ let offset = 2
101
+ let sawFrame = false
102
+ let sawScan = false
103
+ let inScan = false
104
+ while (offset < bytes.length) {
105
+ if (inScan && bytes[offset] !== 0xff) {
106
+ offset += 1
107
+ continue
108
+ }
109
+ if (bytes[offset] !== 0xff) return false
110
+ while (offset < bytes.length && bytes[offset] === 0xff) offset += 1
111
+ if (offset >= bytes.length) return false
112
+ const marker = bytes[offset] ?? 0
113
+ offset += 1
114
+ if (inScan && (marker === 0x00 || (marker >= 0xd0 && marker <= 0xd7))) continue
115
+ inScan = false
116
+ if (marker === 0xd9) return sawFrame && sawScan && offset === bytes.length
117
+ if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue
118
+ if (offset + 2 > bytes.length) return false
119
+ const length = buffer.readUInt16BE(offset)
120
+ if (length < 2 || offset + length > bytes.length) return false
121
+ if (JPEG_SOF_MARKERS.has(marker)) {
122
+ if (length < 8) return false
123
+ const height = buffer.readUInt16BE(offset + 3)
124
+ const width = buffer.readUInt16BE(offset + 5)
125
+ if (!dimensionsAreSafe(width, height)) return false
126
+ sawFrame = true
127
+ }
128
+ offset += length
129
+ if (marker === 0xda) {
130
+ sawScan = true
131
+ inScan = true
132
+ }
133
+ }
134
+ return false
135
+ }
136
+
137
+ function skipGifSubBlocks(bytes: Uint8Array, start: number): number | null {
138
+ let offset = start
139
+ while (offset < bytes.length) {
140
+ const length = bytes[offset] ?? 0
141
+ offset += 1
142
+ if (length === 0) return offset
143
+ if (offset + length > bytes.length) return null
144
+ offset += length
145
+ }
146
+ return null
147
+ }
148
+
149
+ function isGif(bytes: Uint8Array): boolean {
150
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
151
+ if (bytes.length < 14) return false
152
+ const signature = buffer.toString('ascii', 0, 6)
153
+ if (signature !== 'GIF87a' && signature !== 'GIF89a') return false
154
+ if (!dimensionsAreSafe(buffer.readUInt16LE(6), buffer.readUInt16LE(8))) return false
155
+ let offset = 13
156
+ const packed = bytes[10] ?? 0
157
+ if ((packed & 0x80) !== 0) offset += 3 * 2 ** ((packed & 0x07) + 1)
158
+ if (offset > bytes.length) return false
159
+ let sawImage = false
160
+ while (offset < bytes.length) {
161
+ const marker = bytes[offset] ?? 0
162
+ offset += 1
163
+ if (marker === 0x3b) return sawImage && offset === bytes.length
164
+ if (marker === 0x21) {
165
+ if (offset >= bytes.length) return false
166
+ offset += 1
167
+ const next = skipGifSubBlocks(bytes, offset)
168
+ if (next === null) return false
169
+ offset = next
170
+ continue
171
+ }
172
+ if (marker !== 0x2c || offset + 9 > bytes.length) return false
173
+ const width = buffer.readUInt16LE(offset + 4)
174
+ const height = buffer.readUInt16LE(offset + 6)
175
+ if (!dimensionsAreSafe(width, height)) return false
176
+ const imagePacked = bytes[offset + 8] ?? 0
177
+ offset += 9
178
+ if ((imagePacked & 0x80) !== 0) offset += 3 * 2 ** ((imagePacked & 0x07) + 1)
179
+ if (offset >= bytes.length) return false
180
+ offset += 1
181
+ const next = skipGifSubBlocks(bytes, offset)
182
+ if (next === null) return false
183
+ offset = next
184
+ sawImage = true
185
+ }
186
+ return false
187
+ }
188
+
189
+ function readUInt24LE(bytes: Uint8Array, offset: number): number {
190
+ return (bytes[offset] ?? 0) | ((bytes[offset + 1] ?? 0) << 8) | ((bytes[offset + 2] ?? 0) << 16)
191
+ }
192
+
193
+ function webpDimensions(type: string, data: Uint8Array): readonly [number, number] | null {
194
+ const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength)
195
+ if (type === 'VP8X') {
196
+ if (data.length < 10) return null
197
+ return [readUInt24LE(data, 4) + 1, readUInt24LE(data, 7) + 1]
198
+ }
199
+ if (type === 'VP8L') {
200
+ if (data.length < 5 || data[0] !== 0x2f) return null
201
+ const bits = buffer.readUInt32LE(1)
202
+ return [(bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1]
203
+ }
204
+ if (type === 'VP8 ') {
205
+ if (data.length < 10 || data[3] !== 0x9d || data[4] !== 0x01 || data[5] !== 0x2a) {
206
+ return null
207
+ }
208
+ return [buffer.readUInt16LE(6) & 0x3fff, buffer.readUInt16LE(8) & 0x3fff]
209
+ }
210
+ return null
211
+ }
212
+
213
+ function isWebp(bytes: Uint8Array): boolean {
214
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
215
+ if (
216
+ bytes.length < 20 ||
217
+ buffer.toString('ascii', 0, 4) !== 'RIFF' ||
218
+ buffer.readUInt32LE(4) + 8 !== bytes.length ||
219
+ buffer.toString('ascii', 8, 12) !== 'WEBP'
220
+ ) {
221
+ return false
222
+ }
223
+ let offset = 12
224
+ let dimensions: readonly [number, number] | null = null
225
+ let sawImagePayload = false
226
+ while (offset + 8 <= bytes.length) {
227
+ const type = buffer.toString('ascii', offset, offset + 4)
228
+ const length = buffer.readUInt32LE(offset + 4)
229
+ const dataStart = offset + 8
230
+ const dataEnd = dataStart + length
231
+ if (dataEnd < dataStart || dataEnd > bytes.length) return false
232
+ const current = webpDimensions(type, bytes.subarray(dataStart, dataEnd))
233
+ if (current && dimensions === null) dimensions = current
234
+ if (type === 'VP8 ' || type === 'VP8L') sawImagePayload = current !== null
235
+ offset = dataEnd + (length & 1)
236
+ }
237
+ return (
238
+ offset === bytes.length &&
239
+ dimensions !== null &&
240
+ sawImagePayload &&
241
+ dimensionsAreSafe(dimensions[0], dimensions[1])
242
+ )
243
+ }
244
+
245
+ function imageIsAdmitted(block: MCPImageBlock): boolean {
246
+ if (!isImageMediaType(block.mimeType)) return false
247
+ const bytes = decodeCanonicalBase64(block.data)
248
+ if (!bytes) return false
249
+ switch (block.mimeType) {
250
+ case 'image/png':
251
+ return isPng(bytes)
252
+ case 'image/jpeg':
253
+ return isJpeg(bytes)
254
+ case 'image/gif':
255
+ return isGif(bytes)
256
+ case 'image/webp':
257
+ return isWebp(bytes)
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Admit an MCP image batch atomically before any member becomes model input.
263
+ *
264
+ * A remote server controls the declared MIME and bytes. Canonical base64 is
265
+ * only an encoding property; the decoded value must also be a complete,
266
+ * bounded raster container whose format agrees with that declaration. One bad
267
+ * member withholds the complete batch so a model is never shown a partial
268
+ * result while the host retains every raw block for inspection.
269
+ */
270
+ export function admitMcpImageBatch(images: readonly MCPImageBlock[]): boolean {
271
+ return images.length === 0 || images.every(imageIsAdmitted)
272
+ }