@nodetool-ai/agents 0.8.1 → 0.8.2

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 (361) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/analysis/audio-dsp.d.ts +3 -2
  3. package/dist/analysis/audio-dsp.d.ts.map +1 -1
  4. package/dist/analysis/audio-dsp.js +11 -8
  5. package/dist/analysis/audio-dsp.js.map +1 -1
  6. package/dist/analysis/media-decode.d.ts +13 -0
  7. package/dist/analysis/media-decode.d.ts.map +1 -1
  8. package/dist/analysis/media-decode.js +33 -6
  9. package/dist/analysis/media-decode.js.map +1 -1
  10. package/dist/author-graph.d.ts +2 -3
  11. package/dist/author-graph.d.ts.map +1 -1
  12. package/dist/author-graph.js +3 -5
  13. package/dist/author-graph.js.map +1 -1
  14. package/dist/browser-gate.d.ts +2 -2
  15. package/dist/browser-gate.js +2 -2
  16. package/dist/capabilities/GamePublishRequest.d.ts +14 -0
  17. package/dist/capabilities/GamePublishRequest.d.ts.map +1 -0
  18. package/dist/capabilities/GamePublishRequest.js +2 -0
  19. package/dist/capabilities/GamePublishRequest.js.map +1 -0
  20. package/dist/capabilities/adapters.d.ts +4 -13
  21. package/dist/capabilities/adapters.d.ts.map +1 -1
  22. package/dist/capabilities/adapters.js +9 -14
  23. package/dist/capabilities/adapters.js.map +1 -1
  24. package/dist/capabilities/agent-activity.d.ts +27 -0
  25. package/dist/capabilities/agent-activity.d.ts.map +1 -0
  26. package/dist/capabilities/agent-activity.js +103 -0
  27. package/dist/capabilities/agent-activity.js.map +1 -0
  28. package/dist/capabilities/agentic-storyboard-finish.d.ts +67 -0
  29. package/dist/capabilities/agentic-storyboard-finish.d.ts.map +1 -0
  30. package/dist/capabilities/agentic-storyboard-finish.js +1321 -0
  31. package/dist/capabilities/agentic-storyboard-finish.js.map +1 -0
  32. package/dist/capabilities/agents.d.ts +5 -2
  33. package/dist/capabilities/agents.d.ts.map +1 -1
  34. package/dist/capabilities/agents.js +14 -4
  35. package/dist/capabilities/agents.js.map +1 -1
  36. package/dist/capabilities/agents.specs.d.ts +3 -0
  37. package/dist/capabilities/agents.specs.d.ts.map +1 -1
  38. package/dist/capabilities/agents.specs.js +71 -1
  39. package/dist/capabilities/agents.specs.js.map +1 -1
  40. package/dist/capabilities/analysis.d.ts.map +1 -1
  41. package/dist/capabilities/analysis.js +33 -22
  42. package/dist/capabilities/analysis.js.map +1 -1
  43. package/dist/capabilities/analysis.specs.js +2 -2
  44. package/dist/capabilities/analysis.specs.js.map +1 -1
  45. package/dist/capabilities/apps.d.ts.map +1 -1
  46. package/dist/capabilities/apps.js +5 -3
  47. package/dist/capabilities/apps.js.map +1 -1
  48. package/dist/capabilities/args.d.ts +2 -5
  49. package/dist/capabilities/args.d.ts.map +1 -1
  50. package/dist/capabilities/args.js +2 -5
  51. package/dist/capabilities/args.js.map +1 -1
  52. package/dist/capabilities/browser.d.ts +1 -1
  53. package/dist/capabilities/browser.js +1 -1
  54. package/dist/capabilities/browser.specs.d.ts +1 -1
  55. package/dist/capabilities/browser.specs.js +1 -1
  56. package/dist/capabilities/code.d.ts +5 -0
  57. package/dist/capabilities/code.d.ts.map +1 -1
  58. package/dist/capabilities/code.js +128 -89
  59. package/dist/capabilities/code.js.map +1 -1
  60. package/dist/capabilities/errors.d.ts +19 -0
  61. package/dist/capabilities/errors.d.ts.map +1 -0
  62. package/dist/capabilities/errors.js +109 -0
  63. package/dist/capabilities/errors.js.map +1 -0
  64. package/dist/capabilities/errors.specs.d.ts +11 -0
  65. package/dist/capabilities/errors.specs.d.ts.map +1 -0
  66. package/dist/capabilities/errors.specs.js +100 -0
  67. package/dist/capabilities/errors.specs.js.map +1 -0
  68. package/dist/capabilities/example-timelines.d.ts.map +1 -1
  69. package/dist/capabilities/example-timelines.js +52 -10
  70. package/dist/capabilities/example-timelines.js.map +1 -1
  71. package/dist/capabilities/example-timelines.specs.d.ts +1 -0
  72. package/dist/capabilities/example-timelines.specs.d.ts.map +1 -1
  73. package/dist/capabilities/example-timelines.specs.js +21 -3
  74. package/dist/capabilities/example-timelines.specs.js.map +1 -1
  75. package/dist/capabilities/finish-storyboard.d.ts +5 -0
  76. package/dist/capabilities/finish-storyboard.d.ts.map +1 -0
  77. package/dist/capabilities/finish-storyboard.js +278 -0
  78. package/dist/capabilities/finish-storyboard.js.map +1 -0
  79. package/dist/capabilities/game-asset-source.js +1 -1
  80. package/dist/capabilities/game-asset-source.js.map +1 -1
  81. package/dist/capabilities/game.d.ts +14 -2
  82. package/dist/capabilities/game.d.ts.map +1 -1
  83. package/dist/capabilities/game.js +209 -39
  84. package/dist/capabilities/game.js.map +1 -1
  85. package/dist/capabilities/game.specs.d.ts +2 -0
  86. package/dist/capabilities/game.specs.d.ts.map +1 -1
  87. package/dist/capabilities/game.specs.js +25 -11
  88. package/dist/capabilities/game.specs.js.map +1 -1
  89. package/dist/capabilities/game3d.d.ts +17 -0
  90. package/dist/capabilities/game3d.d.ts.map +1 -0
  91. package/dist/capabilities/game3d.js +433 -0
  92. package/dist/capabilities/game3d.js.map +1 -0
  93. package/dist/capabilities/gate-from-context.d.ts +1 -1
  94. package/dist/capabilities/gate-from-context.js +1 -1
  95. package/dist/capabilities/generations.d.ts +2 -0
  96. package/dist/capabilities/generations.d.ts.map +1 -1
  97. package/dist/capabilities/generations.js +7 -2
  98. package/dist/capabilities/generations.js.map +1 -1
  99. package/dist/capabilities/index.d.ts +2 -2
  100. package/dist/capabilities/index.d.ts.map +1 -1
  101. package/dist/capabilities/index.js +2 -2
  102. package/dist/capabilities/index.js.map +1 -1
  103. package/dist/capabilities/invoke.d.ts +14 -25
  104. package/dist/capabilities/invoke.d.ts.map +1 -1
  105. package/dist/capabilities/invoke.js +72 -35
  106. package/dist/capabilities/invoke.js.map +1 -1
  107. package/dist/capabilities/jobs.d.ts.map +1 -1
  108. package/dist/capabilities/jobs.js +47 -3
  109. package/dist/capabilities/jobs.js.map +1 -1
  110. package/dist/capabilities/jobs.specs.d.ts.map +1 -1
  111. package/dist/capabilities/jobs.specs.js +3 -1
  112. package/dist/capabilities/jobs.specs.js.map +1 -1
  113. package/dist/capabilities/js-scripts.d.ts +19 -1
  114. package/dist/capabilities/js-scripts.d.ts.map +1 -1
  115. package/dist/capabilities/js-scripts.js +109 -2
  116. package/dist/capabilities/js-scripts.js.map +1 -1
  117. package/dist/capabilities/js-scripts.specs.d.ts +2 -0
  118. package/dist/capabilities/js-scripts.specs.d.ts.map +1 -1
  119. package/dist/capabilities/js-scripts.specs.js +50 -0
  120. package/dist/capabilities/js-scripts.specs.js.map +1 -1
  121. package/dist/capabilities/lazy-tool.d.ts +23 -24
  122. package/dist/capabilities/lazy-tool.d.ts.map +1 -1
  123. package/dist/capabilities/lazy-tool.js +29 -35
  124. package/dist/capabilities/lazy-tool.js.map +1 -1
  125. package/dist/capabilities/legacy-tools.d.ts +15 -0
  126. package/dist/capabilities/legacy-tools.d.ts.map +1 -0
  127. package/dist/capabilities/legacy-tools.js +98 -0
  128. package/dist/capabilities/legacy-tools.js.map +1 -0
  129. package/dist/capabilities/media.d.ts +0 -9
  130. package/dist/capabilities/media.d.ts.map +1 -1
  131. package/dist/capabilities/media.js +301 -260
  132. package/dist/capabilities/media.js.map +1 -1
  133. package/dist/capabilities/models.d.ts +2 -2
  134. package/dist/capabilities/models.d.ts.map +1 -1
  135. package/dist/capabilities/models.js +5 -3
  136. package/dist/capabilities/models.js.map +1 -1
  137. package/dist/capabilities/registry.d.ts +2 -0
  138. package/dist/capabilities/registry.d.ts.map +1 -1
  139. package/dist/capabilities/registry.js +28 -0
  140. package/dist/capabilities/registry.js.map +1 -1
  141. package/dist/capabilities/run-agent.d.ts +19 -0
  142. package/dist/capabilities/run-agent.d.ts.map +1 -0
  143. package/dist/capabilities/run-agent.js +183 -0
  144. package/dist/capabilities/run-agent.js.map +1 -0
  145. package/dist/capabilities/runs.d.ts +9 -0
  146. package/dist/capabilities/runs.d.ts.map +1 -0
  147. package/dist/capabilities/runs.js +46 -0
  148. package/dist/capabilities/runs.js.map +1 -0
  149. package/dist/capabilities/runs.specs.d.ts +39 -0
  150. package/dist/capabilities/runs.specs.d.ts.map +1 -0
  151. package/dist/capabilities/runs.specs.js +52 -0
  152. package/dist/capabilities/runs.specs.js.map +1 -0
  153. package/dist/capabilities/scripts.d.ts.map +1 -1
  154. package/dist/capabilities/scripts.js +11 -5
  155. package/dist/capabilities/scripts.js.map +1 -1
  156. package/dist/capabilities/sketches.d.ts.map +1 -1
  157. package/dist/capabilities/sketches.js +4 -2
  158. package/dist/capabilities/sketches.js.map +1 -1
  159. package/dist/capabilities/skills.d.ts.map +1 -1
  160. package/dist/capabilities/skills.js +22 -2
  161. package/dist/capabilities/skills.js.map +1 -1
  162. package/dist/capabilities/storyboard-layout-check.d.ts +33 -0
  163. package/dist/capabilities/storyboard-layout-check.d.ts.map +1 -0
  164. package/dist/capabilities/storyboard-layout-check.js +229 -0
  165. package/dist/capabilities/storyboard-layout-check.js.map +1 -0
  166. package/dist/capabilities/storyboard-measured-sources.d.ts +10 -0
  167. package/dist/capabilities/storyboard-measured-sources.d.ts.map +1 -0
  168. package/dist/capabilities/storyboard-measured-sources.js +67 -0
  169. package/dist/capabilities/storyboard-measured-sources.js.map +1 -0
  170. package/dist/capabilities/storyboard-review-times.d.ts +4 -0
  171. package/dist/capabilities/storyboard-review-times.d.ts.map +1 -0
  172. package/dist/capabilities/storyboard-review-times.js +39 -0
  173. package/dist/capabilities/storyboard-review-times.js.map +1 -0
  174. package/dist/capabilities/storyboards.d.ts +2 -18
  175. package/dist/capabilities/storyboards.d.ts.map +1 -1
  176. package/dist/capabilities/storyboards.js +315 -94
  177. package/dist/capabilities/storyboards.js.map +1 -1
  178. package/dist/capabilities/storyboards.specs.d.ts +3 -0
  179. package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
  180. package/dist/capabilities/storyboards.specs.js +42 -3
  181. package/dist/capabilities/storyboards.specs.js.map +1 -1
  182. package/dist/capabilities/timeline-bridge.d.ts +359 -0
  183. package/dist/capabilities/timeline-bridge.d.ts.map +1 -0
  184. package/dist/capabilities/timeline-bridge.js +1016 -0
  185. package/dist/capabilities/timeline-bridge.js.map +1 -0
  186. package/dist/capabilities/timeline-clip-frames.d.ts +17 -0
  187. package/dist/capabilities/timeline-clip-frames.d.ts.map +1 -0
  188. package/dist/capabilities/timeline-clip-frames.js +84 -0
  189. package/dist/capabilities/timeline-clip-frames.js.map +1 -0
  190. package/dist/capabilities/timeline-media-edit.d.ts +8 -0
  191. package/dist/capabilities/timeline-media-edit.d.ts.map +1 -0
  192. package/dist/capabilities/timeline-media-edit.js +94 -0
  193. package/dist/capabilities/timeline-media-edit.js.map +1 -0
  194. package/dist/capabilities/timeline-native-media.d.ts.map +1 -1
  195. package/dist/capabilities/timeline-native-media.js +40 -0
  196. package/dist/capabilities/timeline-native-media.js.map +1 -1
  197. package/dist/capabilities/timelines.d.ts +64 -4
  198. package/dist/capabilities/timelines.d.ts.map +1 -1
  199. package/dist/capabilities/timelines.js +566 -67
  200. package/dist/capabilities/timelines.js.map +1 -1
  201. package/dist/capabilities/timelines.specs.d.ts +8 -0
  202. package/dist/capabilities/timelines.specs.d.ts.map +1 -1
  203. package/dist/capabilities/timelines.specs.js +173 -8
  204. package/dist/capabilities/timelines.specs.js.map +1 -1
  205. package/dist/capabilities/types.d.ts +3 -1
  206. package/dist/capabilities/types.d.ts.map +1 -1
  207. package/dist/capabilities/types.js.map +1 -1
  208. package/dist/capabilities/ui.d.ts.map +1 -1
  209. package/dist/capabilities/ui.js +24 -2
  210. package/dist/capabilities/ui.js.map +1 -1
  211. package/dist/capabilities/workflows.d.ts.map +1 -1
  212. package/dist/capabilities/workflows.js +53 -26
  213. package/dist/capabilities/workflows.js.map +1 -1
  214. package/dist/capabilities/workflows.specs.d.ts.map +1 -1
  215. package/dist/capabilities/workflows.specs.js +7 -1
  216. package/dist/capabilities/workflows.specs.js.map +1 -1
  217. package/dist/codeact/action-diagnostics.d.ts.map +1 -1
  218. package/dist/codeact/action-diagnostics.js +51 -1
  219. package/dist/codeact/action-diagnostics.js.map +1 -1
  220. package/dist/codeact/codeact-executor.d.ts.map +1 -1
  221. package/dist/codeact/codeact-executor.js +16 -8
  222. package/dist/codeact/codeact-executor.js.map +1 -1
  223. package/dist/codeact/compact-ids.d.ts.map +1 -1
  224. package/dist/codeact/compact-ids.js +5 -1
  225. package/dist/codeact/compact-ids.js.map +1 -1
  226. package/dist/codeact/graph-model.d.ts +1 -1
  227. package/dist/codeact/graph-model.d.ts.map +1 -1
  228. package/dist/codeact/graph-model.js +2 -3
  229. package/dist/codeact/graph-model.js.map +1 -1
  230. package/dist/codeact/nodetool-api.d.ts +1 -1
  231. package/dist/codeact/nodetool-api.d.ts.map +1 -1
  232. package/dist/codeact/nodetool-api.js +202 -284
  233. package/dist/codeact/nodetool-api.js.map +1 -1
  234. package/dist/codeact/tool-api.d.ts +6 -3
  235. package/dist/codeact/tool-api.d.ts.map +1 -1
  236. package/dist/codeact/tool-api.js +22 -15
  237. package/dist/codeact/tool-api.js.map +1 -1
  238. package/dist/evals/codeact-api-cases.d.ts.map +1 -1
  239. package/dist/evals/codeact-api-cases.js +2 -0
  240. package/dist/evals/codeact-api-cases.js.map +1 -1
  241. package/dist/evals/codeact-api-runs.d.ts +3 -0
  242. package/dist/evals/codeact-api-runs.d.ts.map +1 -0
  243. package/dist/evals/codeact-api-runs.js +33 -0
  244. package/dist/evals/codeact-api-runs.js.map +1 -0
  245. package/dist/evals/surfaces/sketch.d.ts +2 -0
  246. package/dist/evals/surfaces/sketch.d.ts.map +1 -1
  247. package/dist/evals/surfaces/sketch.js +11 -3
  248. package/dist/evals/surfaces/sketch.js.map +1 -1
  249. package/dist/evals/surfaces/timeline.d.ts +4 -357
  250. package/dist/evals/surfaces/timeline.d.ts.map +1 -1
  251. package/dist/evals/surfaces/timeline.js +9 -2621
  252. package/dist/evals/surfaces/timeline.js.map +1 -1
  253. package/dist/evals/tool-loop-bridge.d.ts +4 -0
  254. package/dist/evals/tool-loop-bridge.d.ts.map +1 -1
  255. package/dist/evals/tool-loop-bridge.js.map +1 -1
  256. package/dist/evals/tool-loop-eval.d.ts +2 -0
  257. package/dist/evals/tool-loop-eval.d.ts.map +1 -1
  258. package/dist/evals/tool-loop-eval.js +6 -1
  259. package/dist/evals/tool-loop-eval.js.map +1 -1
  260. package/dist/evals/tool-loop-permission.d.ts +1 -15
  261. package/dist/evals/tool-loop-permission.d.ts.map +1 -1
  262. package/dist/evals/tool-loop-permission.js +23 -41
  263. package/dist/evals/tool-loop-permission.js.map +1 -1
  264. package/dist/game-authoring.d.ts +4737 -0
  265. package/dist/game-authoring.d.ts.map +1 -0
  266. package/dist/game-authoring.js +146 -0
  267. package/dist/game-authoring.js.map +1 -0
  268. package/dist/game-code-bake.d.ts +26 -0
  269. package/dist/game-code-bake.d.ts.map +1 -0
  270. package/dist/game-code-bake.js +98 -0
  271. package/dist/game-code-bake.js.map +1 -0
  272. package/dist/game-examples.d.ts +872 -16
  273. package/dist/game-examples.d.ts.map +1 -1
  274. package/dist/game-examples.js +5 -4
  275. package/dist/game-examples.js.map +1 -1
  276. package/dist/index.d.ts +15 -4
  277. package/dist/index.d.ts.map +1 -1
  278. package/dist/index.js +13 -3
  279. package/dist/index.js.map +1 -1
  280. package/dist/js-sandbox-worker/engine.d.ts +4 -0
  281. package/dist/js-sandbox-worker/engine.d.ts.map +1 -0
  282. package/dist/js-sandbox-worker/engine.js +45 -0
  283. package/dist/js-sandbox-worker/engine.js.map +1 -0
  284. package/dist/js-sandbox-worker/host.js +1 -1
  285. package/dist/js-sandbox-worker/host.js.map +1 -1
  286. package/dist/js-sandbox-worker/interpreter.d.ts +1 -1
  287. package/dist/js-sandbox-worker/interpreter.d.ts.map +1 -1
  288. package/dist/js-sandbox-worker/interpreter.js +94 -2
  289. package/dist/js-sandbox-worker/interpreter.js.map +1 -1
  290. package/dist/js-sandbox-worker/worker-entry.d.ts.map +1 -1
  291. package/dist/js-sandbox-worker/worker-entry.js +3 -2
  292. package/dist/js-sandbox-worker/worker-entry.js.map +1 -1
  293. package/dist/js-sandbox.d.ts +2 -0
  294. package/dist/js-sandbox.d.ts.map +1 -1
  295. package/dist/js-sandbox.js +56 -11
  296. package/dist/js-sandbox.js.map +1 -1
  297. package/dist/js-script-app-runner.d.ts +2 -0
  298. package/dist/js-script-app-runner.d.ts.map +1 -1
  299. package/dist/js-script-app-runner.js +8 -3
  300. package/dist/js-script-app-runner.js.map +1 -1
  301. package/dist/js-script-sandbox.d.ts +12 -18
  302. package/dist/js-script-sandbox.d.ts.map +1 -1
  303. package/dist/js-script-sandbox.js +3 -49
  304. package/dist/js-script-sandbox.js.map +1 -1
  305. package/dist/prompts/product-knowledge.d.ts +1 -1
  306. package/dist/prompts/product-knowledge.d.ts.map +1 -1
  307. package/dist/prompts/product-knowledge.js +82 -41
  308. package/dist/prompts/product-knowledge.js.map +1 -1
  309. package/dist/sandbox-pack-resolution.d.ts +21 -0
  310. package/dist/sandbox-pack-resolution.d.ts.map +1 -0
  311. package/dist/sandbox-pack-resolution.js +42 -0
  312. package/dist/sandbox-pack-resolution.js.map +1 -0
  313. package/dist/sandbox-toolbelt.d.ts +3 -1
  314. package/dist/sandbox-toolbelt.d.ts.map +1 -1
  315. package/dist/sandbox-toolbelt.js +8 -2
  316. package/dist/sandbox-toolbelt.js.map +1 -1
  317. package/dist/skill-prompt.d.ts +11 -1
  318. package/dist/skill-prompt.d.ts.map +1 -1
  319. package/dist/skill-prompt.js +17 -6
  320. package/dist/skill-prompt.js.map +1 -1
  321. package/dist/system-skills.d.ts +7 -1
  322. package/dist/system-skills.d.ts.map +1 -1
  323. package/dist/system-skills.js +2 -2
  324. package/dist/system-skills.js.map +1 -1
  325. package/dist/timeline-capture.d.ts +21 -0
  326. package/dist/timeline-capture.d.ts.map +1 -0
  327. package/dist/timeline-capture.js +564 -0
  328. package/dist/timeline-capture.js.map +1 -0
  329. package/dist/timeline-code-bake.d.ts +75 -0
  330. package/dist/timeline-code-bake.d.ts.map +1 -0
  331. package/dist/timeline-code-bake.js +327 -0
  332. package/dist/timeline-code-bake.js.map +1 -0
  333. package/dist/timeline-preview/frames.d.ts +26 -0
  334. package/dist/timeline-preview/frames.d.ts.map +1 -1
  335. package/dist/timeline-preview/frames.js +155 -70
  336. package/dist/timeline-preview/frames.js.map +1 -1
  337. package/dist/tools/base-tool.d.ts +1 -0
  338. package/dist/tools/base-tool.d.ts.map +1 -1
  339. package/dist/tools/base-tool.js +1 -1
  340. package/dist/tools/base-tool.js.map +1 -1
  341. package/dist/tools/builtin-tools.d.ts.map +1 -1
  342. package/dist/tools/builtin-tools.js +13 -0
  343. package/dist/tools/builtin-tools.js.map +1 -1
  344. package/dist/tools/external-capability-tools.d.ts +1 -1
  345. package/dist/tools/external-capability-tools.js +1 -1
  346. package/dist/tools/mcp-tool-support.d.ts +10 -9
  347. package/dist/tools/mcp-tool-support.d.ts.map +1 -1
  348. package/dist/tools/mcp-tool-support.js +14 -13
  349. package/dist/tools/mcp-tool-support.js.map +1 -1
  350. package/dist/tools/mcp-tools.d.ts +6 -4
  351. package/dist/tools/mcp-tools.d.ts.map +1 -1
  352. package/dist/tools/mcp-tools.js +65 -62
  353. package/dist/tools/mcp-tools.js.map +1 -1
  354. package/dist/tools/tool-permissions.d.ts +1 -1
  355. package/dist/tools/tool-permissions.js +1 -1
  356. package/package.json +24 -22
  357. package/dist/capabilities/gate-tools.d.ts +0 -18
  358. package/dist/capabilities/gate-tools.d.ts.map +0 -1
  359. package/dist/capabilities/gate-tools.js +0 -66
  360. package/dist/capabilities/gate-tools.js.map +0 -1
  361. package/dist/tsconfig.tests.tmp.tsbuildinfo +0 -1
