@ai-setting/roy-agent-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/dist/index.js +99145 -0
  2. package/package.json +114 -0
  3. package/src/config/config-component.test.ts +627 -0
  4. package/src/config/config-component.ts +906 -0
  5. package/src/config/config-parser.test.ts +319 -0
  6. package/src/config/config-parser.ts +203 -0
  7. package/src/config/decentralized-config.test.ts +740 -0
  8. package/src/config/env-key.ts +210 -0
  9. package/src/config/env-source.test.ts +252 -0
  10. package/src/config/env-source.ts +301 -0
  11. package/src/config/file-source.test.ts +357 -0
  12. package/src/config/file-source.ts +421 -0
  13. package/src/config/index.ts +24 -0
  14. package/src/config/protocol-resolver.test.ts +217 -0
  15. package/src/config/protocol-resolver.ts +228 -0
  16. package/src/env/agent/agent-component.abort.test.ts +511 -0
  17. package/src/env/agent/agent-component.record-session.test.ts +349 -0
  18. package/src/env/agent/agent-component.test.ts +1389 -0
  19. package/src/env/agent/agent-component.tool-error.test.ts +327 -0
  20. package/src/env/agent/agent-component.ts +1711 -0
  21. package/src/env/agent/agent-config-registration.test.ts +226 -0
  22. package/src/env/agent/agent-config-registration.ts +46 -0
  23. package/src/env/agent/agent-reminder-plugin.integration.test.ts +243 -0
  24. package/src/env/agent/index.ts +10 -0
  25. package/src/env/agent/summary-agent.parse-hint.test.ts +360 -0
  26. package/src/env/agent/summary-agent.ts +508 -0
  27. package/src/env/agent/types.ts +536 -0
  28. package/src/env/commands/commands-component.test.ts +364 -0
  29. package/src/env/commands/commands-component.ts +604 -0
  30. package/src/env/commands/commands-config-registration.test.ts +198 -0
  31. package/src/env/commands/commands-config-registration.ts +38 -0
  32. package/src/env/commands/index.ts +21 -0
  33. package/src/env/commands/parser.test.ts +203 -0
  34. package/src/env/commands/parser.ts +115 -0
  35. package/src/env/commands/types.ts +184 -0
  36. package/src/env/commands-prompt-integration.test.ts +243 -0
  37. package/src/env/component-env.test.ts +119 -0
  38. package/src/env/component.ts +335 -0
  39. package/src/env/constants.test.ts +72 -0
  40. package/src/env/constants.ts +123 -0
  41. package/src/env/debug/debug-component.test.ts +114 -0
  42. package/src/env/debug/debug-component.ts +547 -0
  43. package/src/env/debug/formatters/index.ts +9 -0
  44. package/src/env/debug/formatters/repl-formatter.test.ts +139 -0
  45. package/src/env/debug/formatters/repl-formatter.ts +358 -0
  46. package/src/env/debug/formatters/trace-formatter.test.ts +119 -0
  47. package/src/env/debug/formatters/trace-formatter.ts +191 -0
  48. package/src/env/debug/formatters/tree-formatter.test.ts +107 -0
  49. package/src/env/debug/formatters/tree-formatter.ts +325 -0
  50. package/src/env/debug/index.ts +38 -0
  51. package/src/env/debug/parser/regex-parser.test.ts +201 -0
  52. package/src/env/debug/parser/regex-parser.ts +196 -0
  53. package/src/env/debug/parser/span-builder.test.ts +241 -0
  54. package/src/env/debug/parser/span-builder.ts +386 -0
  55. package/src/env/debug/reader/log-reader.test.ts +170 -0
  56. package/src/env/debug/reader/log-reader.ts +186 -0
  57. package/src/env/debug/reader/span-db-reader.test.ts +118 -0
  58. package/src/env/debug/reader/span-db-reader.ts +201 -0
  59. package/src/env/debug/types.test.ts +187 -0
  60. package/src/env/debug/types.ts +171 -0
  61. package/src/env/environment-init.test.ts +183 -0
  62. package/src/env/environment-lifecycle.test.ts +516 -0
  63. package/src/env/environment-service.test.ts +332 -0
  64. package/src/env/environment.handle-query.test.ts +96 -0
  65. package/src/env/environment.test.ts +232 -0
  66. package/src/env/environment.ts +708 -0
  67. package/src/env/errors.test.ts +165 -0
  68. package/src/env/errors.ts +157 -0
  69. package/src/env/event-source/event-source-agent-handler.test.ts +193 -0
  70. package/src/env/event-source/event-source-agent-handler.ts +111 -0
  71. package/src/env/event-source/event-source-component.process-cleanup.test.ts +236 -0
  72. package/src/env/event-source/event-source-component.stop.test.ts +346 -0
  73. package/src/env/event-source/event-source-component.test.ts +1207 -0
  74. package/src/env/event-source/event-source-component.ts +1379 -0
  75. package/src/env/event-source/event-source-config-registration.test.ts +242 -0
  76. package/src/env/event-source/event-source-config-registration.ts +37 -0
  77. package/src/env/event-source/event-source-integration.test.ts +320 -0
  78. package/src/env/event-source/event-source-platform.test.ts +630 -0
  79. package/src/env/event-source/types.ts +298 -0
  80. package/src/env/hook/global-hook-manager.ts +162 -0
  81. package/src/env/hook/hook-manager.test.ts +374 -0
  82. package/src/env/hook/hook-manager.ts +309 -0
  83. package/src/env/hook/index.ts +38 -0
  84. package/src/env/hook/types.ts +138 -0
  85. package/src/env/index.ts +144 -0
  86. package/src/env/interface.ts +203 -0
  87. package/src/env/llm/hooks.test.ts +293 -0
  88. package/src/env/llm/hooks.ts +316 -0
  89. package/src/env/llm/index.ts +61 -0
  90. package/src/env/llm/invoke-threshold-check.test.ts +88 -0
  91. package/src/env/llm/invoke-timeout.test.ts +54 -0
  92. package/src/env/llm/invoke.test.ts +71 -0
  93. package/src/env/llm/invoke.ts +1039 -0
  94. package/src/env/llm/llm-config.test.ts +523 -0
  95. package/src/env/llm/llm.test.ts +233 -0
  96. package/src/env/llm/llm.ts +568 -0
  97. package/src/env/llm/provider.test.ts +182 -0
  98. package/src/env/llm/provider.ts +108 -0
  99. package/src/env/llm/transform.test.ts +251 -0
  100. package/src/env/llm/transform.ts +286 -0
  101. package/src/env/llm/types.test.ts +580 -0
  102. package/src/env/llm/types.ts +424 -0
  103. package/src/env/log-trace/decorator-otel.test.ts +182 -0
  104. package/src/env/log-trace/decorator.ts +230 -0
  105. package/src/env/log-trace/index.ts +79 -0
  106. package/src/env/log-trace/log-trace-component.test.ts +242 -0
  107. package/src/env/log-trace/log-trace-component.ts +497 -0
  108. package/src/env/log-trace/log-trace-config-registration.test.ts +348 -0
  109. package/src/env/log-trace/log-trace-config-registration.ts +45 -0
  110. package/src/env/log-trace/logger.test.ts +149 -0
  111. package/src/env/log-trace/logger.ts +522 -0
  112. package/src/env/log-trace/opentelemetry/cli-propagation.test.ts +147 -0
  113. package/src/env/log-trace/opentelemetry/cli-propagation.ts +194 -0
  114. package/src/env/log-trace/opentelemetry/integration.test.ts +668 -0
  115. package/src/env/log-trace/opentelemetry/mod.ts +25 -0
  116. package/src/env/log-trace/opentelemetry/propagation-env.test.ts +181 -0
  117. package/src/env/log-trace/opentelemetry/propagation-env.ts +136 -0
  118. package/src/env/log-trace/opentelemetry/propagation.test.ts +259 -0
  119. package/src/env/log-trace/opentelemetry/propagation.ts +215 -0
  120. package/src/env/log-trace/opentelemetry/tracer-provider-context.test.ts +166 -0
  121. package/src/env/log-trace/opentelemetry/tracer-provider.test.ts +379 -0
  122. package/src/env/log-trace/opentelemetry/tracer-provider.ts +612 -0
  123. package/src/env/log-trace/span-storage.test.ts +145 -0
  124. package/src/env/log-trace/span-storage.ts +230 -0
  125. package/src/env/log-trace/trace-context.test.ts +187 -0
  126. package/src/env/log-trace/trace-context.ts +162 -0
  127. package/src/env/log-trace/types.test.ts +63 -0
  128. package/src/env/log-trace/types.ts +172 -0
  129. package/src/env/mcp/README.md +244 -0
  130. package/src/env/mcp/__integration__/mcp-component.integration.test.ts +373 -0
  131. package/src/env/mcp/config.test.ts +74 -0
  132. package/src/env/mcp/config.ts +116 -0
  133. package/src/env/mcp/index.ts +41 -0
  134. package/src/env/mcp/loader.test.ts +161 -0
  135. package/src/env/mcp/loader.ts +209 -0
  136. package/src/env/mcp/mcp-component.test.ts +111 -0
  137. package/src/env/mcp/mcp-component.ts +358 -0
  138. package/src/env/mcp/mcp-config-registration.test.ts +304 -0
  139. package/src/env/mcp/mcp-config-registration.ts +50 -0
  140. package/src/env/mcp/scanner.test.ts +170 -0
  141. package/src/env/mcp/scanner.ts +246 -0
  142. package/src/env/mcp/tool/adapter.test.ts +520 -0
  143. package/src/env/mcp/tool/adapter.ts +521 -0
  144. package/src/env/mcp/tool/index.ts +5 -0
  145. package/src/env/mcp/types.test.ts +171 -0
  146. package/src/env/mcp/types.ts +79 -0
  147. package/src/env/memory/README.md +177 -0
  148. package/src/env/memory/built-in/index.ts +59 -0
  149. package/src/env/memory/built-in/recall-memory.ts +103 -0
  150. package/src/env/memory/built-in/record-memory.ts +148 -0
  151. package/src/env/memory/index.ts +20 -0
  152. package/src/env/memory/memory-component.test.ts +239 -0
  153. package/src/env/memory/memory-component.ts +503 -0
  154. package/src/env/memory/memory-config-registration.test.ts +67 -0
  155. package/src/env/memory/memory-config-registration.ts +48 -0
  156. package/src/env/memory/memory-config.ts +45 -0
  157. package/src/env/memory/memory-file.test.ts +268 -0
  158. package/src/env/memory/plugin/index.ts +48 -0
  159. package/src/env/memory/plugin/memory-agent.test.ts +249 -0
  160. package/src/env/memory/plugin/memory-agent.ts +365 -0
  161. package/src/env/memory/plugin/memory-manager.ts +198 -0
  162. package/src/env/memory/plugin/memory-plugin-agent.test.ts +145 -0
  163. package/src/env/memory/plugin/memory-plugin.ts +210 -0
  164. package/src/env/memory/plugin/plugin-simplified.test.ts +51 -0
  165. package/src/env/memory/plugin/recall-memory.test.ts +106 -0
  166. package/src/env/memory/plugin/recall-memory.ts +53 -0
  167. package/src/env/memory/plugin/types.ts +101 -0
  168. package/src/env/memory/tools/memory-agent-tools.ts +228 -0
  169. package/src/env/memory/types.ts +85 -0
  170. package/src/env/paths.ts +118 -0
  171. package/src/env/prompt/index.ts +18 -0
  172. package/src/env/prompt/memory-prompts.test.ts +91 -0
  173. package/src/env/prompt/prompt-component.test.ts +491 -0
  174. package/src/env/prompt/prompt-component.ts +619 -0
  175. package/src/env/prompt/prompt-config-registration.test.ts +213 -0
  176. package/src/env/prompt/prompt-config-registration.ts +39 -0
  177. package/src/env/prompt/prompts-index.ts +504 -0
  178. package/src/env/prompt/renderer.ts +67 -0
  179. package/src/env/prompt/types.ts +136 -0
  180. package/src/env/session/hooks.ts +18 -0
  181. package/src/env/session/index.ts +37 -0
  182. package/src/env/session/search-query-parser.test.ts +425 -0
  183. package/src/env/session/search-query-parser.ts +171 -0
  184. package/src/env/session/session-checkpoint.test.ts +523 -0
  185. package/src/env/session/session-component.extract-recent-messages.test.ts +209 -0
  186. package/src/env/session/session-component.test.ts +132 -0
  187. package/src/env/session/session-component.ts +1249 -0
  188. package/src/env/session/session-config-registration.test.ts +138 -0
  189. package/src/env/session/session-config-registration.ts +52 -0
  190. package/src/env/session/session-message-converter.test.ts +763 -0
  191. package/src/env/session/session-message-converter.ts +415 -0
  192. package/src/env/session/session-message-e2e.test.ts +448 -0
  193. package/src/env/session/session-search.test.ts +391 -0
  194. package/src/env/session/session-store.test.ts +362 -0
  195. package/src/env/session/session-store.ts +141 -0
  196. package/src/env/session/storage/index.ts +6 -0
  197. package/src/env/session/storage/memory.ts +502 -0
  198. package/src/env/session/storage/sqlite.ts +794 -0
  199. package/src/env/session/types.ts +742 -0
  200. package/src/env/skill/config.ts +39 -0
  201. package/src/env/skill/index.ts +6 -0
  202. package/src/env/skill/parser.test.ts +116 -0
  203. package/src/env/skill/parser.ts +77 -0
  204. package/src/env/skill/scanner.test.ts +211 -0
  205. package/src/env/skill/scanner.ts +119 -0
  206. package/src/env/skill/skill-component.test.ts +234 -0
  207. package/src/env/skill/skill-component.ts +352 -0
  208. package/src/env/skill/skill-config-registration.test.ts +60 -0
  209. package/src/env/skill/skill-config-registration.ts +43 -0
  210. package/src/env/skill/tool/index.ts +1 -0
  211. package/src/env/skill/tool/skill-tool.test.ts +100 -0
  212. package/src/env/skill/tool/skill-tool.ts +72 -0
  213. package/src/env/skill/types.ts +64 -0
  214. package/src/env/task/delegate/delegate-tool.test.ts +498 -0
  215. package/src/env/task/delegate/delegate-tool.ts +1014 -0
  216. package/src/env/task/delegate/index.ts +18 -0
  217. package/src/env/task/delegate/stop-tool.test.ts +140 -0
  218. package/src/env/task/delegate/stop-tool.ts +119 -0
  219. package/src/env/task/delegate/task-events.test.ts +178 -0
  220. package/src/env/task/delegate/task-events.ts +143 -0
  221. package/src/env/task/hooks/contexts.test.ts +92 -0
  222. package/src/env/task/hooks/contexts.ts +192 -0
  223. package/src/env/task/hooks/index.ts +23 -0
  224. package/src/env/task/hooks/task-hook-points.test.ts +32 -0
  225. package/src/env/task/hooks/task-hook-points.ts +54 -0
  226. package/src/env/task/index.ts +7 -0
  227. package/src/env/task/plugins/index.ts +13 -0
  228. package/src/env/task/plugins/task-plugin.test.ts +74 -0
  229. package/src/env/task/plugins/task-plugin.ts +89 -0
  230. package/src/env/task/plugins/task-tag-plugin.test.ts +377 -0
  231. package/src/env/task/plugins/task-tag-plugin.ts +319 -0
  232. package/src/env/task/plugins/task-workflow-extractor.integration.test.ts +226 -0
  233. package/src/env/task/plugins/workflow-extractor-agent.test.ts +107 -0
  234. package/src/env/task/plugins/workflow-extractor-agent.ts +225 -0
  235. package/src/env/task/storage/index.ts +6 -0
  236. package/src/env/task/storage/sqlite-task-store.test.ts +283 -0
  237. package/src/env/task/storage/sqlite-task-store.ts +903 -0
  238. package/src/env/task/storage/task-search.test.ts +291 -0
  239. package/src/env/task/tag-service.test.ts +198 -0
  240. package/src/env/task/tag-service.ts +264 -0
  241. package/src/env/task/task-component.test.ts +193 -0
  242. package/src/env/task/task-component.ts +658 -0
  243. package/src/env/task/task-config-registration.test.ts +57 -0
  244. package/src/env/task/task-config-registration.ts +37 -0
  245. package/src/env/task/task-types.test.ts +137 -0
  246. package/src/env/task/tools/complete-tool.ts +44 -0
  247. package/src/env/task/tools/create-tool.ts +49 -0
  248. package/src/env/task/tools/delete-tool.ts +43 -0
  249. package/src/env/task/tools/get-tool.ts +59 -0
  250. package/src/env/task/tools/index.ts +10 -0
  251. package/src/env/task/tools/list-tool.ts +40 -0
  252. package/src/env/task/tools/operation/create-tool.ts +48 -0
  253. package/src/env/task/tools/operation/delete-tool.ts +43 -0
  254. package/src/env/task/tools/operation/get-tool.ts +43 -0
  255. package/src/env/task/tools/operation/index.ts +9 -0
  256. package/src/env/task/tools/operation/list-tool.ts +40 -0
  257. package/src/env/task/tools/operation/operation-tools.test.ts +274 -0
  258. package/src/env/task/tools/operation/operation-types.ts +75 -0
  259. package/src/env/task/tools/operation/update-tool.ts +47 -0
  260. package/src/env/task/tools/task-tools.test.ts +203 -0
  261. package/src/env/task/tools/task-types.test.ts +75 -0
  262. package/src/env/task/tools/task-types.ts +68 -0
  263. package/src/env/task/tools/update-tool.ts +70 -0
  264. package/src/env/task/types.ts +160 -0
  265. package/src/env/tool/built-in/bash.ts +201 -0
  266. package/src/env/tool/built-in/echo.ts +29 -0
  267. package/src/env/tool/built-in/edit-file.test.ts +136 -0
  268. package/src/env/tool/built-in/edit-file.ts +92 -0
  269. package/src/env/tool/built-in/glob.test.ts +94 -0
  270. package/src/env/tool/built-in/glob.ts +65 -0
  271. package/src/env/tool/built-in/grep.test.ts +122 -0
  272. package/src/env/tool/built-in/grep.ts +108 -0
  273. package/src/env/tool/built-in/index.ts +44 -0
  274. package/src/env/tool/built-in/read-file.test.ts +84 -0
  275. package/src/env/tool/built-in/read-file.ts +75 -0
  276. package/src/env/tool/built-in/write-file.test.ts +119 -0
  277. package/src/env/tool/built-in/write-file.ts +68 -0
  278. package/src/env/tool/index.ts +24 -0
  279. package/src/env/tool/registry.test.ts +257 -0
  280. package/src/env/tool/registry.ts +167 -0
  281. package/src/env/tool/tool-component.test.ts +559 -0
  282. package/src/env/tool/tool-component.ts +563 -0
  283. package/src/env/tool/tool-config-registration.test.ts +249 -0
  284. package/src/env/tool/tool-config-registration.ts +46 -0
  285. package/src/env/tool/types.ts +267 -0
  286. package/src/env/tool/validator.test.ts +143 -0
  287. package/src/env/tool/validator.ts +44 -0
  288. package/src/env/types.ts +180 -0
  289. package/src/env/workflow/ask-user-tool-registration.test.ts +216 -0
  290. package/src/env/workflow/complex-workflow.integration.test.ts +1900 -0
  291. package/src/env/workflow/decorators/decorator-node.ts +229 -0
  292. package/src/env/workflow/decorators/decorator.test.ts +196 -0
  293. package/src/env/workflow/decorators/edge.ts +82 -0
  294. package/src/env/workflow/decorators/index.ts +31 -0
  295. package/src/env/workflow/decorators/node-as.ts +98 -0
  296. package/src/env/workflow/decorators/workflow.ts +54 -0
  297. package/src/env/workflow/engine/dag-manager.test.ts +570 -0
  298. package/src/env/workflow/engine/dag-manager.ts +594 -0
  299. package/src/env/workflow/engine/engine.ts +1422 -0
  300. package/src/env/workflow/engine/event-bus.test.ts +359 -0
  301. package/src/env/workflow/engine/event-bus.ts +156 -0
  302. package/src/env/workflow/engine/executor-agent-session.test.ts +84 -0
  303. package/src/env/workflow/engine/executor.test.ts +619 -0
  304. package/src/env/workflow/engine/executor.ts +593 -0
  305. package/src/env/workflow/engine/index.ts +24 -0
  306. package/src/env/workflow/engine/node-registry.test.ts +560 -0
  307. package/src/env/workflow/engine/node-registry.ts +289 -0
  308. package/src/env/workflow/engine/resume-removed.test.ts +22 -0
  309. package/src/env/workflow/engine/scheduler.test.ts +715 -0
  310. package/src/env/workflow/engine/scheduler.ts +318 -0
  311. package/src/env/workflow/engine/workflow-engine.test.ts +815 -0
  312. package/src/env/workflow/extractor/workflow-converter.ts +306 -0
  313. package/src/env/workflow/fixtures.ts +380 -0
  314. package/src/env/workflow/index.ts +38 -0
  315. package/src/env/workflow/integration/run-resume-unified.test.ts +186 -0
  316. package/src/env/workflow/integration/service-integration.test.ts +267 -0
  317. package/src/env/workflow/metadata/keys.ts +12 -0
  318. package/src/env/workflow/nodes/agent-component-adapter.test.ts +318 -0
  319. package/src/env/workflow/nodes/agent-component-adapter.ts +448 -0
  320. package/src/env/workflow/nodes/agent-node.test.ts +371 -0
  321. package/src/env/workflow/nodes/agent-node.ts +598 -0
  322. package/src/env/workflow/nodes/ask-user-node.ts +113 -0
  323. package/src/env/workflow/nodes/condition-node.ts +200 -0
  324. package/src/env/workflow/nodes/index.ts +9 -0
  325. package/src/env/workflow/nodes/merge-node.ts +141 -0
  326. package/src/env/workflow/nodes/skill-node.test.ts +253 -0
  327. package/src/env/workflow/nodes/skill-node.ts +393 -0
  328. package/src/env/workflow/nodes/tool-node.test.ts +251 -0
  329. package/src/env/workflow/nodes/tool-node.ts +493 -0
  330. package/src/env/workflow/nodes/workflow-llm-history.test.ts +455 -0
  331. package/src/env/workflow/nodes/workflow-node.test.ts +315 -0
  332. package/src/env/workflow/nodes/workflow-node.ts +311 -0
  333. package/src/env/workflow/service/index.ts +27 -0
  334. package/src/env/workflow/service/registry.test.ts +133 -0
  335. package/src/env/workflow/service/registry.ts +71 -0
  336. package/src/env/workflow/service/workflow-service.test.ts +310 -0
  337. package/src/env/workflow/service/workflow-service.ts +393 -0
  338. package/src/env/workflow/storage/index.ts +28 -0
  339. package/src/env/workflow/storage/mock-repositories.ts +385 -0
  340. package/src/env/workflow/storage/sqlite.test.ts +179 -0
  341. package/src/env/workflow/storage/sqlite.ts +163 -0
  342. package/src/env/workflow/storage/workflow-repo.test.ts +780 -0
  343. package/src/env/workflow/storage/workflow-repo.ts +342 -0
  344. package/src/env/workflow/tools/ask-user-tool.ts +82 -0
  345. package/src/env/workflow/tools/index.ts +26 -0
  346. package/src/env/workflow/tools/run-workflow.test.ts +352 -0
  347. package/src/env/workflow/tools/run-workflow.ts +214 -0
  348. package/src/env/workflow/types/context.ts +18 -0
  349. package/src/env/workflow/types/decorators-types.ts +198 -0
  350. package/src/env/workflow/types/event.test.ts +515 -0
  351. package/src/env/workflow/types/event.ts +193 -0
  352. package/src/env/workflow/types/index.ts +49 -0
  353. package/src/env/workflow/types/run.test.ts +437 -0
  354. package/src/env/workflow/types/run.ts +173 -0
  355. package/src/env/workflow/types/workflow-hil.ts +114 -0
  356. package/src/env/workflow/types/workflow-message.test.ts +138 -0
  357. package/src/env/workflow/types/workflow-message.ts +196 -0
  358. package/src/env/workflow/types/workflow-session.test.ts +95 -0
  359. package/src/env/workflow/types/workflow-session.ts +59 -0
  360. package/src/env/workflow/types/workflow.test.ts +495 -0
  361. package/src/env/workflow/types/workflow.ts +195 -0
  362. package/src/env/workflow/types_compat.ts +51 -0
  363. package/src/env/workflow/utils/create-workflow.ts +47 -0
  364. package/src/env/workflow/utils/execution-state.ts +245 -0
  365. package/src/env/workflow/utils/index.ts +18 -0
  366. package/src/env/workflow/utils/node-registry-helper.ts +58 -0
  367. package/src/env/workflow/utils/recovery-validator.test.ts +460 -0
  368. package/src/env/workflow/utils/recovery-validator.ts +377 -0
  369. package/src/env/workflow/utils/session-parser.test.ts +111 -0
  370. package/src/env/workflow/utils/session-parser.ts +94 -0
  371. package/src/env/workflow/utils/session-recovery.test.ts +334 -0
  372. package/src/env/workflow/utils/session-recovery.ts +188 -0
  373. package/src/env/workflow/utils/template-resolver.test.ts +258 -0
  374. package/src/env/workflow/utils/template-resolver.ts +436 -0
  375. package/src/env/workflow/utils/validation-rules.ts +149 -0
  376. package/src/env/workflow/workflow-component.ts +544 -0
  377. package/src/index.ts +422 -0
  378. package/src/utils/id.ts +21 -0
