@xopcai/xopc 0.0.85 → 0.0.87

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 (407) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  4. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  5. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  7. package/dist/extensions/telegram/src/routing-integration.js +1 -0
  8. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  11. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  12. package/dist/extensions/weixin/src/api/api.js +2 -2
  13. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  14. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  15. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  16. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  17. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  18. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  19. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  20. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  21. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  22. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  23. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  24. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  25. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  26. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
  27. package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
  29. package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
  35. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
  36. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
  37. package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
  39. package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
  40. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
  41. package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
  42. package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
  43. package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
  44. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
  45. package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
  46. package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
  47. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
  48. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
  49. package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
  50. package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
  51. package/dist/gateway/static/root/index.html +5 -5
  52. package/dist/package.js +1 -1
  53. package/dist/src/agent/agent-scope.d.ts +4 -0
  54. package/dist/src/agent/agent-scope.js +53 -10
  55. package/dist/src/agent/agent-scope.js.map +1 -1
  56. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  57. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  58. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  59. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  60. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  61. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  62. package/dist/src/agent/fallback/candidates.js +2 -2
  63. package/dist/src/agent/fallback/candidates.js.map +1 -1
  64. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  65. package/dist/src/agent/goals/persistent-goal-service.js +0 -1
  66. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  67. package/dist/src/agent/image/generation/normalization.js +2 -12
  68. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  69. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  70. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  71. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  72. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  73. package/dist/src/agent/image/generation/types.d.ts +0 -18
  74. package/dist/src/agent/image/image-helpers.js +6 -1
  75. package/dist/src/agent/image/image-helpers.js.map +1 -1
  76. package/dist/src/agent/image/index.d.ts +1 -1
  77. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  78. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  79. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  80. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  81. package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
  82. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  83. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  84. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  85. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  86. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  87. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  88. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  89. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  90. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  91. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  92. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  93. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  94. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  95. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  96. package/dist/src/agent/messaging/command-handler.js +5 -0
  97. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  98. package/dist/src/agent/prompt/safety.d.ts +0 -7
  99. package/dist/src/agent/prompt/safety.js +1 -20
  100. package/dist/src/agent/prompt/safety.js.map +1 -1
  101. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  102. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  103. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  104. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  105. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  106. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  107. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  108. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  109. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  110. package/dist/src/agent/service/process-direct-streaming.js +34 -4
  111. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  112. package/dist/src/agent/service/webchat-tts.js +1 -1
  113. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  114. package/dist/src/agent/service.d.ts +8 -0
  115. package/dist/src/agent/service.js +21 -1
  116. package/dist/src/agent/service.js.map +1 -1
  117. package/dist/src/agent/tools/create-share-tool.js +27 -20
  118. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  119. package/dist/src/agent/tools/factory.js +1 -1
  120. package/dist/src/agent/tools/index.d.ts +0 -1
  121. package/dist/src/agent/tools/index.js +4 -5
  122. package/dist/src/agent/tools/shell.js +0 -13
  123. package/dist/src/agent/tools/shell.js.map +1 -1
  124. package/dist/src/agent/tools/workflow-tool.js +10 -4
  125. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  126. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
  127. package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
  128. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  129. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  131. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  132. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  133. package/dist/src/agent/workflow/builtins/index.js +11 -1
  134. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
  137. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  138. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  140. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  141. package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
  142. package/dist/src/agent/workflow/builtins/research.js +37 -6
  143. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  144. package/dist/src/agent/workflow/catalog.d.ts +5 -0
  145. package/dist/src/agent/workflow/catalog.js +6 -2
  146. package/dist/src/agent/workflow/catalog.js.map +1 -1
  147. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  148. package/dist/src/agent/workflow/index.d.ts +1 -1
  149. package/dist/src/agent/workflow/lint.d.ts +38 -0
  150. package/dist/src/agent/workflow/lint.js +74 -0
  151. package/dist/src/agent/workflow/lint.js.map +1 -0
  152. package/dist/src/agent/workflow/parser.js +13 -1
  153. package/dist/src/agent/workflow/parser.js.map +1 -1
  154. package/dist/src/agent/workflow/runtime.d.ts +3 -0
  155. package/dist/src/agent/workflow/runtime.js +76 -3
  156. package/dist/src/agent/workflow/runtime.js.map +1 -1
  157. package/dist/src/agent/workflow/types.d.ts +11 -1
  158. package/dist/src/browser/index.js +4 -4
  159. package/dist/src/browser/manager.d.ts +1 -3
  160. package/dist/src/browser/manager.js +0 -6
  161. package/dist/src/browser/manager.js.map +1 -1
  162. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  163. package/dist/src/browser/providers/browser-ext-install.js +38 -85
  164. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  165. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  166. package/dist/src/browser/providers/cloakbrowser.js +2 -55
  167. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  168. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  169. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  170. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  171. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  172. package/dist/src/channels/pairing/pairing-store.js +6 -6
  173. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  174. package/dist/src/chat-commands/builtins/session.js +1 -1
  175. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  176. package/dist/src/chat-commands/builtins/tts.js +2 -2
  177. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  178. package/dist/src/chat-commands/builtins/workflow.js +7 -2
  179. package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
  180. package/dist/src/chat-commands/context.d.ts +3 -0
  181. package/dist/src/chat-commands/context.js +21 -3
  182. package/dist/src/chat-commands/context.js.map +1 -1
  183. package/dist/src/chat-commands/session-key.d.ts +4 -37
  184. package/dist/src/chat-commands/session-key.js +49 -85
  185. package/dist/src/chat-commands/session-key.js.map +1 -1
  186. package/dist/src/chat-commands/types.d.ts +2 -0
  187. package/dist/src/cli/commands/agent/interactive.js +2 -2
  188. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  189. package/dist/src/cli/commands/agent/sessions.js +2 -2
  190. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  191. package/dist/src/cli/commands/agent.js +4 -5
  192. package/dist/src/cli/commands/agent.js.map +1 -1
  193. package/dist/src/cli/commands/channels.js +1 -5
  194. package/dist/src/cli/commands/channels.js.map +1 -1
  195. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  196. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  197. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  198. package/dist/src/cli/commands/gateway/logs.js +50 -17
  199. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  200. package/dist/src/cli/commands/image.js +22 -21
  201. package/dist/src/cli/commands/image.js.map +1 -1
  202. package/dist/src/cli/commands/session/utils.js +2 -2
  203. package/dist/src/cli/commands/session/utils.js.map +1 -1
  204. package/dist/src/cli/commands/update.js +26 -46
  205. package/dist/src/cli/commands/update.js.map +1 -1
  206. package/dist/src/cli/utils/session.d.ts +0 -5
  207. package/dist/src/cli/utils/session.js +1 -6
  208. package/dist/src/cli/utils/session.js.map +1 -1
  209. package/dist/src/commands/agents.config.js +1 -1
  210. package/dist/src/commands/agents.config.js.map +1 -1
  211. package/dist/src/config/agent-profile.js +5 -27
  212. package/dist/src/config/agent-profile.js.map +1 -1
  213. package/dist/src/config/index.js +2 -2
  214. package/dist/src/config/model-input.js +2 -5
  215. package/dist/src/config/model-input.js.map +1 -1
  216. package/dist/src/config/schema.d.ts +201 -217
  217. package/dist/src/config/schema.js +54 -39
  218. package/dist/src/config/schema.js.map +1 -1
  219. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  220. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  221. package/dist/src/daemon/install-plan.js +25 -1
  222. package/dist/src/daemon/install-plan.js.map +1 -1
  223. package/dist/src/daemon/launchd.d.ts +8 -0
  224. package/dist/src/daemon/launchd.js +5 -12
  225. package/dist/src/daemon/launchd.js.map +1 -1
  226. package/dist/src/daemon/schtasks.d.ts +25 -0
  227. package/dist/src/daemon/schtasks.js +166 -46
  228. package/dist/src/daemon/schtasks.js.map +1 -1
  229. package/dist/src/daemon/service.js +5 -4
  230. package/dist/src/daemon/service.js.map +1 -1
  231. package/dist/src/daemon/systemd.d.ts +6 -0
  232. package/dist/src/daemon/systemd.js +18 -3
  233. package/dist/src/daemon/systemd.js.map +1 -1
  234. package/dist/src/extensions/activation-context.js +0 -1
  235. package/dist/src/extensions/activation-context.js.map +1 -1
  236. package/dist/src/extensions/normalize-manifest.js +0 -1
  237. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  238. package/dist/src/extensions/types/manifest.d.ts +0 -2
  239. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  240. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  241. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  242. package/dist/src/gateway/agents-admin.js +10 -2
  243. package/dist/src/gateway/agents-admin.js.map +1 -1
  244. package/dist/src/gateway/heartbeat/service.js +1 -1
  245. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  246. package/dist/src/gateway/hono/app.js +1 -1
  247. package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
  248. package/dist/src/gateway/hono/lib/agent-model.js +24 -35
  249. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  250. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  251. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  252. package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
  253. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  254. package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
  255. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  256. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  257. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  258. package/dist/src/gateway/hono/routes/goals.js +1 -1
  259. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  260. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  261. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  262. package/dist/src/gateway/hono/routes/shares.js +14 -12
  263. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  264. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  265. package/dist/src/gateway/hono/routes/update.js +4 -2
  266. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  267. package/dist/src/gateway/hono/sse.js +16 -33
  268. package/dist/src/gateway/hono/sse.js.map +1 -1
  269. package/dist/src/gateway/lock.js +10 -10
  270. package/dist/src/gateway/lock.js.map +1 -1
  271. package/dist/src/gateway/ports.js +6 -6
  272. package/dist/src/gateway/ports.js.map +1 -1
  273. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  274. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  275. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  276. package/dist/src/gateway/service/run-gateway-agent.js +27 -11
  277. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  278. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  279. package/dist/src/gateway/service/sessions-api.js +8 -0
  280. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  281. package/dist/src/gateway/service.d.ts +0 -2
  282. package/dist/src/gateway/service.js +2 -7
  283. package/dist/src/gateway/service.js.map +1 -1
  284. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  285. package/dist/src/gateway/session-reset-service.js +54 -0
  286. package/dist/src/gateway/session-reset-service.js.map +1 -0
  287. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  288. package/dist/src/gateway/startup-readiness.js +1 -0
  289. package/dist/src/gateway/startup-readiness.js.map +1 -1
  290. package/dist/src/infra/gateway-processes.js +2 -2
  291. package/dist/src/infra/gateway-processes.js.map +1 -1
  292. package/dist/src/infra/run-command.d.ts +16 -0
  293. package/dist/src/infra/run-command.js +67 -0
  294. package/dist/src/infra/run-command.js.map +1 -0
  295. package/dist/src/infra/update-global.d.ts +45 -0
  296. package/dist/src/infra/update-global.js +224 -0
  297. package/dist/src/infra/update-global.js.map +1 -0
  298. package/dist/src/mcp/channel-bridge.js +1 -1
  299. package/dist/src/mcp/channel-shared.js +2 -1
  300. package/dist/src/mcp/channel-shared.js.map +1 -1
  301. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  302. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  303. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  304. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  305. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  306. package/dist/src/routing/agent-session-key.d.ts +58 -0
  307. package/dist/src/routing/agent-session-key.js +164 -0
  308. package/dist/src/routing/agent-session-key.js.map +1 -0
  309. package/dist/src/routing/index.d.ts +1 -1
  310. package/dist/src/routing/index.js +4 -2
  311. package/dist/src/routing/index.js.map +1 -1
  312. package/dist/src/routing/resolve-route.d.ts +15 -0
  313. package/dist/src/routing/resolve-route.js +41 -20
  314. package/dist/src/routing/resolve-route.js.map +1 -1
  315. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  316. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  317. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  318. package/dist/src/routing/session-key-utils.d.ts +24 -0
  319. package/dist/src/routing/session-key-utils.js +92 -0
  320. package/dist/src/routing/session-key-utils.js.map +1 -0
  321. package/dist/src/routing/session-key.d.ts +19 -49
  322. package/dist/src/routing/session-key.js +143 -116
  323. package/dist/src/routing/session-key.js.map +1 -1
  324. package/dist/src/session/index.d.ts +6 -0
  325. package/dist/src/session/index.js +7 -1
  326. package/dist/src/session/init-session-turn.d.ts +30 -0
  327. package/dist/src/session/init-session-turn.js +102 -0
  328. package/dist/src/session/init-session-turn.js.map +1 -0
  329. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  330. package/dist/src/session/lifecycle-timestamps.js +16 -0
  331. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  332. package/dist/src/session/manager.d.ts +7 -1
  333. package/dist/src/session/manager.js +8 -1
  334. package/dist/src/session/manager.js.map +1 -1
  335. package/dist/src/session/parity/transcript-paths.js +2 -2
  336. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  337. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  338. package/dist/src/session/reset-policy.d.ts +32 -0
  339. package/dist/src/session/reset-policy.js +65 -0
  340. package/dist/src/session/reset-policy.js.map +1 -0
  341. package/dist/src/session/reset-triggers.d.ts +20 -0
  342. package/dist/src/session/reset-triggers.js +63 -0
  343. package/dist/src/session/reset-triggers.js.map +1 -0
  344. package/dist/src/session/reset-type.d.ts +12 -0
  345. package/dist/src/session/reset-type.js +25 -0
  346. package/dist/src/session/reset-type.js.map +1 -0
  347. package/dist/src/session/resolve-session.d.ts +30 -0
  348. package/dist/src/session/resolve-session.js +93 -0
  349. package/dist/src/session/resolve-session.js.map +1 -0
  350. package/dist/src/session/session-title.js +3 -2
  351. package/dist/src/session/session-title.js.map +1 -1
  352. package/dist/src/session/store.d.ts +11 -4
  353. package/dist/src/session/store.js +57 -6
  354. package/dist/src/session/store.js.map +1 -1
  355. package/dist/src/session/transcript-events.js +2 -1
  356. package/dist/src/session/transcript-events.js.map +1 -1
  357. package/dist/src/share/share-url.d.ts +33 -0
  358. package/dist/src/share/share-url.js +56 -14
  359. package/dist/src/share/share-url.js.map +1 -1
  360. package/dist/src/tui/backends/embedded-backend.js +4 -9
  361. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  362. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  363. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  364. package/dist/src/tui/components/chat-log.js +3 -3
  365. package/dist/src/tui/components/chat-log.js.map +1 -1
  366. package/dist/src/tui/theme.d.ts +0 -2
  367. package/dist/src/tui/theme.js +1 -3
  368. package/dist/src/tui/theme.js.map +1 -1
  369. package/dist/src/tui/tui-commands.d.ts +3 -0
  370. package/dist/src/tui/tui-commands.js +45 -10
  371. package/dist/src/tui/tui-commands.js.map +1 -1
  372. package/dist/src/tui/tui-keybindings-file.js +1 -21
  373. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  374. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  375. package/dist/src/tui/tui-session-actions.js +88 -0
  376. package/dist/src/tui/tui-session-actions.js.map +1 -0
  377. package/dist/src/tui/tui.js +52 -47
  378. package/dist/src/tui/tui.js.map +1 -1
  379. package/dist/src/utils/string-coerce.d.ts +2 -0
  380. package/dist/src/utils/string-coerce.js +10 -1
  381. package/dist/src/utils/string-coerce.js.map +1 -1
  382. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  383. package/dist/src/voice/stt/config-slice.js +5 -26
  384. package/dist/src/voice/stt/config-slice.js.map +1 -1
  385. package/dist/src/voice/stt/types.d.ts +1 -18
  386. package/dist/src/voice/stt/types.js +4 -2
  387. package/dist/src/voice/stt/types.js.map +1 -1
  388. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  389. package/dist/src/voice/tts/config-slice.js +7 -38
  390. package/dist/src/voice/tts/config-slice.js.map +1 -1
  391. package/dist/src/voice/tts/merge-config.js +2 -48
  392. package/dist/src/voice/tts/merge-config.js.map +1 -1
  393. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  394. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  395. package/dist/src/voice/tts/types.d.ts +1 -29
  396. package/dist/src/voice/tts/types.js +19 -17
  397. package/dist/src/voice/tts/types.js.map +1 -1
  398. package/package.json +1 -4
  399. package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
  400. package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
  401. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  402. package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
  403. package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
  404. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  405. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  406. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  407. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"candidates.js","names":["parseModelRefUtil"],"sources":["../../../../src/agent/fallback/candidates.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { isProviderConfiguredSync } from '../../providers/index.js';\nimport { getDefaultModelSync } from '../../providers/index.js';\nimport { parseModelRef as parseModelRefUtil, normalizeProviderId } from '../models/selection.js';\n\nexport interface ModelCandidate {\n provider: string;\n model: string;\n}\n\nexport interface FallbackAttempt {\n provider: string;\n model: string;\n error: string;\n reason?: string;\n status?: number;\n code?: string;\n}\n\n// Get default model dynamically\nfunction getDefaultModelParts(config?: Config): { provider: string; model: string } {\n const defaultModel = getDefaultModelSync(config);\n const parts = defaultModel.split('/');\n return {\n provider: parts[0] || 'anthropic',\n model: parts[1] || 'claude-sonnet-4-5',\n };\n}\n\n/**\n * Parse model reference string into provider/model parts.\n * Uses the unified implementation from selection.ts.\n */\nfunction parseModelRef(raw: string, defaultProvider?: string): ModelCandidate | null {\n const result = parseModelRefUtil(raw, defaultProvider);\n if (!result) return null;\n return {\n provider: normalizeProviderId(result.provider),\n model: result.model,\n };\n}\n\nexport function resolveFallbackCandidates(params: {\n cfg: Config | undefined;\n provider: string;\n model: string;\n fallbacksOverride?: string[];\n}): ModelCandidate[] {\n const { cfg, provider: inputProvider, model: inputModel, fallbacksOverride } = params;\n\n const modelConfig = cfg?.agents?.defaults?.model;\n const primaryRef = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n const fallbacks = fallbacksOverride ?? (typeof modelConfig === 'object' ? modelConfig.fallbacks : undefined);\n\n const defaultParts = getDefaultModelParts(cfg);\n const primaryResolved = parseModelRef(primaryRef || getDefaultModelSync(cfg));\n const provider = inputProvider.trim() || primaryResolved?.provider || defaultParts.provider;\n const model = inputModel.trim() || primaryResolved?.model || defaultParts.model;\n\n const candidates: ModelCandidate[] = [];\n const seen = new Set<string>();\n\n const addCandidate = (c: ModelCandidate) => {\n const key = `${c.provider}/${c.model}`.toLowerCase();\n if (seen.has(key)) return;\n // Skip providers that are not configured (no API key)\n if (!isProviderConfiguredSync(c.provider)) return;\n seen.add(key);\n candidates.push(c);\n };\n\n addCandidate({ provider, model });\n\n if (fallbacks) {\n for (const fb of fallbacks) {\n const resolved = parseModelRef(fb, provider);\n if (resolved) addCandidate(resolved);\n }\n }\n\n if (!fallbacksOverride && primaryResolved) {\n addCandidate(primaryResolved);\n }\n\n return candidates;\n}\n"],"mappings":";;;gBACoE;AAmBpE,SAAS,qBAAqB,QAAsD;CAElF,MAAM,QADe,oBAAoB,OACf,CAAC,MAAM,IAAI;AACrC,QAAO;EACL,UAAU,MAAM,MAAM;EACtB,OAAO,MAAM,MAAM;EACpB;;;;;;AAOH,SAAS,cAAc,KAAa,iBAAiD;CACnF,MAAM,SAASA,gBAAkB,KAAK,gBAAgB;AACtD,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO;EACL,UAAU,oBAAoB,OAAO,SAAS;EAC9C,OAAO,OAAO;EACf;;AAGH,SAAgB,0BAA0B,QAKrB;CACnB,MAAM,EAAE,KAAK,UAAU,eAAe,OAAO,YAAY,sBAAsB;CAE/E,MAAM,cAAc,KAAK,QAAQ,UAAU;CAC3C,MAAM,aAAa,OAAO,gBAAgB,WAAW,cAAc,aAAa;CAChF,MAAM,YAAY,sBAAsB,OAAO,gBAAgB,WAAW,YAAY,YAAY,KAAA;CAElG,MAAM,eAAe,qBAAqB,IAAI;CAC9C,MAAM,kBAAkB,cAAc,cAAc,oBAAoB,IAAI,CAAC;CAC7E,MAAM,WAAW,cAAc,MAAM,IAAI,iBAAiB,YAAY,aAAa;CACnF,MAAM,QAAQ,WAAW,MAAM,IAAI,iBAAiB,SAAS,aAAa;CAE1E,MAAM,aAA+B,EAAE;CACvC,MAAM,uBAAO,IAAI,KAAa;CAE9B,MAAM,gBAAgB,MAAsB;EAC1C,MAAM,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ,aAAa;AACpD,MAAI,KAAK,IAAI,IAAI,CAAE;AAEnB,MAAI,CAAC,yBAAyB,EAAE,SAAS,CAAE;AAC3C,OAAK,IAAI,IAAI;AACb,aAAW,KAAK,EAAE;;AAGpB,cAAa;EAAE;EAAU;EAAO,CAAC;AAEjC,KAAI,UACF,MAAK,MAAM,MAAM,WAAW;EAC1B,MAAM,WAAW,cAAc,IAAI,SAAS;AAC5C,MAAI,SAAU,cAAa,SAAS;;AAIxC,KAAI,CAAC,qBAAqB,gBACxB,cAAa,gBAAgB;AAG/B,QAAO"}