@@ -22,7 +22,7 @@ import { Buffer } from "node:buffer";
22
22
  import { randomInt, randomUUID } from "node:crypto";
23
23
  import { mkdir, readFile } from "node:fs/promises";
24
24
  import path from "node:path";
25
- import { loadMediaRefBytes } from "@nodetool-ai/runtime";
25
+ import { currentGenerationReceipt, loadMediaRefBytes } from "@nodetool-ai/runtime";
26
26
  import { getMaxLocalUploadBytes } from "@nodetool-ai/storage";
27
27
  import { backgroundGenerationLimitError, startBackgroundGeneration } from "./background-generation.js";
28
28
  import { HostBinaryMissingError, clampTimeoutSeconds, mimeFromFilename, runHostBinary } from "../host-binaries.js";
@@ -31,7 +31,7 @@ import { assertFetchUrlAllowed, assertResolvedHostAllowed } from "../network-gua
31
31
  import { encodeBase64 as encodeMediaBase64 } from "../sandbox-bytes.js";
32
32
  import { imagePixelSize, sniffImageFormat } from "../sandbox-media.js";
33
33
  import { DEFAULT_MIME, MAX_MEDIA_REF_BYTES, filesystemPathForUri, mimeForRef } from "../sandbox-media-ref.js";
34
- import { MIME_TO_EXT, inferImageMime, persistOutput } from "../tools/asset-persist.js";
34
+ import { MIME_TO_EXT, inferImageMime, persistOutput, timestampedName } from "../tools/asset-persist.js";
35
35
  import { persistBinaryOutput } from "../tools/binary-output.js";
