@code-yeongyu/senpi 2026.7.11 → 2026.7.13

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 (267) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +9 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +56 -15
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
  7. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +76 -25
  8. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
  9. package/dist/core/resource-loader.d.ts +1 -0
  10. package/dist/core/resource-loader.d.ts.map +1 -1
  11. package/dist/core/resource-loader.js +20 -2
  12. package/dist/core/resource-loader.js.map +1 -1
  13. package/dist/modes/interactive/compaction-queue-transfer.d.ts +22 -0
  14. package/dist/modes/interactive/compaction-queue-transfer.d.ts.map +1 -0
  15. package/dist/modes/interactive/compaction-queue-transfer.js +82 -0
  16. package/dist/modes/interactive/compaction-queue-transfer.js.map +1 -0
  17. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  18. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  19. package/dist/modes/interactive/interactive-mode.js +83 -69
  20. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  21. package/node_modules/@babel/helper-string-parser/LICENSE +22 -0
  22. package/node_modules/@babel/helper-string-parser/README.md +19 -0
  23. package/node_modules/@babel/helper-string-parser/lib/index.js +295 -0
  24. package/node_modules/@babel/helper-string-parser/lib/index.js.map +1 -0
  25. package/node_modules/@babel/helper-string-parser/package.json +31 -0
  26. package/node_modules/@babel/helper-validator-identifier/LICENSE +22 -0
  27. package/node_modules/@babel/helper-validator-identifier/README.md +19 -0
  28. package/node_modules/@babel/helper-validator-identifier/lib/identifier.js +70 -0
  29. package/node_modules/@babel/helper-validator-identifier/lib/identifier.js.map +1 -0
  30. package/node_modules/@babel/helper-validator-identifier/lib/index.js +57 -0
  31. package/node_modules/@babel/helper-validator-identifier/lib/index.js.map +1 -0
  32. package/node_modules/@babel/helper-validator-identifier/lib/keyword.js +35 -0
  33. package/node_modules/@babel/helper-validator-identifier/lib/keyword.js.map +1 -0
  34. package/node_modules/@babel/helper-validator-identifier/package.json +31 -0
  35. package/node_modules/@babel/parser/LICENSE +19 -0
  36. package/node_modules/@babel/parser/README.md +19 -0
  37. package/node_modules/@babel/parser/bin/babel-parser.js +15 -0
  38. package/node_modules/@babel/parser/lib/index.js +14599 -0
  39. package/node_modules/@babel/parser/lib/index.js.map +1 -0
  40. package/node_modules/@babel/parser/package.json +50 -0
  41. package/node_modules/@babel/parser/typings/babel-parser.d.ts +262 -0
  42. package/node_modules/@babel/types/LICENSE +22 -0
  43. package/node_modules/@babel/types/README.md +19 -0
  44. package/node_modules/@babel/types/lib/asserts/assertNode.js +16 -0
  45. package/node_modules/@babel/types/lib/asserts/assertNode.js.map +1 -0
  46. package/node_modules/@babel/types/lib/asserts/generated/index.js +1251 -0
  47. package/node_modules/@babel/types/lib/asserts/generated/index.js.map +1 -0
  48. package/node_modules/@babel/types/lib/ast-types/generated/index.js +3 -0
  49. package/node_modules/@babel/types/lib/ast-types/generated/index.js.map +1 -0
  50. package/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js +18 -0
  51. package/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js.map +1 -0
  52. package/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js +31 -0
  53. package/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js.map +1 -0
  54. package/node_modules/@babel/types/lib/builders/generated/index.js +29 -0
  55. package/node_modules/@babel/types/lib/builders/generated/index.js.map +1 -0
  56. package/node_modules/@babel/types/lib/builders/generated/lowercase.js +2902 -0
  57. package/node_modules/@babel/types/lib/builders/generated/lowercase.js.map +1 -0
  58. package/node_modules/@babel/types/lib/builders/generated/uppercase.js +272 -0
  59. package/node_modules/@babel/types/lib/builders/generated/uppercase.js.map +1 -0
  60. package/node_modules/@babel/types/lib/builders/productions.js +12 -0
  61. package/node_modules/@babel/types/lib/builders/productions.js.map +1 -0
  62. package/node_modules/@babel/types/lib/builders/react/buildChildren.js +24 -0
  63. package/node_modules/@babel/types/lib/builders/react/buildChildren.js.map +1 -0
  64. package/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js +22 -0
  65. package/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js.map +1 -0
  66. package/node_modules/@babel/types/lib/builders/validateNode.js +21 -0
  67. package/node_modules/@babel/types/lib/builders/validateNode.js.map +1 -0
  68. package/node_modules/@babel/types/lib/clone/clone.js +12 -0
  69. package/node_modules/@babel/types/lib/clone/clone.js.map +1 -0
  70. package/node_modules/@babel/types/lib/clone/cloneDeep.js +12 -0
  71. package/node_modules/@babel/types/lib/clone/cloneDeep.js.map +1 -0
  72. package/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js +12 -0
  73. package/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js.map +1 -0
  74. package/node_modules/@babel/types/lib/clone/cloneNode.js +107 -0
  75. package/node_modules/@babel/types/lib/clone/cloneNode.js.map +1 -0
  76. package/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js +12 -0
  77. package/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js.map +1 -0
  78. package/node_modules/@babel/types/lib/comments/addComment.js +15 -0
  79. package/node_modules/@babel/types/lib/comments/addComment.js.map +1 -0
  80. package/node_modules/@babel/types/lib/comments/addComments.js +22 -0
  81. package/node_modules/@babel/types/lib/comments/addComments.js.map +1 -0
  82. package/node_modules/@babel/types/lib/comments/inheritInnerComments.js +12 -0
  83. package/node_modules/@babel/types/lib/comments/inheritInnerComments.js.map +1 -0
  84. package/node_modules/@babel/types/lib/comments/inheritLeadingComments.js +12 -0
  85. package/node_modules/@babel/types/lib/comments/inheritLeadingComments.js.map +1 -0
  86. package/node_modules/@babel/types/lib/comments/inheritTrailingComments.js +12 -0
  87. package/node_modules/@babel/types/lib/comments/inheritTrailingComments.js.map +1 -0
  88. package/node_modules/@babel/types/lib/comments/inheritsComments.js +17 -0
  89. package/node_modules/@babel/types/lib/comments/inheritsComments.js.map +1 -0
  90. package/node_modules/@babel/types/lib/comments/removeComments.js +15 -0
  91. package/node_modules/@babel/types/lib/comments/removeComments.js.map +1 -0
  92. package/node_modules/@babel/types/lib/constants/generated/index.js +60 -0
  93. package/node_modules/@babel/types/lib/constants/generated/index.js.map +1 -0
  94. package/node_modules/@babel/types/lib/constants/index.js +31 -0
  95. package/node_modules/@babel/types/lib/constants/index.js.map +1 -0
  96. package/node_modules/@babel/types/lib/converters/ensureBlock.js +14 -0
  97. package/node_modules/@babel/types/lib/converters/ensureBlock.js.map +1 -0
  98. package/node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js +65 -0
  99. package/node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js.map +1 -0
  100. package/node_modules/@babel/types/lib/converters/toBindingIdentifierName.js +14 -0
  101. package/node_modules/@babel/types/lib/converters/toBindingIdentifierName.js.map +1 -0
  102. package/node_modules/@babel/types/lib/converters/toBlock.js +29 -0
  103. package/node_modules/@babel/types/lib/converters/toBlock.js.map +1 -0
  104. package/node_modules/@babel/types/lib/converters/toComputedKey.js +14 -0
  105. package/node_modules/@babel/types/lib/converters/toComputedKey.js.map +1 -0
  106. package/node_modules/@babel/types/lib/converters/toExpression.js +28 -0
  107. package/node_modules/@babel/types/lib/converters/toExpression.js.map +1 -0
  108. package/node_modules/@babel/types/lib/converters/toIdentifier.js +25 -0
  109. package/node_modules/@babel/types/lib/converters/toIdentifier.js.map +1 -0
  110. package/node_modules/@babel/types/lib/converters/toKeyAlias.js +38 -0
  111. package/node_modules/@babel/types/lib/converters/toKeyAlias.js.map +1 -0
  112. package/node_modules/@babel/types/lib/converters/toSequenceExpression.js +19 -0
  113. package/node_modules/@babel/types/lib/converters/toSequenceExpression.js.map +1 -0
  114. package/node_modules/@babel/types/lib/converters/toStatement.js +39 -0
  115. package/node_modules/@babel/types/lib/converters/toStatement.js.map +1 -0
  116. package/node_modules/@babel/types/lib/converters/valueToNode.js +89 -0
  117. package/node_modules/@babel/types/lib/converters/valueToNode.js.map +1 -0
  118. package/node_modules/@babel/types/lib/definitions/core.js +1725 -0
  119. package/node_modules/@babel/types/lib/definitions/core.js.map +1 -0
  120. package/node_modules/@babel/types/lib/definitions/deprecated-aliases.js +11 -0
  121. package/node_modules/@babel/types/lib/definitions/deprecated-aliases.js.map +1 -0
  122. package/node_modules/@babel/types/lib/definitions/experimental.js +124 -0
  123. package/node_modules/@babel/types/lib/definitions/experimental.js.map +1 -0
  124. package/node_modules/@babel/types/lib/definitions/flow.js +495 -0
  125. package/node_modules/@babel/types/lib/definitions/flow.js.map +1 -0
  126. package/node_modules/@babel/types/lib/definitions/index.js +106 -0
  127. package/node_modules/@babel/types/lib/definitions/index.js.map +1 -0
  128. package/node_modules/@babel/types/lib/definitions/jsx.js +157 -0
  129. package/node_modules/@babel/types/lib/definitions/jsx.js.map +1 -0
  130. package/node_modules/@babel/types/lib/definitions/misc.js +31 -0
  131. package/node_modules/@babel/types/lib/definitions/misc.js.map +1 -0
  132. package/node_modules/@babel/types/lib/definitions/placeholders.js +27 -0
  133. package/node_modules/@babel/types/lib/definitions/placeholders.js.map +1 -0
  134. package/node_modules/@babel/types/lib/definitions/typescript.js +528 -0
  135. package/node_modules/@babel/types/lib/definitions/typescript.js.map +1 -0
  136. package/node_modules/@babel/types/lib/definitions/utils.js +296 -0
  137. package/node_modules/@babel/types/lib/definitions/utils.js.map +1 -0
  138. package/node_modules/@babel/types/lib/index-legacy.d.ts +2797 -0
  139. package/node_modules/@babel/types/lib/index.d.ts +3617 -0
  140. package/node_modules/@babel/types/lib/index.js +582 -0
  141. package/node_modules/@babel/types/lib/index.js.flow +2650 -0
  142. package/node_modules/@babel/types/lib/index.js.map +1 -0
  143. package/node_modules/@babel/types/lib/modifications/appendToMemberExpression.js +15 -0
  144. package/node_modules/@babel/types/lib/modifications/appendToMemberExpression.js.map +1 -0
  145. package/node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js +65 -0
  146. package/node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js.map +1 -0
  147. package/node_modules/@babel/types/lib/modifications/inherits.js +28 -0
  148. package/node_modules/@babel/types/lib/modifications/inherits.js.map +1 -0
  149. package/node_modules/@babel/types/lib/modifications/prependToMemberExpression.js +17 -0
  150. package/node_modules/@babel/types/lib/modifications/prependToMemberExpression.js.map +1 -0
  151. package/node_modules/@babel/types/lib/modifications/removeProperties.js +24 -0
  152. package/node_modules/@babel/types/lib/modifications/removeProperties.js.map +1 -0
  153. package/node_modules/@babel/types/lib/modifications/removePropertiesDeep.js +14 -0
  154. package/node_modules/@babel/types/lib/modifications/removePropertiesDeep.js.map +1 -0
  155. package/node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js +66 -0
  156. package/node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js.map +1 -0
  157. package/node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js +48 -0
  158. package/node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js.map +1 -0
  159. package/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js +102 -0
  160. package/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js.map +1 -0
  161. package/node_modules/@babel/types/lib/retrievers/getFunctionName.js +63 -0
  162. package/node_modules/@babel/types/lib/retrievers/getFunctionName.js.map +1 -0
  163. package/node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js +13 -0
  164. package/node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js.map +1 -0
  165. package/node_modules/@babel/types/lib/traverse/traverse.js +50 -0
  166. package/node_modules/@babel/types/lib/traverse/traverse.js.map +1 -0
  167. package/node_modules/@babel/types/lib/traverse/traverseFast.js +40 -0
  168. package/node_modules/@babel/types/lib/traverse/traverseFast.js.map +1 -0
  169. package/node_modules/@babel/types/lib/utils/deprecationWarning.js +44 -0
  170. package/node_modules/@babel/types/lib/utils/deprecationWarning.js.map +1 -0
  171. package/node_modules/@babel/types/lib/utils/inherit.js +13 -0
  172. package/node_modules/@babel/types/lib/utils/inherit.js.map +1 -0
  173. package/node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js +40 -0
  174. package/node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js.map +1 -0
  175. package/node_modules/@babel/types/lib/utils/shallowEqual.js +17 -0
  176. package/node_modules/@babel/types/lib/utils/shallowEqual.js.map +1 -0
  177. package/node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js +13 -0
  178. package/node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js.map +1 -0
  179. package/node_modules/@babel/types/lib/validators/generated/index.js +2797 -0
  180. package/node_modules/@babel/types/lib/validators/generated/index.js.map +1 -0
  181. package/node_modules/@babel/types/lib/validators/is.js +27 -0
  182. package/node_modules/@babel/types/lib/validators/is.js.map +1 -0
  183. package/node_modules/@babel/types/lib/validators/isBinding.js +27 -0
  184. package/node_modules/@babel/types/lib/validators/isBinding.js.map +1 -0
  185. package/node_modules/@babel/types/lib/validators/isBlockScoped.js +13 -0
  186. package/node_modules/@babel/types/lib/validators/isBlockScoped.js.map +1 -0
  187. package/node_modules/@babel/types/lib/validators/isImmutable.js +21 -0
  188. package/node_modules/@babel/types/lib/validators/isImmutable.js.map +1 -0
  189. package/node_modules/@babel/types/lib/validators/isLet.js +13 -0
  190. package/node_modules/@babel/types/lib/validators/isLet.js.map +1 -0
  191. package/node_modules/@babel/types/lib/validators/isNode.js +12 -0
  192. package/node_modules/@babel/types/lib/validators/isNode.js.map +1 -0
  193. package/node_modules/@babel/types/lib/validators/isNodesEquivalent.js +57 -0
  194. package/node_modules/@babel/types/lib/validators/isNodesEquivalent.js.map +1 -0
  195. package/node_modules/@babel/types/lib/validators/isPlaceholderType.js +15 -0
  196. package/node_modules/@babel/types/lib/validators/isPlaceholderType.js.map +1 -0
  197. package/node_modules/@babel/types/lib/validators/isReferenced.js +96 -0
  198. package/node_modules/@babel/types/lib/validators/isReferenced.js.map +1 -0
  199. package/node_modules/@babel/types/lib/validators/isScope.js +18 -0
  200. package/node_modules/@babel/types/lib/validators/isScope.js.map +1 -0
  201. package/node_modules/@babel/types/lib/validators/isSpecifierDefault.js +14 -0
  202. package/node_modules/@babel/types/lib/validators/isSpecifierDefault.js.map +1 -0
  203. package/node_modules/@babel/types/lib/validators/isType.js +17 -0
  204. package/node_modules/@babel/types/lib/validators/isType.js.map +1 -0
  205. package/node_modules/@babel/types/lib/validators/isValidES3Identifier.js +13 -0
  206. package/node_modules/@babel/types/lib/validators/isValidES3Identifier.js.map +1 -0
  207. package/node_modules/@babel/types/lib/validators/isValidIdentifier.js +18 -0
  208. package/node_modules/@babel/types/lib/validators/isValidIdentifier.js.map +1 -0
  209. package/node_modules/@babel/types/lib/validators/isVar.js +15 -0
  210. package/node_modules/@babel/types/lib/validators/isVar.js.map +1 -0
  211. package/node_modules/@babel/types/lib/validators/matchesPattern.js +44 -0
  212. package/node_modules/@babel/types/lib/validators/matchesPattern.js.map +1 -0
  213. package/node_modules/@babel/types/lib/validators/react/isCompatTag.js +11 -0
  214. package/node_modules/@babel/types/lib/validators/react/isCompatTag.js.map +1 -0
  215. package/node_modules/@babel/types/lib/validators/react/isReactComponent.js +11 -0
  216. package/node_modules/@babel/types/lib/validators/react/isReactComponent.js.map +1 -0
  217. package/node_modules/@babel/types/lib/validators/validate.js +42 -0
  218. package/node_modules/@babel/types/lib/validators/validate.js.map +1 -0
  219. package/node_modules/@babel/types/package.json +39 -0
  220. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +10 -2
  221. package/node_modules/@code-yeongyu/senpi-codemode/README.md +128 -78
  222. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -3
  223. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +15 -2
  224. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/reserved.ts +9 -0
  225. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +197 -0
  226. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/output-bridge.ts +96 -0
  227. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/schema-injection.ts +3 -0
  228. package/node_modules/@code-yeongyu/senpi-codemode/src/completion/handler.ts +95 -26
  229. package/node_modules/@code-yeongyu/senpi-codemode/src/completion/tool-bridge.ts +11 -2
  230. package/node_modules/@code-yeongyu/senpi-codemode/src/config/settings.ts +96 -8
  231. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +24 -3
  232. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +155 -21
  233. package/node_modules/@code-yeongyu/senpi-codemode/src/interpreters/detect.ts +1 -1
  234. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +252 -48
  235. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +263 -83
  236. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +11 -5
  237. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +108 -0
  238. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +8 -6
  239. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/rewrite-imports.ts +164 -0
  240. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +6 -1
  241. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +307 -41
  242. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +3 -2
  243. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +792 -79
  244. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +234 -21
  245. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/runner.rb +154 -51
  246. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +1 -0
  247. package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +89 -0
  248. package/node_modules/@code-yeongyu/senpi-codemode/src/output/streaming-output.ts +296 -0
  249. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +107 -30
  250. package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/bridge-timeout.ts +2 -0
  251. package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/idle-timeout.ts +8 -19
  252. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +175 -113
  253. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +130 -120
  254. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +274 -0
  255. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/json-tree.ts +247 -0
  256. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +641 -38
  257. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/status-events.ts +12 -0
  258. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +29 -1
  259. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  260. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +16 -16
  261. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  262. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +6 -6
  263. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  264. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  265. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  266. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  267. package/package.json +6 -6