1
+ {"version":3,"file":"candidates.js","names":["parseModelRefUtil"],"sources":["../../../../src/agent/fallback/candidates.ts"],"sourcesContent":["import type { Config } from '../../config/schema.js';\nimport { isProviderConfiguredSync } from '../../providers/index.js';\nimport { getDefaultModelSync } from '../../providers/index.js';\nimport { parseModelRef as parseModelRefUtil, normalizeProviderId } from '../models/selection.js';\n\nexport interface ModelCandidate {\n provider: string;\n model: string;\n}\n\nexport interface FallbackAttempt {\n provider: string;\n model: string;\n error: string;\n reason?: string;\n status?: number;\n code?: string;\n}\n\n// Get default model dynamically\nfunction getDefaultModelParts(config?: Config): { provider: string; model: string } {\n const defaultModel = getDefaultModelSync(config);\n const parts = defaultModel.split('/');\n return {\n provider: parts[0] || 'anthropic',\n model: parts[1] || 'claude-sonnet-4-5',\n };\n}\n\n/**\n * Parse model reference string into provider/model parts.\n * Uses the unified implementation from selection.ts.\n */\nfunction parseModelRef(raw: string, defaultProvider?: string): ModelCandidate | null {\n const result = parseModelRefUtil(raw, defaultProvider);\n if (!result) return null;\n return {\n provider: normalizeProviderId(result.provider),\n model: result.model,\n };\n}\n\nexport function resolveFallbackCandidates(params: {\n cfg: Config | undefined;\n provider: string;\n model: string;\n fallbacksOverride?: string[];\n}): ModelCandidate[] {\n const { cfg, provider: inputProvider, model: inputModel, fallbacksOverride } = params;\n\n const modelConfig = cfg?.agents?.defaults?.model;\n const primaryRef = modelConfig?.primary;\n const fallbacks = fallbacksOverride ?? modelConfig?.fallbacks;\n\n const defaultParts = getDefaultModelParts(cfg);\n const primaryResolved = parseModelRef(primaryRef || getDefaultModelSync(cfg));\n const provider = inputProvider.trim() || primaryResolved?.provider || defaultParts.provider;\n const model = inputModel.trim() || primaryResolved?.model || defaultParts.model;\n\n const candidates: ModelCandidate[] = [];\n const seen = new Set<string>();\n\n const addCandidate = (c: ModelCandidate) => {\n const key = `${c.provider}/${c.model}`.toLowerCase();\n if (seen.has(key)) return;\n // Skip providers that are not configured (no API key)\n if (!isProviderConfiguredSync(c.provider)) return;\n seen.add(key);\n candidates.push(c);\n };\n\n addCandidate({ provider, model });\n\n if (fallbacks) {\n for (const fb of fallbacks) {\n const resolved = parseModelRef(fb, provider);\n if (resolved) addCandidate(resolved);\n }\n }\n\n if (!fallbacksOverride && primaryResolved) {\n addCandidate(primaryResolved);\n }\n\n return candidates;\n}\n"],"mappings":";;;gBACoE;AAmBpE,SAAS,qBAAqB,QAAsD;CAElF,MAAM,QADe,oBAAoB,OACf,CAAC,MAAM,IAAI;AACrC,QAAO;EACL,UAAU,MAAM,MAAM;EACtB,OAAO,MAAM,MAAM;EACpB;;;;;;AAOH,SAAS,cAAc,KAAa,iBAAiD;CACnF,MAAM,SAASA,gBAAkB,KAAK,gBAAgB;AACtD,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO;EACL,UAAU,oBAAoB,OAAO,SAAS;EAC9C,OAAO,OAAO;EACf;;AAGH,SAAgB,0BAA0B,QAKrB;CACnB,MAAM,EAAE,KAAK,UAAU,eAAe,OAAO,YAAY,sBAAsB;CAE/E,MAAM,cAAc,KAAK,QAAQ,UAAU;CAC3C,MAAM,aAAa,aAAa;CAChC,MAAM,YAAY,qBAAqB,aAAa;CAEpD,MAAM,eAAe,qBAAqB,IAAI;CAC9C,MAAM,kBAAkB,cAAc,cAAc,oBAAoB,IAAI,CAAC;CAC7E,MAAM,WAAW,cAAc,MAAM,IAAI,iBAAiB,YAAY,aAAa;CACnF,MAAM,QAAQ,WAAW,MAAM,IAAI,iBAAiB,SAAS,aAAa;CAE1E,MAAM,aAA+B,EAAE;CACvC,MAAM,uBAAO,IAAI,KAAa;CAE9B,MAAM,gBAAgB,MAAsB;EAC1C,MAAM,MAAM,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ,aAAa;AACpD,MAAI,KAAK,IAAI,IAAI,CAAE;AAEnB,MAAI,CAAC,yBAAyB,EAAE,SAAS,CAAE;AAC3C,OAAK,IAAI,IAAI;AACb,aAAW,KAAK,EAAE;;AAGpB,cAAa;EAAE;EAAU;EAAO,CAAC;AAEjC,KAAI,UACF,MAAK,MAAM,MAAM,WAAW;EAC1B,MAAM,WAAW,cAAc,IAAI,SAAS;AAC5C,MAAI,SAAU,cAAa,SAAS;;AAIxC,KAAI,CAAC,qBAAqB,gBACxB,cAAa,gBAAgB;AAG/B,QAAO"}
@@ -5,8 +5,6 @@ export interface PersistentGoalApis {
5
5
  getSessionMetadata(key: string): Promise<SessionMetadata | null>;
6
6
  updateSessionMetadata(key: string, updates: Partial<SessionMetadata>): Promise<void>;
7
7
  loadMessages(key: string): Promise<AgentMessage[]>;
8
- /** @deprecated Prefer {@link appendAssistantReceipt} for runtime turns. */
9
- saveMessages(key: string, messages: AgentMessage[]): Promise<void>;
10
8
  appendAssistantReceipt(sessionKey: string, text: string): Promise<void>;
11
9
  scheduleContinuation(sessionKey: string, message: string): void;
12
10
  /**
@@ -55,7 +55,6 @@ var PersistentGoalService = class {
55
55
  this.opts.onSessionMetadataUpdated?.(k);
56
56
  },
57
57
  loadMessages: (k) => this.opts.sessionStore.loadMessages(k),
58
- saveMessages: (k, m) => this.opts.sessionStore.saveMessages(k, m),
59
58
  appendAssistantReceipt: async (k, text) => {
60
59
  const trimmed = text.trim();
61
60
  if (!trimmed) return;
@@ -1 +1 @@
1
- {"version":3,"file":"persistent-goal-service.js","names":["parseRoutingSessionKey"],"sources":["../../../../src/agent/goals/persistent-goal-service.ts"],"sourcesContent":["/**\n * PersistentGoalService — owns the \"/goal\" runtime: continuation scheduling,\n * the `PersistentGoalApis` bag that command handlers receive, and the post-turn\n * verdict hook called from `OutboundCoordinator`.\n *\n * Previously this logic was scattered across `AgentService`:\n * - `setPersistentGoalWebchatContinuationScheduler` + a private callback field\n * - `schedulePersistentGoalContinuation` (bus vs webchat fork)\n * - `getPersistentGoalApisForCommand` (~40-line API factory)\n * - `recordPersistentGoalStreamOutcome` / `takePersistentGoalStreamOutcome`\n * - the `/goal` half of `emitSessionTurnComplete` (delegated to\n * `handlePersistentGoalPostTurn`)\n *\n * Concentrating it here gives the rest of `AgentService` a cleaner surface\n * (one collaborator instead of five methods) and makes the goal runtime\n * unit-testable in isolation.\n */\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionStore } from '../../session/store.js';\nimport type { SessionStateBag } from '../session/index.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../../routing/session-key.js';\nimport { appendPiTranscriptMessage } from '../../session/parity/jsonl-transcript-io.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { PersistentGoalApis } from './persistent-goal-apis.js';\nimport { handlePersistentGoalPostTurn } from './post-turn.js';\n\nconst log = createLogger('PersistentGoalService');\n\nexport interface PersistentGoalRouting {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundMetadata?: Record<string, unknown>;\n}\n\nexport interface SessionTurnCompletionForGoal {\n sessionKey: string;\n channel: string;\n chatId: string;\n assistantPlainText: string;\n aborted: boolean;\n streamError?: string;\n skipPersistentGoalPostTurn?: boolean;\n outboundMetadata?: Record<string, unknown>;\n}\n\nexport interface PersistentGoalServiceOptions {\n bus: MessageBus;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n sessionState: SessionStateBag;\n /** Effective config snapshot accessor. */\n getConfig: () => Config | undefined;\n /** Resolve the workspace directory for `appendAssistantReceipt` writes. */\n getResolvedWorkspaceForSession: (sessionKey: string) => string;\n /** Notify the gateway UI after a metadata change (replaces the in-bag emit). */\n onSessionMetadataUpdated?: (sessionKey: string) => void;\n /** Push an assistant token + transcript refresh into a live webchat stream. */\n notifyWebchatTranscriptAppend: (sessionKey: string, assistantText: string) => void;\n}\n\nexport class PersistentGoalService {\n private readonly opts: PersistentGoalServiceOptions;\n /** Gateway only: webchat continuations bypass the bus and reuse `runGatewayAgent`. */\n private webchatContinuationScheduler?: (sessionKey: string, message: string) => void;\n\n constructor(opts: PersistentGoalServiceOptions) {\n this.opts = opts;\n }\n\n /** Register the gateway-side webchat continuation hook (set from `web-agent` wiring). */\n setWebchatContinuationScheduler(\n fn: ((sessionKey: string, message: string) => void) | undefined,\n ): void {\n this.webchatContinuationScheduler = fn;\n }\n\n /**\n * Continue a session after `/goal` decides the previous turn needs follow-up.\n * Webchat sessions go through the scheduler; bus-driven channels re-publish the\n * follow-up message as an inbound bus event so the existing inbound loop picks it up.\n */\n scheduleContinuation(\n sessionKey: string,\n message: string,\n routing: { channel: string; chatId: string; inboundMetadata?: Record<string, unknown> },\n ): void {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed?.source === 'webchat' && this.webchatContinuationScheduler) {\n this.webchatContinuationScheduler(sessionKey, message);\n return;\n }\n queueMicrotask(() => {\n void this.opts.bus\n .publishInbound({\n channel: routing.channel,\n chat_id: routing.chatId,\n sender_id: 'persistent-goal',\n content: message,\n metadata: { sessionKey, ...routing.inboundMetadata },\n })\n .catch((err) => {\n log.warn({ err, sessionKey }, 'Persistent goal: publishInbound failed');\n });\n });\n }\n\n /** Build the per-command `PersistentGoalApis` bag (transcript writers + scheduler closures). */\n buildApisForRouting(routing: PersistentGoalRouting): PersistentGoalApis {\n return {\n getSessionMetadata: (k) => this.opts.sessionStore.getMetadata(k),\n updateSessionMetadata: async (k, u) => {\n await this.opts.sessionStore.updateMetadata(k, u);\n this.opts.onSessionMetadataUpdated?.(k);\n },\n loadMessages: (k) => this.opts.sessionStore.loadMessages(k),\n saveMessages: (k, m) => this.opts.sessionStore.saveMessages(k, m),\n appendAssistantReceipt: async (k, text) => {\n const trimmed = text.trim();\n if (!trimmed) return;\n const { absPath } = await this.opts.sessionStore.resolveTranscriptPath(k);\n const workspaceDir = this.opts.getResolvedWorkspaceForSession(k);\n await appendPiTranscriptMessage({\n absPath,\n cwd: workspaceDir,\n message: {\n role: 'assistant',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n } as AgentMessage,\n sessionKey: k,\n });\n this.opts.notifyWebchatTranscriptAppend(k, trimmed);\n },\n scheduleContinuation: (sk, msg) => {\n this.scheduleContinuation(sk, msg, {\n channel: routing.channel,\n chatId: routing.chatId,\n inboundMetadata: routing.inboundMetadata,\n });\n },\n inboundConcurrentDepth: (sk) => this.opts.sessionState.getInboundTurnDepth(sk),\n };\n }\n\n recordStreamOutcome(sessionKey: string, outcome: { skipPersistentGoalPostTurn: boolean }): void {\n this.opts.sessionState.recordPersistentGoalStreamOutcome(sessionKey, outcome);\n }\n\n takeStreamOutcome(sessionKey: string): { skipPersistentGoalPostTurn: boolean } | undefined {\n return this.opts.sessionState.takePersistentGoalStreamOutcome(sessionKey);\n }\n\n /**\n * Run the `/goal` post-turn verdict for a completed user turn (called from\n * `OutboundCoordinator.emitSessionTurnComplete`).\n */\n async runPostTurn(payload: SessionTurnCompletionForGoal): Promise<void> {\n const apis = this.buildApisForRouting({\n sessionKey: payload.sessionKey,\n channel: payload.channel,\n chatId: payload.chatId,\n inboundMetadata: payload.outboundMetadata,\n });\n\n const src = parseRoutingSessionKey(payload.sessionKey)?.source;\n const isWebchat = src === 'webchat';\n const publishVerdict =\n !isWebchat && payload.channel !== 'cli'\n ? async (text: string) => {\n await this.opts.bus.publishOutbound({\n channel: payload.channel,\n chat_id: payload.chatId,\n content: text,\n type: 'message',\n metadata: {\n accountId: payload.outboundMetadata?.accountId,\n threadId: payload.outboundMetadata?.threadId,\n },\n });\n }\n : undefined;\n\n let runtimeSessionModelRef: string | undefined;\n try {\n runtimeSessionModelRef = this.opts.modelManager.getModelForSession(payload.sessionKey);\n } catch {\n runtimeSessionModelRef = undefined;\n }\n\n await handlePersistentGoalPostTurn({\n apis,\n sessionKey: payload.sessionKey,\n assistantPlainText: payload.assistantPlainText,\n aborted: payload.aborted,\n ...(payload.streamError !== undefined ? { streamError: payload.streamError } : {}),\n skipPersistentGoalPostTurn: payload.skipPersistentGoalPostTurn ?? false,\n config: this.opts.getConfig(),\n runtimeSessionModelRef,\n publishVerdictToChannel: publishVerdict,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"persistent-goal-service.js","names":["parseRoutingSessionKey"],"sources":["../../../../src/agent/goals/persistent-goal-service.ts"],"sourcesContent":["/**\n * PersistentGoalService — owns the \"/goal\" runtime: continuation scheduling,\n * the `PersistentGoalApis` bag that command handlers receive, and the post-turn\n * verdict hook called from `OutboundCoordinator`.\n *\n * Previously this logic was scattered across `AgentService`:\n * - `setPersistentGoalWebchatContinuationScheduler` + a private callback field\n * - `schedulePersistentGoalContinuation` (bus vs webchat fork)\n * - `getPersistentGoalApisForCommand` (~40-line API factory)\n * - `recordPersistentGoalStreamOutcome` / `takePersistentGoalStreamOutcome`\n * - the `/goal` half of `emitSessionTurnComplete` (delegated to\n * `handlePersistentGoalPostTurn`)\n *\n * Concentrating it here gives the rest of `AgentService` a cleaner surface\n * (one collaborator instead of five methods) and makes the goal runtime\n * unit-testable in isolation.\n */\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { Config } from '../../config/schema.js';\nimport type { MessageBus } from '../../infra/bus/index.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionStore } from '../../session/store.js';\nimport type { SessionStateBag } from '../session/index.js';\nimport { parseSessionKey as parseRoutingSessionKey } from '../../routing/session-key.js';\nimport { appendPiTranscriptMessage } from '../../session/parity/jsonl-transcript-io.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type { PersistentGoalApis } from './persistent-goal-apis.js';\nimport { handlePersistentGoalPostTurn } from './post-turn.js';\n\nconst log = createLogger('PersistentGoalService');\n\nexport interface PersistentGoalRouting {\n sessionKey: string;\n channel: string;\n chatId: string;\n inboundMetadata?: Record<string, unknown>;\n}\n\nexport interface SessionTurnCompletionForGoal {\n sessionKey: string;\n channel: string;\n chatId: string;\n assistantPlainText: string;\n aborted: boolean;\n streamError?: string;\n skipPersistentGoalPostTurn?: boolean;\n outboundMetadata?: Record<string, unknown>;\n}\n\nexport interface PersistentGoalServiceOptions {\n bus: MessageBus;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n sessionState: SessionStateBag;\n /** Effective config snapshot accessor. */\n getConfig: () => Config | undefined;\n /** Resolve the workspace directory for `appendAssistantReceipt` writes. */\n getResolvedWorkspaceForSession: (sessionKey: string) => string;\n /** Notify the gateway UI after a metadata change (replaces the in-bag emit). */\n onSessionMetadataUpdated?: (sessionKey: string) => void;\n /** Push an assistant token + transcript refresh into a live webchat stream. */\n notifyWebchatTranscriptAppend: (sessionKey: string, assistantText: string) => void;\n}\n\nexport class PersistentGoalService {\n private readonly opts: PersistentGoalServiceOptions;\n /** Gateway only: webchat continuations bypass the bus and reuse `runGatewayAgent`. */\n private webchatContinuationScheduler?: (sessionKey: string, message: string) => void;\n\n constructor(opts: PersistentGoalServiceOptions) {\n this.opts = opts;\n }\n\n /** Register the gateway-side webchat continuation hook (set from `web-agent` wiring). */\n setWebchatContinuationScheduler(\n fn: ((sessionKey: string, message: string) => void) | undefined,\n ): void {\n this.webchatContinuationScheduler = fn;\n }\n\n /**\n * Continue a session after `/goal` decides the previous turn needs follow-up.\n * Webchat sessions go through the scheduler; bus-driven channels re-publish the\n * follow-up message as an inbound bus event so the existing inbound loop picks it up.\n */\n scheduleContinuation(\n sessionKey: string,\n message: string,\n routing: { channel: string; chatId: string; inboundMetadata?: Record<string, unknown> },\n ): void {\n const parsed = parseRoutingSessionKey(sessionKey);\n if (parsed?.source === 'webchat' && this.webchatContinuationScheduler) {\n this.webchatContinuationScheduler(sessionKey, message);\n return;\n }\n queueMicrotask(() => {\n void this.opts.bus\n .publishInbound({\n channel: routing.channel,\n chat_id: routing.chatId,\n sender_id: 'persistent-goal',\n content: message,\n metadata: { sessionKey, ...routing.inboundMetadata },\n })\n .catch((err) => {\n log.warn({ err, sessionKey }, 'Persistent goal: publishInbound failed');\n });\n });\n }\n\n /** Build the per-command `PersistentGoalApis` bag (transcript writers + scheduler closures). */\n buildApisForRouting(routing: PersistentGoalRouting): PersistentGoalApis {\n return {\n getSessionMetadata: (k) => this.opts.sessionStore.getMetadata(k),\n updateSessionMetadata: async (k, u) => {\n await this.opts.sessionStore.updateMetadata(k, u);\n this.opts.onSessionMetadataUpdated?.(k);\n },\n loadMessages: (k) => this.opts.sessionStore.loadMessages(k),\n appendAssistantReceipt: async (k, text) => {\n const trimmed = text.trim();\n if (!trimmed) return;\n const { absPath } = await this.opts.sessionStore.resolveTranscriptPath(k);\n const workspaceDir = this.opts.getResolvedWorkspaceForSession(k);\n await appendPiTranscriptMessage({\n absPath,\n cwd: workspaceDir,\n message: {\n role: 'assistant',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n } as AgentMessage,\n sessionKey: k,\n });\n this.opts.notifyWebchatTranscriptAppend(k, trimmed);\n },\n scheduleContinuation: (sk, msg) => {\n this.scheduleContinuation(sk, msg, {\n channel: routing.channel,\n chatId: routing.chatId,\n inboundMetadata: routing.inboundMetadata,\n });\n },\n inboundConcurrentDepth: (sk) => this.opts.sessionState.getInboundTurnDepth(sk),\n };\n }\n\n recordStreamOutcome(sessionKey: string, outcome: { skipPersistentGoalPostTurn: boolean }): void {\n this.opts.sessionState.recordPersistentGoalStreamOutcome(sessionKey, outcome);\n }\n\n takeStreamOutcome(sessionKey: string): { skipPersistentGoalPostTurn: boolean } | undefined {\n return this.opts.sessionState.takePersistentGoalStreamOutcome(sessionKey);\n }\n\n /**\n * Run the `/goal` post-turn verdict for a completed user turn (called from\n * `OutboundCoordinator.emitSessionTurnComplete`).\n */\n async runPostTurn(payload: SessionTurnCompletionForGoal): Promise<void> {\n const apis = this.buildApisForRouting({\n sessionKey: payload.sessionKey,\n channel: payload.channel,\n chatId: payload.chatId,\n inboundMetadata: payload.outboundMetadata,\n });\n\n const src = parseRoutingSessionKey(payload.sessionKey)?.source;\n const isWebchat = src === 'webchat';\n const publishVerdict =\n !isWebchat && payload.channel !== 'cli'\n ? async (text: string) => {\n await this.opts.bus.publishOutbound({\n channel: payload.channel,\n chat_id: payload.chatId,\n content: text,\n type: 'message',\n metadata: {\n accountId: payload.outboundMetadata?.accountId,\n threadId: payload.outboundMetadata?.threadId,\n },\n });\n }\n : undefined;\n\n let runtimeSessionModelRef: string | undefined;\n try {\n runtimeSessionModelRef = this.opts.modelManager.getModelForSession(payload.sessionKey);\n } catch {\n runtimeSessionModelRef = undefined;\n }\n\n await handlePersistentGoalPostTurn({\n apis,\n sessionKey: payload.sessionKey,\n assistantPlainText: payload.assistantPlainText,\n aborted: payload.aborted,\n ...(payload.streamError !== undefined ? { streamError: payload.streamError } : {}),\n skipPersistentGoalPostTurn: payload.skipPersistentGoalPostTurn ?? false,\n config: this.opts.getConfig(),\n runtimeSessionModelRef,\n publishVerdictToChannel: publishVerdict,\n });\n }\n}\n"],"mappings":";;;;;;kBAyByF;aAEpC;AAIrD,MAAM,MAAM,aAAa,wBAAwB;AAmCjD,IAAa,wBAAb,MAAmC;CACjC;;CAEA;CAEA,YAAY,MAAoC;AAC9C,OAAK,OAAO;;;CAId,gCACE,IACM;AACN,OAAK,+BAA+B;;;;;;;CAQtC,qBACE,YACA,SACA,SACM;AAEN,MADeA,gBAAuB,WAC5B,EAAE,WAAW,aAAa,KAAK,8BAA8B;AACrE,QAAK,6BAA6B,YAAY,QAAQ;AACtD;;AAEF,uBAAqB;AACd,QAAK,KAAK,IACZ,eAAe;IACd,SAAS,QAAQ;IACjB,SAAS,QAAQ;IACjB,WAAW;IACX,SAAS;IACT,UAAU;KAAE;KAAY,GAAG,QAAQ;KAAiB;IACrD,CAAC,CACD,OAAO,QAAQ;AACd,QAAI,KAAK;KAAE;KAAK;KAAY,EAAE,yCAAyC;KACvE;IACJ;;;CAIJ,oBAAoB,SAAoD;AACtE,SAAO;GACL,qBAAqB,MAAM,KAAK,KAAK,aAAa,YAAY,EAAE;GAChE,uBAAuB,OAAO,GAAG,MAAM;AACrC,UAAM,KAAK,KAAK,aAAa,eAAe,GAAG,EAAE;AACjD,SAAK,KAAK,2BAA2B,EAAE;;GAEzC,eAAe,MAAM,KAAK,KAAK,aAAa,aAAa,EAAE;GAC3D,wBAAwB,OAAO,GAAG,SAAS;IACzC,MAAM,UAAU,KAAK,MAAM;AAC3B,QAAI,CAAC,QAAS;IACd,MAAM,EAAE,YAAY,MAAM,KAAK,KAAK,aAAa,sBAAsB,EAAE;AAEzE,UAAM,0BAA0B;KAC9B;KACA,KAHmB,KAAK,KAAK,+BAA+B,EAG3C;KACjB,SAAS;MACP,MAAM;MACN,SAAS,CAAC;OAAE,MAAM;OAAQ,MAAM;OAAS,CAAC;MAC1C,WAAW,KAAK,KAAK;MACtB;KACD,YAAY;KACb,CAAC;AACF,SAAK,KAAK,8BAA8B,GAAG,QAAQ;;GAErD,uBAAuB,IAAI,QAAQ;AACjC,SAAK,qBAAqB,IAAI,KAAK;KACjC,SAAS,QAAQ;KACjB,QAAQ,QAAQ;KAChB,iBAAiB,QAAQ;KAC1B,CAAC;;GAEJ,yBAAyB,OAAO,KAAK,KAAK,aAAa,oBAAoB,GAAG;GAC/E;;CAGH,oBAAoB,YAAoB,SAAwD;AAC9F,OAAK,KAAK,aAAa,kCAAkC,YAAY,QAAQ;;CAG/E,kBAAkB,YAAyE;AACzF,SAAO,KAAK,KAAK,aAAa,gCAAgC,WAAW;;;;;;CAO3E,MAAM,YAAY,SAAsD;EACtE,MAAM,OAAO,KAAK,oBAAoB;GACpC,YAAY,QAAQ;GACpB,SAAS,QAAQ;GACjB,QAAQ,QAAQ;GAChB,iBAAiB,QAAQ;GAC1B,CAAC;EAIF,MAAM,iBACJ,EAHUA,gBAAuB,QAAQ,WAAW,EAAE,WAC9B,cAEV,QAAQ,YAAY,QAC9B,OAAO,SAAiB;AACtB,SAAM,KAAK,KAAK,IAAI,gBAAgB;IAClC,SAAS,QAAQ;IACjB,SAAS,QAAQ;IACjB,SAAS;IACT,MAAM;IACN,UAAU;KACR,WAAW,QAAQ,kBAAkB;KACrC,UAAU,QAAQ,kBAAkB;KACrC;IACF,CAAC;MAEJ,KAAA;EAEN,IAAI;AACJ,MAAI;AACF,4BAAyB,KAAK,KAAK,aAAa,mBAAmB,QAAQ,WAAW;UAChF;AACN,4BAAyB,KAAA;;AAG3B,QAAM,6BAA6B;GACjC;GACA,YAAY,QAAQ;GACpB,oBAAoB,QAAQ;GAC5B,SAAS,QAAQ;GACjB,GAAI,QAAQ,gBAAgB,KAAA,IAAY,EAAE,aAAa,QAAQ,aAAa,GAAG,EAAE;GACjF,4BAA4B,QAAQ,8BAA8B;GAClE,QAAQ,KAAK,KAAK,WAAW;GAC7B;GACA,yBAAyB;GAC1B,CAAC"}
@@ -16,19 +16,9 @@ import "../../media-generation/index.js";
16
16
  *