@@ -0,0 +1,763 @@
1
+ /**
2
+ * @fileoverview Session Message Converter Tests
3
+ *
4
+ * Tests for SessionMessage <-> AI SDK ModelMessage conversion functions.
5
+ * Following TDD: These tests define the expected behavior.
6
+ */
7
+
8
+ import { describe, test, expect, beforeEach } from "bun:test";
9
+ import type {
10
+ SessionMessage,
11
+ TextPart,
12
+ ToolCallPart,
13
+ ToolResultPart,
14
+ ReasoningPart,
15
+ FilePart,
16
+ CheckpointPart,
17
+ SessionPart
18
+ } from "./types";
19
+
20
+ // ============================================================================
21
+ // Test Utilities
22
+ // ============================================================================
23
+
24
+ function createSessionMessage(overrides: Partial<SessionMessage> = {}): SessionMessage {
25
+ return {
26
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
27
+ sessionID: "session_test",
28
+ role: "user",
29
+ content: "",
30
+ timestamp: Date.now(),
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ // ============================================================================
36
+ // RED Phase: Write tests that define expected behavior
37
+ // ============================================================================
38
+
39
+ describe("Session Part Types", () => {
40
+ describe("TextPart", () => {
41
+ test("should have correct structure", () => {
42
+ const textPart: TextPart = {
43
+ type: "text",
44
+ content: "Hello, world!",
45
+ synthetic: false,
46
+ ignored: false,
47
+ };
48
+
49
+ expect(textPart.type).toBe("text");
50
+ expect(textPart.content).toBe("Hello, world!");
51
+ expect(textPart.synthetic).toBe(false);
52
+ expect(textPart.ignored).toBe(false);
53
+ });
54
+
55
+ test("should support optional fields", () => {
56
+ const textPart: TextPart = {
57
+ type: "text",
58
+ content: "Simple text",
59
+ };
60
+
61
+ expect(textPart.type).toBe("text");
62
+ expect(textPart.content).toBe("Simple text");
63
+ expect(textPart.synthetic).toBeUndefined();
64
+ expect(textPart.ignored).toBeUndefined();
65
+ });
66
+ });
67
+
68
+ describe("ToolCallPart", () => {
69
+ test("should have correct structure", () => {
70
+ const toolCallPart: ToolCallPart = {
71
+ type: "tool-call",
72
+ toolCallId: "call_abc123",
73
+ toolName: "grep",
74
+ arguments: { pattern: "TODO", path: "/src" },
75
+ state: "pending",
76
+ };
77
+
78
+ expect(toolCallPart.type).toBe("tool-call");
79
+ expect(toolCallPart.toolCallId).toBe("call_abc123");
80
+ expect(toolCallPart.toolName).toBe("grep");
81
+ expect(toolCallPart.arguments).toEqual({ pattern: "TODO", path: "/src" });
82
+ expect(toolCallPart.state).toBe("pending");
83
+ });
84
+
85
+ test("should support string arguments", () => {
86
+ const toolCallPart: ToolCallPart = {
87
+ type: "tool-call",
88
+ toolCallId: "call_xyz",
89
+ toolName: "bash",
90
+ arguments: '{"command": "ls -la"}',
91
+ state: "pending",
92
+ };
93
+
94
+ expect(typeof toolCallPart.arguments).toBe("string");
95
+ });
96
+
97
+ test("should support running state", () => {
98
+ const toolCallPart: ToolCallPart = {
99
+ type: "tool-call",
100
+ toolCallId: "call_running",
101
+ toolName: "search",
102
+ arguments: {},
103
+ state: "running",
104
+ };
105
+
106
+ expect(toolCallPart.state).toBe("running");
107
+ });
108
+ });
109
+
110
+ describe("ToolResultPart", () => {
111
+ test("should have correct structure", () => {
112
+ const toolResultPart: ToolResultPart = {
113
+ type: "tool-result",
114
+ toolCallId: "call_abc123",
115
+ toolName: "grep",
116
+ output: "Found 3 matches",
117
+ state: "completed",
118
+ };
119
+
120
+ expect(toolResultPart.type).toBe("tool-result");
121
+ expect(toolResultPart.toolCallId).toBe("call_abc123");
122
+ expect(toolResultPart.toolName).toBe("grep");
123
+ expect(toolResultPart.output).toBe("Found 3 matches");
124
+ expect(toolResultPart.state).toBe("completed");
125
+ });
126
+
127
+ test("should support error state", () => {
128
+ const toolResultPart: ToolResultPart = {
129
+ type: "tool-result",
130
+ toolCallId: "call_error",
131
+ toolName: "bash",
132
+ output: "",
133
+ error: "Command failed: exit code 1",
134
+ state: "error",
135
+ };
136
+
137
+ expect(toolResultPart.error).toBe("Command failed: exit code 1");
138
+ expect(toolResultPart.state).toBe("error");
139
+ });
140
+ });
141
+
142
+ describe("ReasoningPart", () => {
143
+ test("should have correct structure", () => {
144
+ const reasoningPart: ReasoningPart = {
145
+ type: "reasoning",
146
+ content: "Let me think about this problem...",
147
+ reasoningType: "core",
148
+ state: "completed",
149
+ };
150
+
151
+ expect(reasoningPart.type).toBe("reasoning");
152
+ expect(reasoningPart.content).toBe("Let me think about this problem...");
153
+ expect(reasoningPart.reasoningType).toBe("core");
154
+ expect(reasoningPart.state).toBe("completed");
155
+ });
156
+
157
+ test("should support optional metadata", () => {
158
+ const reasoningPart: ReasoningPart = {
159
+ type: "reasoning",
160
+ content: "Thinking...",
161
+ reasoningType: "effort",
162
+ state: "thinking",
163
+ isCollapsed: true,
164
+ metadata: {
165
+ tokenCount: 150,
166
+ time: { start: Date.now(), end: Date.now() + 1000 },
167
+ },
168
+ };
169
+
170
+ expect(reasoningPart.isCollapsed).toBe(true);
171
+ expect(reasoningPart.metadata?.tokenCount).toBe(150);
172
+ });
173
+ });
174
+
175
+ describe("FilePart", () => {
176
+ test("should have correct structure", () => {
177
+ const filePart: FilePart = {
178
+ type: "file",
179
+ mime: "image/png",
180
+ url: "file:///path/to/image.png",
181
+ filename: "screenshot.png",
182
+ };
183
+
184
+ expect(filePart.type).toBe("file");
185
+ expect(filePart.mime).toBe("image/png");
186
+ expect(filePart.url).toBe("file:///path/to/image.png");
187
+ expect(filePart.filename).toBe("screenshot.png");
188
+ });
189
+ });
190
+
191
+ describe("CheckpointPart", () => {
192
+ test("should have correct structure", () => {
193
+ const checkpointPart: CheckpointPart = {
194
+ type: "checkpoint",
195
+ checkpointId: "chk_abc123",
196
+ summary: "Session compacted",
197
+ processKeyPoints: ["Analyzed requirements", "Created test files"],
198
+ currentState: "Implementing feature X",
199
+ nextSteps: ["Write implementation", "Test edge cases"],
200
+ };
201
+
202
+ expect(checkpointPart.type).toBe("checkpoint");
203
+ expect(checkpointPart.checkpointId).toBe("chk_abc123");
204
+ expect(checkpointPart.summary).toBe("Session compacted");
205
+ expect(checkpointPart.processKeyPoints).toHaveLength(2);
206
+ expect(checkpointPart.nextSteps).toHaveLength(2);
207
+ });
208
+ });
209
+
210
+ describe("SessionPart Union Type", () => {
211
+ test("should allow all part types in union", () => {
212
+ const parts: SessionPart[] = [
213
+ { type: "text", content: "Hello" },
214
+ { type: "tool-call", toolCallId: "c1", toolName: "t1", arguments: {}, state: "pending" },
215
+ { type: "tool-result", toolCallId: "c1", toolName: "t1", output: "ok", state: "completed" },
216
+ { type: "reasoning", content: "thinking" },
217
+ { type: "file", mime: "text/plain", url: "file://test.txt" },
218
+ { type: "checkpoint", checkpointId: "chk1", summary: "test" },
219
+ ];
220
+
221
+ expect(parts).toHaveLength(6);
222
+ });
223
+ });
224
+ });
225
+
226
+ // ============================================================================
227
+ // SessionMessage Structure Tests
228
+ // ============================================================================
229
+
230
+ describe("SessionMessage Structure", () => {
231
+ test("should support parts array", () => {
232
+ const message = createSessionMessage({
233
+ role: "assistant",
234
+ parts: [
235
+ { type: "text", content: "Hello!" },
236
+ { type: "reasoning", content: "Let me think...", reasoningType: "core" },
237
+ ],
238
+ });
239
+
240
+ expect(message.parts).toBeDefined();
241
+ expect(message.parts).toHaveLength(2);
242
+ expect(message.parts![0].type).toBe("text");
243
+ expect(message.parts![1].type).toBe("reasoning");
244
+ });
245
+
246
+ test("should support metadata", () => {
247
+ const message = createSessionMessage({
248
+ role: "assistant",
249
+ metadata: {
250
+ model: "gpt-4",
251
+ isCheckpoint: true,
252
+ checkpointId: "chk_123",
253
+ },
254
+ });
255
+
256
+ expect(message.metadata).toBeDefined();
257
+ expect(message.metadata?.model).toBe("gpt-4");
258
+ expect(message.metadata?.isCheckpoint).toBe(true);
259
+ });
260
+
261
+ test("should support backward compatibility with content field", () => {
262
+ const message = createSessionMessage({
263
+ role: "user",
264
+ content: "Simple text message",
265
+ });
266
+
267
+ expect(message.content).toBe("Simple text message");
268
+ expect(message.parts).toBeUndefined();
269
+ });
270
+ });
271
+
272
+ // ============================================================================
273
+ // Import tests (will be implemented in GREEN phase)
274
+ // ============================================================================
275
+
276
+ describe("toModelMessage", () => {
277
+ test("should convert user message to ModelMessage", async () => {
278
+ const { toModelMessage } = await import("./session-message-converter");
279
+
280
+ const sessionMsg = createSessionMessage({
281
+ role: "user",
282
+ content: "Hello, AI!",
283
+ });
284
+
285
+ const modelMsg = toModelMessage(sessionMsg);
286
+
287
+ expect(modelMsg.role).toBe("user");
288
+ expect(modelMsg.content).toBe("Hello, AI!");
289
+ });
290
+
291
+ test("should convert assistant text message to ModelMessage", async () => {
292
+ const { toModelMessage } = await import("./session-message-converter");
293
+
294
+ const sessionMsg = createSessionMessage({
295
+ role: "assistant",
296
+ content: "Hello, human!",
297
+ parts: [{ type: "text", content: "Hello, human!" }],
298
+ });
299
+
300
+ const modelMsg = toModelMessage(sessionMsg);
301
+
302
+ expect(modelMsg.role).toBe("assistant");
303
+ expect(modelMsg.content).toHaveLength(1);
304
+ expect(modelMsg.content[0]).toEqual({ type: "text", text: "Hello, human!" });
305
+ });
306
+
307
+ test("should convert reasoning part to ModelMessage", async () => {
308
+ const { toModelMessage } = await import("./session-message-converter");
309
+
310
+ const sessionMsg = createSessionMessage({
311
+ role: "assistant",
312
+ parts: [
313
+ { type: "reasoning", content: "Let me analyze this...", reasoningType: "core" },
314
+ { type: "text", content: "Here's my answer." },
315
+ ],
316
+ });
317
+
318
+ const modelMsg = toModelMessage(sessionMsg);
319
+
320
+ expect(modelMsg.role).toBe("assistant");
321
+ expect(modelMsg.content).toHaveLength(2);
322
+ expect(modelMsg.content[0]).toMatchObject({
323
+ type: "reasoning",
324
+ text: "Let me analyze this..."
325
+ });
326
+ });
327
+
328
+ test("should convert tool-call part to ModelMessage", async () => {
329
+ const { toModelMessage } = await import("./session-message-converter");
330
+
331
+ const sessionMsg = createSessionMessage({
332
+ role: "assistant",
333
+ parts: [
334
+ { type: "text", content: "Searching..." },
335
+ {
336
+ type: "tool-call",
337
+ toolCallId: "call_123",
338
+ toolName: "grep",
339
+ arguments: { pattern: "TODO" },
340
+ state: "pending",
341
+ },
342
+ ],
343
+ });
344
+
345
+ const modelMsg = toModelMessage(sessionMsg);
346
+
347
+ expect(modelMsg.content).toHaveLength(2);
348
+ const toolCall = modelMsg.content.find(c => c.type === "tool-call");
349
+ expect(toolCall).toMatchObject({
350
+ type: "tool-call",
351
+ toolCallId: "call_123",
352
+ toolName: "grep",
353
+ input: { pattern: "TODO" },
354
+ });
355
+ });
356
+
357
+ test("should convert tool message to ModelMessage", async () => {
358
+ const { toModelMessage } = await import("./session-message-converter");
359
+
360
+ const sessionMsg = createSessionMessage({
361
+ role: "tool",
362
+ content: "Found 5 matches",
363
+ parts: [
364
+ {
365
+ type: "tool-result",
366
+ toolCallId: "call_123",
367
+ toolName: "grep",
368
+ output: "Found 5 matches",
369
+ state: "completed",
370
+ },
371
+ ],
372
+ });
373
+
374
+ const modelMsg = toModelMessage(sessionMsg);
375
+
376
+ expect(modelMsg.role).toBe("tool");
377
+ expect(modelMsg.content).toHaveLength(1);
378
+ expect(modelMsg.content[0]).toMatchObject({
379
+ type: "tool-result",
380
+ toolCallId: "call_123",
381
+ toolName: "grep",
382
+ output: "Found 5 matches",
383
+ });
384
+ });
385
+
386
+ test("should convert file part in assistant message", async () => {
387
+ const { toModelMessage } = await import("./session-message-converter");
388
+
389
+ const sessionMsg = createSessionMessage({
390
+ role: "assistant",
391
+ parts: [
392
+ { type: "text", content: "Here's the file" },
393
+ { type: "file", mime: "image/png", url: "file:///tmp/image.png", filename: "screenshot.png" },
394
+ ],
395
+ });
396
+
397
+ const modelMsg = toModelMessage(sessionMsg);
398
+
399
+ expect(modelMsg.role).toBe("assistant");
400
+ expect(Array.isArray(modelMsg.content)).toBe(true);
401
+ const content = modelMsg.content as any[];
402
+ const filePart = content.find(c => c.type === "file");
403
+ expect(filePart).toBeDefined();
404
+ expect(filePart.url).toBe("file:///tmp/image.png");
405
+ expect(filePart.mimeType).toBe("image/png");
406
+ });
407
+
408
+ test("should skip checkpoint parts in assistant message", async () => {
409
+ const { toModelMessage } = await import("./session-message-converter");
410
+
411
+ const sessionMsg = createSessionMessage({
412
+ role: "assistant",
413
+ parts: [
414
+ { type: "text", content: "Response after checkpoint" },
415
+ { type: "checkpoint", checkpointId: "chk_123", summary: "Checkpoint summary" },
416
+ ],
417
+ });
418
+
419
+ const modelMsg = toModelMessage(sessionMsg);
420
+
421
+ expect(modelMsg.role).toBe("assistant");
422
+ expect(Array.isArray(modelMsg.content)).toBe(true);
423
+ const content = modelMsg.content as any[];
424
+ // Checkpoint should be skipped
425
+ expect(content).toHaveLength(1);
426
+ expect(content[0].type).toBe("text");
427
+ });
428
+ });
429
+
430
+ describe("fromModelMessage", () => {
431
+ test("should convert text ModelMessage to SessionMessage", async () => {
432
+ const { fromModelMessage } = await import("./session-message-converter");
433
+
434
+ const modelMsg = {
435
+ role: "user" as const,
436
+ content: "Hello!",
437
+ };
438
+
439
+ const sessionMsg = fromModelMessage(modelMsg);
440
+
441
+ expect(sessionMsg.role).toBe("user");
442
+ expect(sessionMsg.content).toBe("Hello!");
443
+ expect(sessionMsg.parts).toEqual([{ type: "text", content: "Hello!" }]);
444
+ });
445
+
446
+ test("should convert assistant ModelMessage with multiple parts", async () => {
447
+ const { fromModelMessage } = await import("./session-message-converter");
448
+
449
+ const modelMsg = {
450
+ role: "assistant" as const,
451
+ content: [
452
+ { type: "reasoning", text: "Thinking..." },
453
+ { type: "text", text: "Here's my response." },
454
+ { type: "tool-call", toolCallId: "c1", toolName: "bash", input: { cmd: "ls" } },
455
+ ],
456
+ };
457
+
458
+ const sessionMsg = fromModelMessage(modelMsg);
459
+
460
+ expect(sessionMsg.role).toBe("assistant");
461
+ expect(sessionMsg.parts).toHaveLength(3);
462
+ expect(sessionMsg.parts![0]).toMatchObject({ type: "reasoning", content: "Thinking..." });
463
+ expect(sessionMsg.parts![1]).toMatchObject({ type: "text", content: "Here's my response." });
464
+ expect(sessionMsg.parts![2]).toMatchObject({
465
+ type: "tool-call",
466
+ toolCallId: "c1",
467
+ toolName: "bash",
468
+ arguments: { cmd: "ls" },
469
+ });
470
+ });
471
+
472
+ test("should convert tool-result ModelMessage to SessionMessage", async () => {
473
+ const { fromModelMessage } = await import("./session-message-converter");
474
+
475
+ const modelMsg = {
476
+ role: "tool" as const,
477
+ content: [
478
+ {
479
+ type: "tool-result" as const,
480
+ toolCallId: "call_abc",
481
+ toolName: "grep",
482
+ output: "Match 1\nMatch 2",
483
+ },
484
+ ],
485
+ };
486
+
487
+ const sessionMsg = fromModelMessage(modelMsg);
488
+
489
+ expect(sessionMsg.role).toBe("tool");
490
+ expect(sessionMsg.parts![0]).toMatchObject({
491
+ type: "tool-result",
492
+ toolCallId: "call_abc",
493
+ toolName: "grep",
494
+ output: "Match 1\nMatch 2",
495
+ state: "completed",
496
+ });
497
+ // tool messages should have metadata.type = "tool_result"
498
+ expect(sessionMsg.metadata).toEqual({ type: "tool_result" });
499
+ });
500
+
501
+ test("should set metadata.type to tool_call for assistant messages with tool-call parts", async () => {
502
+ const { fromModelMessage } = await import("./session-message-converter");
503
+
504
+ const modelMsg = {
505
+ role: "assistant" as const,
506
+ content: [
507
+ { type: "text", text: "I'll search for that." },
508
+ { type: "tool-call", toolCallId: "c1", toolName: "search", input: { query: "test" } },
509
+ ],
510
+ };
511
+
512
+ const sessionMsg = fromModelMessage(modelMsg);
513
+
514
+ expect(sessionMsg.role).toBe("assistant");
515
+ expect(sessionMsg.metadata).toEqual({ type: "tool_call" });
516
+ });
517
+
518
+ test("should not set metadata.type for assistant messages without tool-call parts", async () => {
519
+ const { fromModelMessage } = await import("./session-message-converter");
520
+
521
+ const modelMsg = {
522
+ role: "assistant" as const,
523
+ content: "Just a regular text response.",
524
+ };
525
+
526
+ const sessionMsg = fromModelMessage(modelMsg);
527
+
528
+ expect(sessionMsg.role).toBe("assistant");
529
+ expect(sessionMsg.metadata).toBeUndefined();
530
+ });
531
+
532
+ test("should set metadata.type to tool_result for tool messages", async () => {
533
+ const { fromModelMessage } = await import("./session-message-converter");
534
+
535
+ const modelMsg = {
536
+ role: "tool" as const,
537
+ content: [
538
+ { type: "tool-result", toolCallId: "c1", toolName: "search", output: "result" },
539
+ ],
540
+ };
541
+
542
+ const sessionMsg = fromModelMessage(modelMsg);
543
+
544
+ expect(sessionMsg.metadata).toEqual({ type: "tool_result" });
545
+ });
546
+ });
547
+
548
+ describe("sessionToHistory", () => {
549
+ test("should convert session messages to ModelMessage array", async () => {
550
+ const { sessionToHistory } = await import("./session-message-converter");
551
+
552
+ const messages: SessionMessage[] = [
553
+ createSessionMessage({ role: "user", content: "Hello!" }),
554
+ createSessionMessage({
555
+ role: "assistant",
556
+ parts: [{ type: "text", content: "Hi there!" }]
557
+ }),
558
+ ];
559
+
560
+ const history = sessionToHistory(messages);
561
+
562
+ expect(history).toHaveLength(2);
563
+ expect(history[0].role).toBe("user");
564
+ expect(history[1].role).toBe("assistant");
565
+ });
566
+
567
+ test("should filter out archived messages", async () => {
568
+ const { sessionToHistory } = await import("./session-message-converter");
569
+
570
+ const messages: SessionMessage[] = [
571
+ createSessionMessage({ role: "user", content: "Message 1" }),
572
+ createSessionMessage({ role: "user", content: "Message 2", isArchived: true }),
573
+ createSessionMessage({ role: "user", content: "Message 3" }),
574
+ ];
575
+
576
+ const history = sessionToHistory(messages);
577
+
578
+ expect(history).toHaveLength(2);
579
+ expect(history[0].content).toBe("Message 1");
580
+ expect(history[1].content).toBe("Message 3");
581
+ });
582
+ });
583
+
584
+ describe("extractProviderOptions", () => {
585
+ test("should extract reasoning options from reasoning parts", async () => {
586
+ const { extractProviderOptions } = await import("./session-message-converter");
587
+
588
+ const message = createSessionMessage({
589
+ role: "assistant",
590
+ parts: [
591
+ { type: "reasoning", content: "Thinking...", reasoningType: "core" },
592
+ ],
593
+ });
594
+
595
+ const options = extractProviderOptions(message);
596
+
597
+ expect(options).toHaveProperty("openai");
598
+ expect(options.openai).toHaveProperty("thinking");
599
+ });
600
+ });
601
+
602
+ describe("migrateMessage", () => {
603
+ test("should migrate old format message to new format", async () => {
604
+ const { migrateMessage } = await import("./session-message-converter");
605
+
606
+ // Old format: content is plain text, no parts
607
+ const oldMsg = {
608
+ id: "msg_old",
609
+ sessionID: "session_test",
610
+ role: "user" as const,
611
+ content: "Hello, old world!",
612
+ timestamp: Date.now(),
613
+ };
614
+
615
+ const migrated = migrateMessage(oldMsg);
616
+
617
+ expect(migrated.content).toBe("Hello, old world!");
618
+ expect(migrated.parts).toBeDefined();
619
+ expect(migrated.parts).toHaveLength(1);
620
+ expect(migrated.parts![0]).toEqual({ type: "text", content: "Hello, old world!" });
621
+ });
622
+
623
+ test("should keep new format message as is", async () => {
624
+ const { migrateMessage } = await import("./session-message-converter");
625
+
626
+ // New format: already has proper parts
627
+ const newMsg = {
628
+ id: "msg_new",
629
+ sessionID: "session_test",
630
+ role: "assistant" as const,
631
+ content: "Hello!",
632
+ timestamp: Date.now(),
633
+ parts: [
634
+ { type: "text", content: "Hello!" },
635
+ { type: "reasoning", content: "Thinking...", reasoningType: "core" as const },
636
+ ],
637
+ };
638
+
639
+ const migrated = migrateMessage(newMsg);
640
+
641
+ expect(migrated.parts).toHaveLength(2);
642
+ expect(migrated.parts![0].type).toBe("text");
643
+ expect(migrated.parts![1].type).toBe("reasoning");
644
+ });
645
+
646
+ test("should migrate message with empty parts array", async () => {
647
+ const { migrateMessage } = await import("./session-message-converter");
648
+
649
+ // Old format with empty parts (malformed)
650
+ const oldMsg = {
651
+ id: "msg_empty_parts",
652
+ sessionID: "session_test",
653
+ role: "user" as const,
654
+ content: "Content without parts field",
655
+ timestamp: Date.now(),
656
+ parts: [],
657
+ };
658
+
659
+ const migrated = migrateMessage(oldMsg);
660
+
661
+ expect(migrated.content).toBe("Content without parts field");
662
+ expect(migrated.parts).toHaveLength(1);
663
+ expect(migrated.parts![0]).toEqual({ type: "text", content: "Content without parts field" });
664
+ });
665
+ });
666
+
667
+ describe("Storage Integration", () => {
668
+ test("SQLite storage should handle new part format", async () => {
669
+ const { SQLiteSessionStore } = await import("./storage/sqlite");
670
+ const { toModelMessage } = await import("./session-message-converter");
671
+
672
+ const store = new SQLiteSessionStore(":memory:");
673
+
674
+ // Create session
675
+ const session = await store.create({ title: "Test Storage" });
676
+
677
+ // Add message with new format parts
678
+ await store.addMessage(session.id, {
679
+ role: "user",
680
+ content: "Test message",
681
+ parts: [
682
+ { type: "text", content: "Test message" },
683
+ ],
684
+ });
685
+
686
+ // Add assistant message with reasoning
687
+ await store.addMessage(session.id, {
688
+ role: "assistant",
689
+ content: "I'll help you!",
690
+ parts: [
691
+ { type: "reasoning", content: "Analyzing the request...", reasoningType: "core" },
692
+ { type: "text", content: "I'll help you!" },
693
+ ],
694
+ });
695
+
696
+ // Retrieve and verify
697
+ const messages = await store.getMessages(session.id);
698
+
699
+ expect(messages).toHaveLength(2);
700
+ expect(messages[0].role).toBe("user");
701
+ expect(messages[0].parts).toHaveLength(1);
702
+
703
+ expect(messages[1].role).toBe("assistant");
704
+ expect(messages[1].parts).toHaveLength(2);
705
+ expect(messages[1].parts![0].type).toBe("reasoning");
706
+ expect(messages[1].parts![1].type).toBe("text");
707
+
708
+ // Convert to ModelMessage
709
+ const modelMsg = toModelMessage(messages[1]);
710
+ expect(modelMsg.role).toBe("assistant");
711
+ expect(modelMsg.content).toHaveLength(2);
712
+
713
+ await store.close();
714
+ });
715
+
716
+ test("Memory storage should handle new part format", async () => {
717
+ const { MemorySessionStore } = await import("./storage/memory");
718
+ const { sessionToHistory } = await import("./session-message-converter");
719
+
720
+ const store = new MemorySessionStore();
721
+
722
+ // Create session
723
+ const session = await store.create({ title: "Test Memory" });
724
+
725
+ // Add user message
726
+ await store.addMessage(session.id, {
727
+ role: "user",
728
+ content: "Find TODO comments",
729
+ });
730
+
731
+ // Add assistant message with tool call
732
+ await store.addMessage(session.id, {
733
+ role: "assistant",
734
+ content: "Let me search...",
735
+ parts: [
736
+ { type: "text", content: "Let me search..." },
737
+ { type: "tool-call", toolCallId: "call_1", toolName: "grep", arguments: { pattern: "TODO" }, state: "pending" as const },
738
+ ],
739
+ });
740
+
741
+ await store.addMessage(session.id, {
742
+ role: "tool",
743
+ content: "Found 3 matches",
744
+ parts: [
745
+ { type: "tool-result", toolCallId: "call_1", toolName: "grep", output: "Found 3 matches", state: "completed" as const },
746
+ ],
747
+ });
748
+
749
+ // Retrieve and convert to history
750
+ const messages = await store.getMessages(session.id);
751
+ const history = sessionToHistory(messages);
752
+
753
+ expect(messages).toHaveLength(3);
754
+ expect(history).toHaveLength(3);
755
+ expect(history[1].role).toBe("assistant");
756
+ expect(history[1].content).toHaveLength(2);
757
+
758
+ const toolCall = history[1].content.find((c: any) => c.type === "tool-call");
759
+ expect(toolCall).toBeDefined();
760
+ expect(toolCall.toolCallId).toBe("call_1");
761
+ expect(toolCall.toolName).toBe("grep");
762
+ });
763
+ });