36
36
  import { extractJSON, extractJSONAllowingTruncation } from "../utils/json-parser.js";
37
37
  import { isYtDlpEnabled } from "../yt-dlp-gate.js";
@@ -267,16 +267,6 @@ async function finishOutput(context, bytes, assets, opts) {
267
267
  }
268
268
  return result;
269
269
  }
270
- /**
271
- * The asset the seam saved for a generation, read off the registry's settled
272
- * outcome. The encoded-audio paths return the provider's result rather than
273
- * the seam's, so this is how a capability learns what was persisted.
274
- */
275
- async function seamAssetFor(id) {
276
- const { generationRegistry } = await import("@nodetool-ai/runtime");
277
- const outcome = generationRegistry.outcome(id);
278
- return outcome?.asset_ids[0] ?? null;
279
- }
280
270
  /** What a `background: true` call answers with. */
281
271
  function backgroundReceipt(id, spec) {
282
272
  return {
@@ -742,6 +732,9 @@ const videoFromReferences = {
742
732
  });
743
733
  }
744
734
  };
735
+ // ---------------------------------------------------------------------------
736
+ // generate_speech
737
+ // ---------------------------------------------------------------------------
745
738
  function int16ToUint8(samples) {
746
739
  const bytes = new Uint8Array(samples.byteLength);
747
740
  const view = new DataView(bytes.buffer);
@@ -840,228 +833,244 @@ async function encodedSpeech(context, m, params, generation) {
840
833
  return await provider.textToSpeechEncoded({
841
834
  text: params.text,
842
835
  model: m.model,
843
- voice: params.voice,
844
- speed: params.speed,
836
+ voice: isString(params.voice) ? params.voice : undefined,
837
+ speed: typeof params.speed === "number" ? params.speed : undefined,
845
838
  audioFormat: params.audioFormat
846
839
  });
847
840
  }
848
841
  throw new Error("this runtime's context has no encoded text_to_speech path");
849
842
  }