@@ -1,15 +1,20 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
1
3
  import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext, ToolDefinition } from "@code-yeongyu/senpi";
2
- import type { HostToKernelMessage, KernelToHostMessage } from "../bridge/protocol.ts";
3
4
  import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
5
+ import { defaultCodemodeSettings, type ResolvedCodemodeSettings } from "../config/settings.ts";
6
+ import type { EvalExecutionTracker } from "../extension/session-manager.ts";
4
7
  import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
8
+ import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
9
+ import { IdleTimeout } from "../timeouts/idle-timeout.ts";
5
10
  import { CellHandler, type CellState } from "./cell-handler.ts";
6
- import { renderEvalCall, renderEvalResult } from "./render.ts";
11
+ import type { EvalImageResizer } from "./image.ts";
7
12
  import {
8
13
  createEvalInputSchema,
9
14
  type EnabledEvalLanguages,
15
+ type EvalInputSchema,
10
16
  type EvalKernel,
11
17
  type EvalKernelManager,
12
- type EvalKernelRunInput,
13
18
  type EvalToolDetails,
14
19
  type EvalToolInput,
15
20
  type ExecuteTool,
@@ -24,111 +29,86 @@ export interface CreateEvalToolOptions {
24
29
  readonly cellTimeoutSeconds: number;
25
30
  readonly executeTool: ExecuteTool;
26
31
  readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
32
+ readonly settings?: ResolvedCodemodeSettings;
33
+ readonly artifactsDir?: string;
34
+ readonly imageResizer?: EvalImageResizer;
35
+ readonly executionTracker?: EvalExecutionTracker;
36
+ readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
37
+ readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
38
+ /** Whether the task-tool spawn helpers (agent()/output()/<dag>) are advertised in the prompt. */
39
+ readonly spawns?: boolean;
40
+ /** Default agent name surfaced in the agent() helper docs when spawns are enabled. */
41
+ readonly spawnDefaultAgent?: string;
27
42
  }
28
43
 
29
44
  interface EvalCellInvocation {
30
45
  readonly cellId: string;
31
46
  readonly input: EvalToolInput;
32
- readonly signal: AbortSignal | undefined;
47
+ readonly signal: AbortSignal;
33
48
  readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
34
49
  readonly ctx: ExtensionContext;
35
50
  }
36
51
 
37
- type ToolReply = Extract<HostToKernelMessage, { type: "tool-reply" }>;
52
+ interface CellExecutionOptions {
53
+ readonly callerSignal: AbortSignal;
54
+ readonly cellId: string;
55
+ readonly onAbort: (error: Error) => void;
56
+ readonly timeoutMs: number;
57
+ }
38
58
 
39
59
  const INTERRUPT_DELIVERY_GRACE_MS = 100;
40
60
 
41
- class CellKernel implements EvalKernel {
42
- readonly #kernel: EvalKernel;
43
- readonly #state: CellState;
44
-
45
- constructor(kernel: EvalKernel, state: CellState) {
46
- this.#kernel = kernel;
47
- this.#state = state;
48
- }
49
-
50
- run(input: EvalKernelRunInput): Promise<Extract<KernelToHostMessage, { type: "result" }>> {
51
- return this.#kernel.run(input);
52
- }
53
-
54
- deliverToolReply(message: ToolReply): void {
55
- if (this.#state.active) this.#kernel.deliverToolReply(message);
56
- }
57
-
58
- interrupt(reason?: string): Promise<void> {
59
- return this.#kernel.interrupt(reason);
60
- }
61
-
62
- reset(): Promise<void> {
63
- return this.#kernel.reset();
64
- }
65
-
66
- close(): Promise<void> {
67
- return this.#kernel.close();
68
- }
69
- }
70
-
71
61
  class CellExecution {
72
- readonly #callerSignal: AbortSignal | undefined;
73
- readonly #timeoutMs: number;
62
+ readonly #callerSignal: AbortSignal;
74
63
  readonly #onAbort: (error: Error) => void;
75
64
  readonly #abortPromise: Promise<never>;
76
- #rejectAbort: (reason?: unknown) => void = () => {};
77
- #kernel: CellKernel | undefined;
78
- #deadline: ReturnType<typeof setTimeout> | undefined;
79
- #abortSettled = false;
65
+ readonly #watchdog: IdleTimeout;
66
+ #rejectAbort: ((reason?: unknown) => void) | undefined;
67
+ #kernel: EvalKernel | undefined;
68
+ #interruptDeadline: ReturnType<typeof setTimeout> | undefined;
80
69
  #active = true;
81
70
 
82
- constructor(callerSignal: AbortSignal | undefined, timeoutMs: number, onAbort: (error: Error) => void) {
83
- this.#callerSignal = callerSignal;
84
- this.#timeoutMs = timeoutMs;
85
- this.#onAbort = onAbort;
71
+ constructor(options: CellExecutionOptions) {
72
+ this.#callerSignal = options.callerSignal;
73
+ this.#onAbort = options.onAbort;
86
74
  this.#abortPromise = new Promise<never>((_resolve, reject) => {
87
75
  this.#rejectAbort = reject;
88
76
  });
89
- callerSignal?.addEventListener("abort", this.#handleCallerAbort, { once: true });
90
- this.startDeadline();
77
+ this.#watchdog = new IdleTimeout({
78
+ cellId: options.cellId,
79
+ timeoutMs: options.timeoutMs,
80
+ onTimeout: ({ error }) => this.#abort(error),
81
+ });
82
+ this.#callerSignal.addEventListener("abort", this.#handleCallerAbort, { once: true });
91
83
  }
92
84
 
93
- startDeadline(): void {
94
- this.clearDeadline();
95
- if (!this.#active) return;
96
- this.#deadline = setTimeout(() => {
97
- const error = new Error(`Cell timed out after ${this.#timeoutMs}ms`);
98
- error.name = "TimeoutError";
99
- this.#abort(error);
100
- }, this.#timeoutMs);
85
+ pause(): void {
86
+ this.#watchdog.pause();
101
87
  }
102
-
103
- clearDeadline(): void {
104
- if (this.#deadline === undefined) return;
105
- clearTimeout(this.#deadline);
106
- this.#deadline = undefined;
88
+ resume(): void {
89
+ this.#watchdog.resume();
107
90
  }
108
-
109
- setKernel(kernel: CellKernel): void {
91
+ setKernel(kernel: EvalKernel): void {
110
92
  this.#kernel = kernel;
111
93
  }
112
-
113
94
  cancel(reason: unknown): void {
114
95
  this.#abort(reason);
115
96
  }
116
-
117
- async wait<T>(operation: Promise<T>): Promise<T> {
118
- const guardedOperation = operation.then(
119
- (value): T | Promise<never> => (this.#active ? value : this.#abortPromise),
120
- (reason: unknown): Promise<never> => (this.#active ? Promise.reject(reason) : this.#abortPromise),
121
- );
122
- return await Promise.race([guardedOperation, this.#abortPromise]);
123
- }
124
-
125
97
  finish(): void {
126
98
  this.#active = false;
127
99
  this.#cleanup();
128
100
  }
129
101
 
102
+ async wait<Result>(operation: Promise<Result>): Promise<Result> {
103
+ const guarded = operation.then(
104
+ (value): Result | Promise<never> => (this.#active ? value : this.#abortPromise),
105
+ (reason: unknown): Promise<never> => (this.#active ? Promise.reject(reason) : this.#abortPromise),
106
+ );
107
+ return await Promise.race([guarded, this.#abortPromise]);
108
+ }
109
+
130
110
  readonly #handleCallerAbort = (): void => {
131
- this.#abort(this.#callerSignal?.reason);
111
+ this.#abort(this.#callerSignal.reason);
132
112
  };
133
113
 
134
114
  #abort(reason: unknown): void {
@@ -138,11 +118,11 @@ class CellExecution {
138
118
  const error = abortError(reason);
139
119
  this.#onAbort(error);
140
120
  const kernel = this.#kernel;
141
- if (!kernel) {
121
+ if (kernel === undefined) {
142
122
  this.#settleAbort(error);
143
123
  return;
144
124
  }
145
- this.#deadline = setTimeout(() => this.#settleAbort(error), INTERRUPT_DELIVERY_GRACE_MS);
125
+ this.#interruptDeadline = setTimeout(() => this.#settleAbort(error), INTERRUPT_DELIVERY_GRACE_MS);
146
126
  void Promise.resolve()
147
127
  .then(() => kernel.interrupt(error.message))
148
128
  .then(
@@ -152,23 +132,27 @@ class CellExecution {
152
132
  }
153
133
 
154
134
  #settleAbort(reason: unknown): void {
155
- if (this.#abortSettled) return;
156
- this.#abortSettled = true;
157
- this.clearDeadline();
158
- this.#rejectAbort(reason);
135
+ const reject = this.#rejectAbort;
136
+ if (reject === undefined) return;
137
+ this.#rejectAbort = undefined;
138
+ if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
139
+ reject(reason);
159
140
  }
160
141
 
161
142
  #cleanup(): void {
162
- this.#callerSignal?.removeEventListener("abort", this.#handleCallerAbort);
163
- this.clearDeadline();
143
+ this.#callerSignal.removeEventListener("abort", this.#handleCallerAbort);
144
+ this.#watchdog.dispose();
145
+ if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
146
+ this.#interruptDeadline = undefined;
164
147
  }
165
148
  }
166
149
 
167
- export function createEvalTool(
168
- options: CreateEvalToolOptions,
169
- ): ToolDefinition<ReturnType<typeof createEvalInputSchema>, EvalToolDetails> {
150
+ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<EvalInputSchema, EvalToolDetails> {
170
151
  const parameters = createEvalInputSchema(options.enabledLanguages);
171
- const prompt = buildEvalPrompt(options.enabledLanguages);
152
+ const prompt = buildEvalPrompt(options.enabledLanguages, {
153
+ spawns: options.spawns ?? false,
154
+ ...(options.spawnDefaultAgent === undefined ? {} : { spawnDefaultAgent: options.spawnDefaultAgent }),
155
+ });
172
156
  const languages = enabledLanguageList(options.enabledLanguages);
173
157
  return {
174
158
  name: "eval",
@@ -178,15 +162,29 @@ export function createEvalTool(
178
162
  promptGuidelines: [...prompt.promptGuidelines],
179
163
  parameters,
180
164
  executionMode: "sequential",
181
- renderCall: renderEvalCall,
182
- renderResult: renderEvalResult,
165
+ ...(options.renderers?.renderCall === undefined ? {} : { renderCall: options.renderers.renderCall }),
166
+ ...(options.renderers?.renderResult === undefined ? {} : { renderResult: options.renderers.renderResult }),
183
167
  async execute(toolCallId, params, signal, onUpdate, ctx) {
184
- if (!languages.includes(params.language)) {
185
- throw new Error(
168
+ if (options.proxyExecutor) return await options.proxyExecutor(params, signal);
169
+ if (!languages.includes(params.language))
170
+ throw new RangeError(
186
171
  `Unsupported eval language "${params.language}". Enabled languages: ${languages.join(", ")}`,
187
172
  );
188
- }
189
- return await runEvalCell(options, { cellId: toolCallId, input: params, signal, onUpdate, ctx });
173
+ options.executionTracker?.assertEvalExecutionAllowed();
174
+ const lifecycleController = new AbortController();
175
+ const combinedSignal = signal
176
+ ? AbortSignal.any([signal, lifecycleController.signal])
177
+ : lifecycleController.signal;
178
+ const execution = runEvalCell(options, {
179
+ cellId: toolCallId,
180
+ input: params,
181
+ signal: combinedSignal,
182
+ onUpdate,
183
+ ctx,
184
+ });
185
+ return options.executionTracker
186
+ ? await options.executionTracker.trackEvalExecution(execution, lifecycleController)
187
+ : await execution;
190
188
  },
191
189
  };
192
190
  }
@@ -195,75 +193,87 @@ async function runEvalCell(
195
193
  options: CreateEvalToolOptions,
196
194
  invocation: EvalCellInvocation,
197
195
  ): Promise<AgentToolResult<EvalToolDetails>> {
198
- if (invocation.signal?.aborted) throw abortError(invocation.signal.reason);
199
- const timeoutMs = Math.floor((invocation.input.timeout ?? options.cellTimeoutSeconds) * 1000);
196
+ if (invocation.signal.aborted) throw abortError(invocation.signal.reason);
197
+ const timeoutMs = Math.floor((invocation.input.timeout ?? options.cellTimeoutSeconds) * 1_000);
200
198
  const bridgeAbortController = new AbortController();
201
- const cellSignal = invocation.signal
202
- ? AbortSignal.any([invocation.signal, bridgeAbortController.signal])
203
- : bridgeAbortController.signal;
199
+ const cellSignal = AbortSignal.any([invocation.signal, bridgeAbortController.signal]);
204
200
  const bridgeContext: ExtensionContext = { ...invocation.ctx, signal: cellSignal };
205
201
  const state: CellState = {
206
- cellId: invocation.cellId,
207
- language: invocation.input.language,
208
- title: invocation.input.title,
202
+ input: invocation.input,
209
203
  signal: cellSignal,
210
204
  onUpdate: invocation.onUpdate,
211
205
  toolCalls: [],
212
- images: [],
213
206
  pendingBridgeCalls: [],
207
+ statusEvents: [],
214
208
  active: true,
215
209
  output: "",
216
210
  phase: undefined,
217
211
  durationMs: 0,
212
+ status: "pending",
218
213
  };
219
- const execution = new CellExecution(invocation.signal, timeoutMs, (error) => {
220
- state.active = false;
221
- bridgeAbortController.abort(error);
214
+ const execution = new CellExecution({
215
+ callerSignal: invocation.signal,
216
+ cellId: invocation.cellId,
217
+ onAbort: (error) => {
218
+ state.active = false;
219
+ bridgeAbortController.abort(error);
220
+ },
221
+ timeoutMs,
222
222
  });
223
223
  let handler: CellHandler | undefined;
224
224
  try {
225
225
  const acquired = await execution.wait(
226
226
  options.kernelManager.getKernel(invocation.input.language, (message) => {
227
227
  if (!state.active || !handler) return;
228
+ if (message.type === "status") {
229
+ if (message.event.op === TIMEOUT_PAUSE_OP) {
230
+ execution.pause();
231
+ return;
232
+ }
233
+ if (message.event.op === TIMEOUT_RESUME_OP) {
234
+ execution.resume();
235
+ return;
236
+ }
237
+ }
228
238
  const pending = handler.handle(message);
229
239
  void pending.catch((error: unknown) => execution.cancel(error));
230
240
  }),
231
241
  );
232
- const kernel = new CellKernel(acquired, state);
242
+ const kernel = acquired;
233
243
  execution.setKernel(kernel);
234
- execution.clearDeadline();
235
244
  handler = new CellHandler(kernel, state, {
236
245
  executeTool: options.executeTool,
237
- complete: options.complete,
246
+ settings: options.settings ?? defaultCodemodeSettings,
247
+ ...(options.complete === undefined ? {} : { complete: options.complete }),
238
248
  ctx: bridgeContext,
249
+ ...(options.artifactsDir === undefined
250
+ ? {}
251
+ : { artifactPath: join(options.artifactsDir, `eval-${randomUUID()}.log`) }),
252
+ ...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
239
253
  });
240
254
  if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
241
255
  options.kernelManager.setContext(bridgeContext);
242
256
  }
243
- if (invocation.input.reset) {
244
- execution.startDeadline();
245
- await execution.wait(kernel.reset());
246
- execution.clearDeadline();
247
- }
248
- const result = await execution.wait(
249
- kernel.run({ cellId: invocation.cellId, code: invocation.input.code, timeoutMs }),
250
- );
251
- if (result.ok && state.pendingBridgeCalls.length > 0) {
252
- execution.startDeadline();
253
- await execution.wait(Promise.all(state.pendingBridgeCalls));
254
- execution.clearDeadline();
257
+ if (invocation.input.reset) await execution.wait(kernel.reset());
258
+ const result = await execution.wait(kernel.run({ cellId: invocation.cellId, code: invocation.input.code }));
259
+ if (result.ok && state.pendingBridgeCalls.length > 0) await execution.wait(Promise.all(state.pendingBridgeCalls));
260
+ return await handler.finalize(result);
261
+ } catch (error) {
262
+ if (handler && error instanceof Error && error.name === "CodemodeSessionDisposedError") {
263
+ return await handler.finalizeCancellation(error);
255
264
  }
256
- return handler.finalize(result);
265
+ throw error;
257
266
  } finally {
258
267
  state.active = false;
259
268
  bridgeAbortController.abort();
260
269
  execution.finish();
270
+ if (handler) await handler.flushOutput();
261
271
  }
262
272
  }
263
273
 
264
274
  function abortError(reason: unknown): Error {
265
275
  if (reason instanceof Error && reason.name !== "AbortError") return reason;
266
- const error = new Error(typeof reason === "string" ? reason : "Eval interrupted");
276
+ const error = new Error(typeof reason === "string" ? reason : "Eval interrupted", { cause: reason });
267
277
  error.name = "AbortError";
268
278
  return error;
269
279
  }
@@ -0,0 +1,274 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import type { AgentToolResult, ExtensionContext } from "@code-yeongyu/senpi";
3
+ import { convertToPng, formatDimensionNote, resizeImage } from "@code-yeongyu/senpi";
4
+ import type { KernelToHostMessage } from "../bridge/protocol.ts";
5
+ import type { TruncationMeta } from "../output/output-meta.ts";
6
+ import {
7
+ artifactNotice,
8
+ DEFAULT_MAX_BYTES,
9
+ DEFAULT_MAX_LINES,
10
+ OutputSink,
11
+ type OutputSummary,
12
+ TailBuffer,
13
+ truncateTail,
14
+ } from "../output/streaming-output.ts";
15
+
16
+ const MAX_DISPLAY_TEXT_BYTES = 8_000;
17
+
18
+ export interface EvalImageContent {
19
+ readonly type: "image";
20
+ readonly data: string;
21
+ readonly mimeType: string;
22
+ }
23
+
24
+ export interface EvalImageResizeResult {
25
+ readonly image: EvalImageContent;
26
+ readonly dimensionNote?: string;
27
+ }
28
+
29
+ export type EvalImageResizer = (
30
+ image: EvalImageContent,
31
+ model: ExtensionContext["model"],
32
+ ) => Promise<EvalImageResizeResult>;
33
+
34
+ export interface EvalOutputOptions {
35
+ readonly artifactPath?: string;
36
+ readonly headBytes: number;
37
+ readonly maxColumns: number;
38
+ readonly model: ExtensionContext["model"];
39
+ readonly imageResizer?: EvalImageResizer;
40
+ readonly onChunk: (aggregateText: string, cellText: string) => void;
41
+ }
42
+
43
+ export interface EvalOutputResult {
44
+ readonly output: string;
45
+ readonly images: readonly EvalImageContent[];
46
+ readonly jsonOutputs: readonly unknown[];
47
+ readonly hasMarkdown: boolean;
48
+ readonly truncated: boolean;
49
+ readonly notice?: string;
50
+ readonly meta?: TruncationMeta;
51
+ }
52
+
53
+ type DisplayMessage = Extract<KernelToHostMessage, { type: "display" }>;
54
+ type WebpModel = { readonly provider: string; readonly api: string } | undefined;
55
+
56
+ class DisplayPayloadError extends Error {
57
+ readonly name = "DisplayPayloadError";
58
+
59
+ constructor(mimeType: string, cause: SyntaxError) {
60
+ super(`Invalid ${mimeType} display payload: ${cause.message}`, { cause });
61
+ }
62
+ }
63
+
64
+ export class EvalOutputCollector {
65
+ readonly #options: EvalOutputOptions;
66
+ readonly #sink: OutputSink;
67
+ readonly #aggregateTail = new TailBuffer(DEFAULT_MAX_BYTES * 2);
68
+ readonly #cellTail = new TailBuffer(DEFAULT_MAX_BYTES * 2);
69
+ readonly #displayImages: EvalImageContent[] = [];
70
+ readonly #images: EvalImageContent[] = [];
71
+ readonly #jsonOutputs: unknown[] = [];
72
+ #hasMarkdown = false;
73
+ #imagesProcessed = false;
74
+
75
+ constructor(options: EvalOutputOptions) {
76
+ this.#options = options;
77
+ this.#sink = new OutputSink({
78
+ artifactPath: options.artifactPath,
79
+ headBytes: options.headBytes,
80
+ maxColumns: options.maxColumns,
81
+ onChunk: (chunk) => {
82
+ this.#aggregateTail.append(chunk);
83
+ this.#cellTail.append(chunk);
84
+ options.onChunk(this.#aggregateTail.text(), this.#cellTail.text());
85
+ },
86
+ });
87
+ }
88
+
89
+ push(text: string): void {
90
+ this.#sink.push(text);
91
+ }
92
+
93
+ display(message: DisplayMessage): void {
94
+ if (message.mimeType.startsWith("image/")) {
95
+ this.#displayImages.push({ type: "image", mimeType: message.mimeType, data: message.dataBase64 });
96
+ return;
97
+ }
98
+ const text = Buffer.from(message.dataBase64, "base64").toString("utf8");
99
+ if (message.mimeType === "application/json") {
100
+ let value: unknown;
101
+ try {
102
+ value = JSON.parse(text);
103
+ } catch (error) {
104
+ if (error instanceof SyntaxError) throw new DisplayPayloadError(message.mimeType, error);
105
+ throw error;
106
+ }
107
+ this.#jsonOutputs.push(value);
108
+ this.#sink.push(`display[${this.#jsonOutputs.length}]:\n${formatDisplayJson(value)}\n`);
109
+ return;
110
+ }
111
+ if (message.mimeType === "text/markdown") this.#hasMarkdown = true;
112
+ this.#sink.push(text.endsWith("\n") ? text : `${text}\n`);
113
+ }
114
+
115
+ aggregateText(): string {
116
+ return this.#aggregateTail.text();
117
+ }
118
+
119
+ async finish(): Promise<EvalOutputResult> {
120
+ await this.#processImages();
121
+ const summary = await this.#finalSummary();
122
+ const meta = truncationMetaFromSummary(summary);
123
+ const notice = summary.artifactId === undefined ? undefined : artifactNotice(summary.artifactId);
124
+ return {
125
+ output: summary.output.trimEnd(),
126
+ images: [...this.#images],
127
+ jsonOutputs: [...this.#jsonOutputs],
128
+ hasMarkdown: this.#hasMarkdown,
129
+ truncated: summary.truncated,
130
+ ...(notice === undefined ? {} : { notice }),
131
+ ...(meta === undefined ? {} : { meta }),
132
+ };
133
+ }
134
+
135
+ async flush(): Promise<void> {
136
+ await this.#sink.dump();
137
+ }
138
+
139
+ async #processImages(): Promise<void> {
140
+ if (this.#imagesProcessed) return;
141
+ this.#imagesProcessed = true;
142
+ const resize = this.#options.imageResizer ?? resizeEvalImage;
143
+ for (const source of this.#displayImages) {
144
+ const resized = await resize(source, this.#options.model);
145
+ this.#images.push(resized.image);
146
+ const description = resized.dimensionNote ?? `[${resized.image.mimeType}]`;
147
+ this.#sink.push(`display image ${this.#images.length}: ${description}\n`);
148
+ }
149
+ }
150
+
151
+ async #finalSummary(): Promise<OutputSummary> {
152
+ const summary = await this.#sink.dump();
153
+ if (summary.truncated || summary.totalLines <= DEFAULT_MAX_LINES) return summary;
154
+ const truncated = truncateTail(summary.output, {
155
+ maxLines: DEFAULT_MAX_LINES,
156
+ maxBytes: Number.MAX_SAFE_INTEGER,
157
+ });
158
+ let artifactId = summary.artifactId;
159
+ if (artifactId === undefined && this.#options.artifactPath !== undefined) {
160
+ await writeFile(this.#options.artifactPath, this.#aggregateTail.text(), "utf8");
161
+ artifactId = this.#options.artifactPath;
162
+ }
163
+ return {
164
+ ...summary,
165
+ output: truncated.content,
166
+ truncated: true,
167
+ outputLines: truncated.outputLines,
168
+ outputBytes: truncated.outputBytes,
169
+ ...(artifactId === undefined ? {} : { artifactId }),
170
+ };
171
+ }
172
+ }
173
+
174
+ export function webpExclusionForModel(model: WebpModel): true | undefined {
175
+ if (model === undefined) return undefined;
176
+ return model.provider === "ollama" ||
177
+ model.provider === "ollama-cloud" ||
178
+ model.provider === "llama.cpp" ||
179
+ model.provider === "lm-studio" ||
180
+ model.provider === "local-server" ||
181
+ model.api === "ollama-chat"
182
+ ? true
183
+ : undefined;
184
+ }
185
+
186
+ export const resizeEvalImage: EvalImageResizer = async (image, model) => {
187
+ const excludeWebP = webpExclusionForModel(model);
188
+ const forceWebpConversion = excludeWebP === true && image.mimeType === "image/webp";
189
+ const resized = await resizeImage(
190
+ Buffer.from(image.data, "base64"),
191
+ image.mimeType,
192
+ forceWebpConversion ? { maxBytes: Buffer.byteLength(image.data, "utf8") } : undefined,
193
+ );
194
+ let output: EvalImageContent =
195
+ resized === null ? image : { type: "image", data: resized.data, mimeType: resized.mimeType };
196
+ if (excludeWebP === true && output.mimeType === "image/webp") {
197
+ const converted = await convertToPng(output.data, output.mimeType);
198
+ if (converted === null)
199
+ throw new TypeError(`Unable to convert ${output.mimeType} display output for the active model`);
200
+ output = { type: "image", data: converted.data, mimeType: converted.mimeType };
201
+ }
202
+ const dimensionNote = resized === null ? undefined : formatDimensionNote(resized);
203
+ return { image: output, ...(dimensionNote === undefined ? {} : { dimensionNote }) };
204
+ };
205
+
206
+ function formatDisplayJson(value: unknown): string {
207
+ let text: string;
208
+ try {
209
+ text = JSON.stringify(value, null, 2) ?? String(value);
210
+ } catch (error) {
211
+ if (!(error instanceof TypeError)) throw error;
212
+ text = String(value);
213
+ }
214
+ if (text.length <= MAX_DISPLAY_TEXT_BYTES) return text;
215
+ return `${text.slice(0, MAX_DISPLAY_TEXT_BYTES)}\n[…${text.length - MAX_DISPLAY_TEXT_BYTES}ch elided…]`;
216
+ }
217
+
218
+ function truncationMetaFromSummary(summary: OutputSummary): TruncationMeta | undefined {
219
+ if (!summary.truncated) return undefined;
220
+ const artifact = summary.artifactId === undefined ? {} : { artifactId: summary.artifactId };
221
+ if (summary.elidedBytes !== undefined && summary.elidedBytes > 0) {
222
+ const elidedLines = summary.elidedLines ?? Math.max(0, summary.totalLines - summary.outputLines);
223
+ const keptLines = Math.max(0, summary.outputLines - 1);
224
+ const headLines = Math.ceil(keptLines / 2);
225
+ const tailLines = keptLines - headLines;
226
+ return {
227
+ direction: "middle",
228
+ truncatedBy: "middle",
229
+ totalLines: summary.totalLines,
230
+ totalBytes: summary.totalBytes,
231
+ outputLines: summary.outputLines,
232
+ outputBytes: summary.outputBytes,
233
+ ...(headLines > 0 ? { headRange: { start: 1, end: headLines } } : {}),
234
+ ...(tailLines > 0
235
+ ? { tailRange: { start: summary.totalLines - tailLines + 1, end: summary.totalLines } }
236
+ : {}),
237
+ elidedBytes: summary.elidedBytes,
238
+ elidedLines,
239
+ ...artifact,
240
+ };
241
+ }
242
+ return {
243
+ direction: "tail",
244
+ truncatedBy: summary.outputBytes < summary.totalBytes ? "bytes" : "lines",
245
+ totalLines: summary.totalLines,
246
+ totalBytes: summary.totalBytes,
247
+ outputLines: summary.outputLines,
248
+ outputBytes: summary.outputBytes,
249
+ shownRange: { start: Math.max(1, summary.totalLines - summary.outputLines + 1), end: summary.totalLines },
250
+ ...artifact,
251
+ };
252
+ }
253
+
254
+ export function marshalToolResult(result: AgentToolResult<unknown>) {
255
+ const texts = result.content.filter((part) => part.type === "text").map((part) => part.text);
256
+ const images = result.content
257
+ .filter((part) => part.type === "image")
258
+ .map((part) => ({ mimeType: part.mimeType, dataBase64: part.data }));
259
+ const details =
260
+ typeof result.details === "object" &&
261
+ result.details !== null &&
262
+ !Array.isArray(result.details) &&
263
+ Object.keys(result.details).length === 0
264
+ ? undefined
265
+ : result.details;
266
+ const hasError = toolResultIsError(result);
267
+ const text = texts.join("\n");
268
+ return images.length === 0 && details === undefined && !hasError ? { text } : { text, details, images, hasError };
269
+ }
270
+
271
+ export function toolResultIsError(result: AgentToolResult<unknown>): boolean {
272
+ const details = result.details;
273
+ return typeof details === "object" && details !== null && "isError" in details && details.isError === true;
274
+ }