@xopcai/xopc 0.0.119 → 0.0.121

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 (286) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/dashscope/src/image-generation-provider.d.ts +6 -5
  3. package/dist/extensions/dashscope/src/image-generation-provider.js +40 -64
  4. package/dist/extensions/dashscope/src/image-generation-provider.js.map +1 -1
  5. package/dist/extensions/dashscope/src/index.d.ts +1 -1
  6. package/dist/extensions/dashscope/src/index.js +2 -2
  7. package/dist/extensions/dashscope/xopc.extension.json +3 -2
  8. package/dist/extensions/minimax/src/image-generation-provider.d.ts +7 -5
  9. package/dist/extensions/minimax/src/image-generation-provider.js +43 -37
  10. package/dist/extensions/minimax/src/image-generation-provider.js.map +1 -1
  11. package/dist/extensions/minimax/src/index.d.ts +1 -1
  12. package/dist/extensions/minimax/src/index.js +2 -2
  13. package/dist/extensions/minimax/xopc.extension.json +1 -1
  14. package/dist/extensions/telegram/xopc.extension.json +1 -1
  15. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
  16. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CU8J6zou.js → agent-avatar-dicebear-4AW4JgSU.js} +1 -1
  17. package/dist/gateway/static/root/assets/agents-2SVUUPlp.js +2 -0
  18. package/dist/gateway/static/root/assets/{app-management-settings-panel-B9vfYoEr.js → app-management-settings-panel-CE6GkGU2.js} +1 -1
  19. package/dist/gateway/static/root/assets/{appearance-settings-ClOzIO24.js → appearance-settings-BmaUjghw.js} +1 -1
  20. package/dist/gateway/static/root/assets/{apps-page-DU0dz9Zo.js → apps-page-BWeQ1I49.js} +1 -1
  21. package/dist/gateway/static/root/assets/{archive-plugin-BkVIgZzA.js → archive-plugin-CX4_KWut.js} +1 -1
  22. package/dist/gateway/static/root/assets/{attachment-load-CaCMWh1D.js → attachment-load-jf-PIIUU.js} +1 -1
  23. package/dist/gateway/static/root/assets/{automation-suggestion-card-DhGDpKP3.js → automation-suggestion-card-BHk5rtJz.js} +1 -1
  24. package/dist/gateway/static/root/assets/automations-page-QHotl53D.js +1 -0
  25. package/dist/gateway/static/root/assets/{binary-plugins-B4TO_bsn.js → binary-plugins-DG9XQikd.js} +1 -1
  26. package/dist/gateway/static/root/assets/{block-editor-DJRWwWNI.js → block-editor-Up_VIxLJ.js} +6 -6
  27. package/dist/gateway/static/root/assets/browser-settings-page-Cmwi6IIQ.js +8 -0
  28. package/dist/gateway/static/root/assets/{capability-presets-api-Befi89CL.js → capability-presets-api-CGM4OyMw.js} +1 -1
  29. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-_G8eb2vJ.js → capability-presets-settings-panel-CzWt0LBb.js} +1 -1
  30. package/dist/gateway/static/root/assets/{channel-recipient-api-BNSuJb5f.js → channel-recipient-api-BEByL4_o.js} +1 -1
  31. package/dist/gateway/static/root/assets/channels-settings-BOMCUe2J.js +1 -0
  32. package/dist/gateway/static/root/assets/connectors-page-C-Dexk_F.js +2 -0
  33. package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +1 -0
  34. package/dist/gateway/static/root/assets/dist-wO9a2_ID.js +1 -0
  35. package/dist/gateway/static/root/assets/dreaming-settings-WPEj8pir.js +1 -0
  36. package/dist/gateway/static/root/assets/{extension-debug-page-DYh402XV.js → extension-debug-page-DZxa_j7-.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-page-Cr_TizrN.js → extension-page-CseziW53.js} +1 -1
  38. package/dist/gateway/static/root/assets/extension-settings-page-B_9J-xyo.js +1 -0
  39. package/dist/gateway/static/root/assets/{fetch-Y7AEX4H3.js → fetch-Cs4ck8zA.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-config-api-RbMmAOpX.js → gateway-config-api-Dl223a7r.js} +1 -1
  41. package/dist/gateway/static/root/assets/gateway-settings-DdzbtW9R.js +1 -0
  42. package/dist/gateway/static/root/assets/{gateway-startup-retry-DiD_Tx4N.js → gateway-startup-retry-IvePwjFE.js} +1 -1
  43. package/dist/gateway/static/root/assets/goal-detail-page-Dxldogms.js +1 -0
  44. package/dist/gateway/static/root/assets/{goals-config-api-DqxVi0p5.js → goals-config-api-CUCP6yCl.js} +1 -1
  45. package/dist/gateway/static/root/assets/goals-page-DU5Nd6Xp.js +2 -0
  46. package/dist/gateway/static/root/assets/goals-settings-Dj87qPxv.js +1 -0
  47. package/dist/gateway/static/root/assets/heartbeat-settings-Cgm5JmwH.js +1 -0
  48. package/dist/gateway/static/root/assets/index-BU1pvxmS.js +191 -0
  49. package/dist/gateway/static/root/assets/index-l54WfSqe.css +1 -0
  50. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Dvg53qnd.js → keyboard-shortcuts-settings-DJgYw0nB.js} +1 -1
  51. package/dist/gateway/static/root/assets/{locale-store-DjbfSsfA.js → locale-store-Ba72bFmP.js} +1 -1
  52. package/dist/gateway/static/root/assets/logs-page-C10aS4XO.js +2 -0
  53. package/dist/gateway/static/root/assets/{markdown-split-CpHxbtWJ.js → markdown-split-gnQ_EiGo.js} +1 -1
  54. package/dist/gateway/static/root/assets/{media-plugins-SFPQRSxq.js → media-plugins-CQV-mgJn.js} +1 -1
  55. package/dist/gateway/static/root/assets/messages-C6Kc4Jhf.js +3 -0
  56. package/dist/gateway/static/root/assets/models-hub-panel-B35CAfg6.js +2 -0
  57. package/dist/gateway/static/root/assets/note-detail-page-Brlwse8x.js +2 -0
  58. package/dist/gateway/static/root/assets/{note-image-lightbox-BUYURRgn.js → note-image-lightbox-BthwKjLA.js} +1 -1
  59. package/dist/gateway/static/root/assets/{notes-page-BsIBSFyZ.js → notes-page-CoAwRc7L.js} +1 -1
  60. package/dist/gateway/static/root/assets/{preview-open-alternatives-BHz9iWYA.js → preview-open-alternatives-BPMXC-2u.js} +1 -1
  61. package/dist/gateway/static/root/assets/remote-access-hub-B36RQ05h.js +1 -0
  62. package/dist/gateway/static/root/assets/{save-bar-controls-CefkOlRg.js → save-bar-controls-BEHqwZjz.js} +1 -1
  63. package/dist/gateway/static/root/assets/schedule-field-CMY25m97.js +1 -0
  64. package/dist/gateway/static/root/assets/schema-form-CxjxsFfg.js +1 -0
  65. package/dist/gateway/static/root/assets/sessions-page-73THChnj.js +1 -0
  66. package/dist/gateway/static/root/assets/settings-advanced-gate-CR9VsY-R.js +1 -0
  67. package/dist/gateway/static/root/assets/{settings-collapsible-section-BouO6JTi.js → settings-collapsible-section-s-kiLAxP.js} +1 -1
  68. package/dist/gateway/static/root/assets/settings-form-section-CIxIEYxU.js +1 -0
  69. package/dist/gateway/static/root/assets/settings-page-BJ1AhfKI.js +1 -0
  70. package/dist/gateway/static/root/assets/{settings-page-layout-BJ4Gsef5.js → settings-page-layout--6qT7ZJa.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-CrUjHPQo.js → setup-status-panel-C1TtS1vJ.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-BDy2btRN.js → share-preview-page-BZADSbS1.js} +1 -1
  73. package/dist/gateway/static/root/assets/shares-settings-gqC8Rmhy.js +1 -0
  74. package/dist/gateway/static/root/assets/skill-reload-api-C9P-WYvS.js +1 -0
  75. package/dist/gateway/static/root/assets/skills-page-CEpSvOhF.js +2 -0
  76. package/dist/gateway/static/root/assets/{system-settings-panel-CRngrlnZ.js → system-settings-panel-Czjf0kLJ.js} +1 -1
  77. package/dist/gateway/static/root/assets/{theme-store-CD2wtgZV.js → theme-store-Bt6vAkSB.js} +1 -1
  78. package/dist/gateway/static/root/assets/{typed-models-lib-BltBc0b-.js → typed-models-lib-Cmt8SYdI.js} +1 -1
  79. package/dist/gateway/static/root/assets/{use-channel-catalog-CjN-51e6.js → use-channel-catalog-DyM-ckEQ.js} +1 -1
  80. package/dist/gateway/static/root/assets/user-profile-settings-panel-BygLv8vm.js +1 -0
  81. package/dist/gateway/static/root/assets/{utils-B5M5-YvK.js → utils-CloW8coB.js} +1 -1
  82. package/dist/gateway/static/root/assets/voice-api-key-field-BsoqJba6.js +1 -0
  83. package/dist/gateway/static/root/assets/{voice-player-store-B7ykUlHM.js → voice-player-store-DduWlJYV.js} +1 -1
  84. package/dist/gateway/static/root/assets/workflow-run-setup-panel-CcQeX6l5.js +3 -0
  85. package/dist/gateway/static/root/assets/workflows-page-CeA8A9Ef.js +18 -0
  86. package/dist/gateway/static/root/index.html +9 -10
  87. package/dist/package.js +1 -1
  88. package/dist/src/agent/context/workspace-templates/AGENTS.md +3 -2
  89. package/dist/src/agent/embedded/xopc-auth-storage.d.ts +1 -5
  90. package/dist/src/agent/embedded/xopc-auth-storage.js +2 -8
  91. package/dist/src/agent/embedded/xopc-auth-storage.js.map +1 -1
  92. package/dist/src/agent/embedded/xopc-tools-bridge.js +23 -0
  93. package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
  94. package/dist/src/agent/goals/checklist-judge.js +1 -1
  95. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  96. package/dist/src/agent/goals/judge.js +1 -1
  97. package/dist/src/agent/goals/judge.js.map +1 -1
  98. package/dist/src/agent/image/tool-model-config.d.ts +2 -0
  99. package/dist/src/agent/image/tool-model-config.js +2 -2
  100. package/dist/src/agent/image/tool-model-config.js.map +1 -1
  101. package/dist/src/agent/image/understanding/pi-ai-provider.js +1 -1
  102. package/dist/src/agent/image/understanding/pi-ai-provider.js.map +1 -1
  103. package/dist/src/agent/memory/builtin-memory-store.d.ts +1 -1
  104. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  105. package/dist/src/agent/memory/builtin-memory-store.js.map +1 -1
  106. package/dist/src/agent/memory/builtin-provider.d.ts +2 -2
  107. package/dist/src/agent/memory/builtin-provider.js +2 -2
  108. package/dist/src/agent/memory/builtin-provider.js.map +1 -1
  109. package/dist/src/agent/memory/compaction.d.ts +1 -1
  110. package/dist/src/agent/memory/compaction.js +1 -1
  111. package/dist/src/agent/memory/compaction.js.map +1 -1
  112. package/dist/src/agent/memory/dreaming/light-sweep.d.ts +0 -7
  113. package/dist/src/agent/memory/dreaming/light-sweep.js +16 -277
  114. package/dist/src/agent/memory/dreaming/light-sweep.js.map +1 -1
  115. package/dist/src/agent/memory/dreaming/rem-patterns.d.ts +1 -1
  116. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  117. package/dist/src/agent/memory/dreaming/rem-patterns.js.map +1 -1
  118. package/dist/src/agent/memory/dreaming/short-term-store.d.ts +3 -3
  119. package/dist/src/agent/memory/dreaming/short-term-store.js +5 -5
  120. package/dist/src/agent/memory/dreaming/short-term-store.js.map +1 -1
  121. package/dist/src/agent/memory/dreaming/utils.d.ts +2 -2
  122. package/dist/src/agent/memory/dreaming/utils.js +2 -2
  123. package/dist/src/agent/memory/dreaming/utils.js.map +1 -1
  124. package/dist/src/agent/memory/index.d.ts +0 -2
  125. package/dist/src/agent/memory/index.js +1 -2
  126. package/dist/src/agent/middleware/self-verify.d.ts +3 -0
  127. package/dist/src/agent/middleware/self-verify.js +18 -0
  128. package/dist/src/agent/middleware/self-verify.js.map +1 -1
  129. package/dist/src/agent/orchestration/agent-event-handler.js +28 -3
  130. package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
  131. package/dist/src/agent/prompt/sections/memory-skills.js +11 -10
  132. package/dist/src/agent/prompt/sections/memory-skills.js.map +1 -1
  133. package/dist/src/agent/prompt/sections/tooling.js +23 -7
  134. package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
  135. package/dist/src/agent/prompt/service-prompt-builder.js +1 -0
  136. package/dist/src/agent/prompt/service-prompt-builder.js.map +1 -1
  137. package/dist/src/agent/prompt/system-prompt.d.ts +1 -0
  138. package/dist/src/agent/prompt/system-prompt.js +13 -1
  139. package/dist/src/agent/prompt/system-prompt.js.map +1 -1
  140. package/dist/src/agent/reply/startup-context.js +6 -105
  141. package/dist/src/agent/reply/startup-context.js.map +1 -1
  142. package/dist/src/agent/sandbox/path-policy.js +30 -6
  143. package/dist/src/agent/sandbox/path-policy.js.map +1 -1
  144. package/dist/src/agent/service/btw-query.js +1 -1
  145. package/dist/src/agent/service/btw-query.js.map +1 -1
  146. package/dist/src/agent/service.d.ts +0 -3
  147. package/dist/src/agent/service.js +0 -41
  148. package/dist/src/agent/service.js.map +1 -1
  149. package/dist/src/agent/starter-agents.js +16 -16
  150. package/dist/src/agent/starter-agents.js.map +1 -1
  151. package/dist/src/agent/tools/image-generate-tool.js +12 -1
  152. package/dist/src/agent/tools/image-generate-tool.js.map +1 -1
  153. package/dist/src/agent/tools/memory-tool.js +2 -2
  154. package/dist/src/agent/tools/memory-tool.js.map +1 -1
  155. package/dist/src/agent/tools/session-search-tool.js +1 -1
  156. package/dist/src/agent/tools/session-search-tool.js.map +1 -1
  157. package/dist/src/agent/tools/web-extract.js +1 -1
  158. package/dist/src/agent/tools/web-extract.js.map +1 -1
  159. package/dist/src/agent-manifest/index.js +2 -2
  160. package/dist/src/agent-manifest/schema.d.ts +66 -0
  161. package/dist/src/agent-manifest/schema.js +28 -2
  162. package/dist/src/agent-manifest/schema.js.map +1 -1
  163. package/dist/src/automations/draft/automation-draft-service.js +1 -1
  164. package/dist/src/automations/draft/automation-draft-service.js.map +1 -1
  165. package/dist/src/cli/commands/agent.js +2 -2
  166. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  167. package/dist/src/cli/commands/init.js +1 -1
  168. package/dist/src/cli/commands/init.js.map +1 -1
  169. package/dist/src/cli/commands/models.js +1 -1
  170. package/dist/src/cli/commands/onboard/model.js +161 -11
  171. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  172. package/dist/src/cli/commands/onboard.js +1 -1
  173. package/dist/src/cli/commands/profile.js +1 -1
  174. package/dist/src/cli/commands/tunnel.js +1 -1
  175. package/dist/src/cli/commands/update.js +1 -1
  176. package/dist/src/cli/extension-cli-register.js +1 -1
  177. package/dist/src/cli/templates.js +3 -2
  178. package/dist/src/cli/templates.js.map +1 -1
  179. package/dist/src/config/agent-profile.js +1 -1
  180. package/dist/src/config/default-model.d.ts +11 -0
  181. package/dist/src/config/default-model.js +19 -0
  182. package/dist/src/config/default-model.js.map +1 -0
  183. package/dist/src/config/index.js +3 -3
  184. package/dist/src/config/schema.d.ts +73 -13
  185. package/dist/src/config/schema.js +59 -24
  186. package/dist/src/config/schema.js.map +1 -1
  187. package/dist/src/connectors/registries/smithery.js +10 -9
  188. package/dist/src/connectors/registries/smithery.js.map +1 -1
  189. package/dist/src/daemon/install-plan.js +2 -1
  190. package/dist/src/daemon/install-plan.js.map +1 -1
  191. package/dist/src/gateway/agents-admin.js +1 -1
  192. package/dist/src/gateway/global-defaults-admin.js +6 -16
  193. package/dist/src/gateway/global-defaults-admin.js.map +1 -1
  194. package/dist/src/gateway/hono/lib/config-payload.d.ts +24 -0
  195. package/dist/src/gateway/hono/routes/agents.js +1 -79
  196. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  197. package/dist/src/gateway/hono/routes/config-patch/misc.js +4 -1
  198. package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
  199. package/dist/src/gateway/hono/routes/models.js +132 -14
  200. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  201. package/dist/src/gateway/hono/routes/voice.js +1 -1
  202. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  203. package/dist/src/gateway/image-capabilities.d.ts +11 -1
  204. package/dist/src/gateway/image-capabilities.js +32 -43
  205. package/dist/src/gateway/image-capabilities.js.map +1 -1
  206. package/dist/src/gateway/run-loop.js +1 -1
  207. package/dist/src/gateway/workspace-editor-path.js +9 -1
  208. package/dist/src/gateway/workspace-editor-path.js.map +1 -1
  209. package/dist/src/generated/bundled-image-generation-providers.d.ts +4 -2
  210. package/dist/src/generated/bundled-image-generation-providers.js +6 -4
  211. package/dist/src/generated/bundled-image-generation-providers.js.map +1 -1
  212. package/dist/src/infra/update-restart.js +1 -1
  213. package/dist/src/mcp/channel-bridge.js +1 -1
  214. package/dist/src/media/paths.js +3 -4
  215. package/dist/src/media/paths.js.map +1 -1
  216. package/dist/src/notes/service.js +1 -1
  217. package/dist/src/notes/service.js.map +1 -1
  218. package/dist/src/providers/domestic-presets.d.ts +615 -0
  219. package/dist/src/providers/domestic-presets.js +834 -0
  220. package/dist/src/providers/domestic-presets.js.map +1 -0
  221. package/dist/src/providers/env-keys.js +29 -5
  222. package/dist/src/providers/env-keys.js.map +1 -1
  223. package/dist/src/providers/extension-stream-bridge.js +1 -1
  224. package/dist/src/providers/extension-stream-bridge.js.map +1 -1
  225. package/dist/src/providers/index.d.ts +5 -5
  226. package/dist/src/providers/index.js +101 -21
  227. package/dist/src/providers/index.js.map +1 -1
  228. package/dist/src/providers/model-discovery.d.ts +17 -0
  229. package/dist/src/providers/model-discovery.js +61 -0
  230. package/dist/src/providers/model-discovery.js.map +1 -0
  231. package/dist/src/providers/model-registry.d.ts +1 -1
  232. package/dist/src/providers/model-registry.js +1 -1
  233. package/dist/src/providers/model-registry.js.map +1 -1
  234. package/dist/src/providers/presentation.js +185 -14
  235. package/dist/src/providers/presentation.js.map +1 -1
  236. package/dist/src/session/session-title.js +1 -1
  237. package/dist/src/session/session-title.js.map +1 -1
  238. package/dist/src/storage/sqlite/row-mappers.js +3 -1
  239. package/dist/src/storage/sqlite/row-mappers.js.map +1 -1
  240. package/dist/src/tui/backends/embedded-backend.js +1 -2
  241. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  242. package/dist/src/tui/xopc-tui-keybindings.d.ts +1 -1
  243. package/dist/src/voice/tts/summarize.js +1 -1
  244. package/dist/src/voice/tts/summarize.js.map +1 -1
  245. package/dist/src/workflows/draft/workflow-draft-service.js +1 -1
  246. package/dist/src/workflows/draft/workflow-draft-service.js.map +1 -1
  247. package/package.json +6 -6
  248. package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +0 -41
  249. package/dist/gateway/static/root/assets/agents-B0hkpLhP.js +0 -2
  250. package/dist/gateway/static/root/assets/automations-page-CTedroVo.js +0 -1
  251. package/dist/gateway/static/root/assets/browser-settings-page-FCDwOOBK.js +0 -8
  252. package/dist/gateway/static/root/assets/channels-settings-C3d5JBJX.js +0 -1
  253. package/dist/gateway/static/root/assets/connectors-page-yaUNvt5u.js +0 -2
  254. package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +0 -1
  255. package/dist/gateway/static/root/assets/dist-CkobR7mk.js +0 -1
  256. package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +0 -1
  257. package/dist/gateway/static/root/assets/dreaming-settings-DNrdg1Ve.js +0 -1
  258. package/dist/gateway/static/root/assets/extension-settings-page-BU1KoKRj.js +0 -1
  259. package/dist/gateway/static/root/assets/gateway-settings-C79wkXef.js +0 -1
  260. package/dist/gateway/static/root/assets/goal-detail-page-9paXuHdo.js +0 -1
  261. package/dist/gateway/static/root/assets/goals-page-CS0R-Af2.js +0 -2
  262. package/dist/gateway/static/root/assets/goals-settings-CSuzZ8Ae.js +0 -1
  263. package/dist/gateway/static/root/assets/heartbeat-settings-DXPaYJm3.js +0 -1
  264. package/dist/gateway/static/root/assets/index-BoKafJIZ.js +0 -191
  265. package/dist/gateway/static/root/assets/index-CArhCcYn.css +0 -1
  266. package/dist/gateway/static/root/assets/logs-page-DLXPcksQ.js +0 -2
  267. package/dist/gateway/static/root/assets/messages-CrDHEbss.js +0 -3
  268. package/dist/gateway/static/root/assets/models-hub-panel-BDF2iyRk.js +0 -2
  269. package/dist/gateway/static/root/assets/note-detail-page-CSb_hwig.js +0 -2
  270. package/dist/gateway/static/root/assets/remote-access-hub-CwzSovcS.js +0 -1
  271. package/dist/gateway/static/root/assets/schedule-field-CmTRoOcE.js +0 -1
  272. package/dist/gateway/static/root/assets/schema-form-FMKv1vGu.js +0 -1
  273. package/dist/gateway/static/root/assets/sessions-page-PHmX-NmA.js +0 -1
  274. package/dist/gateway/static/root/assets/settings-advanced-gate-BRAeKJN4.js +0 -1
  275. package/dist/gateway/static/root/assets/settings-form-section-D-4lBl6X.js +0 -1
  276. package/dist/gateway/static/root/assets/settings-page-KKrbFjy8.js +0 -1
  277. package/dist/gateway/static/root/assets/shares-settings-B18OBmH5.js +0 -1
  278. package/dist/gateway/static/root/assets/skill-reload-api-C28-2uKQ.js +0 -1
  279. package/dist/gateway/static/root/assets/skills-page-Bm2ebkwl.js +0 -2
  280. package/dist/gateway/static/root/assets/user-profile-settings-panel-DJ1dKwZy.js +0 -1
  281. package/dist/gateway/static/root/assets/voice-api-key-field-CeEevm7z.js +0 -1
  282. package/dist/gateway/static/root/assets/workflow-run-setup-panel-C4PzUQjB.js +0 -3
  283. package/dist/gateway/static/root/assets/workflows-page-BL84_-Ky.js +0 -18
  284. package/dist/src/agent/memory/memory-flush.d.ts +0 -27
  285. package/dist/src/agent/memory/memory-flush.js +0 -124
  286. package/dist/src/agent/memory/memory-flush.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"image-generate-tool.js","names":[],"sources":["../../../../src/agent/tools/image-generate-tool.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { Config } from '../../config/schema.js';\nimport { isFailoverError } from '../failover-error.js';\nimport {\n imageAssetFromDataUrl,\n imageFileExtensionForMimeType,\n mimeTypeFromFileName,\n parseImageDataUrl,\n sniffImageMimeType,\n} from '../image/generation/image-assets.js';\nimport {\n generateImage,\n listImageGenerationProvidersSummary,\n} from '../image/generation/runtime.js';\nimport { getImageGenerationProvider } from '../image/generation/provider-registry.js';\nimport type {\n ImageGenerationBackground,\n ImageGenerationOutputFormat,\n ImageGenerationProviderOptions,\n ImageGenerationQuality,\n ImageGenerationResolution,\n ImageGenerationSourceImage,\n} from '../image/generation/types.js';\nimport { applyImageGenerationModelConfigDefaults } from '../image/image-helpers.js';\nimport type { ToolModelConfig } from '../image/tool-model-config.js';\n\nconst DEFAULT_COUNT = 1;\nconst MAX_COUNT = 9;\nconst ALLOWED_QUALITIES: ImageGenerationQuality[] = ['low', 'medium', 'high', 'auto'];\nconst ALLOWED_OUTPUT_FORMATS: ImageGenerationOutputFormat[] = ['png', 'jpeg', 'webp'];\nconst ALLOWED_BACKGROUNDS: ImageGenerationBackground[] = ['transparent', 'opaque', 'auto'];\nconst ALLOWED_RESOLUTIONS: ImageGenerationResolution[] = ['1K', '2K', '4K'];\n\nconst ImageGenerateToolSchema = Type.Object({\n action: Type.Optional(\n Type.String({\n description: 'Optional: \"generate\" (default) or \"list\" available image-generation providers.',\n }),\n ),\n prompt: Type.Optional(Type.String({ description: 'Image generation prompt.' })),\n model: Type.Optional(\n Type.String({\n description:\n 'Optional provider/model override, e.g. openai/gpt-image-1 / dashscope/wan2.7-image-pro / minimax/image-01.',\n }),\n ),\n filename: Type.Optional(Type.String({ description: 'Optional basename hint for saved files.' })),\n size: Type.Optional(Type.String({ description: 'Optional pixel size, e.g. 1024x1024.' })),\n aspectRatio: Type.Optional(\n Type.String({ description: 'Optional aspect ratio, e.g. 16:9 / 9:16 / 1:1.' }),\n ),\n resolution: Type.Optional(\n Type.String({\n description: 'Optional resolution tier: 1K / 2K / 4K (provider-dependent).',\n }),\n ),\n count: Type.Optional(\n Type.Number({ minimum: 1, maximum: MAX_COUNT, description: `Number of images (1–${MAX_COUNT}).` }),\n ),\n quality: Type.Optional(\n Type.String({ description: 'Optional quality: low / medium / high / auto.' }),\n ),\n outputFormat: Type.Optional(\n Type.String({ description: 'Optional output format: png / jpeg / webp.' }),\n ),\n background: Type.Optional(\n Type.String({ description: 'Optional background: transparent / opaque / auto.' }),\n ),\n inputImages: Type.Optional(\n Type.Array(\n Type.Object({\n source: Type.String({\n description: 'Workspace-relative file path or `data:image/...;base64,...` URL.',\n }),\n }),\n { description: 'Reference images for edit mode (provider must support edit).' },\n ),\n ),\n providerOptions: Type.Optional(\n Type.Object({\n openai: Type.Optional(\n Type.Object({\n moderation: Type.Optional(Type.String()),\n outputCompression: Type.Optional(Type.Number()),\n user: Type.Optional(Type.String()),\n }),\n ),\n }),\n ),\n});\n\nfunction readStringParam(args: Record<string, unknown>, key: string): string | undefined {\n const v = args[key];\n return typeof v === 'string' && v.trim() ? v.trim() : undefined;\n}\n\nfunction readEnumParam<T extends string>(\n args: Record<string, unknown>,\n key: string,\n allowed: ReadonlyArray<T>,\n): T | undefined {\n const raw = readStringParam(args, key);\n if (!raw) return undefined;\n const lower = raw.toLowerCase() as T;\n return allowed.includes(lower) ? lower : undefined;\n}\n\nfunction readResolutionParam(args: Record<string, unknown>): ImageGenerationResolution | undefined {\n const raw = readStringParam(args, 'resolution');\n if (!raw) return undefined;\n const upper = raw.toUpperCase();\n return (ALLOWED_RESOLUTIONS as ReadonlyArray<string>).includes(upper)\n ? (upper as ImageGenerationResolution)\n : undefined;\n}\n\nfunction readProviderOptions(args: Record<string, unknown>): ImageGenerationProviderOptions | undefined {\n const raw = args.providerOptions;\n if (!raw || typeof raw !== 'object') return undefined;\n const out: ImageGenerationProviderOptions = {};\n const oa = (raw as Record<string, unknown>).openai;\n if (oa && typeof oa === 'object') {\n const oaRec = oa as Record<string, unknown>;\n const moderation = typeof oaRec.moderation === 'string' ? oaRec.moderation.toLowerCase() : undefined;\n const outputCompression =\n typeof oaRec.outputCompression === 'number' && Number.isFinite(oaRec.outputCompression)\n ? oaRec.outputCompression\n : undefined;\n const user = typeof oaRec.user === 'string' && oaRec.user.trim() ? oaRec.user.trim() : undefined;\n out.openai = {\n ...(moderation === 'low' || moderation === 'auto' ? { moderation: moderation as 'low' | 'auto' } : {}),\n ...(outputCompression !== undefined ? { outputCompression } : {}),\n ...(user ? { user } : {}),\n };\n if (Object.keys(out.openai).length === 0) delete out.openai;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nclass ToolInputError extends Error {}\n\nasync function loadInputImages(params: {\n workspace: string;\n sources: ReadonlyArray<{ source: string }>;\n}): Promise<ImageGenerationSourceImage[]> {\n if (!params.sources?.length) return [];\n const out: ImageGenerationSourceImage[] = [];\n const workspaceAbs = path.resolve(params.workspace);\n for (const entry of params.sources) {\n const source = entry?.source;\n if (typeof source !== 'string' || !source.trim()) {\n throw new ToolInputError('inputImages[].source is required.');\n }\n const trimmed = source.trim();\n if (/^data:image\\//i.test(trimmed)) {\n const parsed = imageAssetFromDataUrl(trimmed);\n if (!parsed) throw new ToolInputError('Invalid data URL in inputImages[].source.');\n out.push(parsed);\n continue;\n }\n if (/^https?:\\/\\//i.test(trimmed)) {\n throw new ToolInputError('Sandboxed image_generate does not allow remote URLs.');\n }\n const resolved = path.resolve(workspaceAbs, trimmed);\n if (resolved !== workspaceAbs && !resolved.startsWith(workspaceAbs + path.sep)) {\n throw new ToolInputError('inputImages[].source escapes workspace.');\n }\n let buffer: Buffer;\n try {\n buffer = await readFile(resolved);\n } catch (e) {\n const em = e instanceof Error ? e.message : String(e);\n throw new ToolInputError(`inputImages[].source not readable: ${em}`);\n }\n const mimeType = mimeTypeFromFileName(resolved) ?? sniffImageMimeType(buffer).mimeType;\n out.push({ buffer, mimeType, fileName: path.basename(resolved) });\n }\n return out;\n}\n\n// Re-exported for callers / tests; no behavioural change vs imported helper.\nexport { parseImageDataUrl };\n\nexport function resolveImageGenerationModelConfigForTool(params: { cfg?: Config }): ToolModelConfig | null {\n // Step 2: tool default = enumerate every provider whose isConfigured() is true,\n // ordered as registered. No more hard-coded openai/dashscope fallback.\n const providers = listImageGenerationProvidersSummary(params.cfg);\n const candidates: string[] = [];\n for (const providerSummary of providers) {\n const provider = getImageGenerationProvider(providerSummary.id, params.cfg);\n let configured = false;\n try {\n configured = provider?.isConfigured?.({ cfg: params.cfg }) ?? false;\n } catch {\n configured = false;\n }\n const modelRef = `${providerSummary.id}/${providerSummary.defaultModel ?? providerSummary.models[0] ?? ''}`;\n if (configured && /^[a-z0-9-]+\\/.+/.test(modelRef) && !candidates.includes(modelRef)) {\n candidates.push(modelRef);\n }\n }\n\n if (candidates.length === 0) {\n return null;\n }\n\n return {\n primary: candidates[0],\n ...(candidates.length > 1 ? { fallbacks: candidates.slice(1) } : {}),\n };\n}\n\nasync function saveGeneratedImages(params: {\n workspace: string;\n images: Array<{ buffer: Buffer; mimeType: string; fileName?: string }>;\n filenameHint?: string;\n}): Promise<string[]> {\n const dir = path.join(params.workspace, 'media', 'generated');\n await mkdir(dir, { recursive: true });\n const out: string[] = [];\n const random = randomBytes(4).toString('hex');\n let i = 0;\n for (const img of params.images) {\n i += 1;\n const ext = imageFileExtensionForMimeType(img.mimeType);\n const base = (params.filenameHint?.replace(/[^\\w.-]/g, '') || 'image') + `-${random}`;\n const name = `${base}-${i}.${ext}`;\n const full = path.join(dir, name);\n await writeFile(full, img.buffer);\n out.push(full);\n }\n return out;\n}\n\nexport function createImageGenerateTool(options: {\n config?: Config;\n workspace: string;\n}): AgentTool<any, Record<string, unknown>> | null {\n const imageGenerationModelConfig = resolveImageGenerationModelConfigForTool({ cfg: options.config });\n if (!imageGenerationModelConfig) {\n return null;\n }\n\n const effectiveCfg =\n applyImageGenerationModelConfigDefaults(options.config, imageGenerationModelConfig) ??\n options.config;\n\n return {\n name: 'image_generate',\n label: 'Image Generation',\n description:\n 'Generate images with the configured image-generation model (default OpenAI). Use action=\"list\" to see providers. Saves files under workspace/media/generated/.',\n parameters: ImageGenerateToolSchema,\n async execute(\n _toolCallId: string,\n args: Record<string, unknown>,\n ): Promise<AgentToolResult<Record<string, unknown>>> {\n const params = args as Record<string, unknown>;\n const action = (readStringParam(params, 'action') || 'generate').toLowerCase();\n if (action === 'list') {\n const providers = listImageGenerationProvidersSummary(effectiveCfg);\n const lines = providers.flatMap((p) => [\n `${p.id}${p.defaultModel ? ` (default ${p.defaultModel})` : ''}`,\n ` models: ${p.models.join(', ')}`,\n ]);\n return {\n content: [{ type: 'text', text: lines.join('\\n') }],\n details: { providers },\n };\n }\n if (action !== 'generate') {\n return {\n content: [{ type: 'text', text: 'action must be \"generate\" or \"list\".' }],\n details: { error: 'bad_action' },\n };\n }\n\n const prompt = readStringParam(params, 'prompt');\n if (!prompt) {\n return {\n content: [{ type: 'text', text: 'prompt is required for image generation.' }],\n details: { error: 'missing_prompt' },\n };\n }\n\n const modelOverride = readStringParam(params, 'model');\n const filename = readStringParam(params, 'filename');\n const size = readStringParam(params, 'size');\n const aspectRatio = readStringParam(params, 'aspectRatio');\n const resolution = readResolutionParam(params);\n const quality = readEnumParam(params, 'quality', ALLOWED_QUALITIES);\n const outputFormat = readEnumParam(params, 'outputFormat', ALLOWED_OUTPUT_FORMATS);\n const background = readEnumParam(params, 'background', ALLOWED_BACKGROUNDS);\n const providerOptions = readProviderOptions(params);\n\n const countRaw = params.count;\n const count =\n typeof countRaw === 'number' && Number.isFinite(countRaw)\n ? Math.min(MAX_COUNT, Math.max(1, Math.floor(countRaw)))\n : DEFAULT_COUNT;\n\n let inputImages: ImageGenerationSourceImage[] = [];\n try {\n inputImages = await loadInputImages({\n workspace: options.workspace,\n sources: (params.inputImages as Array<{ source: string }>) ?? [],\n });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `Image generation failed: ${msg}` }],\n details: {\n error: e instanceof ToolInputError ? 'invalid_input_images' : 'generation_failed',\n },\n };\n }\n\n try {\n const result = await generateImage({\n cfg: effectiveCfg,\n modelConfig: imageGenerationModelConfig,\n prompt,\n ...(modelOverride ? { modelOverride } : {}),\n count,\n ...(size ? { size } : {}),\n ...(aspectRatio ? { aspectRatio } : {}),\n ...(resolution ? { resolution } : {}),\n ...(quality ? { quality } : {}),\n ...(outputFormat ? { outputFormat } : {}),\n ...(background ? { background } : {}),\n ...(inputImages.length > 0 ? { inputImages } : {}),\n ...(providerOptions ? { providerOptions } : {}),\n });\n\n const paths = await saveGeneratedImages({\n workspace: options.workspace,\n images: result.images,\n ...(filename ? { filenameHint: filename } : {}),\n });\n\n const workspaceRelativePaths = paths.map((p) =>\n path.relative(options.workspace, p).split(path.sep).join('/'),\n );\n\n const lines: string[] = [\n `Generated ${paths.length} image(s) with ${result.provider}/${result.model}.`,\n ...paths.map((p) => `Saved: ${p}`),\n ];\n for (const note of buildNormalizationNotes(result.normalization, result.ignoredOverrides)) {\n lines.push(note);\n }\n\n return {\n content: [{ type: 'text', text: lines.join('\\n') }],\n details: {\n provider: result.provider,\n model: result.model,\n paths,\n workspaceRelativePaths,\n attempts: result.attempts,\n ...(result.normalization ? { normalization: result.normalization } : {}),\n ...(result.ignoredOverrides.length > 0\n ? { ignoredOverrides: result.ignoredOverrides }\n : {}),\n ...(result.metadata ? { metadata: result.metadata } : {}),\n },\n };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n const failoverFields = isFailoverError(e)\n ? {\n ...(e.reason !== undefined ? { reason: e.reason } : {}),\n ...(e.status !== undefined ? { status: e.status } : {}),\n ...(e.code !== undefined ? { code: e.code } : {}),\n ...(e.provider !== undefined ? { provider: e.provider } : {}),\n ...(e.model !== undefined ? { model: e.model } : {}),\n attempts: e.attempts,\n }\n : {};\n return {\n content: [{ type: 'text', text: `Image generation failed: ${msg}` }],\n details: { error: 'generation_failed', ...failoverFields },\n };\n }\n },\n } as any;\n}\n\nfunction buildNormalizationNotes(\n normalization: import('../image/generation/types.js').ImageGenerationNormalization | undefined,\n ignoredOverrides: ReadonlyArray<import('../image/generation/types.js').ImageGenerationIgnoredOverride>,\n): string[] {\n const notes: string[] = [];\n if (normalization) {\n for (const [key, entry] of Object.entries(normalization)) {\n if (!entry) continue;\n const requested = entry.requested;\n const applied = entry.applied;\n if (requested !== undefined && applied !== undefined && requested !== applied) {\n notes.push(`Note: requested ${key}=\"${requested}\" → applied \"${applied}\".`);\n } else if (applied !== undefined && entry.derivedFrom) {\n notes.push(`Note: ${key}=\"${applied}\" derived from ${entry.derivedFrom}.`);\n }\n }\n }\n for (const ig of ignoredOverrides) {\n notes.push(`Note: ignored ${ig.key}=\"${ig.value}\" (not supported by provider).`);\n }\n return notes;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"image-generate-tool.js","names":[],"sources":["../../../../src/agent/tools/image-generate-tool.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { Config } from '../../config/schema.js';\nimport { getAgentDefaultImageGenerationModelConfig } from '../../config/schema.js';\nimport { isFailoverError } from '../failover-error.js';\nimport {\n imageAssetFromDataUrl,\n imageFileExtensionForMimeType,\n mimeTypeFromFileName,\n parseImageDataUrl,\n sniffImageMimeType,\n} from '../image/generation/image-assets.js';\nimport {\n generateImage,\n listImageGenerationProvidersSummary,\n} from '../image/generation/runtime.js';\nimport { getImageGenerationProvider } from '../image/generation/provider-registry.js';\nimport type {\n ImageGenerationBackground,\n ImageGenerationOutputFormat,\n ImageGenerationProviderOptions,\n ImageGenerationQuality,\n ImageGenerationResolution,\n ImageGenerationSourceImage,\n} from '../image/generation/types.js';\nimport { applyImageGenerationModelConfigDefaults } from '../image/image-helpers.js';\nimport type { ToolModelConfig } from '../image/tool-model-config.js';\n\nconst DEFAULT_COUNT = 1;\nconst MAX_COUNT = 9;\nconst ALLOWED_QUALITIES: ImageGenerationQuality[] = ['low', 'medium', 'high', 'auto'];\nconst ALLOWED_OUTPUT_FORMATS: ImageGenerationOutputFormat[] = ['png', 'jpeg', 'webp'];\nconst ALLOWED_BACKGROUNDS: ImageGenerationBackground[] = ['transparent', 'opaque', 'auto'];\nconst ALLOWED_RESOLUTIONS: ImageGenerationResolution[] = ['1K', '2K', '4K'];\n\nconst ImageGenerateToolSchema = Type.Object({\n action: Type.Optional(\n Type.String({\n description: 'Optional: \"generate\" (default) or \"list\" available image-generation providers.',\n }),\n ),\n prompt: Type.Optional(Type.String({ description: 'Image generation prompt.' })),\n model: Type.Optional(\n Type.String({\n description:\n 'Optional provider/model override, e.g. openai/gpt-image-1 / dashscope/wan2.7-image-pro / minimax/image-01.',\n }),\n ),\n filename: Type.Optional(Type.String({ description: 'Optional basename hint for saved files.' })),\n size: Type.Optional(Type.String({ description: 'Optional pixel size, e.g. 1024x1024.' })),\n aspectRatio: Type.Optional(\n Type.String({ description: 'Optional aspect ratio, e.g. 16:9 / 9:16 / 1:1.' }),\n ),\n resolution: Type.Optional(\n Type.String({\n description: 'Optional resolution tier: 1K / 2K / 4K (provider-dependent).',\n }),\n ),\n count: Type.Optional(\n Type.Number({ minimum: 1, maximum: MAX_COUNT, description: `Number of images (1–${MAX_COUNT}).` }),\n ),\n quality: Type.Optional(\n Type.String({ description: 'Optional quality: low / medium / high / auto.' }),\n ),\n outputFormat: Type.Optional(\n Type.String({ description: 'Optional output format: png / jpeg / webp.' }),\n ),\n background: Type.Optional(\n Type.String({ description: 'Optional background: transparent / opaque / auto.' }),\n ),\n inputImages: Type.Optional(\n Type.Array(\n Type.Object({\n source: Type.String({\n description: 'Workspace-relative file path or `data:image/...;base64,...` URL.',\n }),\n }),\n { description: 'Reference images for edit mode (provider must support edit).' },\n ),\n ),\n providerOptions: Type.Optional(\n Type.Object({\n openai: Type.Optional(\n Type.Object({\n moderation: Type.Optional(Type.String()),\n outputCompression: Type.Optional(Type.Number()),\n user: Type.Optional(Type.String()),\n }),\n ),\n }),\n ),\n});\n\nfunction readStringParam(args: Record<string, unknown>, key: string): string | undefined {\n const v = args[key];\n return typeof v === 'string' && v.trim() ? v.trim() : undefined;\n}\n\nfunction readEnumParam<T extends string>(\n args: Record<string, unknown>,\n key: string,\n allowed: ReadonlyArray<T>,\n): T | undefined {\n const raw = readStringParam(args, key);\n if (!raw) return undefined;\n const lower = raw.toLowerCase() as T;\n return allowed.includes(lower) ? lower : undefined;\n}\n\nfunction readResolutionParam(args: Record<string, unknown>): ImageGenerationResolution | undefined {\n const raw = readStringParam(args, 'resolution');\n if (!raw) return undefined;\n const upper = raw.toUpperCase();\n return (ALLOWED_RESOLUTIONS as ReadonlyArray<string>).includes(upper)\n ? (upper as ImageGenerationResolution)\n : undefined;\n}\n\nfunction readProviderOptions(args: Record<string, unknown>): ImageGenerationProviderOptions | undefined {\n const raw = args.providerOptions;\n if (!raw || typeof raw !== 'object') return undefined;\n const out: ImageGenerationProviderOptions = {};\n const oa = (raw as Record<string, unknown>).openai;\n if (oa && typeof oa === 'object') {\n const oaRec = oa as Record<string, unknown>;\n const moderation = typeof oaRec.moderation === 'string' ? oaRec.moderation.toLowerCase() : undefined;\n const outputCompression =\n typeof oaRec.outputCompression === 'number' && Number.isFinite(oaRec.outputCompression)\n ? oaRec.outputCompression\n : undefined;\n const user = typeof oaRec.user === 'string' && oaRec.user.trim() ? oaRec.user.trim() : undefined;\n out.openai = {\n ...(moderation === 'low' || moderation === 'auto' ? { moderation: moderation as 'low' | 'auto' } : {}),\n ...(outputCompression !== undefined ? { outputCompression } : {}),\n ...(user ? { user } : {}),\n };\n if (Object.keys(out.openai).length === 0) delete out.openai;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nclass ToolInputError extends Error {}\n\nasync function loadInputImages(params: {\n workspace: string;\n sources: ReadonlyArray<{ source: string }>;\n}): Promise<ImageGenerationSourceImage[]> {\n if (!params.sources?.length) return [];\n const out: ImageGenerationSourceImage[] = [];\n const workspaceAbs = path.resolve(params.workspace);\n for (const entry of params.sources) {\n const source = entry?.source;\n if (typeof source !== 'string' || !source.trim()) {\n throw new ToolInputError('inputImages[].source is required.');\n }\n const trimmed = source.trim();\n if (/^data:image\\//i.test(trimmed)) {\n const parsed = imageAssetFromDataUrl(trimmed);\n if (!parsed) throw new ToolInputError('Invalid data URL in inputImages[].source.');\n out.push(parsed);\n continue;\n }\n if (/^https?:\\/\\//i.test(trimmed)) {\n throw new ToolInputError('Sandboxed image_generate does not allow remote URLs.');\n }\n const resolved = path.resolve(workspaceAbs, trimmed);\n if (resolved !== workspaceAbs && !resolved.startsWith(workspaceAbs + path.sep)) {\n throw new ToolInputError('inputImages[].source escapes workspace.');\n }\n let buffer: Buffer;\n try {\n buffer = await readFile(resolved);\n } catch (e) {\n const em = e instanceof Error ? e.message : String(e);\n throw new ToolInputError(`inputImages[].source not readable: ${em}`);\n }\n const mimeType = mimeTypeFromFileName(resolved) ?? sniffImageMimeType(buffer).mimeType;\n out.push({ buffer, mimeType, fileName: path.basename(resolved) });\n }\n return out;\n}\n\n// Re-exported for callers / tests; no behavioural change vs imported helper.\nexport { parseImageDataUrl };\n\nexport function resolveImageGenerationModelConfigForTool(params: { cfg?: Config }): ToolModelConfig | null {\n const explicit = params.cfg ? getAgentDefaultImageGenerationModelConfig(params.cfg) : undefined;\n if (explicit?.primary?.trim()) {\n return {\n primary: explicit.primary.trim(),\n ...(explicit.fallbacks?.length ? { fallbacks: explicit.fallbacks } : {}),\n ...(explicit.timeoutMs ? { timeoutMs: explicit.timeoutMs } : {}),\n ...(explicit.autoProviderFallback ? { autoProviderFallback: true } : {}),\n };\n }\n\n // Step 2: tool default = enumerate every provider whose isConfigured() is true,\n // ordered as registered. No more hard-coded openai/dashscope fallback.\n const providers = listImageGenerationProvidersSummary(params.cfg);\n const candidates: string[] = [];\n for (const providerSummary of providers) {\n const provider = getImageGenerationProvider(providerSummary.id, params.cfg);\n let configured = false;\n try {\n configured = provider?.isConfigured?.({ cfg: params.cfg }) ?? false;\n } catch {\n configured = false;\n }\n const modelRef = `${providerSummary.id}/${providerSummary.defaultModel ?? providerSummary.models[0] ?? ''}`;\n if (configured && /^[a-z0-9-]+\\/.+/.test(modelRef) && !candidates.includes(modelRef)) {\n candidates.push(modelRef);\n }\n }\n\n if (candidates.length === 0) {\n return null;\n }\n\n return {\n primary: candidates[0],\n ...(candidates.length > 1 ? { fallbacks: candidates.slice(1) } : {}),\n };\n}\n\nasync function saveGeneratedImages(params: {\n workspace: string;\n images: Array<{ buffer: Buffer; mimeType: string; fileName?: string }>;\n filenameHint?: string;\n}): Promise<string[]> {\n const dir = path.join(params.workspace, 'media', 'generated');\n await mkdir(dir, { recursive: true });\n const out: string[] = [];\n const random = randomBytes(4).toString('hex');\n let i = 0;\n for (const img of params.images) {\n i += 1;\n const ext = imageFileExtensionForMimeType(img.mimeType);\n const base = (params.filenameHint?.replace(/[^\\w.-]/g, '') || 'image') + `-${random}`;\n const name = `${base}-${i}.${ext}`;\n const full = path.join(dir, name);\n await writeFile(full, img.buffer);\n out.push(full);\n }\n return out;\n}\n\nexport function createImageGenerateTool(options: {\n config?: Config;\n workspace: string;\n}): AgentTool<any, Record<string, unknown>> | null {\n const imageGenerationModelConfig = resolveImageGenerationModelConfigForTool({ cfg: options.config });\n if (!imageGenerationModelConfig) {\n return null;\n }\n\n const effectiveCfg =\n applyImageGenerationModelConfigDefaults(options.config, imageGenerationModelConfig) ??\n options.config;\n\n return {\n name: 'image_generate',\n label: 'Image Generation',\n description:\n 'Generate images with the configured image-generation model (default OpenAI). Use action=\"list\" to see providers. Saves files under workspace/media/generated/.',\n parameters: ImageGenerateToolSchema,\n async execute(\n _toolCallId: string,\n args: Record<string, unknown>,\n ): Promise<AgentToolResult<Record<string, unknown>>> {\n const params = args as Record<string, unknown>;\n const action = (readStringParam(params, 'action') || 'generate').toLowerCase();\n if (action === 'list') {\n const providers = listImageGenerationProvidersSummary(effectiveCfg);\n const lines = providers.flatMap((p) => [\n `${p.id}${p.defaultModel ? ` (default ${p.defaultModel})` : ''}`,\n ` models: ${p.models.join(', ')}`,\n ]);\n return {\n content: [{ type: 'text', text: lines.join('\\n') }],\n details: { providers },\n };\n }\n if (action !== 'generate') {\n return {\n content: [{ type: 'text', text: 'action must be \"generate\" or \"list\".' }],\n details: { error: 'bad_action' },\n };\n }\n\n const prompt = readStringParam(params, 'prompt');\n if (!prompt) {\n return {\n content: [{ type: 'text', text: 'prompt is required for image generation.' }],\n details: { error: 'missing_prompt' },\n };\n }\n\n const modelOverride = readStringParam(params, 'model');\n const filename = readStringParam(params, 'filename');\n const size = readStringParam(params, 'size');\n const aspectRatio = readStringParam(params, 'aspectRatio');\n const resolution = readResolutionParam(params);\n const quality = readEnumParam(params, 'quality', ALLOWED_QUALITIES);\n const outputFormat = readEnumParam(params, 'outputFormat', ALLOWED_OUTPUT_FORMATS);\n const background = readEnumParam(params, 'background', ALLOWED_BACKGROUNDS);\n const providerOptions = readProviderOptions(params);\n\n const countRaw = params.count;\n const count =\n typeof countRaw === 'number' && Number.isFinite(countRaw)\n ? Math.min(MAX_COUNT, Math.max(1, Math.floor(countRaw)))\n : DEFAULT_COUNT;\n\n let inputImages: ImageGenerationSourceImage[] = [];\n try {\n inputImages = await loadInputImages({\n workspace: options.workspace,\n sources: (params.inputImages as Array<{ source: string }>) ?? [],\n });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text', text: `Image generation failed: ${msg}` }],\n details: {\n error: e instanceof ToolInputError ? 'invalid_input_images' : 'generation_failed',\n },\n };\n }\n\n try {\n const result = await generateImage({\n cfg: effectiveCfg,\n modelConfig: imageGenerationModelConfig,\n prompt,\n ...(modelOverride ? { modelOverride } : {}),\n count,\n ...(size ? { size } : {}),\n ...(aspectRatio ? { aspectRatio } : {}),\n ...(resolution ? { resolution } : {}),\n ...(quality ? { quality } : {}),\n ...(outputFormat ? { outputFormat } : {}),\n ...(background ? { background } : {}),\n ...(inputImages.length > 0 ? { inputImages } : {}),\n ...(providerOptions ? { providerOptions } : {}),\n ...(imageGenerationModelConfig.timeoutMs ? { timeoutMs: imageGenerationModelConfig.timeoutMs } : {}),\n ...(imageGenerationModelConfig.autoProviderFallback\n ? { autoProviderFallback: true }\n : {}),\n });\n\n const paths = await saveGeneratedImages({\n workspace: options.workspace,\n images: result.images,\n ...(filename ? { filenameHint: filename } : {}),\n });\n\n const workspaceRelativePaths = paths.map((p) =>\n path.relative(options.workspace, p).split(path.sep).join('/'),\n );\n\n const lines: string[] = [\n `Generated ${paths.length} image(s) with ${result.provider}/${result.model}.`,\n ...paths.map((p) => `Saved: ${p}`),\n ];\n for (const note of buildNormalizationNotes(result.normalization, result.ignoredOverrides)) {\n lines.push(note);\n }\n\n return {\n content: [{ type: 'text', text: lines.join('\\n') }],\n details: {\n provider: result.provider,\n model: result.model,\n paths,\n workspaceRelativePaths,\n attempts: result.attempts,\n ...(result.normalization ? { normalization: result.normalization } : {}),\n ...(result.ignoredOverrides.length > 0\n ? { ignoredOverrides: result.ignoredOverrides }\n : {}),\n ...(result.metadata ? { metadata: result.metadata } : {}),\n },\n };\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n const failoverFields = isFailoverError(e)\n ? {\n ...(e.reason !== undefined ? { reason: e.reason } : {}),\n ...(e.status !== undefined ? { status: e.status } : {}),\n ...(e.code !== undefined ? { code: e.code } : {}),\n ...(e.provider !== undefined ? { provider: e.provider } : {}),\n ...(e.model !== undefined ? { model: e.model } : {}),\n attempts: e.attempts,\n }\n : {};\n return {\n content: [{ type: 'text', text: `Image generation failed: ${msg}` }],\n details: { error: 'generation_failed', ...failoverFields },\n };\n }\n },\n } as any;\n}\n\nfunction buildNormalizationNotes(\n normalization: import('../image/generation/types.js').ImageGenerationNormalization | undefined,\n ignoredOverrides: ReadonlyArray<import('../image/generation/types.js').ImageGenerationIgnoredOverride>,\n): string[] {\n const notes: string[] = [];\n if (normalization) {\n for (const [key, entry] of Object.entries(normalization)) {\n if (!entry) continue;\n const requested = entry.requested;\n const applied = entry.applied;\n if (requested !== undefined && applied !== undefined && requested !== applied) {\n notes.push(`Note: requested ${key}=\"${requested}\" → applied \"${applied}\".`);\n } else if (applied !== undefined && entry.derivedFrom) {\n notes.push(`Note: ${key}=\"${applied}\" derived from ${entry.derivedFrom}.`);\n }\n }\n }\n for (const ig of ignoredOverrides) {\n notes.push(`Note: ignored ${ig.key}=\"${ig.value}\" (not supported by provider).`);\n }\n return notes;\n}\n"],"mappings":"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"}
@@ -11,7 +11,7 @@ function createMemorySearchTool(options) {
11
11
  return {
12
12
  name: "memory_search",
13
13
  label: "🔍 Memory Search",
14
- description: "Mandatory recall step: semantically search profile MEMORY.md, agent-home `memories/*.md`, and workspace `memory/*.md` before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with path + lines.",
14
+ description: "Mandatory recall step: search indexed profile, curated, and session memory sources before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with source path + lines.",
15
15
  parameters: MemorySearchSchema,
16
16
  async execute(_toolCallId, params, _signal) {
17
17
  const { query, maxResults, minScore } = params;
@@ -86,7 +86,7 @@ function createMemoryGetTool(options) {
86
86
  return {
87
87
  name: "memory_get",
88
88
  label: "📄 Memory Get",
89
- description: "Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; use after memory_search to pull only the needed lines and keep context small.",
89
+ description: "Safe snippet read from a source path returned by memory_search, with optional from/lines; pull only the needed lines and keep context small.",
90
90
  parameters: MemoryGetSchema,
91
91
  async execute(_toolCallId, params, _signal) {
92
92
  const { path, from, lines } = params;
@@ -1 +1 @@
1
- {"version":3,"file":"memory-tool.js","names":[],"sources":["../../../../src/agent/tools/memory-tool.ts"],"sourcesContent":["// Memory search tools for xopc agent\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { recordDreamingRecalls } from '../memory/dreaming/short-term-store.js';\nimport type { MemoryManager } from '../memory/manager.js';\n\n// =============================================================================\n// Memory Search Tool\n// =============================================================================\nconst MemorySearchSchema = Type.Object({\n query: Type.String(),\n maxResults: Type.Optional(Type.Number()),\n minScore: Type.Optional(Type.Number()),\n});\n\ntype MemorySearchParams = { query: string; maxResults?: number; minScore?: number };\n\nexport interface MemoryToolOptions {\n workspaceDir: string;\n getMemoryManager: () => MemoryManager;\n}\n\nexport function createMemorySearchTool(options: MemoryToolOptions): AgentTool {\n const { workspaceDir, getMemoryManager } = options;\n return {\n name: 'memory_search',\n label: '🔍 Memory Search',\n description:\n 'Mandatory recall step: semantically search profile MEMORY.md, agent-home `memories/*.md`, and workspace `memory/*.md` before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with path + lines.',\n parameters: MemorySearchSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const { query, maxResults, minScore } = params as MemorySearchParams;\n\n try {\n const results = await getMemoryManager().search({ query, maxResults, minScore });\n for (const result of results) {\n getMemoryManager().recordSignal({\n source: 'search_recall',\n recordId: result.record.id,\n score: result.score,\n content: result.snippet,\n metadata: {\n providerId: result.citation.providerId,\n path: result.citation.path,\n lineStart: result.citation.lineStart,\n lineEnd: result.citation.lineEnd,\n },\n });\n }\n // Dreaming: record short-term recall evidence from memory_search.\n // Only records workspace daily notes (`memory/YYYY-MM-DD.md`) and ignores curated/long-term files.\n void recordDreamingRecalls({\n workspaceDir,\n query,\n matches: results.map((entry) => ({\n file: entry.citation.path ?? entry.record.source.path ?? entry.record.id,\n lines: entry.snippet,\n score: entry.score,\n lineNumbers:\n entry.citation.lineStart != null\n ? [entry.citation.lineStart, entry.citation.lineEnd ?? entry.citation.lineStart]\n : [],\n })),\n }).catch(() => {});\n const withCitations = results.map((entry) => ({\n id: entry.record.id,\n kind: entry.record.kind,\n file: entry.citation.path ?? entry.record.source.path,\n lines: entry.snippet,\n score: entry.score,\n lineNumbers:\n entry.citation.lineStart != null\n ? [entry.citation.lineStart, entry.citation.lineEnd ?? entry.citation.lineStart]\n : [],\n citation: `${entry.citation.path ?? entry.record.id}${entry.citation.lineStart != null ? `#L${entry.citation.lineStart}${entry.citation.lineEnd && entry.citation.lineEnd !== entry.citation.lineStart ? `-L${entry.citation.lineEnd}` : ''}` : ''}`,\n snippet: `${entry.snippet.trim()}\\n\\nSource: ${entry.citation.path ?? entry.record.id}${entry.citation.lineStart != null ? `#L${entry.citation.lineStart}${entry.citation.lineEnd && entry.citation.lineEnd !== entry.citation.lineStart ? `-L${entry.citation.lineEnd}` : ''}` : ''}`,\n }));\n\n return {\n content: [{ type: 'text', text: JSON.stringify({ results: withCitations, provider: 'memory-manager' }, null, 2) }],\n details: { results: withCitations },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Search error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n\n// =============================================================================\n// Memory Get Tool\n// =============================================================================\nconst MemoryGetSchema = Type.Object({\n path: Type.String(),\n from: Type.Optional(Type.Number()),\n lines: Type.Optional(Type.Number()),\n});\n\ntype MemoryGetParams = { path: string; from?: number; lines?: number };\n\nexport function createMemoryGetTool(options: MemoryToolOptions): AgentTool {\n const { getMemoryManager } = options;\n return {\n name: 'memory_get',\n label: '📄 Memory Get',\n description: 'Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; use after memory_search to pull only the needed lines and keep context small.',\n parameters: MemoryGetSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const { path, from, lines } = params as MemoryGetParams;\n\n try {\n const result = await getMemoryManager().read({ path, from, lines });\n if (!result) {\n return {\n content: [{ type: 'text', text: `File not found: ${path}` }],\n details: { path, text: '' },\n };\n }\n return {\n content: [{ type: 'text', text: result.record.content }],\n details: { path, text: result.record.content, lineNumbers: result.lineNumbers },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Read error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-tool.js","names":[],"sources":["../../../../src/agent/tools/memory-tool.ts"],"sourcesContent":["// Memory search tools for xopc agent\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { recordDreamingRecalls } from '../memory/dreaming/short-term-store.js';\nimport type { MemoryManager } from '../memory/manager.js';\n\n// =============================================================================\n// Memory Search Tool\n// =============================================================================\nconst MemorySearchSchema = Type.Object({\n query: Type.String(),\n maxResults: Type.Optional(Type.Number()),\n minScore: Type.Optional(Type.Number()),\n});\n\ntype MemorySearchParams = { query: string; maxResults?: number; minScore?: number };\n\nexport interface MemoryToolOptions {\n workspaceDir: string;\n getMemoryManager: () => MemoryManager;\n}\n\nexport function createMemorySearchTool(options: MemoryToolOptions): AgentTool {\n const { workspaceDir, getMemoryManager } = options;\n return {\n name: 'memory_search',\n label: '🔍 Memory Search',\n description:\n 'Mandatory recall step: search indexed profile, curated, and session memory sources before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with source path + lines.',\n parameters: MemorySearchSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const { query, maxResults, minScore } = params as MemorySearchParams;\n\n try {\n const results = await getMemoryManager().search({ query, maxResults, minScore });\n for (const result of results) {\n getMemoryManager().recordSignal({\n source: 'search_recall',\n recordId: result.record.id,\n score: result.score,\n content: result.snippet,\n metadata: {\n providerId: result.citation.providerId,\n path: result.citation.path,\n lineStart: result.citation.lineStart,\n lineEnd: result.citation.lineEnd,\n },\n });\n }\n // Dreaming: record short-term recall evidence from memory_search.\n void recordDreamingRecalls({\n workspaceDir,\n query,\n matches: results.map((entry) => ({\n file: entry.citation.path ?? entry.record.source.path ?? entry.record.id,\n lines: entry.snippet,\n score: entry.score,\n lineNumbers:\n entry.citation.lineStart != null\n ? [entry.citation.lineStart, entry.citation.lineEnd ?? entry.citation.lineStart]\n : [],\n })),\n }).catch(() => {});\n const withCitations = results.map((entry) => ({\n id: entry.record.id,\n kind: entry.record.kind,\n file: entry.citation.path ?? entry.record.source.path,\n lines: entry.snippet,\n score: entry.score,\n lineNumbers:\n entry.citation.lineStart != null\n ? [entry.citation.lineStart, entry.citation.lineEnd ?? entry.citation.lineStart]\n : [],\n citation: `${entry.citation.path ?? entry.record.id}${entry.citation.lineStart != null ? `#L${entry.citation.lineStart}${entry.citation.lineEnd && entry.citation.lineEnd !== entry.citation.lineStart ? `-L${entry.citation.lineEnd}` : ''}` : ''}`,\n snippet: `${entry.snippet.trim()}\\n\\nSource: ${entry.citation.path ?? entry.record.id}${entry.citation.lineStart != null ? `#L${entry.citation.lineStart}${entry.citation.lineEnd && entry.citation.lineEnd !== entry.citation.lineStart ? `-L${entry.citation.lineEnd}` : ''}` : ''}`,\n }));\n\n return {\n content: [{ type: 'text', text: JSON.stringify({ results: withCitations, provider: 'memory-manager' }, null, 2) }],\n details: { results: withCitations },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Search error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n\n// =============================================================================\n// Memory Get Tool\n// =============================================================================\nconst MemoryGetSchema = Type.Object({\n path: Type.String(),\n from: Type.Optional(Type.Number()),\n lines: Type.Optional(Type.Number()),\n});\n\ntype MemoryGetParams = { path: string; from?: number; lines?: number };\n\nexport function createMemoryGetTool(options: MemoryToolOptions): AgentTool {\n const { getMemoryManager } = options;\n return {\n name: 'memory_get',\n label: '📄 Memory Get',\n description: 'Safe snippet read from a source path returned by memory_search, with optional from/lines; pull only the needed lines and keep context small.',\n parameters: MemoryGetSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n _signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const { path, from, lines } = params as MemoryGetParams;\n\n try {\n const result = await getMemoryManager().read({ path, from, lines });\n if (!result) {\n return {\n content: [{ type: 'text', text: `File not found: ${path}` }],\n details: { path, text: '' },\n };\n }\n return {\n content: [{ type: 'text', text: result.record.content }],\n details: { path, text: result.record.content, lineNumbers: result.lineNumbers },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Read error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { getDefaultModelSync, init_providers, resolveModel } from "../../providers/index.js";
4
4
  import { readAgentMessageContent } from "../memory/agent-message-access.js";
5
- import { complete } from "@earendil-works/pi-ai";
5
+ import { complete } from "@earendil-works/pi-ai/compat";
6
6
  import { Type } from "@sinclair/typebox";
7
7
  //#region src/agent/tools/session-search-tool.ts
8
8
  init_providers();
@@ -1 +1 @@
1
- {"version":3,"file":"session-search-tool.js","names":[],"sources":["../../../../src/agent/tools/session-search-tool.ts"],"sourcesContent":["// Cross-session transcript search + optional LLM summaries\nimport { Type } from '@sinclair/typebox';\nimport type { AgentMessage, AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Agent:SessionSearch');\n\nconst MAX_SUMMARY_CHARS = 20_000;\n\nfunction resolveSummaryModel(getConfig?: () => Config | undefined) {\n const envRef = process.env.XOPC_SESSION_SEARCH_MODEL?.trim();\n const ref = envRef;\n if (ref) {\n try {\n return resolveModel(ref);\n } catch (err) {\n log.warn({ err, ref }, 'session_search: summary model resolve failed, using fallback');\n }\n }\n try {\n return resolveModel('openai/gpt-4o-mini');\n } catch {\n const d = getDefaultModelSync(getConfig?.());\n return resolveModel(d);\n }\n}\n\nfunction extractTextFromContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const item of content) {\n if (typeof item !== 'object' || item === null || !('type' in item)) {\n continue;\n }\n const c = item as { type?: string; text?: string };\n if (c.type === 'text' && typeof c.text === 'string') {\n parts.push(c.text);\n }\n }\n return parts.join(' ');\n}\n\nfunction formatMessagesForSummary(messages: AgentMessage[]): string {\n const lines: string[] = [];\n let total = 0;\n for (const msg of messages) {\n const role = String(msg.role || 'unknown').toUpperCase();\n const text = extractTextFromContent(readAgentMessageContent(msg));\n const line = `${role}: ${text}`;\n if (total + line.length > MAX_SUMMARY_CHARS) {\n lines.push('… [truncated]');\n break;\n }\n lines.push(line);\n total += line.length + 1;\n }\n return lines.join('\\n\\n');\n}\n\nasync function summarizeSession(\n messages: AgentMessage[],\n query: string,\n getConfig: (() => Config | undefined) | undefined,\n signal: AbortSignal | undefined,\n logMeta?: { summarizingSessionKey: string },\n): Promise<string> {\n if (messages.length === 0) {\n return 'No messages in session.';\n }\n\n const formatted = formatMessagesForSummary(messages);\n const prompt = `Summarize this conversation for someone searching with: \"${query}\". Focus on facts, decisions, and names. Max 200 words. Use the same language as the conversation when possible.\n\nConversation:\n${formatted}`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const model = resolveSummaryModel(getConfig);\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 400,\n temperature: 0.15,\n signal: signal as AbortSignal,\n },\n );\n\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n return text.trim() || '[Empty summary]';\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n summarizingSessionKey: logMeta?.summarizingSessionKey,\n queryLength: query.length,\n messageCount: messages.length,\n },\n `session_search LLM summarization failed${logMeta?.summarizingSessionKey ? ` (session ${logMeta.summarizingSessionKey})` : ''}: ${msg}`,\n );\n return `[Summarization failed: ${msg}]`;\n }\n}\n\nconst SessionSearchSchema = Type.Object({\n query: Type.Optional(\n Type.String({ description: 'Keyword search over past sessions. Omit to list recent sessions.' }),\n ),\n roleFilter: Type.Optional(\n Type.Union([\n Type.Literal('user'),\n Type.Literal('assistant'),\n Type.Literal('system'),\n Type.Literal('tool'),\n Type.Literal('toolResult'),\n ]),\n ),\n limit: Type.Optional(Type.Number({ minimum: 1, maximum: 15 })),\n excludeSessionKey: Type.Optional(\n Type.String({ description: 'Exclude this session key from keyword results (default: current chat).' }),\n ),\n});\n\nexport interface SessionSearchToolDeps {\n getSessionStore: () => SessionStore;\n getConfig?: () => Config | undefined;\n getCurrentSessionKey?: () => string | undefined;\n}\n\ntype SessionSearchParams = {\n query?: string;\n roleFilter?: 'user' | 'assistant' | 'system' | 'tool' | 'toolResult';\n limit?: number;\n excludeSessionKey?: string;\n};\n\nexport function createSessionSearchTool(deps: SessionSearchToolDeps): AgentTool {\n return {\n name: 'session_search',\n label: 'Session search',\n description:\n 'Search other chat sessions by keywords and get short summaries, or omit `query` to list recent sessions (no LLM cost). Uses the same session store as the gateway. Narrow with roleFilter if needed.',\n parameters: SessionSearchSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const p = params as SessionSearchParams;\n const store = deps.getSessionStore();\n const limit = Math.min(15, Math.max(1, p.limit ?? 5));\n const query = p.query?.trim() ?? '';\n\n try {\n if (!query) {\n const listed = await store.list({\n limit: limit + 5,\n sortBy: 'updatedAt',\n sortOrder: 'desc',\n });\n const items = listed.items.slice(0, limit).map((s) => ({\n key: s.key,\n name: s.name,\n updatedAt: s.updatedAt,\n messageCount: s.messageCount,\n sourceChannel: s.sourceChannel,\n }));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { success: true, mode: 'recent', results: items, total: listed.total },\n null,\n 2,\n ),\n },\n ],\n details: { mode: 'recent', items },\n };\n }\n\n const listed = await store.list({\n search: query,\n limit: limit + 10,\n sortBy: 'updatedAt',\n sortOrder: 'desc',\n });\n let matches = listed.items.map((item) => ({ key: item.key, score: 1 }));\n\n const exclude = p.excludeSessionKey?.trim() || deps.getCurrentSessionKey?.() || '';\n if (exclude) {\n matches = matches.filter((m) => m.key !== exclude);\n }\n\n const top = matches.slice(0, limit);\n\n const summaries = await Promise.all(\n top.map(async ({ key, score }) => {\n let messages = await store.load(key);\n\n if (p.roleFilter) {\n messages = messages.filter((m) => m.role === p.roleFilter);\n }\n\n const summary = await summarizeSession(messages, query, deps.getConfig, signal, {\n summarizingSessionKey: key,\n });\n return { sessionKey: key, score, summary };\n }),\n );\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n mode: 'keyword',\n query,\n results: summaries,\n count: summaries.length,\n },\n null,\n 2,\n ),\n },\n ],\n details: { query, summaries },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `session_search error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-search-tool.js","names":[],"sources":["../../../../src/agent/tools/session-search-tool.ts"],"sourcesContent":["// Cross-session transcript search + optional LLM summaries\nimport { Type } from '@sinclair/typebox';\nimport type { AgentMessage, AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport type { SessionStore } from '../../session/store.js';\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('Agent:SessionSearch');\n\nconst MAX_SUMMARY_CHARS = 20_000;\n\nfunction resolveSummaryModel(getConfig?: () => Config | undefined) {\n const envRef = process.env.XOPC_SESSION_SEARCH_MODEL?.trim();\n const ref = envRef;\n if (ref) {\n try {\n return resolveModel(ref);\n } catch (err) {\n log.warn({ err, ref }, 'session_search: summary model resolve failed, using fallback');\n }\n }\n try {\n return resolveModel('openai/gpt-4o-mini');\n } catch {\n const d = getDefaultModelSync(getConfig?.());\n return resolveModel(d);\n }\n}\n\nfunction extractTextFromContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const item of content) {\n if (typeof item !== 'object' || item === null || !('type' in item)) {\n continue;\n }\n const c = item as { type?: string; text?: string };\n if (c.type === 'text' && typeof c.text === 'string') {\n parts.push(c.text);\n }\n }\n return parts.join(' ');\n}\n\nfunction formatMessagesForSummary(messages: AgentMessage[]): string {\n const lines: string[] = [];\n let total = 0;\n for (const msg of messages) {\n const role = String(msg.role || 'unknown').toUpperCase();\n const text = extractTextFromContent(readAgentMessageContent(msg));\n const line = `${role}: ${text}`;\n if (total + line.length > MAX_SUMMARY_CHARS) {\n lines.push('… [truncated]');\n break;\n }\n lines.push(line);\n total += line.length + 1;\n }\n return lines.join('\\n\\n');\n}\n\nasync function summarizeSession(\n messages: AgentMessage[],\n query: string,\n getConfig: (() => Config | undefined) | undefined,\n signal: AbortSignal | undefined,\n logMeta?: { summarizingSessionKey: string },\n): Promise<string> {\n if (messages.length === 0) {\n return 'No messages in session.';\n }\n\n const formatted = formatMessagesForSummary(messages);\n const prompt = `Summarize this conversation for someone searching with: \"${query}\". Focus on facts, decisions, and names. Max 200 words. Use the same language as the conversation when possible.\n\nConversation:\n${formatted}`;\n\n const userMsg: UserMessage = { role: 'user', content: prompt, timestamp: Date.now() };\n const model = resolveSummaryModel(getConfig);\n\n try {\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens: 400,\n temperature: 0.15,\n signal: signal as AbortSignal,\n },\n );\n\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n return text.trim() || '[Empty summary]';\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n summarizingSessionKey: logMeta?.summarizingSessionKey,\n queryLength: query.length,\n messageCount: messages.length,\n },\n `session_search LLM summarization failed${logMeta?.summarizingSessionKey ? ` (session ${logMeta.summarizingSessionKey})` : ''}: ${msg}`,\n );\n return `[Summarization failed: ${msg}]`;\n }\n}\n\nconst SessionSearchSchema = Type.Object({\n query: Type.Optional(\n Type.String({ description: 'Keyword search over past sessions. Omit to list recent sessions.' }),\n ),\n roleFilter: Type.Optional(\n Type.Union([\n Type.Literal('user'),\n Type.Literal('assistant'),\n Type.Literal('system'),\n Type.Literal('tool'),\n Type.Literal('toolResult'),\n ]),\n ),\n limit: Type.Optional(Type.Number({ minimum: 1, maximum: 15 })),\n excludeSessionKey: Type.Optional(\n Type.String({ description: 'Exclude this session key from keyword results (default: current chat).' }),\n ),\n});\n\nexport interface SessionSearchToolDeps {\n getSessionStore: () => SessionStore;\n getConfig?: () => Config | undefined;\n getCurrentSessionKey?: () => string | undefined;\n}\n\ntype SessionSearchParams = {\n query?: string;\n roleFilter?: 'user' | 'assistant' | 'system' | 'tool' | 'toolResult';\n limit?: number;\n excludeSessionKey?: string;\n};\n\nexport function createSessionSearchTool(deps: SessionSearchToolDeps): AgentTool {\n return {\n name: 'session_search',\n label: 'Session search',\n description:\n 'Search other chat sessions by keywords and get short summaries, or omit `query` to list recent sessions (no LLM cost). Uses the same session store as the gateway. Narrow with roleFilter if needed.',\n parameters: SessionSearchSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{}>> {\n const p = params as SessionSearchParams;\n const store = deps.getSessionStore();\n const limit = Math.min(15, Math.max(1, p.limit ?? 5));\n const query = p.query?.trim() ?? '';\n\n try {\n if (!query) {\n const listed = await store.list({\n limit: limit + 5,\n sortBy: 'updatedAt',\n sortOrder: 'desc',\n });\n const items = listed.items.slice(0, limit).map((s) => ({\n key: s.key,\n name: s.name,\n updatedAt: s.updatedAt,\n messageCount: s.messageCount,\n sourceChannel: s.sourceChannel,\n }));\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { success: true, mode: 'recent', results: items, total: listed.total },\n null,\n 2,\n ),\n },\n ],\n details: { mode: 'recent', items },\n };\n }\n\n const listed = await store.list({\n search: query,\n limit: limit + 10,\n sortBy: 'updatedAt',\n sortOrder: 'desc',\n });\n let matches = listed.items.map((item) => ({ key: item.key, score: 1 }));\n\n const exclude = p.excludeSessionKey?.trim() || deps.getCurrentSessionKey?.() || '';\n if (exclude) {\n matches = matches.filter((m) => m.key !== exclude);\n }\n\n const top = matches.slice(0, limit);\n\n const summaries = await Promise.all(\n top.map(async ({ key, score }) => {\n let messages = await store.load(key);\n\n if (p.roleFilter) {\n messages = messages.filter((m) => m.role === p.roleFilter);\n }\n\n const summary = await summarizeSession(messages, query, deps.getConfig, signal, {\n summarizingSessionKey: key,\n });\n return { sessionKey: key, score, summary };\n }),\n );\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n mode: 'keyword',\n query,\n results: summaries,\n count: summaries.length,\n },\n null,\n 2,\n ),\n },\n ],\n details: { query, summaries },\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `session_search error: ${message}` }],\n details: { error: message },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { getDefaultModelSync, init_providers, resolveModel } from "../../providers/index.js";
4
4
  import { checkUrlSafety, checkWebsiteBlocklist, cleanBase64Images } from "./url-safety.js";
5
- import { complete } from "@earendil-works/pi-ai";
5
+ import { complete } from "@earendil-works/pi-ai/compat";
6
6
  import { Type } from "@sinclair/typebox";
7
7
  //#region src/agent/tools/web-extract.ts
8
8
  init_providers();
@@ -1 +1 @@
1
- {"version":3,"file":"web-extract.js","names":[],"sources":["../../../../src/agent/tools/web-extract.ts"],"sourcesContent":["import { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { checkUrlSafety, checkWebsiteBlocklist, cleanBase64Images } from './url-safety.js';\n\nconst log = createLogger('web-extract');\n\nexport const DEFAULT_WEB_EXTRACT_MAX_LENGTH = 15_000;\nexport const MAX_RAW_HTML_CHARS_FOR_WEB_EXTRACT = 200_000;\nconst EXTRACTION_TIMEOUT_MS = 30_000;\n\n// Large-document chunked processing thresholds (inspired by hermes-agent)\nconst MAX_CONTENT_SIZE = 2_000_000; // 2M chars — refuse entirely above this\nconst CHUNK_THRESHOLD = 500_000; // 500k chars — use chunked processing above this\nconst CHUNK_SIZE = 100_000; // 100k chars per chunk\nconst MAX_OUTPUT_SIZE = 15_000; // Hard cap on final output size\n\nconst WebExtractSchema = Type.Object({\n url: Type.String({ description: 'URL to extract content from' }),\n instruction: Type.Optional(\n Type.String({\n description:\n 'What to extract or focus on. Examples: \"main article text\", ' +\n '\"pricing table\", \"API documentation for the auth endpoint\". ' +\n 'If omitted, extracts the main content.',\n }),\n ),\n maxLength: Type.Optional(\n Type.Number({\n description: 'Maximum characters in the extracted content (default: from config or 15000)',\n }),\n ),\n});\n\nexport interface WebExtractDeps {\n getConfig: () => Config | undefined;\n}\n\nasync function fetchPageContent(url: string, signal?: AbortSignal): Promise<string> {\n const response = await fetch(url, {\n signal,\n headers: {\n 'User-Agent': 'Mozilla/5.0 (compatible; xopc/1.0)',\n Accept: 'text/html,application/xhtml+xml,text/plain,application/json',\n },\n redirect: 'follow',\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n const json: unknown = await response.json();\n return JSON.stringify(json, null, 2);\n }\n\n return await response.text();\n}\n\nexport function stripHtmlBoilerplate(\n html: string,\n maxRaw = MAX_RAW_HTML_CHARS_FOR_WEB_EXTRACT,\n): string {\n let cleaned = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, '')\n .replace(/<!--[\\s\\S]*?-->/g, '');\n\n // Remove base64 images early to avoid wasting characters on them\n cleaned = cleanBase64Images(cleaned);\n cleaned = cleaned.replace(/\\s+/g, ' ').trim();\n\n if (cleaned.length > maxRaw) {\n cleaned = cleaned.slice(0, maxRaw) + '\\n\\n[...truncated]';\n }\n\n return cleaned;\n}\n\nfunction resolveExtractionModel(config?: Config) {\n const envRef = process.env.XOPC_WEB_EXTRACT_MODEL?.trim();\n const ref = envRef;\n\n if (ref) {\n try {\n return resolveModel(ref);\n } catch {\n /* fall through */\n }\n }\n\n const defaultRef = getDefaultModelSync(config);\n if (!defaultRef) {\n throw new Error('No global default model configured for web extraction');\n }\n\n return resolveModel(defaultRef);\n}\n\nfunction buildExtractionSystemPrompt(maxLength: number): string {\n return (\n 'You are a web content extractor. Given raw HTML/text from a web page, ' +\n 'extract the relevant content as clean, well-structured markdown.\\n\\n' +\n 'Rules:\\n' +\n '- Remove navigation, headers, footers, ads, cookie banners, sidebars\\n' +\n '- Preserve the main content structure (headings, lists, tables, code blocks)\\n' +\n '- Keep URLs for important links\\n' +\n '- If the page has structured data (tables, specs), preserve the structure\\n' +\n `- Keep output under ${maxLength} characters\\n` +\n '- If content is truncated, note what was cut\\n' +\n '- Output ONLY the extracted content, no meta-commentary'\n );\n}\n\n// ---------------------------------------------------------------------------\n// LLM call helper — shared by single-pass and chunked extraction\n// ---------------------------------------------------------------------------\n\nfunction buildTimeoutSignal(signal: AbortSignal | undefined): AbortSignal | undefined {\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(EXTRACTION_TIMEOUT_MS)\n : undefined;\n if (signal && timeoutSignal && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([signal, timeoutSignal]);\n }\n return signal ?? timeoutSignal;\n}\n\nfunction extractTextFromCompletion(result: { content?: unknown }): string {\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n return text.trim();\n}\n\nasync function callExtractionLlm(\n systemPrompt: string,\n userPrompt: string,\n maxTokens: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n): Promise<string> {\n const model = resolveExtractionModel(config);\n const userMsg: UserMessage = {\n role: 'user',\n content: `${systemPrompt}\\n\\n---\\n\\n${userPrompt}`,\n timestamp: Date.now(),\n };\n\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens,\n temperature: 0.2,\n signal: buildTimeoutSignal(signal) as AbortSignal,\n },\n );\n\n return extractTextFromCompletion(result);\n}\n\n// ---------------------------------------------------------------------------\n// Single-pass extraction (content < CHUNK_THRESHOLD)\n// ---------------------------------------------------------------------------\n\nasync function extractWithLlm(\n rawContent: string,\n url: string,\n instruction: string | undefined,\n maxLength: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n): Promise<string> {\n const contentLength = rawContent.length;\n\n // Refuse absurdly large content\n if (contentLength > MAX_CONTENT_SIZE) {\n const sizeMb = (contentLength / 1_000_000).toFixed(1);\n return (\n `[Content too large to process: ${sizeMb}MB. ` +\n 'Try using web_fetch for raw text, or search for a more focused source.]'\n );\n }\n\n // Route to chunked processing for large documents\n if (contentLength > CHUNK_THRESHOLD) {\n log.info(\n { url, contentLength, chunkSize: CHUNK_SIZE },\n 'Large document detected, using chunked extraction',\n );\n return extractLargeDocumentChunked(rawContent, url, instruction, maxLength, config, signal);\n }\n\n // Standard single-pass extraction\n const systemPrompt = buildExtractionSystemPrompt(maxLength);\n const body = instruction\n ? `Extract from this page (${url}):\\n\\nFOCUS: ${instruction}\\n\\n---\\n\\n${rawContent}`\n : `Extract the main content from this page (${url}):\\n\\n---\\n\\n${rawContent}`;\n\n const text = await callExtractionLlm(\n systemPrompt,\n body,\n Math.min(Math.ceil(maxLength / 3), 8000),\n config,\n signal,\n );\n\n if (!text) return 'No content could be extracted.';\n\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n if (text.length > outputCap) {\n return text.slice(0, outputCap) + '\\n\\n[...output truncated to maxLength]';\n }\n\n log.debug(\n { url, originalChars: contentLength, extractedChars: text.length },\n 'Single-pass extraction complete',\n );\n return text;\n}\n\n// ---------------------------------------------------------------------------\n// Chunked extraction for large documents (inspired by hermes-agent)\n//\n// Flow: split → parallel chunk summaries → synthesis pass\n// ---------------------------------------------------------------------------\n\nasync function extractLargeDocumentChunked(\n content: string,\n url: string,\n instruction: string | undefined,\n maxLength: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n): Promise<string> {\n // Split content into chunks\n const chunks: string[] = [];\n for (let offset = 0; offset < content.length; offset += CHUNK_SIZE) {\n chunks.push(content.slice(offset, offset + CHUNK_SIZE));\n }\n log.info({ url, chunkCount: chunks.length, chunkSize: CHUNK_SIZE }, 'Split into chunks');\n\n // Summarize each chunk in parallel\n const chunkPromises = chunks.map(async (chunk, index) => {\n const chunkLabel = `[Chunk ${index + 1}/${chunks.length}]`;\n try {\n const systemPrompt =\n 'You are an expert content analyst processing a SECTION of a larger document. ' +\n 'Extract and summarize the key information from THIS SECTION ONLY.\\n\\n' +\n 'Guidelines:\\n' +\n '- Do NOT write introductions or conclusions — this is a partial document\\n' +\n '- Extract ALL key facts, figures, data points, and insights\\n' +\n '- Preserve important quotes, code snippets, and specific details verbatim\\n' +\n '- Use bullet points and structured formatting\\n' +\n '- Note references to other sections without trying to resolve them';\n\n const focusLine = instruction ? `\\nFOCUS: ${instruction}\\n` : '';\n const userPrompt =\n `${chunkLabel} Extract key information from this section of ${url}:` +\n `${focusLine}\\n\\n${chunk}`;\n\n const summary = await callExtractionLlm(systemPrompt, userPrompt, 6000, config, signal);\n if (summary) {\n log.debug(\n { url, chunk: index + 1, originalChars: chunk.length, summaryChars: summary.length },\n 'Chunk summarized',\n );\n }\n return { index, summary: summary || null };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n log.warn({ url, chunk: index + 1, error: message }, 'Chunk summarization failed');\n return { index, summary: null };\n }\n });\n\n const chunkResults = await Promise.all(chunkPromises);\n\n // Collect successful summaries in order\n const summaries: string[] = [];\n for (const { index, summary } of chunkResults.sort((a, b) => a.index - b.index)) {\n if (summary) {\n summaries.push(`## Section ${index + 1}\\n${summary}`);\n }\n }\n\n if (summaries.length === 0) {\n // All chunks failed — fall back to truncated raw content\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n const truncated = content.slice(0, outputCap);\n return truncated + '\\n\\n[...all chunk summaries failed; showing truncated raw content]';\n }\n\n log.info(\n { url, successfulChunks: summaries.length, totalChunks: chunks.length },\n 'Chunk summaries collected, synthesizing',\n );\n\n // Single chunk — return directly\n if (summaries.length === 1) {\n const result = summaries[0];\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n return result.length > outputCap\n ? result.slice(0, outputCap) + '\\n\\n[...truncated]'\n : result;\n }\n\n // Synthesize multiple chunk summaries into one cohesive summary\n return synthesizeChunkSummaries(summaries, url, instruction, maxLength, config, signal, content.length);\n}\n\nasync function synthesizeChunkSummaries(\n summaries: string[],\n url: string,\n instruction: string | undefined,\n maxLength: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n originalContentLength: number,\n): Promise<string> {\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n\n const systemPrompt =\n 'You synthesize multiple section summaries into one cohesive, comprehensive summary. ' +\n 'Remove redundancy, preserve all key facts and actionable information, ' +\n 'and organize with clear markdown structure.';\n\n const focusLine = instruction ? `\\nFOCUS: ${instruction}\\n` : '';\n const combined = summaries.join('\\n\\n---\\n\\n');\n const userPrompt =\n `Synthesize these section summaries from ${url} into ONE unified markdown summary ` +\n `(under ${outputCap} characters):${focusLine}\\n\\n${combined}`;\n\n try {\n const synthesized = await callExtractionLlm(systemPrompt, userPrompt, 8000, config, signal);\n\n if (!synthesized) {\n // Synthesis LLM returned empty — fall back to concatenation\n log.warn({ url }, 'Synthesis returned empty, concatenating chunk summaries');\n const fallback = summaries.join('\\n\\n');\n return fallback.length > outputCap\n ? fallback.slice(0, outputCap) + '\\n\\n[...truncated]'\n : fallback;\n }\n\n const finalOutput = synthesized.length > outputCap\n ? synthesized.slice(0, outputCap) + '\\n\\n[...summary truncated for context management]'\n : synthesized;\n\n const compressionRatio = finalOutput.length / originalContentLength;\n log.info(\n {\n url,\n originalChars: originalContentLength,\n extractedChars: finalOutput.length,\n compressionPercent: `${(compressionRatio * 100).toFixed(1)}%`,\n },\n 'Chunked extraction complete',\n );\n\n return finalOutput;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n log.warn({ url, error: message }, 'Synthesis failed, concatenating chunk summaries');\n const fallback = summaries.join('\\n\\n');\n return fallback.length > outputCap\n ? fallback.slice(0, outputCap) + '\\n\\n[...truncated due to synthesis failure]'\n : fallback;\n }\n}\n\ntype WebExtractParams = { url: string; instruction?: string; maxLength?: number };\n\nfunction resolveConfiguredMaxLength(): number {\n return DEFAULT_WEB_EXTRACT_MAX_LENGTH;\n}\n\nexport function createWebExtractTool(deps: WebExtractDeps): AgentTool {\n return {\n name: 'web_extract',\n label: '📄 Web Extract',\n description:\n 'Extract and summarize content from a web page using LLM-powered extraction.\\n\\n' +\n 'Unlike web_fetch (which returns cleaned plain text from HTML), this tool uses a ' +\n 'dedicated model pass to produce focused markdown. Use when you need semantic extraction ' +\n 'or a specific focus (instruction), not just a single readability pass.\\n\\n' +\n 'WHEN TO USE:\\n' +\n '- Reading articles, blog posts, documentation\\n' +\n '- Extracting specific data (pricing, specs, API docs)\\n' +\n '- Summarizing long pages to save context\\n\\n' +\n 'WHEN TO USE web_fetch INSTEAD:\\n' +\n '- Inspecting raw-ish page text quickly without an extra LLM call\\n' +\n '- Downloading JSON/API responses\\n' +\n '- Debugging page rendering issues',\n parameters: WebExtractSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{ url: string; extractedLength: number }>> {\n const { url, instruction } = params as WebExtractParams;\n const cfg = deps.getConfig();\n const maxLength = (params as WebExtractParams).maxLength ?? resolveConfiguredMaxLength();\n\n try {\n // SSRF protection\n const safety = checkUrlSafety(url);\n if (!safety.safe) {\n return {\n content: [{ type: 'text', text: `Blocked: ${safety.reason}` }],\n details: { url, extractedLength: 0 },\n };\n }\n\n // Website blocklist check\n const blocked = checkWebsiteBlocklist(url, cfg?.tools?.web?.blocklist);\n if (blocked) {\n return {\n content: [{ type: 'text', text: `Blocked: ${blocked.message}` }],\n details: { url, extractedLength: 0 },\n };\n }\n\n const rawContent = await fetchPageContent(url, signal);\n const cleaned = stripHtmlBoilerplate(rawContent);\n const trimmed = cleaned.trim();\n const looksStructured = trimmed.startsWith('{') || trimmed.startsWith('[');\n if (trimmed.length < 100 && !looksStructured) {\n return {\n content: [\n { type: 'text', text: `Page at ${url} appears to have no extractable content.` },\n ],\n details: { url, extractedLength: 0 },\n };\n }\n\n const extracted = await extractWithLlm(\n cleaned,\n url,\n instruction,\n maxLength,\n cfg,\n signal,\n );\n\n return {\n content: [{ type: 'text', text: extracted }],\n details: { url, extractedLength: extracted.length },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n log.warn({ url, error: message }, 'web_extract failed');\n return {\n content: [{ type: 'text', text: `Failed to extract content from ${url}: ${message}` }],\n details: { url, extractedLength: 0 },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"web-extract.js","names":[],"sources":["../../../../src/agent/tools/web-extract.ts"],"sourcesContent":["import { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport type { Config } from '../../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../../providers/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { checkUrlSafety, checkWebsiteBlocklist, cleanBase64Images } from './url-safety.js';\n\nconst log = createLogger('web-extract');\n\nexport const DEFAULT_WEB_EXTRACT_MAX_LENGTH = 15_000;\nexport const MAX_RAW_HTML_CHARS_FOR_WEB_EXTRACT = 200_000;\nconst EXTRACTION_TIMEOUT_MS = 30_000;\n\n// Large-document chunked processing thresholds (inspired by hermes-agent)\nconst MAX_CONTENT_SIZE = 2_000_000; // 2M chars — refuse entirely above this\nconst CHUNK_THRESHOLD = 500_000; // 500k chars — use chunked processing above this\nconst CHUNK_SIZE = 100_000; // 100k chars per chunk\nconst MAX_OUTPUT_SIZE = 15_000; // Hard cap on final output size\n\nconst WebExtractSchema = Type.Object({\n url: Type.String({ description: 'URL to extract content from' }),\n instruction: Type.Optional(\n Type.String({\n description:\n 'What to extract or focus on. Examples: \"main article text\", ' +\n '\"pricing table\", \"API documentation for the auth endpoint\". ' +\n 'If omitted, extracts the main content.',\n }),\n ),\n maxLength: Type.Optional(\n Type.Number({\n description: 'Maximum characters in the extracted content (default: from config or 15000)',\n }),\n ),\n});\n\nexport interface WebExtractDeps {\n getConfig: () => Config | undefined;\n}\n\nasync function fetchPageContent(url: string, signal?: AbortSignal): Promise<string> {\n const response = await fetch(url, {\n signal,\n headers: {\n 'User-Agent': 'Mozilla/5.0 (compatible; xopc/1.0)',\n Accept: 'text/html,application/xhtml+xml,text/plain,application/json',\n },\n redirect: 'follow',\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n const json: unknown = await response.json();\n return JSON.stringify(json, null, 2);\n }\n\n return await response.text();\n}\n\nexport function stripHtmlBoilerplate(\n html: string,\n maxRaw = MAX_RAW_HTML_CHARS_FOR_WEB_EXTRACT,\n): string {\n let cleaned = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, '')\n .replace(/<!--[\\s\\S]*?-->/g, '');\n\n // Remove base64 images early to avoid wasting characters on them\n cleaned = cleanBase64Images(cleaned);\n cleaned = cleaned.replace(/\\s+/g, ' ').trim();\n\n if (cleaned.length > maxRaw) {\n cleaned = cleaned.slice(0, maxRaw) + '\\n\\n[...truncated]';\n }\n\n return cleaned;\n}\n\nfunction resolveExtractionModel(config?: Config) {\n const envRef = process.env.XOPC_WEB_EXTRACT_MODEL?.trim();\n const ref = envRef;\n\n if (ref) {\n try {\n return resolveModel(ref);\n } catch {\n /* fall through */\n }\n }\n\n const defaultRef = getDefaultModelSync(config);\n if (!defaultRef) {\n throw new Error('No global default model configured for web extraction');\n }\n\n return resolveModel(defaultRef);\n}\n\nfunction buildExtractionSystemPrompt(maxLength: number): string {\n return (\n 'You are a web content extractor. Given raw HTML/text from a web page, ' +\n 'extract the relevant content as clean, well-structured markdown.\\n\\n' +\n 'Rules:\\n' +\n '- Remove navigation, headers, footers, ads, cookie banners, sidebars\\n' +\n '- Preserve the main content structure (headings, lists, tables, code blocks)\\n' +\n '- Keep URLs for important links\\n' +\n '- If the page has structured data (tables, specs), preserve the structure\\n' +\n `- Keep output under ${maxLength} characters\\n` +\n '- If content is truncated, note what was cut\\n' +\n '- Output ONLY the extracted content, no meta-commentary'\n );\n}\n\n// ---------------------------------------------------------------------------\n// LLM call helper — shared by single-pass and chunked extraction\n// ---------------------------------------------------------------------------\n\nfunction buildTimeoutSignal(signal: AbortSignal | undefined): AbortSignal | undefined {\n const timeoutSignal =\n typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'\n ? AbortSignal.timeout(EXTRACTION_TIMEOUT_MS)\n : undefined;\n if (signal && timeoutSignal && typeof AbortSignal.any === 'function') {\n return AbortSignal.any([signal, timeoutSignal]);\n }\n return signal ?? timeoutSignal;\n}\n\nfunction extractTextFromCompletion(result: { content?: unknown }): string {\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n return text.trim();\n}\n\nasync function callExtractionLlm(\n systemPrompt: string,\n userPrompt: string,\n maxTokens: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n): Promise<string> {\n const model = resolveExtractionModel(config);\n const userMsg: UserMessage = {\n role: 'user',\n content: `${systemPrompt}\\n\\n---\\n\\n${userPrompt}`,\n timestamp: Date.now(),\n };\n\n const result = await complete(\n model,\n { messages: [userMsg] },\n {\n maxTokens,\n temperature: 0.2,\n signal: buildTimeoutSignal(signal) as AbortSignal,\n },\n );\n\n return extractTextFromCompletion(result);\n}\n\n// ---------------------------------------------------------------------------\n// Single-pass extraction (content < CHUNK_THRESHOLD)\n// ---------------------------------------------------------------------------\n\nasync function extractWithLlm(\n rawContent: string,\n url: string,\n instruction: string | undefined,\n maxLength: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n): Promise<string> {\n const contentLength = rawContent.length;\n\n // Refuse absurdly large content\n if (contentLength > MAX_CONTENT_SIZE) {\n const sizeMb = (contentLength / 1_000_000).toFixed(1);\n return (\n `[Content too large to process: ${sizeMb}MB. ` +\n 'Try using web_fetch for raw text, or search for a more focused source.]'\n );\n }\n\n // Route to chunked processing for large documents\n if (contentLength > CHUNK_THRESHOLD) {\n log.info(\n { url, contentLength, chunkSize: CHUNK_SIZE },\n 'Large document detected, using chunked extraction',\n );\n return extractLargeDocumentChunked(rawContent, url, instruction, maxLength, config, signal);\n }\n\n // Standard single-pass extraction\n const systemPrompt = buildExtractionSystemPrompt(maxLength);\n const body = instruction\n ? `Extract from this page (${url}):\\n\\nFOCUS: ${instruction}\\n\\n---\\n\\n${rawContent}`\n : `Extract the main content from this page (${url}):\\n\\n---\\n\\n${rawContent}`;\n\n const text = await callExtractionLlm(\n systemPrompt,\n body,\n Math.min(Math.ceil(maxLength / 3), 8000),\n config,\n signal,\n );\n\n if (!text) return 'No content could be extracted.';\n\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n if (text.length > outputCap) {\n return text.slice(0, outputCap) + '\\n\\n[...output truncated to maxLength]';\n }\n\n log.debug(\n { url, originalChars: contentLength, extractedChars: text.length },\n 'Single-pass extraction complete',\n );\n return text;\n}\n\n// ---------------------------------------------------------------------------\n// Chunked extraction for large documents (inspired by hermes-agent)\n//\n// Flow: split → parallel chunk summaries → synthesis pass\n// ---------------------------------------------------------------------------\n\nasync function extractLargeDocumentChunked(\n content: string,\n url: string,\n instruction: string | undefined,\n maxLength: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n): Promise<string> {\n // Split content into chunks\n const chunks: string[] = [];\n for (let offset = 0; offset < content.length; offset += CHUNK_SIZE) {\n chunks.push(content.slice(offset, offset + CHUNK_SIZE));\n }\n log.info({ url, chunkCount: chunks.length, chunkSize: CHUNK_SIZE }, 'Split into chunks');\n\n // Summarize each chunk in parallel\n const chunkPromises = chunks.map(async (chunk, index) => {\n const chunkLabel = `[Chunk ${index + 1}/${chunks.length}]`;\n try {\n const systemPrompt =\n 'You are an expert content analyst processing a SECTION of a larger document. ' +\n 'Extract and summarize the key information from THIS SECTION ONLY.\\n\\n' +\n 'Guidelines:\\n' +\n '- Do NOT write introductions or conclusions — this is a partial document\\n' +\n '- Extract ALL key facts, figures, data points, and insights\\n' +\n '- Preserve important quotes, code snippets, and specific details verbatim\\n' +\n '- Use bullet points and structured formatting\\n' +\n '- Note references to other sections without trying to resolve them';\n\n const focusLine = instruction ? `\\nFOCUS: ${instruction}\\n` : '';\n const userPrompt =\n `${chunkLabel} Extract key information from this section of ${url}:` +\n `${focusLine}\\n\\n${chunk}`;\n\n const summary = await callExtractionLlm(systemPrompt, userPrompt, 6000, config, signal);\n if (summary) {\n log.debug(\n { url, chunk: index + 1, originalChars: chunk.length, summaryChars: summary.length },\n 'Chunk summarized',\n );\n }\n return { index, summary: summary || null };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n log.warn({ url, chunk: index + 1, error: message }, 'Chunk summarization failed');\n return { index, summary: null };\n }\n });\n\n const chunkResults = await Promise.all(chunkPromises);\n\n // Collect successful summaries in order\n const summaries: string[] = [];\n for (const { index, summary } of chunkResults.sort((a, b) => a.index - b.index)) {\n if (summary) {\n summaries.push(`## Section ${index + 1}\\n${summary}`);\n }\n }\n\n if (summaries.length === 0) {\n // All chunks failed — fall back to truncated raw content\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n const truncated = content.slice(0, outputCap);\n return truncated + '\\n\\n[...all chunk summaries failed; showing truncated raw content]';\n }\n\n log.info(\n { url, successfulChunks: summaries.length, totalChunks: chunks.length },\n 'Chunk summaries collected, synthesizing',\n );\n\n // Single chunk — return directly\n if (summaries.length === 1) {\n const result = summaries[0];\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n return result.length > outputCap\n ? result.slice(0, outputCap) + '\\n\\n[...truncated]'\n : result;\n }\n\n // Synthesize multiple chunk summaries into one cohesive summary\n return synthesizeChunkSummaries(summaries, url, instruction, maxLength, config, signal, content.length);\n}\n\nasync function synthesizeChunkSummaries(\n summaries: string[],\n url: string,\n instruction: string | undefined,\n maxLength: number,\n config: Config | undefined,\n signal: AbortSignal | undefined,\n originalContentLength: number,\n): Promise<string> {\n const outputCap = Math.min(maxLength, MAX_OUTPUT_SIZE);\n\n const systemPrompt =\n 'You synthesize multiple section summaries into one cohesive, comprehensive summary. ' +\n 'Remove redundancy, preserve all key facts and actionable information, ' +\n 'and organize with clear markdown structure.';\n\n const focusLine = instruction ? `\\nFOCUS: ${instruction}\\n` : '';\n const combined = summaries.join('\\n\\n---\\n\\n');\n const userPrompt =\n `Synthesize these section summaries from ${url} into ONE unified markdown summary ` +\n `(under ${outputCap} characters):${focusLine}\\n\\n${combined}`;\n\n try {\n const synthesized = await callExtractionLlm(systemPrompt, userPrompt, 8000, config, signal);\n\n if (!synthesized) {\n // Synthesis LLM returned empty — fall back to concatenation\n log.warn({ url }, 'Synthesis returned empty, concatenating chunk summaries');\n const fallback = summaries.join('\\n\\n');\n return fallback.length > outputCap\n ? fallback.slice(0, outputCap) + '\\n\\n[...truncated]'\n : fallback;\n }\n\n const finalOutput = synthesized.length > outputCap\n ? synthesized.slice(0, outputCap) + '\\n\\n[...summary truncated for context management]'\n : synthesized;\n\n const compressionRatio = finalOutput.length / originalContentLength;\n log.info(\n {\n url,\n originalChars: originalContentLength,\n extractedChars: finalOutput.length,\n compressionPercent: `${(compressionRatio * 100).toFixed(1)}%`,\n },\n 'Chunked extraction complete',\n );\n\n return finalOutput;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n log.warn({ url, error: message }, 'Synthesis failed, concatenating chunk summaries');\n const fallback = summaries.join('\\n\\n');\n return fallback.length > outputCap\n ? fallback.slice(0, outputCap) + '\\n\\n[...truncated due to synthesis failure]'\n : fallback;\n }\n}\n\ntype WebExtractParams = { url: string; instruction?: string; maxLength?: number };\n\nfunction resolveConfiguredMaxLength(): number {\n return DEFAULT_WEB_EXTRACT_MAX_LENGTH;\n}\n\nexport function createWebExtractTool(deps: WebExtractDeps): AgentTool {\n return {\n name: 'web_extract',\n label: '📄 Web Extract',\n description:\n 'Extract and summarize content from a web page using LLM-powered extraction.\\n\\n' +\n 'Unlike web_fetch (which returns cleaned plain text from HTML), this tool uses a ' +\n 'dedicated model pass to produce focused markdown. Use when you need semantic extraction ' +\n 'or a specific focus (instruction), not just a single readability pass.\\n\\n' +\n 'WHEN TO USE:\\n' +\n '- Reading articles, blog posts, documentation\\n' +\n '- Extracting specific data (pricing, specs, API docs)\\n' +\n '- Summarizing long pages to save context\\n\\n' +\n 'WHEN TO USE web_fetch INSTEAD:\\n' +\n '- Inspecting raw-ish page text quickly without an extra LLM call\\n' +\n '- Downloading JSON/API responses\\n' +\n '- Debugging page rendering issues',\n parameters: WebExtractSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{ url: string; extractedLength: number }>> {\n const { url, instruction } = params as WebExtractParams;\n const cfg = deps.getConfig();\n const maxLength = (params as WebExtractParams).maxLength ?? resolveConfiguredMaxLength();\n\n try {\n // SSRF protection\n const safety = checkUrlSafety(url);\n if (!safety.safe) {\n return {\n content: [{ type: 'text', text: `Blocked: ${safety.reason}` }],\n details: { url, extractedLength: 0 },\n };\n }\n\n // Website blocklist check\n const blocked = checkWebsiteBlocklist(url, cfg?.tools?.web?.blocklist);\n if (blocked) {\n return {\n content: [{ type: 'text', text: `Blocked: ${blocked.message}` }],\n details: { url, extractedLength: 0 },\n };\n }\n\n const rawContent = await fetchPageContent(url, signal);\n const cleaned = stripHtmlBoilerplate(rawContent);\n const trimmed = cleaned.trim();\n const looksStructured = trimmed.startsWith('{') || trimmed.startsWith('[');\n if (trimmed.length < 100 && !looksStructured) {\n return {\n content: [\n { type: 'text', text: `Page at ${url} appears to have no extractable content.` },\n ],\n details: { url, extractedLength: 0 },\n };\n }\n\n const extracted = await extractWithLlm(\n cleaned,\n url,\n instruction,\n maxLength,\n cfg,\n signal,\n );\n\n return {\n content: [{ type: 'text', text: extracted }],\n details: { url, extractedLength: extracted.length },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n log.warn({ url, error: message }, 'web_extract failed');\n return {\n content: [{ type: 'text', text: `Failed to extract content from ${url}: ${message}` }],\n details: { url, extractedLength: 0 },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
- import { AgentConfigEntrySchema, AgentIdentitySchema, AgentManifestSchema, AgentResponsibilitiesSchema, BoundaryPolicySchema, CapabilityPresetSchema, DEFAULT_CAPABILITY_PRESET_ID, DreamingDeepPolicySchema, DreamingLightPolicySchema, DreamingPhasePolicySchema, DreamingPolicySchema, DreamingRemPolicySchema, ManifestModelRoleSchema, MemoryPolicySchema, ModelPolicyPatchSchema, ModelPolicySchema, PromptPolicySchema, RuntimePolicySchema, SkillPolicySchema, ToolModeSchema, ToolPolicySchema, ToolPolicySetSchema, ToolScopeSchema, WorkflowPolicySchema, WorkspacePolicySchema, init_schema } from "./schema.js";
1
+ import { AgentConfigEntrySchema, AgentIdentitySchema, AgentManifestSchema, AgentResponsibilitiesSchema, AgentToolModelSchema, BoundaryPolicySchema, CapabilityPresetSchema, DEFAULT_CAPABILITY_PRESET_ID, DreamingDeepPolicySchema, DreamingLightPolicySchema, DreamingPhasePolicySchema, DreamingPolicySchema, DreamingRemPolicySchema, ManifestModelRoleSchema, MemoryPolicySchema, ModelPolicyPatchSchema, ModelPolicySchema, PromptPolicySchema, RuntimePolicySchema, SkillPolicySchema, ToolModeSchema, ToolPolicySchema, ToolPolicySetSchema, ToolScopeSchema, WorkflowPolicySchema, WorkspacePolicySchema, init_schema } from "./schema.js";
2
2
  import { getManifestSource, resolveEffectiveAgentManifest } from "./resolver.js";
3
3
  import { validateAgentManifest } from "./validator.js";
4
4
  import { buildAgentManifestPromptSection } from "./prompt.js";
5
5
  //#region src/agent-manifest/index.ts
6
6
  init_schema();
7
7
  //#endregion
8
- export { AgentConfigEntrySchema, AgentIdentitySchema, AgentManifestSchema, AgentResponsibilitiesSchema, BoundaryPolicySchema, CapabilityPresetSchema, DEFAULT_CAPABILITY_PRESET_ID, DreamingDeepPolicySchema, DreamingLightPolicySchema, DreamingPhasePolicySchema, DreamingPolicySchema, DreamingRemPolicySchema, ManifestModelRoleSchema, MemoryPolicySchema, ModelPolicyPatchSchema, ModelPolicySchema, PromptPolicySchema, RuntimePolicySchema, SkillPolicySchema, ToolModeSchema, ToolPolicySchema, ToolPolicySetSchema, ToolScopeSchema, WorkflowPolicySchema, WorkspacePolicySchema, buildAgentManifestPromptSection, getManifestSource, resolveEffectiveAgentManifest, validateAgentManifest };
8
+ export { AgentConfigEntrySchema, AgentIdentitySchema, AgentManifestSchema, AgentResponsibilitiesSchema, AgentToolModelSchema, BoundaryPolicySchema, CapabilityPresetSchema, DEFAULT_CAPABILITY_PRESET_ID, DreamingDeepPolicySchema, DreamingLightPolicySchema, DreamingPhasePolicySchema, DreamingPolicySchema, DreamingRemPolicySchema, ManifestModelRoleSchema, MemoryPolicySchema, ModelPolicyPatchSchema, ModelPolicySchema, PromptPolicySchema, RuntimePolicySchema, SkillPolicySchema, ToolModeSchema, ToolPolicySchema, ToolPolicySetSchema, ToolScopeSchema, WorkflowPolicySchema, WorkspacePolicySchema, buildAgentManifestPromptSection, getManifestSource, resolveEffectiveAgentManifest, validateAgentManifest };
9
9
 
10
10
  //# sourceMappingURL=index.js.map
@@ -5,6 +5,12 @@ export declare const ManifestModelRoleSchema: z.ZodObject<{
5
5
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
6
  description: z.ZodOptional<z.ZodString>;
7
7
  }, z.core.$strict>;
8
+ export declare const AgentToolModelSchema: z.ZodObject<{
9
+ primary: z.ZodString;
10
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
12
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
13
+ }, z.core.$strict>;
8
14
  export declare const ModelPolicySchema: z.ZodObject<{
9
15
  defaultRole: z.ZodString;
10
16
  roles: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -12,6 +18,18 @@ export declare const ModelPolicySchema: z.ZodObject<{
12
18
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
19
  description: z.ZodOptional<z.ZodString>;
14
20
  }, z.core.$strict>>>;
21
+ imageModel: z.ZodOptional<z.ZodObject<{
22
+ primary: z.ZodString;
23
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
25
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
26
+ }, z.core.$strict>>;
27
+ imageGenerationModel: z.ZodOptional<z.ZodObject<{
28
+ primary: z.ZodString;
29
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
30
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
31
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
32
+ }, z.core.$strict>>;
15
33
  policy: z.ZodOptional<z.ZodObject<{
16
34
  allowFallbacks: z.ZodOptional<z.ZodBoolean>;
17
35
  maxCostTier: z.ZodOptional<z.ZodEnum<{
@@ -28,6 +46,18 @@ export declare const ModelPolicyPatchSchema: z.ZodObject<{
28
46
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
29
47
  description: z.ZodOptional<z.ZodString>;
30
48
  }, z.core.$strict>>>;
49
+ imageModel: z.ZodOptional<z.ZodObject<{
50
+ primary: z.ZodString;
51
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
52
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
53
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
54
+ }, z.core.$strict>>;
55
+ imageGenerationModel: z.ZodOptional<z.ZodObject<{
56
+ primary: z.ZodString;
57
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
59
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
60
+ }, z.core.$strict>>;
31
61
  policy: z.ZodOptional<z.ZodObject<{
32
62
  allowFallbacks: z.ZodOptional<z.ZodBoolean>;
33
63
  maxCostTier: z.ZodOptional<z.ZodEnum<{
@@ -359,6 +389,18 @@ export declare const AgentManifestSchema: z.ZodObject<{
359
389
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
360
390
  description: z.ZodOptional<z.ZodString>;
361
391
  }, z.core.$strict>>>;
392
+ imageModel: z.ZodOptional<z.ZodObject<{
393
+ primary: z.ZodString;
394
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
395
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
396
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
397
+ }, z.core.$strict>>;
398
+ imageGenerationModel: z.ZodOptional<z.ZodObject<{
399
+ primary: z.ZodString;
400
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
401
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
402
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
403
+ }, z.core.$strict>>;
362
404
  policy: z.ZodOptional<z.ZodObject<{
363
405
  allowFallbacks: z.ZodOptional<z.ZodBoolean>;
364
406
  maxCostTier: z.ZodOptional<z.ZodEnum<{
@@ -784,6 +826,18 @@ export declare const AgentConfigEntrySchema: z.ZodObject<{
784
826
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
785
827
  description: z.ZodOptional<z.ZodString>;
786
828
  }, z.core.$strict>>>;
829
+ imageModel: z.ZodOptional<z.ZodObject<{
830
+ primary: z.ZodString;
831
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
832
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
833
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
834
+ }, z.core.$strict>>;
835
+ imageGenerationModel: z.ZodOptional<z.ZodObject<{
836
+ primary: z.ZodString;
837
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
838
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
839
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
840
+ }, z.core.$strict>>;
787
841
  policy: z.ZodOptional<z.ZodObject<{
788
842
  allowFallbacks: z.ZodOptional<z.ZodBoolean>;
789
843
  maxCostTier: z.ZodOptional<z.ZodEnum<{
@@ -807,6 +861,18 @@ export declare const CapabilityPresetSchema: z.ZodObject<{
807
861
  fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
808
862
  description: z.ZodOptional<z.ZodString>;
809
863
  }, z.core.$strict>>>;
864
+ imageModel: z.ZodOptional<z.ZodObject<{
865
+ primary: z.ZodString;
866
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
867
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
868
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
869
+ }, z.core.$strict>>;
870
+ imageGenerationModel: z.ZodOptional<z.ZodObject<{
871
+ primary: z.ZodString;
872
+ fallbacks: z.ZodOptional<z.ZodArray<z.ZodString>>;
873
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
874
+ autoProviderFallback: z.ZodOptional<z.ZodBoolean>;
875
+ }, z.core.$strict>>;
810
876
  policy: z.ZodOptional<z.ZodObject<{
811
877
  allowFallbacks: z.ZodOptional<z.ZodBoolean>;
812
878
  maxCostTier: z.ZodOptional<z.ZodEnum<{
@@ -1,7 +1,7 @@
1
1
  import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
2
2
  import { z } from "zod";
3
3
  //#region src/agent-manifest/schema.ts
4
- var IdSchema, DEFAULT_CAPABILITY_PRESET_ID, ManifestModelRoleSchema, ModelPolicySchema, ModelPolicyPatchSchema, ToolModeSchema, ToolScopeSchema, ToolPolicySchema, ToolPolicySetSchema, SkillPolicySchema, DreamingPhasePolicySchema, DreamingLightPolicySchema, DreamingDeepPolicySchema, DreamingRemPolicySchema, DreamingPolicySchema, MemoryPolicySchema, WorkflowPolicySchema, BoundaryPolicySchema, WorkspacePolicySchema, RuntimePolicySchema, PromptPolicySchema, AgentIdentitySchema, AgentResponsibilitiesSchema, AgentManifestSchema, AgentConfigEntrySchema, CapabilityPresetSchema;
4
+ var IdSchema, DEFAULT_CAPABILITY_PRESET_ID, ManifestModelRoleSchema, AgentToolModelSchema, ModelPolicySchema, ModelPolicyPatchSchema, ToolModeSchema, ToolScopeSchema, ToolPolicySchema, ToolPolicySetSchema, SkillPolicySchema, DreamingPhasePolicySchema, DreamingLightPolicySchema, DreamingDeepPolicySchema, DreamingRemPolicySchema, DreamingPolicySchema, MemoryPolicySchema, WorkflowPolicySchema, BoundaryPolicySchema, WorkspacePolicySchema, RuntimePolicySchema, PromptPolicySchema, AgentIdentitySchema, AgentResponsibilitiesSchema, AgentManifestSchema, AgentConfigEntrySchema, CapabilityPresetSchema;
5
5
  var init_schema = __esmMin((() => {
6
6
  IdSchema = z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/);
7
7
  DEFAULT_CAPABILITY_PRESET_ID = "default";
@@ -26,9 +26,33 @@ var init_schema = __esmMin((() => {
26
26
  path: ["fallbacks", index]
27
27
  });
28
28
  });
29
+ AgentToolModelSchema = z.object({
30
+ primary: z.string().min(1),
31
+ fallbacks: z.array(z.string().min(1)).optional(),
32
+ timeoutMs: z.number().int().positive().optional(),
33
+ autoProviderFallback: z.boolean().optional()
34
+ }).strict().superRefine((entry, ctx) => {
35
+ const validateRef = (ref) => {
36
+ const trimmed = ref.trim();
37
+ const idx = trimmed.indexOf("/");
38
+ return idx > 0 && idx < trimmed.length - 1;
39
+ };
40
+ if (!validateRef(entry.primary)) ctx.addIssue({
41
+ code: "custom",
42
+ message: `primary must be provider/model format (got '${entry.primary}')`,
43
+ path: ["primary"]
44
+ });
45
+ for (const [index, fallback] of (entry.fallbacks ?? []).entries()) if (!validateRef(fallback)) ctx.addIssue({
46
+ code: "custom",
47
+ message: `fallback must be provider/model format (got '${fallback}')`,
48
+ path: ["fallbacks", index]
49
+ });
50
+ });
29
51
  ModelPolicySchema = z.object({
30
52
  defaultRole: IdSchema,
31
53
  roles: z.record(IdSchema, ManifestModelRoleSchema).default({}),
54
+ imageModel: AgentToolModelSchema.optional(),
55
+ imageGenerationModel: AgentToolModelSchema.optional(),
32
56
  policy: z.object({
33
57
  allowFallbacks: z.boolean().optional(),
34
58
  maxCostTier: z.enum([
@@ -41,6 +65,8 @@ var init_schema = __esmMin((() => {
41
65
  ModelPolicyPatchSchema = z.object({
42
66
  defaultRole: IdSchema.optional(),
43
67
  roles: z.record(IdSchema, ManifestModelRoleSchema).optional(),
68
+ imageModel: AgentToolModelSchema.optional(),
69
+ imageGenerationModel: AgentToolModelSchema.optional(),
44
70
  policy: z.object({
45
71
  allowFallbacks: z.boolean().optional(),
46
72
  maxCostTier: z.enum([
@@ -273,6 +299,6 @@ var init_schema = __esmMin((() => {
273
299
  }));
274
300
  //#endregion
275
301
  init_schema();
276
- export { AgentConfigEntrySchema, AgentIdentitySchema, AgentManifestSchema, AgentResponsibilitiesSchema, BoundaryPolicySchema, CapabilityPresetSchema, DEFAULT_CAPABILITY_PRESET_ID, DreamingDeepPolicySchema, DreamingLightPolicySchema, DreamingPhasePolicySchema, DreamingPolicySchema, DreamingRemPolicySchema, ManifestModelRoleSchema, MemoryPolicySchema, ModelPolicyPatchSchema, ModelPolicySchema, PromptPolicySchema, RuntimePolicySchema, SkillPolicySchema, ToolModeSchema, ToolPolicySchema, ToolPolicySetSchema, ToolScopeSchema, WorkflowPolicySchema, WorkspacePolicySchema, init_schema };
302
+ export { AgentConfigEntrySchema, AgentIdentitySchema, AgentManifestSchema, AgentResponsibilitiesSchema, AgentToolModelSchema, BoundaryPolicySchema, CapabilityPresetSchema, DEFAULT_CAPABILITY_PRESET_ID, DreamingDeepPolicySchema, DreamingLightPolicySchema, DreamingPhasePolicySchema, DreamingPolicySchema, DreamingRemPolicySchema, ManifestModelRoleSchema, MemoryPolicySchema, ModelPolicyPatchSchema, ModelPolicySchema, PromptPolicySchema, RuntimePolicySchema, SkillPolicySchema, ToolModeSchema, ToolPolicySchema, ToolPolicySetSchema, ToolScopeSchema, WorkflowPolicySchema, WorkspacePolicySchema, init_schema };
277
303
 
278
304
  //# sourceMappingURL=schema.js.map