17
17
  * Decision table — see docs/image-generation-rearchitecture.md §6.3
18
18
  */
19
- /**
20
- * Narrow `provider.capabilities` to the new nested shape. The registry accepts
21
- * the legacy flat shape too, but normalization only understands the new one;
22
- * legacy fields surface here as `undefined`.
23
- */
24
- function asNewCapabilities(caps) {
25
- if (!caps) return {};
26
- if (caps.supportsEdit !== void 0 && caps.generate === void 0 && caps.edit === void 0) return {};
27
- return caps;
28
- }
29
19
  function resolveImageGenerationOverrides(params) {
30
20
  const provider = params.provider;
31
- const caps = asNewCapabilities(provider.capabilities);
21
+ const caps = provider.capabilities ?? {};
32
22
  const isEdit = (params.inputImages?.length ?? 0) > 0;
33
23
  assertEditSupported(provider, isEdit, params.inputImages);
34
24
  const modeCaps = isEdit ? caps.edit : caps.generate;
@@ -159,7 +149,7 @@ function resolveGeometry(params) {
159
149
  }
160
150
  function assertEditSupported(provider, isEdit, inputImages) {
161
151
  if (!isEdit) return;
162
- const editCap = asNewCapabilities(provider.capabilities).edit;
152
+ const editCap = (provider.capabilities ?? {}).edit;
163
153
  if (!(editCap?.enabled === true)) throw new Error(`${provider.id} image editing is not supported.`);
164
154
  const max = editCap.maxInputImages;
165
155
  if (typeof max === "number" && (inputImages?.length ?? 0) > max) throw new Error(`${provider.id} accepts at most ${max} input image(s); got ${inputImages?.length ?? 0}.`);
@@ -1 +1 @@
1
- {"version":3,"file":"normalization.js","names":[],"sources":["../../../../../src/agent/image/generation/normalization.ts"],"sourcesContent":["/**\n * Image-generation parameter normalization.\n *\n * Reconciles user-supplied geometry / output overrides against the active\n * provider's declared {@link ImageGenerationProviderCapabilities}. Returns:\n * - the actually-applied values to forward to the provider\n * - per-field {@link MediaNormalizationEntry} (requested / applied / derivedFrom)\n * - a list of {@link ImageGenerationIgnoredOverride} entries for unsupported\n * soft fields (so the LLM / UI can surface a Note)\n *\n * Hard errors (unsupported edit, too many input images) are thrown so the\n * caller can fail fast — they are NOT downgraded to ignored overrides.\n *\n * Decision table — see docs/image-generation-rearchitecture.md §6.3\n */\n\nimport {\n resolveClosestAspectRatio,\n resolveClosestResolution,\n resolveClosestSize,\n} from '../../media-generation/index.js';\nimport type {\n ImageGenerationBackground,\n ImageGenerationCapabilitiesLegacy,\n ImageGenerationGeometryCapability,\n ImageGenerationIgnoredOverride,\n ImageGenerationIgnoredOverrideKey,\n ImageGenerationNormalization,\n ImageGenerationOutputFormat,\n ImageGenerationProvider,\n ImageGenerationProviderCapabilities,\n ImageGenerationQuality,\n ImageGenerationResolution,\n ImageGenerationSourceImage,\n} from './types.js';\n\n/**\n * Narrow `provider.capabilities` to the new nested shape. The registry accepts\n * the legacy flat shape too, but normalization only understands the new one;\n * legacy fields surface here as `undefined`.\n */\nfunction asNewCapabilities(\n caps: ImageGenerationProviderCapabilities | ImageGenerationCapabilitiesLegacy | undefined,\n): ImageGenerationProviderCapabilities {\n if (!caps) return {};\n // Legacy flat shape exposes `supportsEdit` (boolean). When seen, treat as\n // an empty new-shape capability map so normalization defaults kick in.\n if ((caps as ImageGenerationCapabilitiesLegacy).supportsEdit !== undefined &&\n (caps as ImageGenerationProviderCapabilities).generate === undefined &&\n (caps as ImageGenerationProviderCapabilities).edit === undefined) {\n return {};\n }\n return caps as ImageGenerationProviderCapabilities;\n}\n\nexport interface ResolveImageGenerationOverridesParams {\n provider: ImageGenerationProvider;\n size?: string;\n aspectRatio?: string;\n resolution?: ImageGenerationResolution;\n quality?: ImageGenerationQuality;\n outputFormat?: ImageGenerationOutputFormat;\n background?: ImageGenerationBackground;\n inputImages?: ImageGenerationSourceImage[];\n}\n\nexport interface ResolvedImageGenerationOverrides {\n size?: string;\n aspectRatio?: string;\n resolution?: ImageGenerationResolution;\n quality?: ImageGenerationQuality;\n outputFormat?: ImageGenerationOutputFormat;\n background?: ImageGenerationBackground;\n ignoredOverrides: ImageGenerationIgnoredOverride[];\n normalization?: ImageGenerationNormalization;\n}\n\nexport function resolveImageGenerationOverrides(\n params: ResolveImageGenerationOverridesParams,\n): ResolvedImageGenerationOverrides {\n const provider = params.provider;\n const caps = asNewCapabilities(provider.capabilities);\n const isEdit = (params.inputImages?.length ?? 0) > 0;\n\n // Hard checks (throw, do not downgrade) ---------------------------------\n assertEditSupported(provider, isEdit, params.inputImages);\n\n const modeCaps = isEdit ? caps.edit : caps.generate;\n const supportsSize = modeCaps?.supportsSize === true;\n const supportsAspectRatio = modeCaps?.supportsAspectRatio === true;\n const supportsResolution = caps.generate?.supportsResolution === true && !isEdit;\n\n const ignoredOverrides: ImageGenerationIgnoredOverride[] = [];\n const normalization: ImageGenerationNormalization = {};\n\n // Geometry --------------------------------------------------------------\n const { size, aspectRatio, resolution } = resolveGeometry({\n requestedSize: params.size,\n requestedAspectRatio: params.aspectRatio,\n requestedResolution: params.resolution,\n supportsSize,\n supportsAspectRatio,\n supportsResolution,\n geometry: caps.geometry,\n ignoredOverrides,\n normalization,\n });\n\n // Soft fields (drop into ignoredOverrides on mismatch) -----------------\n const quality = filterEnumOverride<'quality', ImageGenerationQuality>({\n key: 'quality',\n requested: params.quality,\n supported: caps.output?.qualities,\n ignoredOverrides,\n });\n const outputFormat = filterEnumOverride<'outputFormat', ImageGenerationOutputFormat>({\n key: 'outputFormat',\n requested: params.outputFormat,\n supported: caps.output?.formats,\n ignoredOverrides,\n });\n const background = filterEnumOverride<'background', ImageGenerationBackground>({\n key: 'background',\n requested: params.background,\n supported: caps.output?.backgrounds,\n ignoredOverrides,\n });\n\n return {\n ...(size !== undefined ? { size } : {}),\n ...(aspectRatio !== undefined ? { aspectRatio } : {}),\n ...(resolution !== undefined ? { resolution } : {}),\n ...(quality !== undefined ? { quality } : {}),\n ...(outputFormat !== undefined ? { outputFormat } : {}),\n ...(background !== undefined ? { background } : {}),\n ignoredOverrides,\n ...(hasAnyEntry(normalization) ? { normalization } : {}),\n };\n}\n\n// ============================================\n// Geometry decision table (§6.3)\n// ============================================\n\ninterface ResolveGeometryParams {\n requestedSize?: string;\n requestedAspectRatio?: string;\n requestedResolution?: ImageGenerationResolution;\n supportsSize: boolean;\n supportsAspectRatio: boolean;\n supportsResolution: boolean;\n geometry: ImageGenerationGeometryCapability | undefined;\n ignoredOverrides: ImageGenerationIgnoredOverride[];\n normalization: ImageGenerationNormalization;\n}\n\nfunction resolveGeometry(params: ResolveGeometryParams): {\n size?: string;\n aspectRatio?: string;\n resolution?: ImageGenerationResolution;\n} {\n const supportedSizes = params.geometry?.sizes;\n const supportedAspectRatios = params.geometry?.aspectRatios;\n const supportedResolutions = params.geometry?.resolutions;\n\n let size: string | undefined;\n let aspectRatio: string | undefined;\n let resolution: ImageGenerationResolution | undefined;\n\n // ----- size -----------------------------------------------------------\n if (params.requestedSize) {\n if (params.supportsSize) {\n const applied = resolveClosestSize({\n requestedSize: params.requestedSize,\n supportedSizes,\n });\n if (applied) {\n size = applied;\n params.normalization.size = entry({\n requested: params.requestedSize,\n applied,\n supportedValues: supportedSizes,\n });\n } else {\n // No supported list → forward as-is.\n size = params.requestedSize;\n }\n } else if (params.supportsAspectRatio) {\n const derived = resolveClosestAspectRatio({\n requestedSize: params.requestedSize,\n supportedAspectRatios,\n });\n if (derived) {\n aspectRatio = derived;\n params.normalization.aspectRatio = entry({\n applied: derived,\n derivedFrom: 'size',\n supportedValues: supportedAspectRatios,\n });\n } else {\n ignored(params.ignoredOverrides, 'size', params.requestedSize);\n }\n } else {\n ignored(params.ignoredOverrides, 'size', params.requestedSize);\n }\n }\n\n // ----- aspectRatio -----------------------------------------------------\n if (params.requestedAspectRatio) {\n if (params.supportsAspectRatio) {\n const applied = resolveClosestAspectRatio({\n requestedAspectRatio: params.requestedAspectRatio,\n supportedAspectRatios,\n });\n const finalApplied = applied ?? params.requestedAspectRatio;\n aspectRatio = finalApplied;\n params.normalization.aspectRatio = entry({\n requested: params.requestedAspectRatio,\n applied: finalApplied,\n supportedValues: supportedAspectRatios,\n });\n } else if (params.supportsSize && size === undefined) {\n const derived = resolveClosestSize({\n requestedAspectRatio: params.requestedAspectRatio,\n supportedSizes,\n });\n if (derived) {\n size = derived;\n params.normalization.size = entry({\n applied: derived,\n derivedFrom: 'aspectRatio',\n supportedValues: supportedSizes,\n });\n } else {\n ignored(params.ignoredOverrides, 'aspectRatio', params.requestedAspectRatio);\n }\n } else if (!params.supportsSize) {\n ignored(params.ignoredOverrides, 'aspectRatio', params.requestedAspectRatio);\n }\n // else: size already supplied and provider only supports size → ignore\n // to avoid contradiction; record in ignoredOverrides as well.\n if (\n !params.supportsAspectRatio &&\n params.supportsSize &&\n size !== undefined &&\n params.normalization.size?.derivedFrom !== 'aspectRatio'\n ) {\n ignored(params.ignoredOverrides, 'aspectRatio', params.requestedAspectRatio);\n }\n }\n\n // ----- resolution ------------------------------------------------------\n if (params.requestedResolution) {\n if (params.supportsResolution) {\n const applied = resolveClosestResolution({\n requestedResolution: params.requestedResolution,\n supportedResolutions,\n });\n const finalApplied = applied ?? params.requestedResolution;\n resolution = finalApplied;\n params.normalization.resolution = entry({\n requested: params.requestedResolution,\n applied: finalApplied,\n supportedValues: supportedResolutions,\n });\n } else {\n ignored(params.ignoredOverrides, 'resolution', params.requestedResolution);\n }\n }\n\n return { size, aspectRatio, resolution };\n}\n\n// ============================================\n// Hard-check helpers\n// ============================================\n\nfunction assertEditSupported(\n provider: ImageGenerationProvider,\n isEdit: boolean,\n inputImages: ImageGenerationSourceImage[] | undefined,\n): void {\n if (!isEdit) return;\n const caps = asNewCapabilities(provider.capabilities);\n const editCap = caps.edit;\n const enabled = editCap?.enabled === true;\n if (!enabled) {\n throw new Error(`${provider.id} image editing is not supported.`);\n }\n const max = editCap.maxInputImages;\n if (typeof max === 'number' && (inputImages?.length ?? 0) > max) {\n throw new Error(\n `${provider.id} accepts at most ${max} input image(s); got ${inputImages?.length ?? 0}.`,\n );\n }\n}\n\n// ============================================\n// Tiny helpers\n// ============================================\n\ninterface FilterEnumOverrideParams<TKey extends ImageGenerationIgnoredOverrideKey, TValue extends string> {\n key: TKey;\n requested: TValue | undefined;\n supported: ReadonlyArray<TValue> | undefined;\n ignoredOverrides: ImageGenerationIgnoredOverride[];\n}\n\nfunction filterEnumOverride<TKey extends ImageGenerationIgnoredOverrideKey, TValue extends string>(\n params: FilterEnumOverrideParams<TKey, TValue>,\n): TValue | undefined {\n if (params.requested === undefined) return undefined;\n if (!params.supported || params.supported.length === 0) {\n // Provider declares no constraint → forward.\n return params.requested;\n }\n if (params.supported.includes(params.requested)) return params.requested;\n ignored(params.ignoredOverrides, params.key, params.requested);\n return undefined;\n}\n\nfunction ignored(\n list: ImageGenerationIgnoredOverride[],\n key: ImageGenerationIgnoredOverrideKey,\n value: string,\n): void {\n list.push({ key, value });\n}\n\nfunction entry<T extends string>(init: {\n requested?: T;\n applied?: T;\n derivedFrom?: 'size' | 'aspectRatio' | 'resolution';\n supportedValues?: ReadonlyArray<T>;\n}) {\n return {\n ...(init.requested !== undefined ? { requested: init.requested } : {}),\n ...(init.applied !== undefined ? { applied: init.applied } : {}),\n ...(init.derivedFrom !== undefined ? { derivedFrom: init.derivedFrom } : {}),\n ...(init.supportedValues ? { supportedValues: [...init.supportedValues] } : {}),\n };\n}\n\nfunction hasAnyEntry(n: ImageGenerationNormalization): boolean {\n return (\n n.size !== undefined || n.aspectRatio !== undefined || n.resolution !== undefined\n );\n}\n\n// Re-export for convenience so callers don't need a second import.\nexport type { ImageGenerationProviderCapabilities };\n"],"mappings":"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"}
1
+ {"version":3,"file":"normalization.js","names":[],"sources":["../../../../../src/agent/image/generation/normalization.ts"],"sourcesContent":["/**\n * Image-generation parameter normalization.\n *\n * Reconciles user-supplied geometry / output overrides against the active\n * provider's declared {@link ImageGenerationProviderCapabilities}. Returns:\n * - the actually-applied values to forward to the provider\n * - per-field {@link MediaNormalizationEntry} (requested / applied / derivedFrom)\n * - a list of {@link ImageGenerationIgnoredOverride} entries for unsupported\n * soft fields (so the LLM / UI can surface a Note)\n *\n * Hard errors (unsupported edit, too many input images) are thrown so the\n * caller can fail fast — they are NOT downgraded to ignored overrides.\n *\n * Decision table — see docs/image-generation-rearchitecture.md §6.3\n */\n\nimport {\n resolveClosestAspectRatio,\n resolveClosestResolution,\n resolveClosestSize,\n} from '../../media-generation/index.js';\nimport type {\n ImageGenerationBackground,\n ImageGenerationGeometryCapability,\n ImageGenerationIgnoredOverride,\n ImageGenerationIgnoredOverrideKey,\n ImageGenerationNormalization,\n ImageGenerationOutputFormat,\n ImageGenerationProvider,\n ImageGenerationProviderCapabilities,\n ImageGenerationQuality,\n ImageGenerationResolution,\n ImageGenerationSourceImage,\n} from './types.js';\n\nexport interface ResolveImageGenerationOverridesParams {\n provider: ImageGenerationProvider;\n size?: string;\n aspectRatio?: string;\n resolution?: ImageGenerationResolution;\n quality?: ImageGenerationQuality;\n outputFormat?: ImageGenerationOutputFormat;\n background?: ImageGenerationBackground;\n inputImages?: ImageGenerationSourceImage[];\n}\n\nexport interface ResolvedImageGenerationOverrides {\n size?: string;\n aspectRatio?: string;\n resolution?: ImageGenerationResolution;\n quality?: ImageGenerationQuality;\n outputFormat?: ImageGenerationOutputFormat;\n background?: ImageGenerationBackground;\n ignoredOverrides: ImageGenerationIgnoredOverride[];\n normalization?: ImageGenerationNormalization;\n}\n\nexport function resolveImageGenerationOverrides(\n params: ResolveImageGenerationOverridesParams,\n): ResolvedImageGenerationOverrides {\n const provider = params.provider;\n const caps = provider.capabilities ?? {};\n const isEdit = (params.inputImages?.length ?? 0) > 0;\n\n // Hard checks (throw, do not downgrade) ---------------------------------\n assertEditSupported(provider, isEdit, params.inputImages);\n\n const modeCaps = isEdit ? caps.edit : caps.generate;\n const supportsSize = modeCaps?.supportsSize === true;\n const supportsAspectRatio = modeCaps?.supportsAspectRatio === true;\n const supportsResolution = caps.generate?.supportsResolution === true && !isEdit;\n\n const ignoredOverrides: ImageGenerationIgnoredOverride[] = [];\n const normalization: ImageGenerationNormalization = {};\n\n // Geometry --------------------------------------------------------------\n const { size, aspectRatio, resolution } = resolveGeometry({\n requestedSize: params.size,\n requestedAspectRatio: params.aspectRatio,\n requestedResolution: params.resolution,\n supportsSize,\n supportsAspectRatio,\n supportsResolution,\n geometry: caps.geometry,\n ignoredOverrides,\n normalization,\n });\n\n // Soft fields (drop into ignoredOverrides on mismatch) -----------------\n const quality = filterEnumOverride<'quality', ImageGenerationQuality>({\n key: 'quality',\n requested: params.quality,\n supported: caps.output?.qualities,\n ignoredOverrides,\n });\n const outputFormat = filterEnumOverride<'outputFormat', ImageGenerationOutputFormat>({\n key: 'outputFormat',\n requested: params.outputFormat,\n supported: caps.output?.formats,\n ignoredOverrides,\n });\n const background = filterEnumOverride<'background', ImageGenerationBackground>({\n key: 'background',\n requested: params.background,\n supported: caps.output?.backgrounds,\n ignoredOverrides,\n });\n\n return {\n ...(size !== undefined ? { size } : {}),\n ...(aspectRatio !== undefined ? { aspectRatio } : {}),\n ...(resolution !== undefined ? { resolution } : {}),\n ...(quality !== undefined ? { quality } : {}),\n ...(outputFormat !== undefined ? { outputFormat } : {}),\n ...(background !== undefined ? { background } : {}),\n ignoredOverrides,\n ...(hasAnyEntry(normalization) ? { normalization } : {}),\n };\n}\n\n// ============================================\n// Geometry decision table (§6.3)\n// ============================================\n\ninterface ResolveGeometryParams {\n requestedSize?: string;\n requestedAspectRatio?: string;\n requestedResolution?: ImageGenerationResolution;\n supportsSize: boolean;\n supportsAspectRatio: boolean;\n supportsResolution: boolean;\n geometry: ImageGenerationGeometryCapability | undefined;\n ignoredOverrides: ImageGenerationIgnoredOverride[];\n normalization: ImageGenerationNormalization;\n}\n\nfunction resolveGeometry(params: ResolveGeometryParams): {\n size?: string;\n aspectRatio?: string;\n resolution?: ImageGenerationResolution;\n} {\n const supportedSizes = params.geometry?.sizes;\n const supportedAspectRatios = params.geometry?.aspectRatios;\n const supportedResolutions = params.geometry?.resolutions;\n\n let size: string | undefined;\n let aspectRatio: string | undefined;\n let resolution: ImageGenerationResolution | undefined;\n\n // ----- size -----------------------------------------------------------\n if (params.requestedSize) {\n if (params.supportsSize) {\n const applied = resolveClosestSize({\n requestedSize: params.requestedSize,\n supportedSizes,\n });\n if (applied) {\n size = applied;\n params.normalization.size = entry({\n requested: params.requestedSize,\n applied,\n supportedValues: supportedSizes,\n });\n } else {\n // No supported list → forward as-is.\n size = params.requestedSize;\n }\n } else if (params.supportsAspectRatio) {\n const derived = resolveClosestAspectRatio({\n requestedSize: params.requestedSize,\n supportedAspectRatios,\n });\n if (derived) {\n aspectRatio = derived;\n params.normalization.aspectRatio = entry({\n applied: derived,\n derivedFrom: 'size',\n supportedValues: supportedAspectRatios,\n });\n } else {\n ignored(params.ignoredOverrides, 'size', params.requestedSize);\n }\n } else {\n ignored(params.ignoredOverrides, 'size', params.requestedSize);\n }\n }\n\n // ----- aspectRatio -----------------------------------------------------\n if (params.requestedAspectRatio) {\n if (params.supportsAspectRatio) {\n const applied = resolveClosestAspectRatio({\n requestedAspectRatio: params.requestedAspectRatio,\n supportedAspectRatios,\n });\n const finalApplied = applied ?? params.requestedAspectRatio;\n aspectRatio = finalApplied;\n params.normalization.aspectRatio = entry({\n requested: params.requestedAspectRatio,\n applied: finalApplied,\n supportedValues: supportedAspectRatios,\n });\n } else if (params.supportsSize && size === undefined) {\n const derived = resolveClosestSize({\n requestedAspectRatio: params.requestedAspectRatio,\n supportedSizes,\n });\n if (derived) {\n size = derived;\n params.normalization.size = entry({\n applied: derived,\n derivedFrom: 'aspectRatio',\n supportedValues: supportedSizes,\n });\n } else {\n ignored(params.ignoredOverrides, 'aspectRatio', params.requestedAspectRatio);\n }\n } else if (!params.supportsSize) {\n ignored(params.ignoredOverrides, 'aspectRatio', params.requestedAspectRatio);\n }\n // else: size already supplied and provider only supports size → ignore\n // to avoid contradiction; record in ignoredOverrides as well.\n if (\n !params.supportsAspectRatio &&\n params.supportsSize &&\n size !== undefined &&\n params.normalization.size?.derivedFrom !== 'aspectRatio'\n ) {\n ignored(params.ignoredOverrides, 'aspectRatio', params.requestedAspectRatio);\n }\n }\n\n // ----- resolution ------------------------------------------------------\n if (params.requestedResolution) {\n if (params.supportsResolution) {\n const applied = resolveClosestResolution({\n requestedResolution: params.requestedResolution,\n supportedResolutions,\n });\n const finalApplied = applied ?? params.requestedResolution;\n resolution = finalApplied;\n params.normalization.resolution = entry({\n requested: params.requestedResolution,\n applied: finalApplied,\n supportedValues: supportedResolutions,\n });\n } else {\n ignored(params.ignoredOverrides, 'resolution', params.requestedResolution);\n }\n }\n\n return { size, aspectRatio, resolution };\n}\n\n// ============================================\n// Hard-check helpers\n// ============================================\n\nfunction assertEditSupported(\n provider: ImageGenerationProvider,\n isEdit: boolean,\n inputImages: ImageGenerationSourceImage[] | undefined,\n): void {\n if (!isEdit) return;\n const caps = provider.capabilities ?? {};\n const editCap = caps.edit;\n const enabled = editCap?.enabled === true;\n if (!enabled) {\n throw new Error(`${provider.id} image editing is not supported.`);\n }\n const max = editCap.maxInputImages;\n if (typeof max === 'number' && (inputImages?.length ?? 0) > max) {\n throw new Error(\n `${provider.id} accepts at most ${max} input image(s); got ${inputImages?.length ?? 0}.`,\n );\n }\n}\n\n// ============================================\n// Tiny helpers\n// ============================================\n\ninterface FilterEnumOverrideParams<TKey extends ImageGenerationIgnoredOverrideKey, TValue extends string> {\n key: TKey;\n requested: TValue | undefined;\n supported: ReadonlyArray<TValue> | undefined;\n ignoredOverrides: ImageGenerationIgnoredOverride[];\n}\n\nfunction filterEnumOverride<TKey extends ImageGenerationIgnoredOverrideKey, TValue extends string>(\n params: FilterEnumOverrideParams<TKey, TValue>,\n): TValue | undefined {\n if (params.requested === undefined) return undefined;\n if (!params.supported || params.supported.length === 0) {\n // Provider declares no constraint → forward.\n return params.requested;\n }\n if (params.supported.includes(params.requested)) return params.requested;\n ignored(params.ignoredOverrides, params.key, params.requested);\n return undefined;\n}\n\nfunction ignored(\n list: ImageGenerationIgnoredOverride[],\n key: ImageGenerationIgnoredOverrideKey,\n value: string,\n): void {\n list.push({ key, value });\n}\n\nfunction entry<T extends string>(init: {\n requested?: T;\n applied?: T;\n derivedFrom?: 'size' | 'aspectRatio' | 'resolution';\n supportedValues?: ReadonlyArray<T>;\n}) {\n return {\n ...(init.requested !== undefined ? { requested: init.requested } : {}),\n ...(init.applied !== undefined ? { applied: init.applied } : {}),\n ...(init.derivedFrom !== undefined ? { derivedFrom: init.derivedFrom } : {}),\n ...(init.supportedValues ? { supportedValues: [...init.supportedValues] } : {}),\n };\n}\n\nfunction hasAnyEntry(n: ImageGenerationNormalization): boolean {\n return (\n n.size !== undefined || n.aspectRatio !== undefined || n.resolution !== undefined\n );\n}\n\n// Re-export for convenience so callers don't need a second import.\nexport type { ImageGenerationProviderCapabilities };\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import type { Config } from '../../../config/schema.js';
2
2
  import type { ImageProviderUiMetadata } from './image-provider-ui.js';
3
- import type { ImageGenerationCapabilitiesLegacy, ImageGenerationProviderCapabilities, ImageGenerationRequest, ImageGenerationResult } from './types.js';
3
+ import type { ImageGenerationProviderCapabilities, ImageGenerationRequest, ImageGenerationResult } from './types.js';
4
4
  /** Lifecycle context for {@link ImageGenerationProvider#isConfigured}. */
5
5
  export interface ImageGenerationProviderConfiguredContext {
6
6
  cfg?: Config;
@@ -14,12 +14,8 @@ export interface ImageGenerationProvider {
14
14
  label?: string;
15
15
  defaultModel?: string;
16
16
  models?: string[];
17
- /**
18
- * New nested capability shape (Step 2). Legacy flat shape from Step 1
19
- * is also accepted for backward compatibility while three vendor modules
20
- * migrate.
21
- */
22
- capabilities?: ImageGenerationProviderCapabilities | ImageGenerationCapabilitiesLegacy;
17
+ /** Provider capability map. */
18
+ capabilities?: ImageGenerationProviderCapabilities;
23
19
  /**
24
20
  * Synchronous configuration check (Step 2 — was async in Step 1).
25
21
  * MUST NOT touch keychain or trigger OS prompts.
@@ -43,7 +39,7 @@ export interface ImageGenerationProviderSummary {
43
39
  defaultModel?: string;
44
40
  models: string[];
45
41
  aliases?: string[];
46
- capabilities?: ImageGenerationProviderCapabilities | ImageGenerationCapabilitiesLegacy;
42
+ capabilities?: ImageGenerationProviderCapabilities;
47
43
  ui?: ImageProviderUiMetadata;
48
44
  }
49
45
  export declare function listImageGenerationProvidersSummary(cfg?: Config): ImageGenerationProviderSummary[];
@@ -1 +1 @@
1
- {"version":3,"file":"provider-registry.js","names":[],"sources":["../../../../../src/agent/image/generation/provider-registry.ts"],"sourcesContent":["import type { Config } from '../../../config/schema.js';\nimport type { ImageProviderUiMetadata } from './image-provider-ui.js';\nimport type {\n ImageGenerationCapabilitiesLegacy,\n ImageGenerationProviderCapabilities,\n ImageGenerationRequest,\n ImageGenerationResult,\n} from './types.js';\n\n/** Lifecycle context for {@link ImageGenerationProvider#isConfigured}. */\nexport interface ImageGenerationProviderConfiguredContext {\n cfg?: Config;\n agentDir?: string;\n}\n\nexport interface ImageGenerationProvider {\n /** Lower-case provider id (registry key). */\n id: string;\n /** Optional aliases — alternate ids that resolve to this provider. */\n aliases?: string[];\n label?: string;\n defaultModel?: string;\n models?: string[];\n /**\n * New nested capability shape (Step 2). Legacy flat shape from Step 1\n * is also accepted for backward compatibility while three vendor modules\n * migrate.\n */\n capabilities?: ImageGenerationProviderCapabilities | ImageGenerationCapabilitiesLegacy;\n /**\n * Synchronous configuration check (Step 2 — was async in Step 1).\n * MUST NOT touch keychain or trigger OS prompts.\n */\n isConfigured?: (ctx: ImageGenerationProviderConfiguredContext) => boolean;\n /** Gateway console presets (regions / base URLs); defined by each bundled extension. */\n ui?: ImageProviderUiMetadata;\n generateImage(req: ImageGenerationRequest): Promise<ImageGenerationResult>;\n}\n\n/**\n * Provider ids that must never be registered (defensive — guards against\n * accidentally shadowing well-known internal namespaces / prototype keys).\n */\nconst UNSAFE_PROVIDER_IDS = new Set<string>(['__proto__', 'constructor', 'prototype']);\n\nconst registry = new Map<string, ImageGenerationProvider>();\nconst aliasIndex = new Map<string, string>();\n\nfunction normalizeId(id: string): string {\n return id.trim().toLowerCase();\n}\n\nfunction isExtensionDisabled(cfg: Config | undefined, providerId: string): boolean {\n if (!cfg) return false;\n // `cfg.extensions[<id>].enabled === false` ⇒ skip the provider in listings.\n // We keep the lookup in a defensively-typed shape; Step 4 may move this to\n // a typed schema entry.\n const extensions = (cfg as unknown as { extensions?: Record<string, unknown> } | undefined)?.extensions;\n if (!extensions || typeof extensions !== 'object') return false;\n const entry = (extensions as Record<string, unknown>)[providerId];\n if (!entry || typeof entry !== 'object') return false;\n const enabled = (entry as { enabled?: unknown }).enabled;\n return enabled === false;\n}\n\nexport function registerImageGenerationProvider(provider: ImageGenerationProvider): void {\n if (!provider.id?.trim()) {\n throw new Error('Image generation provider id is required');\n }\n const id = normalizeId(provider.id);\n if (UNSAFE_PROVIDER_IDS.has(id)) {\n throw new Error(`Image generation provider id is reserved: ${provider.id}`);\n }\n // Drop any stale alias entries that pointed at the previous registration.\n for (const [alias, target] of [...aliasIndex.entries()]) {\n if (target === id) aliasIndex.delete(alias);\n }\n registry.set(id, provider);\n for (const aliasRaw of provider.aliases ?? []) {\n if (typeof aliasRaw !== 'string') continue;\n const alias = normalizeId(aliasRaw);\n if (!alias || alias === id) continue;\n if (UNSAFE_PROVIDER_IDS.has(alias)) continue;\n if (registry.has(alias)) continue; // Real ids always win over aliases.\n aliasIndex.set(alias, id);\n }\n}\n\n/**\n * Resolve a provider by id or alias.\n *\n * `cfg` is honoured to skip providers disabled via `cfg.extensions[<id>].enabled = false`.\n */\nexport function getImageGenerationProvider(\n providerId: string,\n cfg?: Config,\n): ImageGenerationProvider | undefined {\n if (typeof providerId !== 'string') return undefined;\n const id = normalizeId(providerId);\n if (!id) return undefined;\n const direct = registry.get(id);\n if (direct) {\n return isExtensionDisabled(cfg, direct.id) ? undefined : direct;\n }\n const aliasTarget = aliasIndex.get(id);\n if (!aliasTarget) return undefined;\n const aliased = registry.get(aliasTarget);\n if (!aliased) return undefined;\n return isExtensionDisabled(cfg, aliased.id) ? undefined : aliased;\n}\n\nexport function listImageGenerationProviders(cfg?: Config): ImageGenerationProvider[] {\n const out: ImageGenerationProvider[] = [];\n for (const provider of registry.values()) {\n if (isExtensionDisabled(cfg, provider.id)) continue;\n out.push(provider);\n }\n return out;\n}\n\nexport interface ImageGenerationProviderSummary {\n id: string;\n label?: string;\n defaultModel?: string;\n models: string[];\n aliases?: string[];\n capabilities?: ImageGenerationProviderCapabilities | ImageGenerationCapabilitiesLegacy;\n ui?: ImageProviderUiMetadata;\n}\n\nexport function listImageGenerationProvidersSummary(\n cfg?: Config,\n): ImageGenerationProviderSummary[] {\n return listImageGenerationProviders(cfg).map((provider) => ({\n id: provider.id,\n ...(provider.label ? { label: provider.label } : {}),\n ...(provider.defaultModel ? { defaultModel: provider.defaultModel } : {}),\n models: provider.models ?? (provider.defaultModel ? [provider.defaultModel] : []),\n ...(provider.aliases?.length ? { aliases: [...provider.aliases] } : {}),\n ...(provider.capabilities ? { capabilities: provider.capabilities } : {}),\n ...(provider.ui ? { ui: provider.ui } : {}),\n }));\n}\n\nexport function clearImageGenerationRegistryForTests(): void {\n registry.clear();\n aliasIndex.clear();\n}\n"],"mappings":";;;;;AA2CA,MAAM,sBAAsB,IAAI,IAAY;CAAC;CAAa;CAAe;CAAY,CAAC;AAEtF,MAAM,2BAAW,IAAI,KAAsC;AAC3D,MAAM,6BAAa,IAAI,KAAqB;AAE5C,SAAS,YAAY,IAAoB;AACvC,QAAO,GAAG,MAAM,CAAC,aAAa;;AAGhC,SAAS,oBAAoB,KAAyB,YAA6B;AACjF,KAAI,CAAC,IAAK,QAAO;CAIjB,MAAM,aAAc,KAAyE;AAC7F,KAAI,CAAC,cAAc,OAAO,eAAe,SAAU,QAAO;CAC1D,MAAM,QAAS,WAAuC;AACtD,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,QADiB,MAAgC,YAC9B;;AAGrB,SAAgB,gCAAgC,UAAyC;AACvF,KAAI,CAAC,SAAS,IAAI,MAAM,CACtB,OAAM,IAAI,MAAM,2CAA2C;CAE7D,MAAM,KAAK,YAAY,SAAS,GAAG;AACnC,KAAI,oBAAoB,IAAI,GAAG,CAC7B,OAAM,IAAI,MAAM,6CAA6C,SAAS,KAAK;AAG7E,MAAK,MAAM,CAAC,OAAO,WAAW,CAAC,GAAG,WAAW,SAAS,CAAC,CACrD,KAAI,WAAW,GAAI,YAAW,OAAO,MAAM;AAE7C,UAAS,IAAI,IAAI,SAAS;AAC1B,MAAK,MAAM,YAAY,SAAS,WAAW,EAAE,EAAE;AAC7C,MAAI,OAAO,aAAa,SAAU;EAClC,MAAM,QAAQ,YAAY,SAAS;AACnC,MAAI,CAAC,SAAS,UAAU,GAAI;AAC5B,MAAI,oBAAoB,IAAI,MAAM,CAAE;AACpC,MAAI,SAAS,IAAI,MAAM,CAAE;AACzB,aAAW,IAAI,OAAO,GAAG;;;;;;;;AAS7B,SAAgB,2BACd,YACA,KACqC;AACrC,KAAI,OAAO,eAAe,SAAU,QAAO,KAAA;CAC3C,MAAM,KAAK,YAAY,WAAW;AAClC,KAAI,CAAC,GAAI,QAAO,KAAA;CAChB,MAAM,SAAS,SAAS,IAAI,GAAG;AAC/B,KAAI,OACF,QAAO,oBAAoB,KAAK,OAAO,GAAG,GAAG,KAAA,IAAY;CAE3D,MAAM,cAAc,WAAW,IAAI,GAAG;AACtC,KAAI,CAAC,YAAa,QAAO,KAAA;CACzB,MAAM,UAAU,SAAS,IAAI,YAAY;AACzC,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,QAAO,oBAAoB,KAAK,QAAQ,GAAG,GAAG,KAAA,IAAY;;AAG5D,SAAgB,6BAA6B,KAAyC;CACpF,MAAM,MAAiC,EAAE;AACzC,MAAK,MAAM,YAAY,SAAS,QAAQ,EAAE;AACxC,MAAI,oBAAoB,KAAK,SAAS,GAAG,CAAE;AAC3C,MAAI,KAAK,SAAS;;AAEpB,QAAO;;AAaT,SAAgB,oCACd,KACkC;AAClC,QAAO,6BAA6B,IAAI,CAAC,KAAK,cAAc;EAC1D,IAAI,SAAS;EACb,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,OAAO,GAAG,EAAE;EACnD,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,cAAc,GAAG,EAAE;EACxE,QAAQ,SAAS,WAAW,SAAS,eAAe,CAAC,SAAS,aAAa,GAAG,EAAE;EAChF,GAAI,SAAS,SAAS,SAAS,EAAE,SAAS,CAAC,GAAG,SAAS,QAAQ,EAAE,GAAG,EAAE;EACtE,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,cAAc,GAAG,EAAE;EACxE,GAAI,SAAS,KAAK,EAAE,IAAI,SAAS,IAAI,GAAG,EAAE;EAC3C,EAAE;;AAGL,SAAgB,uCAA6C;AAC3D,UAAS,OAAO;AAChB,YAAW,OAAO"}
1
+ {"version":3,"file":"provider-registry.js","names":[],"sources":["../../../../../src/agent/image/generation/provider-registry.ts"],"sourcesContent":["import type { Config } from '../../../config/schema.js';\nimport type { ImageProviderUiMetadata } from './image-provider-ui.js';\nimport type {\n ImageGenerationProviderCapabilities,\n ImageGenerationRequest,\n ImageGenerationResult,\n} from './types.js';\n\n/** Lifecycle context for {@link ImageGenerationProvider#isConfigured}. */\nexport interface ImageGenerationProviderConfiguredContext {\n cfg?: Config;\n agentDir?: string;\n}\n\nexport interface ImageGenerationProvider {\n /** Lower-case provider id (registry key). */\n id: string;\n /** Optional aliases — alternate ids that resolve to this provider. */\n aliases?: string[];\n label?: string;\n defaultModel?: string;\n models?: string[];\n /** Provider capability map. */\n capabilities?: ImageGenerationProviderCapabilities;\n /**\n * Synchronous configuration check (Step 2 — was async in Step 1).\n * MUST NOT touch keychain or trigger OS prompts.\n */\n isConfigured?: (ctx: ImageGenerationProviderConfiguredContext) => boolean;\n /** Gateway console presets (regions / base URLs); defined by each bundled extension. */\n ui?: ImageProviderUiMetadata;\n generateImage(req: ImageGenerationRequest): Promise<ImageGenerationResult>;\n}\n\n/**\n * Provider ids that must never be registered (defensive — guards against\n * accidentally shadowing well-known internal namespaces / prototype keys).\n */\nconst UNSAFE_PROVIDER_IDS = new Set<string>(['__proto__', 'constructor', 'prototype']);\n\nconst registry = new Map<string, ImageGenerationProvider>();\nconst aliasIndex = new Map<string, string>();\n\nfunction normalizeId(id: string): string {\n return id.trim().toLowerCase();\n}\n\nfunction isExtensionDisabled(cfg: Config | undefined, providerId: string): boolean {\n if (!cfg) return false;\n // `cfg.extensions[<id>].enabled === false` ⇒ skip the provider in listings.\n // We keep the lookup in a defensively-typed shape; Step 4 may move this to\n // a typed schema entry.\n const extensions = (cfg as unknown as { extensions?: Record<string, unknown> } | undefined)?.extensions;\n if (!extensions || typeof extensions !== 'object') return false;\n const entry = (extensions as Record<string, unknown>)[providerId];\n if (!entry || typeof entry !== 'object') return false;\n const enabled = (entry as { enabled?: unknown }).enabled;\n return enabled === false;\n}\n\nexport function registerImageGenerationProvider(provider: ImageGenerationProvider): void {\n if (!provider.id?.trim()) {\n throw new Error('Image generation provider id is required');\n }\n const id = normalizeId(provider.id);\n if (UNSAFE_PROVIDER_IDS.has(id)) {\n throw new Error(`Image generation provider id is reserved: ${provider.id}`);\n }\n // Drop any stale alias entries that pointed at the previous registration.\n for (const [alias, target] of [...aliasIndex.entries()]) {\n if (target === id) aliasIndex.delete(alias);\n }\n registry.set(id, provider);\n for (const aliasRaw of provider.aliases ?? []) {\n if (typeof aliasRaw !== 'string') continue;\n const alias = normalizeId(aliasRaw);\n if (!alias || alias === id) continue;\n if (UNSAFE_PROVIDER_IDS.has(alias)) continue;\n if (registry.has(alias)) continue; // Real ids always win over aliases.\n aliasIndex.set(alias, id);\n }\n}\n\n/**\n * Resolve a provider by id or alias.\n *\n * `cfg` is honoured to skip providers disabled via `cfg.extensions[<id>].enabled = false`.\n */\nexport function getImageGenerationProvider(\n providerId: string,\n cfg?: Config,\n): ImageGenerationProvider | undefined {\n if (typeof providerId !== 'string') return undefined;\n const id = normalizeId(providerId);\n if (!id) return undefined;\n const direct = registry.get(id);\n if (direct) {\n return isExtensionDisabled(cfg, direct.id) ? undefined : direct;\n }\n const aliasTarget = aliasIndex.get(id);\n if (!aliasTarget) return undefined;\n const aliased = registry.get(aliasTarget);\n if (!aliased) return undefined;\n return isExtensionDisabled(cfg, aliased.id) ? undefined : aliased;\n}\n\nexport function listImageGenerationProviders(cfg?: Config): ImageGenerationProvider[] {\n const out: ImageGenerationProvider[] = [];\n for (const provider of registry.values()) {\n if (isExtensionDisabled(cfg, provider.id)) continue;\n out.push(provider);\n }\n return out;\n}\n\nexport interface ImageGenerationProviderSummary {\n id: string;\n label?: string;\n defaultModel?: string;\n models: string[];\n aliases?: string[];\n capabilities?: ImageGenerationProviderCapabilities;\n ui?: ImageProviderUiMetadata;\n}\n\nexport function listImageGenerationProvidersSummary(\n cfg?: Config,\n): ImageGenerationProviderSummary[] {\n return listImageGenerationProviders(cfg).map((provider) => ({\n id: provider.id,\n ...(provider.label ? { label: provider.label } : {}),\n ...(provider.defaultModel ? { defaultModel: provider.defaultModel } : {}),\n models: provider.models ?? (provider.defaultModel ? [provider.defaultModel] : []),\n ...(provider.aliases?.length ? { aliases: [...provider.aliases] } : {}),\n ...(provider.capabilities ? { capabilities: provider.capabilities } : {}),\n ...(provider.ui ? { ui: provider.ui } : {}),\n }));\n}\n\nexport function clearImageGenerationRegistryForTests(): void {\n registry.clear();\n aliasIndex.clear();\n}\n"],"mappings":";;;;;AAsCA,MAAM,sBAAsB,IAAI,IAAY;CAAC;CAAa;CAAe;CAAY,CAAC;AAEtF,MAAM,2BAAW,IAAI,KAAsC;AAC3D,MAAM,6BAAa,IAAI,KAAqB;AAE5C,SAAS,YAAY,IAAoB;AACvC,QAAO,GAAG,MAAM,CAAC,aAAa;;AAGhC,SAAS,oBAAoB,KAAyB,YAA6B;AACjF,KAAI,CAAC,IAAK,QAAO;CAIjB,MAAM,aAAc,KAAyE;AAC7F,KAAI,CAAC,cAAc,OAAO,eAAe,SAAU,QAAO;CAC1D,MAAM,QAAS,WAAuC;AACtD,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,QADiB,MAAgC,YAC9B;;AAGrB,SAAgB,gCAAgC,UAAyC;AACvF,KAAI,CAAC,SAAS,IAAI,MAAM,CACtB,OAAM,IAAI,MAAM,2CAA2C;CAE7D,MAAM,KAAK,YAAY,SAAS,GAAG;AACnC,KAAI,oBAAoB,IAAI,GAAG,CAC7B,OAAM,IAAI,MAAM,6CAA6C,SAAS,KAAK;AAG7E,MAAK,MAAM,CAAC,OAAO,WAAW,CAAC,GAAG,WAAW,SAAS,CAAC,CACrD,KAAI,WAAW,GAAI,YAAW,OAAO,MAAM;AAE7C,UAAS,IAAI,IAAI,SAAS;AAC1B,MAAK,MAAM,YAAY,SAAS,WAAW,EAAE,EAAE;AAC7C,MAAI,OAAO,aAAa,SAAU;EAClC,MAAM,QAAQ,YAAY,SAAS;AACnC,MAAI,CAAC,SAAS,UAAU,GAAI;AAC5B,MAAI,oBAAoB,IAAI,MAAM,CAAE;AACpC,MAAI,SAAS,IAAI,MAAM,CAAE;AACzB,aAAW,IAAI,OAAO,GAAG;;;;;;;;AAS7B,SAAgB,2BACd,YACA,KACqC;AACrC,KAAI,OAAO,eAAe,SAAU,QAAO,KAAA;CAC3C,MAAM,KAAK,YAAY,WAAW;AAClC,KAAI,CAAC,GAAI,QAAO,KAAA;CAChB,MAAM,SAAS,SAAS,IAAI,GAAG;AAC/B,KAAI,OACF,QAAO,oBAAoB,KAAK,OAAO,GAAG,GAAG,KAAA,IAAY;CAE3D,MAAM,cAAc,WAAW,IAAI,GAAG;AACtC,KAAI,CAAC,YAAa,QAAO,KAAA;CACzB,MAAM,UAAU,SAAS,IAAI,YAAY;AACzC,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,QAAO,oBAAoB,KAAK,QAAQ,GAAG,GAAG,KAAA,IAAY;;AAG5D,SAAgB,6BAA6B,KAAyC;CACpF,MAAM,MAAiC,EAAE;AACzC,MAAK,MAAM,YAAY,SAAS,QAAQ,EAAE;AACxC,MAAI,oBAAoB,KAAK,SAAS,GAAG,CAAE;AAC3C,MAAI,KAAK,SAAS;;AAEpB,QAAO;;AAaT,SAAgB,oCACd,KACkC;AAClC,QAAO,6BAA6B,IAAI,CAAC,KAAK,cAAc;EAC1D,IAAI,SAAS;EACb,GAAI,SAAS,QAAQ,EAAE,OAAO,SAAS,OAAO,GAAG,EAAE;EACnD,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,cAAc,GAAG,EAAE;EACxE,QAAQ,SAAS,WAAW,SAAS,eAAe,CAAC,SAAS,aAAa,GAAG,EAAE;EAChF,GAAI,SAAS,SAAS,SAAS,EAAE,SAAS,CAAC,GAAG,SAAS,QAAQ,EAAE,GAAG,EAAE;EACtE,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,cAAc,GAAG,EAAE;EACxE,GAAI,SAAS,KAAK,EAAE,IAAI,SAAS,IAAI,GAAG,EAAE;EAC3C,EAAE;;AAGL,SAAgB,uCAA6C;AAC3D,UAAS,OAAO;AAChB,YAAW,OAAO"}
@@ -1,6 +1,6 @@
1
1
  import type { Config } from '../../../config/schema.js';
2
2
  import { type ImageGenerationProviderSummary } from './provider-registry.js';
3
- import type { ImageGenerationProviderCapabilities, ImageGenerationCapabilitiesLegacy } from './types.js';
3
+ import type { ImageGenerationProviderCapabilities } from './types.js';
4
4
  import './bundled.js';
5
5
  export type { GenerateImageParams, GenerateImageRuntimeResult, ImageGenerationRuntimeDeps, } from './runtime-types.js';
6
6
  import type { GenerateImageParams, GenerateImageRuntimeResult, ImageGenerationRuntimeDeps } from './runtime-types.js';
@@ -10,5 +10,5 @@ import type { GenerateImageParams, GenerateImageRuntimeResult, ImageGenerationRu
10
10
  */
11
11
  export declare function generateImage(params: GenerateImageParams, deps?: ImageGenerationRuntimeDeps): Promise<GenerateImageRuntimeResult>;
12
12
  export declare function listImageGenerationProvidersSummary(cfg?: Config): ImageGenerationProviderSummary[];
13
- export type { ImageGenerationCapabilitiesLegacy, ImageGenerationProviderCapabilities, };
13
+ export type { ImageGenerationProviderCapabilities };
14
14
  export { getImageGenerationProvider, listImageGenerationProviders, } from './provider-registry.js';
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","names":["listProvidersSummaryFromRegistry"],"sources":["../../../../../src/agent/image/generation/runtime.ts"],"sourcesContent":["import type { Config } from '../../../config/schema.js';\nimport { PROVIDER_ENV_MAP } from '../../../providers/env-keys.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport {\n buildMediaGenerationNormalizationMetadata,\n buildNoCapabilityModelConfiguredMessage,\n recordCapabilityCandidateFailure,\n resolveCapabilityModelCandidates,\n throwCapabilityGenerationFailure,\n type CapabilityProviderCandidate,\n} from '../../media-generation/index.js';\nimport { describeFailoverError, isFailoverError, type FallbackAttempt } from '../../failover-error.js';\nimport { parseImageGenerationModelRef } from './model-ref.js';\nimport { resolveImageGenerationOverrides } from './normalization.js';\nimport {\n getImageGenerationProvider,\n listImageGenerationProviders,\n listImageGenerationProvidersSummary as listProvidersSummaryFromRegistry,\n type ImageGenerationProvider,\n type ImageGenerationProviderSummary,\n} from './provider-registry.js';\nimport type {\n ImageGenerationProviderCapabilities,\n ImageGenerationCapabilitiesLegacy,\n ImageGenerationRequest,\n ImageGenerationSourceImage,\n} from './types.js';\n\n// Side-effect: registers every bundled image-generation provider listed in\n// src/generated/bundled-image-generation-providers.ts (regenerated via\n// `pnpm run generate:bundled-image-providers`). Vendor implementations live\n// under extensions/<vendor>/src/image-generation-provider.ts.\nimport './bundled.js';\n\nexport type {\n GenerateImageParams,\n GenerateImageRuntimeResult,\n ImageGenerationRuntimeDeps,\n} from './runtime-types.js';\n\nimport type {\n GenerateImageParams,\n GenerateImageRuntimeResult,\n ImageGenerationRuntimeDeps,\n} from './runtime-types.js';\n\nconst log = createLogger('ImageGen');\n\n/**\n * Generate one or more images, walking the candidate model list with\n * structured failover. See {@link GenerateImageParams} / {@link GenerateImageRuntimeResult}.\n */\nexport async function generateImage(\n params: GenerateImageParams,\n deps: ImageGenerationRuntimeDeps = {},\n): Promise<GenerateImageRuntimeResult> {\n const listProviders = (cfg?: Config) =>\n (deps.listProviders ?? listImageGenerationProviders)(cfg);\n const getProvider = (id: string, cfg?: Config) =>\n (deps.getProvider ?? getImageGenerationProvider)(id, cfg);\n\n const candidates = resolveCapabilityModelCandidates({\n cfg: params.cfg,\n modelConfig: params.cfg?.agents?.defaults?.imageGenerationModel,\n modelOverride: params.modelOverride,\n parseModelRef: parseImageGenerationModelRef,\n agentDir: params.agentDir,\n listProviders: (cfg) => listProviders(cfg).map(toCapabilityCandidate),\n autoProviderFallback: params.autoProviderFallback,\n });\n\n if (candidates.length === 0) {\n throw new Error(buildNoImageGenerationModelConfiguredMessage(params.cfg, deps));\n }\n\n const attempts: FallbackAttempt[] = [];\n let lastError: unknown;\n\n for (const candidate of candidates) {\n const startedAt = Date.now();\n const provider = getProvider(candidate.provider, params.cfg);\n if (!provider) {\n const errorMessage = `Image generation provider not registered: ${candidate.provider}`;\n attempts.push({\n provider: candidate.provider,\n model: candidate.model,\n error: errorMessage,\n reason: 'config',\n });\n lastError = new Error(errorMessage);\n log.warn(\n { provider: candidate.provider, model: candidate.model, phase: 'candidate_skipped' },\n `image-generation candidate skipped: ${errorMessage}`,\n );\n continue;\n }\n\n try {\n const sanitized = resolveImageGenerationOverrides({\n provider,\n size: params.size,\n aspectRatio: params.aspectRatio,\n resolution: params.resolution,\n quality: params.quality,\n outputFormat: params.outputFormat,\n background: params.background,\n inputImages: params.inputImages,\n });\n\n const request = buildProviderRequest({\n provider,\n candidate,\n params,\n sanitized,\n });\n\n log.debug(\n {\n provider: candidate.provider,\n model: candidate.model,\n phase: 'provider_invoked',\n normalizationCount: sanitized.normalization\n ? Object.keys(sanitized.normalization).length\n : 0,\n ignoredCount: sanitized.ignoredOverrides.length,\n },\n `image-generation provider invoked: ${candidate.provider}/${candidate.model}`,\n );\n\n const result = await provider.generateImage(request);\n if (!result.images?.length) {\n throw new Error('Image generation provider returned no images.');\n }\n\n const normalizationMetadata = buildMediaGenerationNormalizationMetadata({\n normalization: sanitized.normalization as Record<string, unknown> | undefined,\n });\n\n return {\n images: result.images,\n provider: candidate.provider,\n model: result.model ?? candidate.model,\n attempts,\n ...(sanitized.normalization ? { normalization: sanitized.normalization } : {}),\n ignoredOverrides: sanitized.ignoredOverrides,\n metadata: { ...(result.metadata ?? {}), ...normalizationMetadata },\n };\n } catch (err) {\n lastError = err;\n const durationMs = Date.now() - startedAt;\n recordCapabilityCandidateFailure({\n attempts,\n provider: candidate.provider,\n model: candidate.model,\n error: err,\n durationMs,\n });\n const last = attempts[attempts.length - 1];\n const description = isFailoverError(err) ? describeFailoverError(err) : undefined;\n log.warn(\n {\n err,\n provider: candidate.provider,\n model: candidate.model,\n status: last?.status,\n reason: last?.reason,\n durationMs,\n phase: 'candidate_failed',\n attemptCount: attempts.length,\n },\n `image-generation candidate failed: ${candidate.provider}/${candidate.model}: ${\n description ?? last?.error ?? (err instanceof Error ? err.message : String(err))\n }`,\n );\n }\n }\n\n return throwCapabilityGenerationFailure({\n capabilityLabel: 'image generation',\n attempts,\n lastError,\n });\n}\n\nexport function listImageGenerationProvidersSummary(\n cfg?: Config,\n): ImageGenerationProviderSummary[] {\n return listProvidersSummaryFromRegistry(cfg);\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction buildProviderRequest(input: {\n provider: ImageGenerationProvider;\n candidate: { provider: string; model: string };\n params: GenerateImageParams;\n sanitized: ReturnType<typeof resolveImageGenerationOverrides>;\n}): ImageGenerationRequest {\n const { provider, candidate, params, sanitized } = input;\n const inputImages = params.inputImages\n ? cloneInputImages(params.inputImages)\n : undefined;\n return {\n provider: provider.id,\n model: candidate.model,\n prompt: params.prompt,\n ...(params.cfg ? { cfg: params.cfg } : {}),\n ...(params.agentDir ? { agentDir: params.agentDir } : {}),\n ...(params.authStore ? { authStore: params.authStore } : {}),\n ...(typeof params.timeoutMs === 'number' ? { timeoutMs: params.timeoutMs } : {}),\n ...(params.signal ? { signal: params.signal } : {}),\n ...(typeof params.count === 'number' ? { count: params.count } : {}),\n ...(sanitized.size !== undefined ? { size: sanitized.size } : {}),\n ...(sanitized.aspectRatio !== undefined ? { aspectRatio: sanitized.aspectRatio } : {}),\n ...(sanitized.resolution !== undefined ? { resolution: sanitized.resolution } : {}),\n ...(sanitized.quality !== undefined ? { quality: sanitized.quality } : {}),\n ...(sanitized.outputFormat !== undefined ? { outputFormat: sanitized.outputFormat } : {}),\n ...(sanitized.background !== undefined ? { background: sanitized.background } : {}),\n ...(inputImages ? { inputImages } : {}),\n ...(params.providerOptions ? { providerOptions: params.providerOptions } : {}),\n };\n}\n\nfunction cloneInputImages(images: ImageGenerationSourceImage[]): ImageGenerationSourceImage[] {\n return images.map((img) => ({\n buffer: img.buffer,\n mimeType: img.mimeType,\n ...(img.fileName ? { fileName: img.fileName } : {}),\n ...(img.metadata ? { metadata: img.metadata } : {}),\n }));\n}\n\nfunction toCapabilityCandidate(provider: ImageGenerationProvider): CapabilityProviderCandidate {\n return {\n id: provider.id,\n ...(provider.aliases ? { aliases: provider.aliases } : {}),\n defaultModel: provider.defaultModel ?? null,\n models: provider.models ?? [],\n isConfigured: provider.isConfigured,\n };\n}\n\nfunction buildNoImageGenerationModelConfiguredMessage(\n cfg: Config | undefined,\n deps: ImageGenerationRuntimeDeps,\n): string {\n const list = (deps.listProviders ?? listImageGenerationProviders)(cfg);\n return buildNoCapabilityModelConfiguredMessage({\n capabilityLabel: 'image-generation',\n modelConfigKey: 'imageGenerationModel',\n providers: list.map(toCapabilityCandidate),\n getProviderEnvVars: deps.getProviderEnvVars ?? ((id) => PROVIDER_ENV_MAP[id]),\n });\n}\n\n// Touch capability + legacy types so they remain part of the public surface\n// callers can rely on (avoids accidental removal from re-exports).\nexport type {\n ImageGenerationCapabilitiesLegacy,\n ImageGenerationProviderCapabilities,\n};\n// Re-export the provider-registry surface that gateway routes / CLI consume\n// through this single barrel module. NOTE: `listImageGenerationProvidersSummary`\n// is implemented locally above (it overlays cfg-aware `configured` flags), so\n// it must not be re-exported from the registry to avoid a name clash.\nexport {\n getImageGenerationProvider,\n listImageGenerationProviders,\n} from './provider-registry.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime.js","names":["listProvidersSummaryFromRegistry"],"sources":["../../../../../src/agent/image/generation/runtime.ts"],"sourcesContent":["import type { Config } from '../../../config/schema.js';\nimport { PROVIDER_ENV_MAP } from '../../../providers/env-keys.js';\nimport { createLogger } from '../../../utils/logger.js';\nimport {\n buildMediaGenerationNormalizationMetadata,\n buildNoCapabilityModelConfiguredMessage,\n recordCapabilityCandidateFailure,\n resolveCapabilityModelCandidates,\n throwCapabilityGenerationFailure,\n type CapabilityProviderCandidate,\n} from '../../media-generation/index.js';\nimport { describeFailoverError, isFailoverError, type FallbackAttempt } from '../../failover-error.js';\nimport { parseImageGenerationModelRef } from './model-ref.js';\nimport { resolveImageGenerationOverrides } from './normalization.js';\nimport {\n getImageGenerationProvider,\n listImageGenerationProviders,\n listImageGenerationProvidersSummary as listProvidersSummaryFromRegistry,\n type ImageGenerationProvider,\n type ImageGenerationProviderSummary,\n} from './provider-registry.js';\nimport type {\n ImageGenerationProviderCapabilities,\n ImageGenerationRequest,\n ImageGenerationSourceImage,\n} from './types.js';\n\n// Side-effect: registers every bundled image-generation provider listed in\n// src/generated/bundled-image-generation-providers.ts (regenerated via\n// `pnpm run generate:bundled-image-providers`). Vendor implementations live\n// under extensions/<vendor>/src/image-generation-provider.ts.\nimport './bundled.js';\n\nexport type {\n GenerateImageParams,\n GenerateImageRuntimeResult,\n ImageGenerationRuntimeDeps,\n} from './runtime-types.js';\n\nimport type {\n GenerateImageParams,\n GenerateImageRuntimeResult,\n ImageGenerationRuntimeDeps,\n} from './runtime-types.js';\n\nconst log = createLogger('ImageGen');\n\n/**\n * Generate one or more images, walking the candidate model list with\n * structured failover. See {@link GenerateImageParams} / {@link GenerateImageRuntimeResult}.\n */\nexport async function generateImage(\n params: GenerateImageParams,\n deps: ImageGenerationRuntimeDeps = {},\n): Promise<GenerateImageRuntimeResult> {\n const listProviders = (cfg?: Config) =>\n (deps.listProviders ?? listImageGenerationProviders)(cfg);\n const getProvider = (id: string, cfg?: Config) =>\n (deps.getProvider ?? getImageGenerationProvider)(id, cfg);\n\n const candidates = resolveCapabilityModelCandidates({\n cfg: params.cfg,\n modelConfig: params.cfg?.agents?.defaults?.imageGenerationModel,\n modelOverride: params.modelOverride,\n parseModelRef: parseImageGenerationModelRef,\n agentDir: params.agentDir,\n listProviders: (cfg) => listProviders(cfg).map(toCapabilityCandidate),\n autoProviderFallback: params.autoProviderFallback,\n });\n\n if (candidates.length === 0) {\n throw new Error(buildNoImageGenerationModelConfiguredMessage(params.cfg, deps));\n }\n\n const attempts: FallbackAttempt[] = [];\n let lastError: unknown;\n\n for (const candidate of candidates) {\n const startedAt = Date.now();\n const provider = getProvider(candidate.provider, params.cfg);\n if (!provider) {\n const errorMessage = `Image generation provider not registered: ${candidate.provider}`;\n attempts.push({\n provider: candidate.provider,\n model: candidate.model,\n error: errorMessage,\n reason: 'config',\n });\n lastError = new Error(errorMessage);\n log.warn(\n { provider: candidate.provider, model: candidate.model, phase: 'candidate_skipped' },\n `image-generation candidate skipped: ${errorMessage}`,\n );\n continue;\n }\n\n try {\n const sanitized = resolveImageGenerationOverrides({\n provider,\n size: params.size,\n aspectRatio: params.aspectRatio,\n resolution: params.resolution,\n quality: params.quality,\n outputFormat: params.outputFormat,\n background: params.background,\n inputImages: params.inputImages,\n });\n\n const request = buildProviderRequest({\n provider,\n candidate,\n params,\n sanitized,\n });\n\n log.debug(\n {\n provider: candidate.provider,\n model: candidate.model,\n phase: 'provider_invoked',\n normalizationCount: sanitized.normalization\n ? Object.keys(sanitized.normalization).length\n : 0,\n ignoredCount: sanitized.ignoredOverrides.length,\n },\n `image-generation provider invoked: ${candidate.provider}/${candidate.model}`,\n );\n\n const result = await provider.generateImage(request);\n if (!result.images?.length) {\n throw new Error('Image generation provider returned no images.');\n }\n\n const normalizationMetadata = buildMediaGenerationNormalizationMetadata({\n normalization: sanitized.normalization as Record<string, unknown> | undefined,\n });\n\n return {\n images: result.images,\n provider: candidate.provider,\n model: result.model ?? candidate.model,\n attempts,\n ...(sanitized.normalization ? { normalization: sanitized.normalization } : {}),\n ignoredOverrides: sanitized.ignoredOverrides,\n metadata: { ...(result.metadata ?? {}), ...normalizationMetadata },\n };\n } catch (err) {\n lastError = err;\n const durationMs = Date.now() - startedAt;\n recordCapabilityCandidateFailure({\n attempts,\n provider: candidate.provider,\n model: candidate.model,\n error: err,\n durationMs,\n });\n const last = attempts[attempts.length - 1];\n const description = isFailoverError(err) ? describeFailoverError(err) : undefined;\n log.warn(\n {\n err,\n provider: candidate.provider,\n model: candidate.model,\n status: last?.status,\n reason: last?.reason,\n durationMs,\n phase: 'candidate_failed',\n attemptCount: attempts.length,\n },\n `image-generation candidate failed: ${candidate.provider}/${candidate.model}: ${\n description ?? last?.error ?? (err instanceof Error ? err.message : String(err))\n }`,\n );\n }\n }\n\n return throwCapabilityGenerationFailure({\n capabilityLabel: 'image generation',\n attempts,\n lastError,\n });\n}\n\nexport function listImageGenerationProvidersSummary(\n cfg?: Config,\n): ImageGenerationProviderSummary[] {\n return listProvidersSummaryFromRegistry(cfg);\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction buildProviderRequest(input: {\n provider: ImageGenerationProvider;\n candidate: { provider: string; model: string };\n params: GenerateImageParams;\n sanitized: ReturnType<typeof resolveImageGenerationOverrides>;\n}): ImageGenerationRequest {\n const { provider, candidate, params, sanitized } = input;\n const inputImages = params.inputImages\n ? cloneInputImages(params.inputImages)\n : undefined;\n return {\n provider: provider.id,\n model: candidate.model,\n prompt: params.prompt,\n ...(params.cfg ? { cfg: params.cfg } : {}),\n ...(params.agentDir ? { agentDir: params.agentDir } : {}),\n ...(params.authStore ? { authStore: params.authStore } : {}),\n ...(typeof params.timeoutMs === 'number' ? { timeoutMs: params.timeoutMs } : {}),\n ...(params.signal ? { signal: params.signal } : {}),\n ...(typeof params.count === 'number' ? { count: params.count } : {}),\n ...(sanitized.size !== undefined ? { size: sanitized.size } : {}),\n ...(sanitized.aspectRatio !== undefined ? { aspectRatio: sanitized.aspectRatio } : {}),\n ...(sanitized.resolution !== undefined ? { resolution: sanitized.resolution } : {}),\n ...(sanitized.quality !== undefined ? { quality: sanitized.quality } : {}),\n ...(sanitized.outputFormat !== undefined ? { outputFormat: sanitized.outputFormat } : {}),\n ...(sanitized.background !== undefined ? { background: sanitized.background } : {}),\n ...(inputImages ? { inputImages } : {}),\n ...(params.providerOptions ? { providerOptions: params.providerOptions } : {}),\n };\n}\n\nfunction cloneInputImages(images: ImageGenerationSourceImage[]): ImageGenerationSourceImage[] {\n return images.map((img) => ({\n buffer: img.buffer,\n mimeType: img.mimeType,\n ...(img.fileName ? { fileName: img.fileName } : {}),\n ...(img.metadata ? { metadata: img.metadata } : {}),\n }));\n}\n\nfunction toCapabilityCandidate(provider: ImageGenerationProvider): CapabilityProviderCandidate {\n return {\n id: provider.id,\n ...(provider.aliases ? { aliases: provider.aliases } : {}),\n defaultModel: provider.defaultModel ?? null,\n models: provider.models ?? [],\n isConfigured: provider.isConfigured,\n };\n}\n\nfunction buildNoImageGenerationModelConfiguredMessage(\n cfg: Config | undefined,\n deps: ImageGenerationRuntimeDeps,\n): string {\n const list = (deps.listProviders ?? listImageGenerationProviders)(cfg);\n return buildNoCapabilityModelConfiguredMessage({\n capabilityLabel: 'image-generation',\n modelConfigKey: 'imageGenerationModel',\n providers: list.map(toCapabilityCandidate),\n getProviderEnvVars: deps.getProviderEnvVars ?? ((id) => PROVIDER_ENV_MAP[id]),\n });\n}\n\n// Re-export the canonical capability type for callers consuming this barrel.\nexport type { ImageGenerationProviderCapabilities };\n// Re-export the provider-registry surface that gateway routes / CLI consume\n// through this single barrel module. NOTE: `listImageGenerationProvidersSummary`\n// is implemented locally above (it overlays cfg-aware `configured` flags), so\n// it must not be re-exported from the registry to avoid a name clash.\nexport {\n getImageGenerationProvider,\n listImageGenerationProviders,\n} from './provider-registry.js';\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import type { Config } from '../../../config/schema.js';
2
2
  import type { AuthProfileStore } from '../../../providers/auth-runtime/index.js';
3
- import type { FallbackAttempt } from '../../failover-error.js';
4
3
  import type { MediaNormalizationEntry } from '../../media-generation/normalization.types.js';
5
4
  export type { ImageGenerationProvider, ImageGenerationProviderConfiguredContext, ImageGenerationProviderSummary, } from './provider-registry.js';
6
5
  export type { ImageProviderUiBaseUrlPreset, ImageProviderUiMetadata, ImageProviderUiPresetKind, ImageProviderUiRegionOption, } from './image-provider-ui.js';
@@ -104,20 +103,3 @@ export interface ImageGenerationResult {
104
103
  model?: string;
105
104
  metadata?: Record<string, unknown>;
106
105
  }
107
- /** @deprecated Use {@link FallbackAttempt} from `../failover-error.js`. */
108
- export type ImageGenFallbackAttempt = FallbackAttempt;
109
- /**
110
- * @deprecated Legacy flat capability shape from Step 1. Kept for backward
111
- * compatibility while older code migrates to {@link ImageGenerationProviderCapabilities}.
112
- */
113
- export interface ImageGenerationCapabilitiesLegacy {
114
- supportsEdit: boolean;
115
- maxInputImages?: number;
116
- maxOutputImages?: number;
117
- supportedSizes?: string[];
118
- }
119
- /**
120
- * @deprecated Use {@link ImageGenerationProviderCapabilities}. The legacy alias
121
- * remains so existing code compiles unchanged during Step 2.
122
- */
123
- export type ImageGenerationCapabilities = ImageGenerationCapabilitiesLegacy;
@@ -30,13 +30,18 @@ function coerceImageModelConfig(cfg) {
30
30
  }
31
31
  function applyImageGenerationModelConfigDefaults(cfg, imageGenerationModelConfig) {
32
32
  if (!cfg) return;
33
+ const primary = imageGenerationModelConfig.primary?.trim();
34
+ if (!primary) return cfg;
33
35
  return {
34
36
  ...cfg,
35
37
  agents: {
36
38
  ...cfg.agents,
37
39
  defaults: {
38
40
  ...cfg.agents?.defaults,
39
- imageGenerationModel: imageGenerationModelConfig
41
+ imageGenerationModel: {
42
+ primary,
43
+ ...imageGenerationModelConfig.fallbacks?.length ? { fallbacks: imageGenerationModelConfig.fallbacks } : {}
44
+ }
40
45
  }
41
46
  }
42
47
  };
@@ -1 +1 @@
1
- {"version":3,"file":"image-helpers.js","names":[],"sources":["../../../../src/agent/image/image-helpers.ts"],"sourcesContent":["import type { AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { AssistantMessage } from '@earendil-works/pi-ai';\nimport type { Config } from '../../config/schema.js';\nimport { extractTextContent } from '../context/workspace.js';\nimport type { ImageAttempt } from './image-model-fallback.js';\nimport { coerceToolModelConfig, type ToolModelConfig } from './tool-model-config.js';\n\nexport type ImageModelConfig = ToolModelConfig;\n\nexport function decodeDataUrl(dataUrl: string): {\n buffer: Buffer;\n mimeType: string;\n kind: 'image';\n} {\n const trimmed = dataUrl.trim();\n const match = /^data:([^;,]+);base64,([a-z0-9+/=\\r\\n]+)$/i.exec(trimmed);\n if (!match) {\n throw new Error('Invalid data URL (expected base64 data: URL).');\n }\n const mimeType = (match[1] ?? '').trim().toLowerCase();\n if (!mimeType.startsWith('image/')) {\n throw new Error(`Unsupported data URL type: ${mimeType || 'unknown'}`);\n }\n const b64 = (match[2] ?? '').trim();\n const buffer = Buffer.from(b64, 'base64');\n if (buffer.length === 0) {\n throw new Error('Invalid data URL: empty payload.');\n }\n return { buffer, mimeType, kind: 'image' };\n}\n\nexport function coerceImageAssistantText(params: {\n message: AssistantMessage;\n provider: string;\n model: string;\n}): string {\n const stop = params.message.stopReason;\n const errorMessage = params.message.errorMessage?.trim();\n if (stop === 'error' || stop === 'aborted') {\n throw new Error(\n errorMessage\n ? `Image model failed (${params.provider}/${params.model}): ${errorMessage}`\n : `Image model failed (${params.provider}/${params.model})`,\n );\n }\n if (errorMessage) {\n throw new Error(`Image model failed (${params.provider}/${params.model}): ${errorMessage}`);\n }\n const text = extractTextContent(\n params.message.content as Array<{ type: string; text?: string }>,\n );\n if (text.trim()) {\n return text.trim();\n }\n throw new Error(`Image model returned no text (${params.provider}/${params.model}).`);\n}\n\nexport function coerceImageModelConfig(cfg?: Config): ImageModelConfig {\n return coerceToolModelConfig(cfg?.agents?.defaults?.imageModel);\n}\n\nexport function applyImageGenerationModelConfigDefaults(\n cfg: Config | undefined,\n imageGenerationModelConfig: ToolModelConfig,\n): Config | undefined {\n if (!cfg) {\n return undefined;\n }\n return {\n ...cfg,\n agents: {\n ...cfg.agents,\n defaults: {\n ...cfg.agents?.defaults,\n imageGenerationModel: imageGenerationModelConfig,\n },\n },\n };\n}\n\nexport function resolvePromptAndModelOverride(\n args: Record<string, unknown>,\n defaultPrompt: string,\n): {\n prompt: string;\n modelOverride?: string;\n} {\n const prompt =\n typeof args.prompt === 'string' && args.prompt.trim() ? args.prompt.trim() : defaultPrompt;\n const modelOverride =\n typeof args.model === 'string' && args.model.trim() ? args.model.trim() : undefined;\n return { prompt, modelOverride };\n}\n\nexport function buildImageToolTextResult(\n result: { text: string; provider: string; model: string; attempts: ImageAttempt[] },\n extraDetails: Record<string, unknown>,\n): AgentToolResult<Record<string, unknown>> {\n return {\n content: [{ type: 'text', text: result.text }],\n details: {\n model: `${result.provider}/${result.model}`,\n ...extraDetails,\n attempts: result.attempts,\n },\n };\n}\n"],"mappings":";;;AASA,SAAgB,cAAc,SAI5B;CACA,MAAM,UAAU,QAAQ,MAAM;CAC9B,MAAM,QAAQ,6CAA6C,KAAK,QAAQ;AACxE,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,gDAAgD;CAElE,MAAM,YAAY,MAAM,MAAM,IAAI,MAAM,CAAC,aAAa;AACtD,KAAI,CAAC,SAAS,WAAW,SAAS,CAChC,OAAM,IAAI,MAAM,8BAA8B,YAAY,YAAY;CAExE,MAAM,OAAO,MAAM,MAAM,IAAI,MAAM;CACnC,MAAM,SAAS,OAAO,KAAK,KAAK,SAAS;AACzC,KAAI,OAAO,WAAW,EACpB,OAAM,IAAI,MAAM,mCAAmC;AAErD,QAAO;EAAE;EAAQ;EAAU,MAAM;EAAS;;AAG5C,SAAgB,yBAAyB,QAI9B;CACT,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,eAAe,OAAO,QAAQ,cAAc,MAAM;AACxD,KAAI,SAAS,WAAW,SAAS,UAC/B,OAAM,IAAI,MACR,eACI,uBAAuB,OAAO,SAAS,GAAG,OAAO,MAAM,KAAK,iBAC5D,uBAAuB,OAAO,SAAS,GAAG,OAAO,MAAM,GAC5D;AAEH,KAAI,aACF,OAAM,IAAI,MAAM,uBAAuB,OAAO,SAAS,GAAG,OAAO,MAAM,KAAK,eAAe;CAE7F,MAAM,OAAO,mBACX,OAAO,QAAQ,QAChB;AACD,KAAI,KAAK,MAAM,CACb,QAAO,KAAK,MAAM;AAEpB,OAAM,IAAI,MAAM,iCAAiC,OAAO,SAAS,GAAG,OAAO,MAAM,IAAI;;AAGvF,SAAgB,uBAAuB,KAAgC;AACrE,QAAO,sBAAsB,KAAK,QAAQ,UAAU,WAAW;;AAGjE,SAAgB,wCACd,KACA,4BACoB;AACpB,KAAI,CAAC,IACH;AAEF,QAAO;EACL,GAAG;EACH,QAAQ;GACN,GAAG,IAAI;GACP,UAAU;IACR,GAAG,IAAI,QAAQ;IACf,sBAAsB;IACvB;GACF;EACF;;AAGH,SAAgB,8BACd,MACA,eAIA;AAKA,QAAO;EAAE,QAHP,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,MAAM,GAAG,KAAK,OAAO,MAAM,GAAG;EAG9D,eADf,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,KAAK,MAAM,MAAM,GAAG,KAAA;EAC5C;;AAGlC,SAAgB,yBACd,QACA,cAC0C;AAC1C,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,OAAO;GAAM,CAAC;EAC9C,SAAS;GACP,OAAO,GAAG,OAAO,SAAS,GAAG,OAAO;GACpC,GAAG;GACH,UAAU,OAAO;GAClB;EACF"}
1
+ {"version":3,"file":"image-helpers.js","names":[],"sources":["../../../../src/agent/image/image-helpers.ts"],"sourcesContent":["import type { AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { AssistantMessage } from '@earendil-works/pi-ai';\nimport type { Config } from '../../config/schema.js';\nimport { extractTextContent } from '../context/workspace.js';\nimport type { ImageAttempt } from './image-model-fallback.js';\nimport { coerceToolModelConfig, type ToolModelConfig } from './tool-model-config.js';\n\nexport type ImageModelConfig = ToolModelConfig;\n\nexport function decodeDataUrl(dataUrl: string): {\n buffer: Buffer;\n mimeType: string;\n kind: 'image';\n} {\n const trimmed = dataUrl.trim();\n const match = /^data:([^;,]+);base64,([a-z0-9+/=\\r\\n]+)$/i.exec(trimmed);\n if (!match) {\n throw new Error('Invalid data URL (expected base64 data: URL).');\n }\n const mimeType = (match[1] ?? '').trim().toLowerCase();\n if (!mimeType.startsWith('image/')) {\n throw new Error(`Unsupported data URL type: ${mimeType || 'unknown'}`);\n }\n const b64 = (match[2] ?? '').trim();\n const buffer = Buffer.from(b64, 'base64');\n if (buffer.length === 0) {\n throw new Error('Invalid data URL: empty payload.');\n }\n return { buffer, mimeType, kind: 'image' };\n}\n\nexport function coerceImageAssistantText(params: {\n message: AssistantMessage;\n provider: string;\n model: string;\n}): string {\n const stop = params.message.stopReason;\n const errorMessage = params.message.errorMessage?.trim();\n if (stop === 'error' || stop === 'aborted') {\n throw new Error(\n errorMessage\n ? `Image model failed (${params.provider}/${params.model}): ${errorMessage}`\n : `Image model failed (${params.provider}/${params.model})`,\n );\n }\n if (errorMessage) {\n throw new Error(`Image model failed (${params.provider}/${params.model}): ${errorMessage}`);\n }\n const text = extractTextContent(\n params.message.content as Array<{ type: string; text?: string }>,\n );\n if (text.trim()) {\n return text.trim();\n }\n throw new Error(`Image model returned no text (${params.provider}/${params.model}).`);\n}\n\nexport function coerceImageModelConfig(cfg?: Config): ImageModelConfig {\n return coerceToolModelConfig(cfg?.agents?.defaults?.imageModel);\n}\n\nexport function applyImageGenerationModelConfigDefaults(\n cfg: Config | undefined,\n imageGenerationModelConfig: ToolModelConfig,\n): Config | undefined {\n if (!cfg) {\n return undefined;\n }\n const primary = imageGenerationModelConfig.primary?.trim();\n if (!primary) {\n return cfg;\n }\n return {\n ...cfg,\n agents: {\n ...cfg.agents,\n defaults: {\n ...cfg.agents?.defaults,\n imageGenerationModel: {\n primary,\n ...(imageGenerationModelConfig.fallbacks?.length ? { fallbacks: imageGenerationModelConfig.fallbacks } : {}),\n },\n },\n },\n };\n}\n\nexport function resolvePromptAndModelOverride(\n args: Record<string, unknown>,\n defaultPrompt: string,\n): {\n prompt: string;\n modelOverride?: string;\n} {\n const prompt =\n typeof args.prompt === 'string' && args.prompt.trim() ? args.prompt.trim() : defaultPrompt;\n const modelOverride =\n typeof args.model === 'string' && args.model.trim() ? args.model.trim() : undefined;\n return { prompt, modelOverride };\n}\n\nexport function buildImageToolTextResult(\n result: { text: string; provider: string; model: string; attempts: ImageAttempt[] },\n extraDetails: Record<string, unknown>,\n): AgentToolResult<Record<string, unknown>> {\n return {\n content: [{ type: 'text', text: result.text }],\n details: {\n model: `${result.provider}/${result.model}`,\n ...extraDetails,\n attempts: result.attempts,\n },\n };\n}\n"],"mappings":";;;AASA,SAAgB,cAAc,SAI5B;CACA,MAAM,UAAU,QAAQ,MAAM;CAC9B,MAAM,QAAQ,6CAA6C,KAAK,QAAQ;AACxE,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,gDAAgD;CAElE,MAAM,YAAY,MAAM,MAAM,IAAI,MAAM,CAAC,aAAa;AACtD,KAAI,CAAC,SAAS,WAAW,SAAS,CAChC,OAAM,IAAI,MAAM,8BAA8B,YAAY,YAAY;CAExE,MAAM,OAAO,MAAM,MAAM,IAAI,MAAM;CACnC,MAAM,SAAS,OAAO,KAAK,KAAK,SAAS;AACzC,KAAI,OAAO,WAAW,EACpB,OAAM,IAAI,MAAM,mCAAmC;AAErD,QAAO;EAAE;EAAQ;EAAU,MAAM;EAAS;;AAG5C,SAAgB,yBAAyB,QAI9B;CACT,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,eAAe,OAAO,QAAQ,cAAc,MAAM;AACxD,KAAI,SAAS,WAAW,SAAS,UAC/B,OAAM,IAAI,MACR,eACI,uBAAuB,OAAO,SAAS,GAAG,OAAO,MAAM,KAAK,iBAC5D,uBAAuB,OAAO,SAAS,GAAG,OAAO,MAAM,GAC5D;AAEH,KAAI,aACF,OAAM,IAAI,MAAM,uBAAuB,OAAO,SAAS,GAAG,OAAO,MAAM,KAAK,eAAe;CAE7F,MAAM,OAAO,mBACX,OAAO,QAAQ,QAChB;AACD,KAAI,KAAK,MAAM,CACb,QAAO,KAAK,MAAM;AAEpB,OAAM,IAAI,MAAM,iCAAiC,OAAO,SAAS,GAAG,OAAO,MAAM,IAAI;;AAGvF,SAAgB,uBAAuB,KAAgC;AACrE,QAAO,sBAAsB,KAAK,QAAQ,UAAU,WAAW;;AAGjE,SAAgB,wCACd,KACA,4BACoB;AACpB,KAAI,CAAC,IACH;CAEF,MAAM,UAAU,2BAA2B,SAAS,MAAM;AAC1D,KAAI,CAAC,QACH,QAAO;AAET,QAAO;EACL,GAAG;EACH,QAAQ;GACN,GAAG,IAAI;GACP,UAAU;IACR,GAAG,IAAI,QAAQ;IACf,sBAAsB;KACpB;KACA,GAAI,2BAA2B,WAAW,SAAS,EAAE,WAAW,2BAA2B,WAAW,GAAG,EAAE;KAC5G;IACF;GACF;EACF;;AAGH,SAAgB,8BACd,MACA,eAIA;AAKA,QAAO;EAAE,QAHP,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,MAAM,GAAG,KAAK,OAAO,MAAM,GAAG;EAG9D,eADf,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,MAAM,GAAG,KAAK,MAAM,MAAM,GAAG,KAAA;EAC5C;;AAGlC,SAAgB,yBACd,QACA,cAC0C;AAC1C,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,OAAO;GAAM,CAAC;EAC9C,SAAS;GACP,OAAO,GAAG,OAAO,SAAS,GAAG,OAAO;GACpC,GAAG;GACH,UAAU,OAAO;GAClB;EACF"}
@@ -13,5 +13,5 @@ export type { ToolModelConfig } from './tool-model-config.js';
13
13
  export { resolveImageModelConfigForTool } from './tool-model-config.js';
14
14
  export { generateImage, listImageGenerationProvidersSummary, type GenerateImageParams, type GenerateImageRuntimeResult, } from './generation/runtime.js';
15
15
  export { registerImageGenerationProvider, getImageGenerationProvider, listImageGenerationProviders, type ImageGenerationProvider, } from './generation/provider-registry.js';
16
- export type { ImageGenerationSourceImage, ImageGenerationCapabilities, } from './generation/types.js';
16
+ export type { ImageGenerationSourceImage, ImageGenerationProviderCapabilities, } from './generation/types.js';
17
17
  export { modelSupportsVision, resolveImageHandlingStrategy, type ImageHandlingStrategy, } from './vision-detection.js';
@@ -57,6 +57,11 @@ export interface InboundLoopConfig {
57
57
  enqueueMaybeAutoTitleAfterPersist: (sessionKey: string) => void;
58
58
  /** Effective merged config snapshot. */
59
59
  getConfig: () => Config | undefined;
60
+ /** Archive transcript + new session id (freshness / reset trigger rollover). */
61
+ resetSession: (sessionKey: string) => Promise<{
62
+ sessionId: string;
63
+ previousSessionId: string;
64
+ } | null>;
60
65
  /** Connect a channel stream handle for partial assistant text rendering. */
61
66
  setStreamHandle: (handle: StreamHandle) => void;
62
67
  }
@@ -1,5 +1,7 @@
1
1
  import { runWithLogContext, updateAsyncLogContext } from "../../utils/logger/context.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
+ import { shouldSkipResetOverlapCommand } from "../../session/reset-triggers.js";
4
+ import { initSessionTurn } from "../../session/init-session-turn.js";
3
5
  import { MessageBusShutdownError } from "../../infra/bus/queue.js";
4
6
  import "../../infra/bus/index.js";
5
7
  import { inboundMessageLogRequestId } from "../service-inbound-utils.js";
@@ -92,22 +94,51 @@ var InboundLoop = class {
92
94
  let typingController = null;
93
95
  let inboundTurnArmed = false;
94
96
  let busProcessFailed;
97
+ let inboundMsg = msg;
95
98
  try {
96
99
  if (msg.channel === "system") {
97
100
  await this.handleSystemMessage(msg, sessionContext);
98
101
  return;
99
102
  }
100
103
  if (this.channelManagerRef && msg.channel !== "cli") await this.channelManagerRef.dispatchInboundMessageAction(msg);
101
- if (isCommand && command) {
102
- if (await this.cfg.commandHandler.executeCommand(command, commandArgs || "", {
104
+ const cfg = this.cfg.getConfig();
105
+ let effectiveContent = msg.content;
106
+ let resetTriggeredAtInit = false;
107
+ if (cfg && typeof msg.content === "string") {
108
+ const turn = await initSessionTurn({
109
+ cfg,
103
110
  sessionKey: sessionContext.sessionKey,
104
- channel: sessionContext.channel,
105
- chatId: sessionContext.chatId,
106
- senderId: sessionContext.senderId,
107
- isGroup: sessionContext.isGroup,
108
- inboundMetadata: msg.metadata
109
- })) return;
111
+ body: msg.content,
112
+ resetSession: (sk) => this.cfg.resetSession(sk)
113
+ });
114
+ resetTriggeredAtInit = turn.resetTriggered;
115
+ if (turn.bareReset && turn.ackMessage) {
116
+ await this.cfg.bus.publishOutbound({
117
+ channel: sessionContext.channel,
118
+ chat_id: sessionContext.chatId,
119
+ content: turn.ackMessage,
120
+ type: "message"
121
+ });
122
+ return;
123
+ }
124
+ effectiveContent = turn.bodyStripped;
125
+ }
126
+ if (isCommand && command) {
127
+ if (!shouldSkipResetOverlapCommand(command, resetTriggeredAtInit)) {
128
+ if (await this.cfg.commandHandler.executeCommand(command, commandArgs || "", {
129
+ sessionKey: sessionContext.sessionKey,
130
+ channel: sessionContext.channel,
131
+ chatId: sessionContext.chatId,
132
+ senderId: sessionContext.senderId,
133
+ isGroup: sessionContext.isGroup,
134
+ inboundMetadata: msg.metadata
135
+ })) return;
136
+ }
110
137
  }
138
+ inboundMsg = effectiveContent !== msg.content ? {
139
+ ...msg,
140
+ content: effectiveContent
141
+ } : msg;
111
142
  typingController = this.cfg.outboundCoordinator.createTypingControllerForInbound(msg);
112
143
  typingController?.start();
113
144
  if (this.channelManagerRef && msg.channel !== "cli") {
@@ -121,7 +152,7 @@ var InboundLoop = class {
121
152
  this.cfg.sessionState.beginInboundTurn(sessionContext.sessionKey);
122
153
  inboundTurnArmed = true;
123
154
  try {
124
- await this.cfg.agentOrchestrator.process(msg, sessionContext);
155
+ await this.cfg.agentOrchestrator.process(inboundMsg, sessionContext);
125
156
  } catch (procErr) {
126
157
  busProcessFailed = procErr instanceof Error ? procErr.message : String(procErr);
127
158
  throw procErr;
@@ -142,7 +173,7 @@ var InboundLoop = class {
142
173
  sessionKey: sessionContext.sessionKey,
143
174
  channel: sessionContext.channel,
144
175
  chatId: sessionContext.chatId,
145
- inboundUserText: msg.content,
176
+ inboundUserText: inboundMsg.content,
146
177
  assistantPlainText,
147
178
  aborted: false,
148
179
  ...busProcessFailed !== void 0 ? { streamError: busProcessFailed } : {},