850
- const generateSpeech = {
851
- spec: generateSpeechSpec,
852
- impl: async (run, params) => {
853
- const context = run.context;
854
- const m = parseModelArgs(params);
855
- if ("error" in m)
856
- return m;
857
- const text = params["text"];
858
- if (!isNonEmptyString(text))
859
- return { error: "text is required" };
860
- const outputFile = isString(params["output_file"])
861
- ? params["output_file"]
862
- : undefined;
863
- const desiredFormat = audioFormatFromOutputFile(outputFile) ?? "mp3";
864
- const generationId = randomUUID();
865
- const generation = {
866
- id: generationId,
867
- origin: { surface: "capability", tool_call_id: toolCallIdOf(params) },
868
- persist: outputFile ? { name: path.basename(outputFile) } : {}
869
- };
870
- const speechParams = {
871
- text,
872
- voice: params["voice"],
873
- speed: params["speed"],
874
- audioFormat: desiredFormat
875
- };
876
- if (params["background"] === true) {
877
- const spec = {
878
- capability: "text_to_speech",
879
- m,
880
- type: "audio",
881
- namePrefix: "generated-speech",
882
- params: speechParams
843
+ async function speechAudio(context, provider, m, params, signal, outputFile) {
844
+ signal.throwIfAborted();
845
+ const providerParams = {
846
+ text: params.text,
847
+ model: m.model,
848
+ voice: isString(params.voice) ? params.voice : undefined,
849
+ speed: typeof params.speed === "number" ? params.speed : undefined,
850
+ audioFormat: params.audioFormat,
851
+ signal
852
+ };
853
+ let encodedError;
854
+ try {
855
+ const encoded = provider
856
+ ? await provider.textToSpeechEncoded(providerParams)
857
+ : await encodedSpeech(context, m, params);
858
+ signal.throwIfAborted();
859
+ if (encoded?.data && encoded.data.length > 0) {
860
+ return {
861
+ data: encoded.data,
862
+ mimeType: encoded.mimeType ?? "audio/mpeg",
863
+ outputFile
883
864
  };
884
- const started = startBackgroundGeneration(context, () => encodedSpeech(context, m, speechParams, generation));
885
- if (!started) {
886
- return backgroundGenerationLimitError();
887
- }
888
- return backgroundReceipt(generationId, spec);
889
865
  }
890
- try {
891
- // Preferred path: ask the provider for fully-encoded audio in the
892
- // desired container (mp3/wav/flac/...). Returns null when the provider
893
- // doesn't support encoded TTS — we then fall through to streaming PCM.
894
- let audio = null;
895
- let mimeType;
896
- let outputFileFinal = outputFile;
897
- // The asset the seam saved on the encoded path, when it could.
898
- let seamAssetId = null;
899
- // Why the encoded path gave up, kept for the failure message. A provider
900
- // that only does encoded TTS (FAL, KIE) leaves the streaming method as
901
- // the base class's, which throws "<provider> does not support
902
- // textToSpeech" — so discarding this error reported the one thing that
903
- // was not wrong, and a session spent three rounds hunting for a TTS
904
- // model when the real answer was in the sentence thrown here.
905
- let encodedError;
906
- try {
907
- const encoded = await encodedSpeech(context, m, speechParams, generation);
908
- if (encoded && encoded.data) {
909
- audio = encoded.data;
910
- mimeType = encoded.mimeType;
911
- seamAssetId = await seamAssetFor(generationId);
912
- }
866
+ }
867
+ catch (error) {
868
+ if (signal.aborted || currentGenerationReceipt()?.provider_request_id) {
869
+ throw error;
870
+ }
871
+ encodedError = error;
872
+ }
873
+ if (currentGenerationReceipt()?.provider_request_id) {
874
+ throw new Error("The accepted speech request returned no audio; recover that request before submitting another.");
875
+ }
876
+ const parts = [];
877
+ let pcmOnly = true;
878
+ let mimeType;
879
+ let sampleRate;
880
+ try {
881
+ const stream = provider
882
+ ? provider.textToSpeech(providerParams)
883
+ : context.streamProviderPrediction({
884
+ provider: m.provider,
885
+ capability: "text_to_speech",
886
+ model: m.model,
887
+ params: { ...providerParams, signal }
888
+ });
889
+ for await (const item of stream) {
890
+ signal.throwIfAborted();
891
+ if (!isRecord(item)) {
892
+ continue;
913
893
  }
914
- catch (e) {
915
- encodedError = e instanceof Error ? e.message : String(e);
894
+ const chunk = item;
895
+ if (chunk.data instanceof Uint8Array || isString(chunk.data)) {
896
+ const data = chunk.data instanceof Uint8Array
897
+ ? chunk.data
898
+ : Buffer.from(chunk.data, "base64");
899
+ if (data.length > 0) {
900
+ parts.push(data);
901
+ }
902
+ if (isString(chunk.mimeType)) {
903
+ mimeType = chunk.mimeType;
904
+ }
905
+ pcmOnly = false;
916
906
  }
917
- if (!audio) {
918
- // Streaming path — provider returns either pre-encoded chunks
919
- // (carrying mimeType) or raw int16 PCM samples that we must wrap in
920
- // a WAV container before writing to disk so the file is playable.
921
- const parts = [];
922
- let pcmOnly = true;
923
- // Capture the PCM sample rate from the stream — providers emit non-24k
924
- // PCM (MiniMax 32000, Together varies). Hardcoding 24000 in the WAV
925
- // header makes those play back at the wrong speed/pitch.
926
- let pcmSampleRate;
927
- try {
928
- for await (const item of context.streamProviderPrediction({
929
- id: generationId,
930
- origin: generation.origin,
931
- provider: m.provider,
932
- capability: "text_to_speech",
933
- model: m.model,
934
- params: {
935
- text,
936
- voice: params["voice"],
937
- speed: params["speed"]
938
- }
939
- })) {
940
- const chunk = item;
941
- if (chunk.data instanceof Uint8Array) {
942
- parts.push(chunk.data);
943
- if (chunk.mimeType)
944
- mimeType = chunk.mimeType;
945
- pcmOnly = false;
946
- }
947
- else if (isString(chunk.data)) {
948
- parts.push(Buffer.from(chunk.data, "base64"));
949
- if (chunk.mimeType)
950
- mimeType = chunk.mimeType;
951
- pcmOnly = false;
952
- }
953
- else if (chunk.samples) {
954
- pcmSampleRate ??= chunk.sampleRate;
955
- parts.push(int16ToUint8(chunk.samples));
956
- }
907
+ else {
908
+ if (chunk.sampleRate !== undefined) {
909
+ if (typeof chunk.sampleRate !== "number" ||
910
+ !Number.isInteger(chunk.sampleRate) ||
911
+ chunk.sampleRate <= 0 ||
912
+ chunk.sampleRate > 0xffffffff / 2) {
913
+ throw new Error("TTS returned an invalid PCM sample rate");
957
914
  }
958
- }
959
- catch (e) {
960
- // Both paths failed. Report the encoded one first: on a provider
961
- // that only does encoded TTS the streaming error is the base class's
962
- // "does not support textToSpeech", which is true of the method and
963
- // false of the provider.
964
- if (encodedError) {
965
- throw new Error(`${encodedError} (the streaming fallback then failed too: ${e instanceof Error ? e.message : String(e)})`);
915
+ if (sampleRate !== undefined && sampleRate !== chunk.sampleRate) {
916
+ throw new Error("TTS changed PCM sample rate during the stream");
966
917
  }
967
- throw e;
918
+ sampleRate = chunk.sampleRate;
968
919
  }
969
- if (parts.length === 0) {
970
- return {
971
- error: encodedError
972
- ? `Provider returned no audio data: ${encodedError}`
973
- : "Provider returned no audio data"
974
- };
920
+ if (chunk.samples instanceof Int16Array && chunk.samples.length > 0) {
921
+ parts.push(int16ToUint8(chunk.samples));
975
922
  }
976
- const merged = concatUint8(parts);
977
- if (pcmOnly && !mimeType) {
978
- // Wrap raw PCM in WAV so the bytes are playable. Rename .mp3 →
979
- // .wav since the actual data is now WAV, not MP3. Honor the provider's
980
- // actual sample rate (defaulting to 24k only when unknown).
981
- audio = wrapPcmAsWav(merged, pcmSampleRate ?? 24000);
982
- mimeType = "audio/wav";
983
- if (outputFileFinal) {
984
- const dir = path.dirname(outputFileFinal);
985
- const base = path.basename(outputFileFinal, path.extname(outputFileFinal));
986
- outputFileFinal = path.join(dir === "." ? "" : dir, `${base}.wav`);
987
- }
923
+ }
924
+ }
925
+ }
926
+ catch (error) {
927
+ if (encodedError) {
928
+ throw new Error(`${encodedError instanceof Error ? encodedError.message : String(encodedError)} (the streaming fallback then failed too: ${error instanceof Error ? error.message : String(error)})`);
929
+ }
930
+ throw error;
931
+ }
932
+ signal.throwIfAborted();
933
+ const merged = concatUint8(parts);
934
+ if (merged.length === 0) {
935
+ throw new Error("Provider returned no audio data");
936
+ }
937
+ if (pcmOnly && !mimeType) {
938
+ if (merged.length < 2 || merged.length % 2 !== 0) {
939
+ throw new Error("TTS produced no complete audio samples");
940
+ }
941
+ const finalPath = outputFile
942
+ ? path.join(path.dirname(outputFile), `${path.basename(outputFile, path.extname(outputFile))}.wav`)
943
+ : undefined;
944
+ return {
945
+ data: wrapPcmAsWav(merged, sampleRate ?? 24000),
946
+ mimeType: "audio/wav",
947
+ outputFile: finalPath
948
+ };
949
+ }
950
+ return { data: merged, mimeType: mimeType ?? "audio/mpeg", outputFile };
951
+ }
952
+ function runAudioGeneration(context, req, spec, params, call) {
953
+ const work = async () => {
954
+ let saved;
955
+ const result = await context.runGenerationWith(req, async (provider, signal) => {
956
+ const audio = await call(provider, signal);
957
+ if (audio.outputFile && req.persist) {
958
+ req.persist.name = path.basename(audio.outputFile);
959
+ }
960
+ return audio;
961
+ }, {
962
+ finalizeOutput: async (output, assets) => {
963
+ if (!isRecord(output) ||
964
+ !(output.data instanceof Uint8Array) ||
965
+ !isString(output.mimeType)) {
966
+ throw new Error("Provider returned invalid audio output");
988
967
  }
989
- else {
990
- audio = merged;
968
+ const mime = output.mimeType;
969
+ const first = assets[0];
970
+ saved = { bytes: output.data.length, mime_type: mime };
971
+ if (first?.asset_id) {
972
+ saved.asset_id = first.asset_id;
973
+ saved.asset_uri = first.uri;
974
+ saved.url = first.uri;
991
975
  }
992
- }
993
- const mime = mimeType ?? "audio/mpeg";
994
- const persisted = await finishOutput(context, audio, seamAssetId
995
- ? [
996
- {
997
- asset_id: seamAssetId,
998
- uri: `asset://${seamAssetId}.${MIME_TO_EXT[mime] ?? "bin"}`
976
+ const outputFile = isString(output.outputFile)
977
+ ? output.outputFile
978
+ : undefined;
979
+ if (outputFile || !saved.asset_id) {
980
+ const destination = outputFile ??
981
+ timestampedName(spec.namePrefix, MIME_TO_EXT[mime] ?? "bin");
982
+ if (!context.workspace) {
983
+ return {
984
+ status: "failed",
985
+ path: destination,
986
+ error: `Could not deliver audio to "${destination}": no workspace is available.`
987
+ };
999
988
  }
1000
- ]
1001
- : [], {
1002
- namePrefix: "generated-speech",
1003
- mime,
1004
- outputFile: outputFileFinal
1005
- });
1006
- return {
1007
- type: "audio",
1008
- provider: m.provider,
1009
- model: m.model,
1010
- generation_id: generationId,
1011
- ...persisted
1012
- };
989
+ try {
990
+ await context.workspace.write(destination, output.data, mime);
991
+ }
992
+ catch (error) {
993
+ return {
994
+ status: "failed",
995
+ path: destination,
996
+ error: error instanceof Error ? error.message : String(error)
997
+ };
998
+ }
999
+ saved.path = destination;
1000
+ return { status: "completed", path: destination };
1001
+ }
1002
+ return { status: "completed" };
1003
+ }
1004
+ });
1005
+ const response = {
1006
+ type: "audio",
1007
+ provider: spec.m.provider,
1008
+ model: spec.m.model,
1009
+ generation_id: result.id,
1010
+ ...saved
1011
+ };
1012
+ if (result.delivery) {
1013
+ response.delivery = result.delivery;
1013
1014
  }
1014
- catch (e) {
1015
- return {
1016
- ...predictionError("text_to_speech", m, e),
1017
- generation_id: generationId
1018
- };
1015
+ return response;
1016
+ };
1017
+ if (params.background === true) {
1018
+ if (!startBackgroundGeneration(context, work)) {
1019
+ return backgroundGenerationLimitError();
1019
1020
  }
1021
+ return backgroundReceipt(req.id ?? "", spec);
1022
+ }
1023
+ return work().catch((error) => ({
1024
+ ...predictionError(spec.capability, spec.m, error),
1025
+ generation_id: req.id
1026
+ }));
1027
+ }
1028
+ const generateSpeech = {
1029
+ spec: generateSpeechSpec,
1030
+ impl: async (run, params) => {
1031
+ const m = parseModelArgs(params);
1032
+ if ("error" in m) {
1033
+ return m;
1034
+ }
1035
+ const text = params.text;
1036
+ if (!isNonEmptyString(text)) {
1037
+ return { error: "text is required" };
1038
+ }
1039
+ const outputFile = outputFileOf(params);
1040
+ const speechParams = {
1041
+ text,
1042
+ voice: params.voice,
1043
+ speed: params.speed,
1044
+ audioFormat: audioFormatFromOutputFile(outputFile) ?? "mp3"
1045
+ };
1046
+ const spec = {
1047
+ capability: "text_to_speech",
1048
+ m,
1049
+ type: "audio",
1050
+ namePrefix: "generated-speech",
1051
+ params: { ...speechParams }
1052
+ };
1053
+ const req = generationRequest(randomUUID(), params, spec);
1054
+ return runAudioGeneration(run.context, req, spec, params, (provider, signal) => speechAudio(run.context, provider, m, speechParams, signal, outputFile));
1020
1055
  }
1021
1056
  };
1022
- /**
1023
- * Music from a prompt — the counterpart to {@link generateSpeech}, and the
1024
- * hole every other generation capability made conspicuous.
1025
- *
1026
- * Without it, scoring a cut meant running `nodetool.audio.TextToMusic` through
1027
- * `run_node`: a one-node graph, and therefore the graph validator, a typed
1028
- * `music_model` property and a model ref to satisfy before a single second of
1029
- * audio existed. A live session spent five rounds there and shipped silent.
1030
- */
1031
1057
  const generateMusic = {
1032
1058
  spec: generateMusicSpec,
1033
1059
  impl: async (run, params) => {
1034
- const context = run.context;
1035
1060
  const m = parseModelArgs(params);
1036
- if ("error" in m)
1061
+ if ("error" in m) {
1037
1062
  return m;
1038
- const prompt = params["prompt"];
1039
- if (!isNonEmptyString(prompt))
1063
+ }
1064
+ const prompt = params.prompt;
1065
+ if (!isNonEmptyString(prompt)) {
1040
1066
  return { error: "prompt is required" };
1041
- if (!isFunction(context.textToMusic)) {
1042
- return {
1043
- error: "this runtime's context has no text_to_music path"
1044
- };
1045
1067
  }
1046
- const outputFile = isString(params["output_file"])
1047
- ? params["output_file"]
1048
- : undefined;
1049
- const desiredFormat = audioFormatFromOutputFile(outputFile) ?? "mp3";
1050
- const generationId = randomUUID();
1068
+ const outputFile = outputFileOf(params);
1051
1069
  const musicParams = {
1052
1070
  prompt,
1053
- lyrics: params["lyrics"],
1071
+ lyrics: params.lyrics,
1054
1072
  duration_seconds: aliased(params, "duration_seconds", "durationSeconds", "duration"),
1055
- audioFormat: desiredFormat
1056
- };
1057
- const req = {
1058
- id: generationId,
1059
- provider: m.provider,
1060
- capability: "text_to_music",
1061
- model: m.model,
1062
- params: musicParams,
1063
- origin: { surface: "capability", tool_call_id: toolCallIdOf(params) },
1064
- persist: outputFile ? { name: path.basename(outputFile) } : {}
1073
+ audioFormat: audioFormatFromOutputFile(outputFile) ?? "mp3"
1065
1074
  };
1066
1075
  const spec = {
1067
1076
  capability: "text_to_music",
@@ -1070,45 +1079,31 @@ const generateMusic = {
1070
1079
  namePrefix: "generated-music",
1071
1080
  params: musicParams
1072
1081
  };
1073
- if (params["background"] === true) {
1074
- const started = startBackgroundGeneration(context, () => context.textToMusic(req));
1075
- if (!started) {
1076
- return backgroundGenerationLimitError();
1077
- }
1078
- return backgroundReceipt(generationId, spec);
1079
- }
1080
- try {
1081
- const encoded = await context.textToMusic(req);
1082
+ const req = generationRequest(randomUUID(), params, spec);
1083
+ return runAudioGeneration(run.context, req, spec, params, async (provider, signal) => {
1084
+ signal.throwIfAborted();
1085
+ const encoded = provider
1086
+ ? await provider.textToMusic({
1087
+ prompt,
1088
+ model: { id: m.model, name: m.model, provider: m.provider },
1089
+ lyrics: isString(params.lyrics) ? params.lyrics : undefined,
1090
+ durationSeconds: typeof musicParams.duration_seconds === "number"
1091
+ ? musicParams.duration_seconds
1092
+ : undefined,
1093
+ audioFormat: musicParams.audioFormat,
1094
+ signal
1095
+ })
1096
+ : await run.context.textToMusic(req);
1097
+ signal.throwIfAborted();
1082
1098
  if (!encoded?.data || encoded.data.length === 0) {
1083
- return {
1084
- error: "Provider returned no audio data",
1085
- generation_id: generationId
1086
- };
1099
+ throw new Error("Provider returned no audio data");
1087
1100
  }
1088
- const mime = encoded.mimeType ?? "audio/mpeg";
1089
- const seamAssetId = await seamAssetFor(generationId);
1090
- const persisted = await finishOutput(context, encoded.data, seamAssetId
1091
- ? [
1092
- {
1093
- asset_id: seamAssetId,
1094
- uri: `asset://${seamAssetId}.${MIME_TO_EXT[mime] ?? "bin"}`
1095
- }
1096
- ]
1097
- : [], { namePrefix: "generated-music", mime, outputFile });
1098
1101
  return {
1099
- type: "audio",
1100
- provider: m.provider,
1101
- model: m.model,
1102
- generation_id: generationId,
1103
- ...persisted
1102
+ data: encoded.data,
1103
+ mimeType: encoded.mimeType ?? "audio/mpeg",
1104
+ outputFile
1104
1105
  };
1105
- }
1106
- catch (e) {
1107
- return {
1108
- ...predictionError("text_to_music", m, e),
1109
- generation_id: generationId
1110
- };
1111
- }
1106
+ });
1112
1107
  }
1113
1108
  };
1114
1109
  const transcribeAudio = {
@@ -1244,7 +1239,7 @@ function messageText(message) {
1244
1239
  }
1245
1240
  return "";
1246
1241
  }
1247
- async function judgeCall(context, m, content, maxTokens = JUDGE_MAX_TOKENS) {
1242
+ async function judgeCall(context, m, content, maxTokens = JUDGE_MAX_TOKENS, requiredImages) {
1248
1243
  const result = (await context.runProviderPrediction({
1249
1244
  provider: m.provider,
1250
1245
  capability: "generate_message",
@@ -1252,7 +1247,10 @@ async function judgeCall(context, m, content, maxTokens = JUDGE_MAX_TOKENS) {
1252
1247
  params: {
1253
1248
  messages: [{ role: "user", content }],
1254
1249
  max_tokens: maxTokens,
1255
- temperature: 0
1250
+ temperature: 0,
1251
+ strict_image_evidence: content.some((part) => part.type === "image_url") ||
1252
+ Boolean(requiredImages?.length),
1253
+ required_images: requiredImages?.map(normalizeMediaSource)
1256
1254
  }
1257
1255
  }));
1258
1256
  return messageText(result);
@@ -1403,11 +1401,7 @@ const compareImages = {
1403
1401
  `\n\nRespond with JSON only: {"winner": 1 | 2, "reason": "one sentence"}`;
1404
1402
  const judgePair = async (a, b) => {
1405
1403
  const call = async (first, second) => {
1406
- const text = await judgeCall(context, m, [
1407
- textPart(prompt),
1408
- imagePart(first),
1409
- imagePart(second)
1410
- ]);
1404
+ const text = await judgeCall(context, m, [textPart(prompt), imagePart(first), imagePart(second)], JUDGE_MAX_TOKENS, images.map(String));
1411
1405
  const verdict = parsePairVerdict(text);
1412
1406
  if (!verdict) {
1413
1407
  throw new Error(`Judge did not return parseable JSON: ${text.slice(0, 200)}`);
@@ -1502,7 +1496,7 @@ const scoreImageAdherence = {
1502
1496
  ` visually checkable.` +
1503
1497
  `\n\nBrief:\n${brief}` +
1504
1498
  `\n\nRespond with JSON only: {"questions": ["..."]}`)
1505
- ]);
1499
+ ], JUDGE_MAX_TOKENS, [image]);
1506
1500
  const parsed = extractJSON(decomposeText);
1507
1501
  const list = isRecord(parsed) ? parsed["questions"] : parsed;
1508
1502
  questions = Array.isArray(list) ? list.map(String).filter(Boolean) : [];
@@ -1513,36 +1507,82 @@ const scoreImageAdherence = {
1513
1507
  }
1514
1508
  }
1515
1509
  questions = questions.slice(0, MAX_ADHERENCE_QUESTIONS);
1510
+ const checks = questions.map((question, i) => ({
1511
+ id: `q${i + 1}`,
1512
+ question
1513
+ }));
1516
1514
  const answerText = await judgeCall(context, m, [
1517
1515
  textPart(`Answer each question about the image with a strict yes or no. "yes" only if` +
1518
1516
  ` the image clearly satisfies it; when unsure, answer "no" and say why in the note.` +
1519
- `\n\nQuestions:\n${questions.map((q, i) => `${i + 1}. ${q}`).join("\n")}` +
1517
+ `\n\nQuestions:\n${checks.map((q) => `${q.id}: ${q.question}`).join("\n")}` +
1520
1518
  `\n\nRespond with JSON only:` +
1521
- `\n{"answers": [{"question": "...", "answer": "yes" | "no", "note": "..."}]}`),
1519
+ `\n{"answers": [{"id": "q1", "question": "...", "answer": "yes" | "no", "note": "..."}]}`),
1522
1520
  imagePart(image)
1523
1521
  ]);
1524
1522
  const parsed = extractJSON(answerText);
1525
1523
  const rawAnswers = isRecord(parsed) ? parsed["answers"] : null;
1526
1524
  if (!Array.isArray(rawAnswers) || rawAnswers.length === 0) {
1527
1525
  return {
1528
- error: `Judge did not return parseable answers: ${answerText.slice(0, 300)}`
1526
+ error: `Judge did not return parseable answers: ${answerText.slice(0, 300)}`,
1527
+ evidence_status: "invalid",
1528
+ total: checks.length,
1529
+ missing_ids: checks.map((check) => check.id)
1529
1530
  };
1530
1531
  }
1531
- const answers = rawAnswers
1532
- .filter(isObjectLike)
1533
- .map((a) => ({
1534
- question: String(a["question"] ?? ""),
1535
- answer: a["answer"] === "yes" ? "yes" : "no",
1536
- note: String(a["note"] ?? "")
1537
- }));
1532
+ const byId = new Map();
1533
+ const invalid = [];
1534
+ for (const raw of rawAnswers) {
1535
+ if (!isRecord(raw)) {
1536
+ invalid.push("answer must be an object");
1537
+ continue;
1538
+ }
1539
+ const id = String(raw["id"] ?? "");
1540
+ const check = checks.find((c) => c.id === id);
1541
+ if (!check || raw["question"] !== check.question) {
1542
+ invalid.push(`unrequested or substituted check: ${id}`);
1543
+ continue;
1544
+ }
1545
+ if (byId.has(id)) {
1546
+ invalid.push(`duplicate check: ${id}`);
1547
+ continue;
1548
+ }
1549
+ if (raw["answer"] !== "yes" && raw["answer"] !== "no") {
1550
+ invalid.push(`invalid answer: ${id}`);
1551
+ continue;
1552
+ }
1553
+ byId.set(id, {
1554
+ id,
1555
+ question: check.question,
1556
+ answer: raw["answer"],
1557
+ note: String(raw["note"] ?? "")
1558
+ });
1559
+ }
1560
+ const missingIds = checks.filter((c) => !byId.has(c.id)).map((c) => c.id);
1561
+ if (invalid.length || missingIds.length) {
1562
+ return {
1563
+ error: "Judge returned invalid or incomplete adherence evidence",
1564
+ evidence_status: "invalid",
1565
+ total: checks.length,
1566
+ missing_ids: missingIds,
1567
+ invalid
1568
+ };
1569
+ }
1570
+ const answers = [];
1571
+ for (const check of checks) {
1572
+ const answer = byId.get(check.id);
1573
+ if (answer) {
1574
+ answers.push(answer);
1575
+ }
1576
+ }
1538
1577
  const passed = answers.filter((a) => a.answer === "yes").length;
1539
1578
  return {
1540
1579
  type: "adherence",
1580
+ evidence_status: "complete",
1541
1581
  provider: m.provider,
1542
1582
  model: m.model,
1543
- score: answers.length ? passed / answers.length : 0,
1583
+ score: passed / checks.length,
1544
1584
  passed,
1545
- total: answers.length,
1585
+ total: checks.length,
1546
1586
  failed: answers.filter((a) => a.answer === "no"),
1547
1587
  answers
1548
1588
  };
@@ -1810,7 +1850,8 @@ async function stageInputs(context, workspace, cwd, raw) {
1810
1850
  const localAssetLimit = workspace.localDir
1811
1851
  ? getMaxLocalUploadBytes()
1812
1852
  : undefined;
1813
- const hasLocalAsset = localAssetLimit !== undefined && entries.some(([, ref]) => isNonBlankString(ref) && ref.trim().startsWith("asset://"));
1853
+ const hasLocalAsset = localAssetLimit !== undefined &&
1854
+ entries.some(([, ref]) => isNonBlankString(ref) && ref.trim().startsWith("asset://"));
1814
1855
  const totalLimit = hasLocalAsset
1815
1856
  ? Math.max(MAX_STAGED_TOTAL_BYTES, localAssetLimit ?? 0)
1816
1857
  : MAX_STAGED_TOTAL_BYTES;