@open-mercato/ai-assistant 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (273) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +361 -0
  3. package/README.md +5 -0
  4. package/dist/index.js +154 -0
  5. package/dist/index.js.map +2 -2
  6. package/dist/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.js +73 -0
  7. package/dist/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.js.map +7 -0
  8. package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.js +484 -0
  9. package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.js.map +7 -0
  10. package/dist/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.js +251 -0
  11. package/dist/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.js.map +7 -0
  12. package/dist/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.js +91 -0
  13. package/dist/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.js.map +7 -0
  14. package/dist/modules/ai_assistant/ai-tools/attachments-pack.js +202 -0
  15. package/dist/modules/ai_assistant/ai-tools/attachments-pack.js.map +7 -0
  16. package/dist/modules/ai_assistant/ai-tools/meta-pack.js +121 -0
  17. package/dist/modules/ai_assistant/ai-tools/meta-pack.js.map +7 -0
  18. package/dist/modules/ai_assistant/ai-tools/search-pack.js +94 -0
  19. package/dist/modules/ai_assistant/ai-tools/search-pack.js.map +7 -0
  20. package/dist/modules/ai_assistant/ai-tools.js +14 -0
  21. package/dist/modules/ai_assistant/ai-tools.js.map +7 -0
  22. package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js +175 -0
  23. package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js.map +7 -0
  24. package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js +174 -0
  25. package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js.map +7 -0
  26. package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js +101 -0
  27. package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js.map +7 -0
  28. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js +311 -0
  29. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js.map +7 -0
  30. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js +246 -0
  31. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js.map +7 -0
  32. package/dist/modules/ai_assistant/api/ai/agents/route.js +94 -0
  33. package/dist/modules/ai_assistant/api/ai/agents/route.js.map +7 -0
  34. package/dist/modules/ai_assistant/api/ai/chat/route.js +173 -0
  35. package/dist/modules/ai_assistant/api/ai/chat/route.js.map +7 -0
  36. package/dist/modules/ai_assistant/api/ai/run-object/route.js +167 -0
  37. package/dist/modules/ai_assistant/api/ai/run-object/route.js.map +7 -0
  38. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +1111 -0
  39. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +7 -0
  40. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.js +10 -0
  41. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.js.map +7 -0
  42. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.js +28 -0
  43. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.js.map +7 -0
  44. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +10 -0
  45. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +7 -0
  46. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.js +30 -0
  47. package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.js.map +7 -0
  48. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +4 -6
  49. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +2 -2
  50. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +1 -21
  51. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +2 -2
  52. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +462 -0
  53. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +7 -0
  54. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.js +10 -0
  55. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.js.map +7 -0
  56. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.js +28 -0
  57. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.js.map +7 -0
  58. package/dist/modules/ai_assistant/cli.js +78 -12
  59. package/dist/modules/ai_assistant/cli.js.map +2 -2
  60. package/dist/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.js +5 -0
  61. package/dist/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.js.map +7 -0
  62. package/dist/modules/ai_assistant/data/entities/AiAgentPromptOverride.js +5 -0
  63. package/dist/modules/ai_assistant/data/entities/AiAgentPromptOverride.js.map +7 -0
  64. package/dist/modules/ai_assistant/data/entities/AiPendingAction.js +5 -0
  65. package/dist/modules/ai_assistant/data/entities/AiPendingAction.js.map +7 -0
  66. package/dist/modules/ai_assistant/data/entities.js +228 -0
  67. package/dist/modules/ai_assistant/data/entities.js.map +7 -0
  68. package/dist/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.js +95 -0
  69. package/dist/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.js.map +7 -0
  70. package/dist/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.js +95 -0
  71. package/dist/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.js.map +7 -0
  72. package/dist/modules/ai_assistant/data/repositories/AiPendingActionRepository.js +223 -0
  73. package/dist/modules/ai_assistant/data/repositories/AiPendingActionRepository.js.map +7 -0
  74. package/dist/modules/ai_assistant/events.js +33 -0
  75. package/dist/modules/ai_assistant/events.js.map +7 -0
  76. package/dist/modules/ai_assistant/i18n/de.json +252 -0
  77. package/dist/modules/ai_assistant/i18n/en.json +252 -0
  78. package/dist/modules/ai_assistant/i18n/es.json +252 -0
  79. package/dist/modules/ai_assistant/i18n/pl.json +252 -0
  80. package/dist/modules/ai_assistant/lib/agent-policy.js +168 -0
  81. package/dist/modules/ai_assistant/lib/agent-policy.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/agent-registry.js +195 -0
  83. package/dist/modules/ai_assistant/lib/agent-registry.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/agent-runtime.js +451 -0
  85. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/agent-tools.js +223 -0
  87. package/dist/modules/ai_assistant/lib/agent-tools.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/agent-transport.js +25 -0
  89. package/dist/modules/ai_assistant/lib/agent-transport.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/ai-agent-definition.js +11 -0
  91. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/ai-agents-generated.d.js +1 -0
  93. package/dist/modules/ai_assistant/lib/ai-agents-generated.d.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/ai-api-operation-runner.js +239 -0
  95. package/dist/modules/ai_assistant/lib/ai-api-operation-runner.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/ai-overrides.js +189 -0
  97. package/dist/modules/ai_assistant/lib/ai-overrides.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/ai-tool-definition.js +7 -0
  99. package/dist/modules/ai_assistant/lib/ai-tool-definition.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/ai-tools-generated.d.js +1 -0
  101. package/dist/modules/ai_assistant/lib/ai-tools-generated.d.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/api-backed-tool.js +48 -0
  103. package/dist/modules/ai_assistant/lib/api-backed-tool.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/attachment-bridge-types.js +1 -0
  105. package/dist/modules/ai_assistant/lib/attachment-bridge-types.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/attachment-parts.js +276 -0
  107. package/dist/modules/ai_assistant/lib/attachment-parts.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/model-factory.js +68 -0
  109. package/dist/modules/ai_assistant/lib/model-factory.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/pending-action-cancel.js +86 -0
  111. package/dist/modules/ai_assistant/lib/pending-action-cancel.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/pending-action-client.js +35 -0
  113. package/dist/modules/ai_assistant/lib/pending-action-client.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/pending-action-executor.js +243 -0
  115. package/dist/modules/ai_assistant/lib/pending-action-executor.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/pending-action-recheck.js +246 -0
  117. package/dist/modules/ai_assistant/lib/pending-action-recheck.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/pending-action-types.js +70 -0
  119. package/dist/modules/ai_assistant/lib/pending-action-types.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/prepare-mutation.js +315 -0
  121. package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/prompt-composition-types.js +7 -0
  123. package/dist/modules/ai_assistant/lib/prompt-composition-types.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/prompt-override-merge.js +175 -0
  125. package/dist/modules/ai_assistant/lib/prompt-override-merge.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/schema-utils.js +5 -1
  127. package/dist/modules/ai_assistant/lib/schema-utils.js.map +2 -2
  128. package/dist/modules/ai_assistant/lib/tool-executor.js +13 -2
  129. package/dist/modules/ai_assistant/lib/tool-executor.js.map +2 -2
  130. package/dist/modules/ai_assistant/lib/tool-loader.js +86 -11
  131. package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
  132. package/dist/modules/ai_assistant/lib/tool-test-fixtures.js +120 -0
  133. package/dist/modules/ai_assistant/lib/tool-test-fixtures.js.map +7 -0
  134. package/dist/modules/ai_assistant/lib/tool-test-runner.js +418 -0
  135. package/dist/modules/ai_assistant/lib/tool-test-runner.js.map +7 -0
  136. package/dist/modules/ai_assistant/migrations/Migration20260419100521.js +17 -0
  137. package/dist/modules/ai_assistant/migrations/Migration20260419100521.js.map +7 -0
  138. package/dist/modules/ai_assistant/migrations/Migration20260419132948.js +16 -0
  139. package/dist/modules/ai_assistant/migrations/Migration20260419132948.js.map +7 -0
  140. package/dist/modules/ai_assistant/migrations/Migration20260419134235.js +17 -0
  141. package/dist/modules/ai_assistant/migrations/Migration20260419134235.js.map +7 -0
  142. package/dist/modules/ai_assistant/setup.js +36 -0
  143. package/dist/modules/ai_assistant/setup.js.map +2 -2
  144. package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js +161 -0
  145. package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js.map +7 -0
  146. package/generated/entities/ai_agent_mutation_policy_override/index.ts +9 -0
  147. package/generated/entities/ai_agent_prompt_override/index.ts +10 -0
  148. package/generated/entities/ai_pending_action/index.ts +24 -0
  149. package/generated/entities.ids.generated.ts +13 -0
  150. package/generated/entity-fields-registry.ts +57 -0
  151. package/jest.config.cjs +7 -0
  152. package/package.json +4 -4
  153. package/src/index.ts +215 -0
  154. package/src/modules/ai_assistant/__integration__/README.md +5 -0
  155. package/src/modules/ai_assistant/__integration__/TC-AI-002-agent-policy.spec.ts +115 -0
  156. package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-SETTINGS-005-settings-page.spec.ts +574 -0
  157. package/src/modules/ai_assistant/__integration__/TC-AI-PLAYGROUND-004-playground.spec.ts +333 -0
  158. package/src/modules/ai_assistant/__integration__/TC-INT-AI-TOOLS.spec.ts +135 -0
  159. package/src/modules/ai_assistant/__tests__/events.test.ts +145 -0
  160. package/src/modules/ai_assistant/__tests__/integration/pending-action-contract.test.ts +1015 -0
  161. package/src/modules/ai_assistant/__tests__/integration/ws-c-attachment-bridge.test.ts +235 -0
  162. package/src/modules/ai_assistant/__tests__/integration/ws-c-policy-and-tools.test.ts +330 -0
  163. package/src/modules/ai_assistant/__tests__/integration/ws-c-tool-pack-coverage.test.ts +285 -0
  164. package/src/modules/ai_assistant/ai-tools/__tests__/attachments-pack.test.ts +322 -0
  165. package/src/modules/ai_assistant/ai-tools/__tests__/meta-pack.test.ts +218 -0
  166. package/src/modules/ai_assistant/ai-tools/__tests__/search-pack.test.ts +192 -0
  167. package/src/modules/ai_assistant/ai-tools/attachments-pack.ts +269 -0
  168. package/src/modules/ai_assistant/ai-tools/meta-pack.ts +140 -0
  169. package/src/modules/ai_assistant/ai-tools/search-pack.ts +122 -0
  170. package/src/modules/ai_assistant/ai-tools.ts +21 -0
  171. package/src/modules/ai_assistant/api/ai/actions/[id]/__tests__/route.test.ts +222 -0
  172. package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/__tests__/route.test.ts +286 -0
  173. package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/route.ts +237 -0
  174. package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/__tests__/route.test.ts +339 -0
  175. package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/route.ts +229 -0
  176. package/src/modules/ai_assistant/api/ai/actions/[id]/route.ts +142 -0
  177. package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/__tests__/route.test.ts +367 -0
  178. package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.ts +380 -0
  179. package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/__tests__/route.test.ts +333 -0
  180. package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.ts +307 -0
  181. package/src/modules/ai_assistant/api/ai/agents/route.ts +107 -0
  182. package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +282 -0
  183. package/src/modules/ai_assistant/api/ai/chat/route.ts +207 -0
  184. package/src/modules/ai_assistant/api/ai/run-object/__tests__/route.test.ts +282 -0
  185. package/src/modules/ai_assistant/api/ai/run-object/route.ts +204 -0
  186. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +1419 -0
  187. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/page.meta.ts +26 -0
  188. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/page.tsx +12 -0
  189. package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.meta.ts +28 -0
  190. package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +12 -0
  191. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +8 -23
  192. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +15 -10
  193. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +604 -0
  194. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/page.meta.ts +26 -0
  195. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/page.tsx +12 -0
  196. package/src/modules/ai_assistant/cli.ts +99 -24
  197. package/src/modules/ai_assistant/data/__tests__/schema-unique-indexes.test.ts +69 -0
  198. package/src/modules/ai_assistant/data/entities/AiAgentMutationPolicyOverride.ts +7 -0
  199. package/src/modules/ai_assistant/data/entities/AiAgentPromptOverride.ts +7 -0
  200. package/src/modules/ai_assistant/data/entities/AiPendingAction.ts +7 -0
  201. package/src/modules/ai_assistant/data/entities.ts +270 -0
  202. package/src/modules/ai_assistant/data/repositories/AiAgentMutationPolicyOverrideRepository.ts +129 -0
  203. package/src/modules/ai_assistant/data/repositories/AiAgentPromptOverrideRepository.ts +132 -0
  204. package/src/modules/ai_assistant/data/repositories/AiPendingActionRepository.ts +334 -0
  205. package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentMutationPolicyOverrideRepository.test.ts +195 -0
  206. package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentPromptOverrideRepository.test.ts +197 -0
  207. package/src/modules/ai_assistant/data/repositories/__tests__/AiPendingActionRepository.test.ts +357 -0
  208. package/src/modules/ai_assistant/events.ts +112 -0
  209. package/src/modules/ai_assistant/i18n/de.json +252 -0
  210. package/src/modules/ai_assistant/i18n/en.json +252 -0
  211. package/src/modules/ai_assistant/i18n/es.json +252 -0
  212. package/src/modules/ai_assistant/i18n/pl.json +252 -0
  213. package/src/modules/ai_assistant/lib/__tests__/agent-policy.mutation-override.test.ts +203 -0
  214. package/src/modules/ai_assistant/lib/__tests__/agent-policy.test.ts +385 -0
  215. package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +217 -0
  216. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-object.test.ts +329 -0
  217. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-parity.test.ts +573 -0
  218. package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +291 -0
  219. package/src/modules/ai_assistant/lib/__tests__/agent-tools.test.ts +172 -0
  220. package/src/modules/ai_assistant/lib/__tests__/agent-transport.test.ts +41 -0
  221. package/src/modules/ai_assistant/lib/__tests__/ai-agent-definition.test.ts +183 -0
  222. package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +432 -0
  223. package/src/modules/ai_assistant/lib/__tests__/ai-overrides.test.ts +308 -0
  224. package/src/modules/ai_assistant/lib/__tests__/api-backed-tool.test.ts +302 -0
  225. package/src/modules/ai_assistant/lib/__tests__/attachment-bridge-and-prompt-types.test.ts +188 -0
  226. package/src/modules/ai_assistant/lib/__tests__/attachment-parts.test.ts +531 -0
  227. package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +263 -0
  228. package/src/modules/ai_assistant/lib/__tests__/model-factory.integration.test.ts +183 -0
  229. package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +168 -0
  230. package/src/modules/ai_assistant/lib/__tests__/pending-action-cancel.test.ts +235 -0
  231. package/src/modules/ai_assistant/lib/__tests__/pending-action-client.test.ts +148 -0
  232. package/src/modules/ai_assistant/lib/__tests__/pending-action-executor.test.ts +348 -0
  233. package/src/modules/ai_assistant/lib/__tests__/pending-action-recheck.test.ts +378 -0
  234. package/src/modules/ai_assistant/lib/__tests__/phase-0-additive-contract.test.ts +299 -0
  235. package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +610 -0
  236. package/src/modules/ai_assistant/lib/__tests__/prompt-override-merge.test.ts +136 -0
  237. package/src/modules/ai_assistant/lib/__tests__/tool-loader.test.ts +125 -0
  238. package/src/modules/ai_assistant/lib/agent-policy.ts +270 -0
  239. package/src/modules/ai_assistant/lib/agent-registry.ts +277 -0
  240. package/src/modules/ai_assistant/lib/agent-runtime.ts +751 -0
  241. package/src/modules/ai_assistant/lib/agent-tools.ts +396 -0
  242. package/src/modules/ai_assistant/lib/agent-transport.ts +51 -0
  243. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +86 -0
  244. package/src/modules/ai_assistant/lib/ai-agents-generated.d.ts +18 -0
  245. package/src/modules/ai_assistant/lib/ai-api-operation-runner.ts +333 -0
  246. package/src/modules/ai_assistant/lib/ai-overrides.ts +389 -0
  247. package/src/modules/ai_assistant/lib/ai-tool-definition.ts +7 -0
  248. package/src/modules/ai_assistant/lib/ai-tools-generated.d.ts +7 -0
  249. package/src/modules/ai_assistant/lib/api-backed-tool.ts +85 -0
  250. package/src/modules/ai_assistant/lib/attachment-bridge-types.ts +24 -0
  251. package/src/modules/ai_assistant/lib/attachment-parts.ts +433 -0
  252. package/src/modules/ai_assistant/lib/model-factory.ts +212 -0
  253. package/src/modules/ai_assistant/lib/pending-action-cancel.ts +179 -0
  254. package/src/modules/ai_assistant/lib/pending-action-client.ts +126 -0
  255. package/src/modules/ai_assistant/lib/pending-action-executor.ts +424 -0
  256. package/src/modules/ai_assistant/lib/pending-action-recheck.ts +410 -0
  257. package/src/modules/ai_assistant/lib/pending-action-types.ts +194 -0
  258. package/src/modules/ai_assistant/lib/prepare-mutation.ts +448 -0
  259. package/src/modules/ai_assistant/lib/prompt-composition-types.ts +24 -0
  260. package/src/modules/ai_assistant/lib/prompt-override-merge.ts +253 -0
  261. package/src/modules/ai_assistant/lib/schema-utils.ts +14 -2
  262. package/src/modules/ai_assistant/lib/tool-executor.ts +25 -3
  263. package/src/modules/ai_assistant/lib/tool-loader.ts +159 -13
  264. package/src/modules/ai_assistant/lib/tool-test-fixtures.ts +160 -0
  265. package/src/modules/ai_assistant/lib/tool-test-runner.ts +596 -0
  266. package/src/modules/ai_assistant/lib/types.ts +105 -2
  267. package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +871 -0
  268. package/src/modules/ai_assistant/migrations/Migration20260419100521.ts +17 -0
  269. package/src/modules/ai_assistant/migrations/Migration20260419132948.ts +16 -0
  270. package/src/modules/ai_assistant/migrations/Migration20260419134235.ts +17 -0
  271. package/src/modules/ai_assistant/setup.ts +53 -0
  272. package/src/modules/ai_assistant/workers/__tests__/ai-pending-action-cleanup.test.ts +333 -0
  273. package/src/modules/ai_assistant/workers/ai-pending-action-cleanup.ts +269 -0
@@ -0,0 +1,315 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolveEffectiveMutationPolicy } from "./agent-policy.js";
3
+ import { AiPendingActionRepository } from "../data/repositories/AiPendingActionRepository.js";
4
+ class AiMutationPreparationError extends Error {
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = "AiMutationPreparationError";
9
+ }
10
+ }
11
+ const MUTATION_PREVIEW_CARD_COMPONENT_ID = "mutation-preview-card";
12
+ const NO_RESOLVER_SIDE_EFFECTS_MESSAGE = "Tool did not declare a field-diff resolver; action will proceed without a preview.";
13
+ function assertTenantScope(ctx) {
14
+ if (!ctx.tenantId) {
15
+ throw new AiMutationPreparationError(
16
+ "tenant_scope_missing",
17
+ "prepareMutation requires a tenant-scoped request context."
18
+ );
19
+ }
20
+ return ctx.tenantId;
21
+ }
22
+ function resolveEm(container) {
23
+ if (!container) {
24
+ throw new AiMutationPreparationError(
25
+ "container_missing",
26
+ "prepareMutation requires an Awilix container to resolve the EntityManager."
27
+ );
28
+ }
29
+ let em = null;
30
+ try {
31
+ em = container.resolve("em");
32
+ } catch {
33
+ em = null;
34
+ }
35
+ if (!em) {
36
+ throw new AiMutationPreparationError(
37
+ "em_missing",
38
+ 'prepareMutation could not resolve "em" from the container.'
39
+ );
40
+ }
41
+ return em;
42
+ }
43
+ function toolHandlerContext(ctx) {
44
+ return {
45
+ tenantId: ctx.tenantId,
46
+ organizationId: ctx.organizationId,
47
+ userId: ctx.userId,
48
+ container: ctx.container,
49
+ userFeatures: ctx.features,
50
+ isSuperAdmin: ctx.isSuperAdmin
51
+ };
52
+ }
53
+ function safeStringify(value) {
54
+ const seen = /* @__PURE__ */ new WeakSet();
55
+ return JSON.stringify(value, (_key, raw) => {
56
+ if (raw && typeof raw === "object") {
57
+ if (seen.has(raw)) return "[Circular]";
58
+ seen.add(raw);
59
+ const entries = Object.entries(raw);
60
+ entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
61
+ return entries.reduce((acc, [k, v]) => {
62
+ acc[k] = v;
63
+ return acc;
64
+ }, {});
65
+ }
66
+ return raw;
67
+ });
68
+ }
69
+ function computeMutationIdempotencyKey(input) {
70
+ const canonical = safeStringify({
71
+ tenant: input.tenantId,
72
+ org: input.organizationId ?? null,
73
+ agent: input.agentId,
74
+ conversation: input.conversationId ?? null,
75
+ tool: input.toolName,
76
+ input: input.normalizedInput ?? {}
77
+ });
78
+ return createHash("sha256").update(canonical).digest("hex");
79
+ }
80
+ function computeFieldDiff(before, after) {
81
+ const diff = [];
82
+ const keys = /* @__PURE__ */ new Set([
83
+ ...Object.keys(before ?? {}),
84
+ ...Object.keys(after ?? {})
85
+ ]);
86
+ for (const field of keys) {
87
+ const beforeValue = before ? before[field] : void 0;
88
+ const afterValue = after ? after[field] : void 0;
89
+ if (!Object.is(beforeValue, afterValue) && safeStringify(beforeValue) !== safeStringify(afterValue)) {
90
+ diff.push({ field, before: beforeValue, after: afterValue });
91
+ }
92
+ }
93
+ return diff;
94
+ }
95
+ function extractPatchFromArgs(args) {
96
+ const raw = args?.patch;
97
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
98
+ return raw;
99
+ }
100
+ const envelope = /* @__PURE__ */ new Set([
101
+ "id",
102
+ "recordId",
103
+ "records",
104
+ "attachmentIds",
105
+ "_sessionToken"
106
+ ]);
107
+ const reduced = {};
108
+ for (const [key, value] of Object.entries(args ?? {})) {
109
+ if (envelope.has(key)) continue;
110
+ reduced[key] = value;
111
+ }
112
+ return reduced;
113
+ }
114
+ function matchBatchPatch(args, recordId) {
115
+ const rawList = args?.records;
116
+ if (Array.isArray(rawList)) {
117
+ const match = rawList.find((entry) => {
118
+ if (!entry || typeof entry !== "object") return false;
119
+ const candidate = entry;
120
+ return candidate.recordId === recordId || candidate.id === recordId;
121
+ });
122
+ if (match && typeof match === "object") {
123
+ const patch = match.patch;
124
+ if (patch && typeof patch === "object" && !Array.isArray(patch)) {
125
+ return patch;
126
+ }
127
+ const envelope = /* @__PURE__ */ new Set(["recordId", "id"]);
128
+ const reduced = {};
129
+ for (const [key, value] of Object.entries(match)) {
130
+ if (envelope.has(key)) continue;
131
+ reduced[key] = value;
132
+ }
133
+ return reduced;
134
+ }
135
+ }
136
+ return {};
137
+ }
138
+ function normalizeAttachmentIds(args) {
139
+ const raw = args?.attachmentIds;
140
+ if (!Array.isArray(raw)) return [];
141
+ return raw.filter((value) => typeof value === "string" && value.length > 0);
142
+ }
143
+ async function buildSingleRecordDiff(tool, input, ctx) {
144
+ const resolver = tool.loadBeforeRecord;
145
+ if (!resolver) {
146
+ console.warn(
147
+ `[AI Agents] prepareMutation: tool "${tool.name}" declared isMutation=true but no loadBeforeRecord resolver; shipping empty fieldDiff.`
148
+ );
149
+ return {
150
+ fieldDiff: [],
151
+ targetEntityType: null,
152
+ targetRecordId: null,
153
+ recordVersion: null,
154
+ sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE
155
+ };
156
+ }
157
+ const handlerContext = toolHandlerContext(ctx);
158
+ const before = await resolver(
159
+ input.toolCallArgs,
160
+ handlerContext
161
+ );
162
+ if (!before) {
163
+ return {
164
+ fieldDiff: [],
165
+ targetEntityType: null,
166
+ targetRecordId: null,
167
+ recordVersion: null,
168
+ sideEffectsSummary: null
169
+ };
170
+ }
171
+ const patch = extractPatchFromArgs(input.toolCallArgs);
172
+ const fieldDiff = computeFieldDiff(before.before, patch);
173
+ return {
174
+ fieldDiff,
175
+ targetEntityType: before.entityType,
176
+ targetRecordId: before.recordId,
177
+ recordVersion: before.recordVersion,
178
+ sideEffectsSummary: null
179
+ };
180
+ }
181
+ async function buildBatchRecords(tool, input, ctx) {
182
+ const resolver = tool.loadBeforeRecords;
183
+ if (!resolver) {
184
+ console.warn(
185
+ `[AI Agents] prepareMutation: bulk tool "${tool.name}" declared isMutation=true but no loadBeforeRecords resolver; shipping empty records[].`
186
+ );
187
+ return {
188
+ records: null,
189
+ targetEntityType: null,
190
+ sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE
191
+ };
192
+ }
193
+ const handlerContext = toolHandlerContext(ctx);
194
+ const rows = await resolver(
195
+ input.toolCallArgs,
196
+ handlerContext
197
+ );
198
+ if (!Array.isArray(rows) || rows.length === 0) {
199
+ return {
200
+ records: null,
201
+ targetEntityType: null,
202
+ sideEffectsSummary: null
203
+ };
204
+ }
205
+ const diffs = rows.map((row) => {
206
+ const patch = matchBatchPatch(input.toolCallArgs, row.recordId);
207
+ return {
208
+ recordId: row.recordId,
209
+ entityType: row.entityType,
210
+ label: row.label,
211
+ fieldDiff: computeFieldDiff(row.before, patch),
212
+ recordVersion: row.recordVersion ?? null
213
+ };
214
+ });
215
+ const [firstEntity] = rows;
216
+ return {
217
+ records: diffs,
218
+ targetEntityType: firstEntity ? firstEntity.entityType : null,
219
+ sideEffectsSummary: null
220
+ };
221
+ }
222
+ async function prepareMutation(input, ctx) {
223
+ const { agent, tool } = input;
224
+ if (tool.isMutation !== true) {
225
+ throw new AiMutationPreparationError(
226
+ "not_a_mutation_tool",
227
+ `Tool "${tool.name}" is not a mutation tool; prepareMutation should not be invoked.`
228
+ );
229
+ }
230
+ const effectivePolicy = resolveEffectiveMutationPolicy(
231
+ agent.mutationPolicy,
232
+ input.mutationPolicyOverride ?? null,
233
+ agent.id
234
+ );
235
+ if (effectivePolicy === "read-only") {
236
+ throw new AiMutationPreparationError(
237
+ "read_only_agent",
238
+ `Agent "${agent.id}" has effective mutationPolicy=read-only; mutation tool "${tool.name}" cannot be prepared.`
239
+ );
240
+ }
241
+ const tenantId = assertTenantScope(ctx);
242
+ const em = resolveEm(ctx.container);
243
+ const repo = new AiPendingActionRepository(em);
244
+ const isBulk = tool.isBulk === true;
245
+ let fieldDiff = [];
246
+ let records = null;
247
+ let targetEntityType = null;
248
+ let targetRecordId = null;
249
+ let recordVersion = null;
250
+ let sideEffectsSummary = null;
251
+ if (isBulk) {
252
+ const batch = await buildBatchRecords(tool, input, ctx);
253
+ records = batch.records;
254
+ targetEntityType = batch.targetEntityType;
255
+ sideEffectsSummary = batch.sideEffectsSummary;
256
+ } else {
257
+ const single = await buildSingleRecordDiff(tool, input, ctx);
258
+ fieldDiff = single.fieldDiff;
259
+ targetEntityType = single.targetEntityType;
260
+ targetRecordId = single.targetRecordId;
261
+ recordVersion = single.recordVersion;
262
+ sideEffectsSummary = single.sideEffectsSummary;
263
+ }
264
+ const normalizedInput = input.toolCallArgs ?? {};
265
+ const conversationId = input.conversationId ?? null;
266
+ const idempotencyKey = computeMutationIdempotencyKey({
267
+ tenantId,
268
+ organizationId: ctx.organizationId ?? null,
269
+ agentId: agent.id,
270
+ conversationId,
271
+ toolName: tool.name,
272
+ normalizedInput
273
+ });
274
+ const pendingAction = await repo.create(
275
+ {
276
+ agentId: agent.id,
277
+ toolName: tool.name,
278
+ idempotencyKey,
279
+ createdByUserId: ctx.userId,
280
+ normalizedInput,
281
+ conversationId,
282
+ targetEntityType,
283
+ targetRecordId,
284
+ fieldDiff,
285
+ records,
286
+ sideEffectsSummary,
287
+ recordVersion,
288
+ attachmentIds: normalizeAttachmentIds(normalizedInput),
289
+ now: input.now
290
+ },
291
+ {
292
+ tenantId,
293
+ organizationId: ctx.organizationId ?? null,
294
+ userId: ctx.userId
295
+ }
296
+ );
297
+ const uiPart = {
298
+ componentId: MUTATION_PREVIEW_CARD_COMPONENT_ID,
299
+ props: {
300
+ pendingActionId: pendingAction.id,
301
+ expiresAt: pendingAction.expiresAt.toISOString(),
302
+ ...records ? { records } : { fieldDiff },
303
+ ...sideEffectsSummary ? { sideEffectsSummary } : {}
304
+ }
305
+ };
306
+ return { uiPart, pendingAction };
307
+ }
308
+ const MUTATION_PREVIEW_CARD_COMPONENT = MUTATION_PREVIEW_CARD_COMPONENT_ID;
309
+ export {
310
+ AiMutationPreparationError,
311
+ MUTATION_PREVIEW_CARD_COMPONENT,
312
+ computeMutationIdempotencyKey,
313
+ prepareMutation
314
+ };
315
+ //# sourceMappingURL=prepare-mutation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/prepare-mutation.ts"],
4
+ "sourcesContent": ["import { createHash } from 'node:crypto'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-definition'\nimport type { AiChatRequestContext, AiUiPart } from './attachment-bridge-types'\nimport type {\n AiToolDefinition,\n AiToolLoadBeforeRecord,\n AiToolLoadBeforeSingleRecord,\n McpToolContext,\n} from './types'\nimport { resolveEffectiveMutationPolicy } from './agent-policy'\nimport { AiPendingActionRepository } from '../data/repositories/AiPendingActionRepository'\nimport type { AiPendingAction } from '../data/entities'\nimport type {\n AiPendingActionFieldDiff,\n AiPendingActionRecordDiff,\n} from './pending-action-types'\n\n/**\n * Structured error raised by {@link prepareMutation}. Callers (today the\n * agent-runtime tool wrapper installed by `resolveAiAgentTools`) turn this\n * into a tool-call failure that the model surfaces back to the user without\n * leaking internals. The runtime NEVER reaches this helper when the agent\n * is declared read-only \u2014 the policy gate rejects the tool call upstream \u2014\n * but we keep the fail-closed check as a defensive guard.\n */\nexport class AiMutationPreparationError extends Error {\n constructor(\n public readonly code:\n | 'not_a_mutation_tool'\n | 'read_only_agent'\n | 'tenant_scope_missing'\n | 'container_missing'\n | 'em_missing',\n message: string,\n ) {\n super(message)\n this.name = 'AiMutationPreparationError'\n }\n}\n\nexport interface PrepareMutationInput {\n agent: AiAgentDefinition\n tool: AiToolDefinition\n toolCallArgs: Record<string, unknown>\n conversationId?: string | null\n /**\n * Optional downgrade the caller already resolved (mirror of\n * `resolveAiAgentTools({ mutationPolicyOverride })`). When omitted, the\n * agent's code-declared policy stands alone.\n */\n mutationPolicyOverride?: AiAgentMutationPolicy | null\n /**\n * Deterministic clock hook for tests. Defaults to `new Date()`.\n */\n now?: Date\n}\n\nexport interface PrepareMutationContext extends AiChatRequestContext {\n container: AwilixContainer\n}\n\nexport interface PrepareMutationResult {\n uiPart: AiUiPart\n pendingAction: AiPendingAction\n}\n\nconst MUTATION_PREVIEW_CARD_COMPONENT_ID = 'mutation-preview-card'\n\nconst NO_RESOLVER_SIDE_EFFECTS_MESSAGE =\n 'Tool did not declare a field-diff resolver; action will proceed without a preview.'\n\nfunction assertTenantScope(ctx: PrepareMutationContext): string {\n if (!ctx.tenantId) {\n throw new AiMutationPreparationError(\n 'tenant_scope_missing',\n 'prepareMutation requires a tenant-scoped request context.',\n )\n }\n return ctx.tenantId\n}\n\nfunction resolveEm(container: AwilixContainer): EntityManager {\n if (!container) {\n throw new AiMutationPreparationError(\n 'container_missing',\n 'prepareMutation requires an Awilix container to resolve the EntityManager.',\n )\n }\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) {\n throw new AiMutationPreparationError(\n 'em_missing',\n 'prepareMutation could not resolve \"em\" from the container.',\n )\n }\n return em\n}\n\nfunction toolHandlerContext(ctx: PrepareMutationContext): McpToolContext {\n return {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n container: ctx.container,\n userFeatures: ctx.features,\n isSuperAdmin: ctx.isSuperAdmin,\n }\n}\n\nfunction safeStringify(value: unknown): string {\n const seen = new WeakSet<object>()\n return JSON.stringify(value, (_key, raw) => {\n if (raw && typeof raw === 'object') {\n if (seen.has(raw as object)) return '[Circular]'\n seen.add(raw as object)\n const entries = Object.entries(raw as Record<string, unknown>)\n entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return entries.reduce<Record<string, unknown>>((acc, [k, v]) => {\n acc[k] = v\n return acc\n }, {})\n }\n return raw\n })\n}\n\n/**\n * Hashes `(tenantId, orgId, agentId, conversationId, toolName, normalizedInput)`\n * into a stable SHA-256 digest so that retries of the same tool call with the\n * same payload collapse to a single `AiPendingAction` row inside the TTL\n * window. The input is normalized through `safeStringify` to make object key\n * order irrelevant (spec \u00A78 rule `idempotencyKey prevents double-submission`).\n * Attachments are NOT included \u2014 the attachment set is captured separately on\n * the pending row so that re-uploading the same file set with a different\n * tool-call object never accidentally collides.\n */\nexport function computeMutationIdempotencyKey(input: {\n tenantId: string\n organizationId: string | null\n agentId: string\n conversationId: string | null\n toolName: string\n normalizedInput: Record<string, unknown>\n}): string {\n const canonical = safeStringify({\n tenant: input.tenantId,\n org: input.organizationId ?? null,\n agent: input.agentId,\n conversation: input.conversationId ?? null,\n tool: input.toolName,\n input: input.normalizedInput ?? {},\n })\n return createHash('sha256').update(canonical).digest('hex')\n}\n\nfunction computeFieldDiff(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): AiPendingActionFieldDiff[] {\n const diff: AiPendingActionFieldDiff[] = []\n const keys = new Set<string>([\n ...Object.keys(before ?? {}),\n ...Object.keys(after ?? {}),\n ])\n for (const field of keys) {\n const beforeValue = before ? before[field] : undefined\n const afterValue = after ? after[field] : undefined\n if (!Object.is(beforeValue, afterValue) && safeStringify(beforeValue) !== safeStringify(afterValue)) {\n diff.push({ field, before: beforeValue, after: afterValue })\n }\n }\n return diff\n}\n\nfunction extractPatchFromArgs(\n args: Record<string, unknown>,\n): Record<string, unknown> {\n const raw = args?.patch\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n return raw as Record<string, unknown>\n }\n // Fall back: treat the whole args object (minus well-known envelope keys)\n // as the patch. This preserves compatibility with tools whose schema is\n // flat (`{ productId, name }`) rather than nested (`{ productId, patch }`).\n const envelope = new Set([\n 'id',\n 'recordId',\n 'records',\n 'attachmentIds',\n '_sessionToken',\n ])\n const reduced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(args ?? {})) {\n if (envelope.has(key)) continue\n reduced[key] = value\n }\n return reduced\n}\n\nfunction matchBatchPatch(\n args: Record<string, unknown>,\n recordId: string,\n): Record<string, unknown> {\n const rawList = args?.records\n if (Array.isArray(rawList)) {\n const match = rawList.find((entry) => {\n if (!entry || typeof entry !== 'object') return false\n const candidate = entry as Record<string, unknown>\n return candidate.recordId === recordId || candidate.id === recordId\n })\n if (match && typeof match === 'object') {\n const patch = (match as Record<string, unknown>).patch\n if (patch && typeof patch === 'object' && !Array.isArray(patch)) {\n return patch as Record<string, unknown>\n }\n const envelope = new Set(['recordId', 'id'])\n const reduced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(match as Record<string, unknown>)) {\n if (envelope.has(key)) continue\n reduced[key] = value\n }\n return reduced\n }\n }\n return {}\n}\n\nfunction normalizeAttachmentIds(args: Record<string, unknown>): string[] {\n const raw = args?.attachmentIds\n if (!Array.isArray(raw)) return []\n return raw.filter((value): value is string => typeof value === 'string' && value.length > 0)\n}\n\nasync function buildSingleRecordDiff(\n tool: AiToolDefinition,\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<{\n fieldDiff: AiPendingActionFieldDiff[]\n targetEntityType: string | null\n targetRecordId: string | null\n recordVersion: string | null\n sideEffectsSummary: string | null\n}> {\n const resolver = tool.loadBeforeRecord\n if (!resolver) {\n console.warn(\n `[AI Agents] prepareMutation: tool \"${tool.name}\" declared isMutation=true but no loadBeforeRecord resolver; shipping empty fieldDiff.`,\n )\n return {\n fieldDiff: [],\n targetEntityType: null,\n targetRecordId: null,\n recordVersion: null,\n sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE,\n }\n }\n const handlerContext = toolHandlerContext(ctx)\n const before: AiToolLoadBeforeSingleRecord | null = await resolver(\n input.toolCallArgs as never,\n handlerContext,\n )\n if (!before) {\n return {\n fieldDiff: [],\n targetEntityType: null,\n targetRecordId: null,\n recordVersion: null,\n sideEffectsSummary: null,\n }\n }\n const patch = extractPatchFromArgs(input.toolCallArgs)\n const fieldDiff = computeFieldDiff(before.before, patch)\n return {\n fieldDiff,\n targetEntityType: before.entityType,\n targetRecordId: before.recordId,\n recordVersion: before.recordVersion,\n sideEffectsSummary: null,\n }\n}\n\nasync function buildBatchRecords(\n tool: AiToolDefinition,\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<{\n records: AiPendingActionRecordDiff[] | null\n targetEntityType: string | null\n sideEffectsSummary: string | null\n}> {\n const resolver = tool.loadBeforeRecords\n if (!resolver) {\n console.warn(\n `[AI Agents] prepareMutation: bulk tool \"${tool.name}\" declared isMutation=true but no loadBeforeRecords resolver; shipping empty records[].`,\n )\n return {\n records: null,\n targetEntityType: null,\n sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE,\n }\n }\n const handlerContext = toolHandlerContext(ctx)\n const rows: AiToolLoadBeforeRecord[] = await resolver(\n input.toolCallArgs as never,\n handlerContext,\n )\n if (!Array.isArray(rows) || rows.length === 0) {\n return {\n records: null,\n targetEntityType: null,\n sideEffectsSummary: null,\n }\n }\n const diffs: AiPendingActionRecordDiff[] = rows.map((row) => {\n const patch = matchBatchPatch(input.toolCallArgs, row.recordId)\n return {\n recordId: row.recordId,\n entityType: row.entityType,\n label: row.label,\n fieldDiff: computeFieldDiff(row.before, patch),\n recordVersion: row.recordVersion ?? null,\n }\n })\n const [firstEntity] = rows\n return {\n records: diffs,\n targetEntityType: firstEntity ? firstEntity.entityType : null,\n sideEffectsSummary: null,\n }\n}\n\n/**\n * Intercepts a mutation tool call and turns it into an `AiPendingAction` +\n * `mutation-preview-card` UI part (spec Phase 3 WS-C \u00A79). The caller MUST\n * have already confirmed the agent's effective `mutationPolicy` is NOT\n * `read-only`; this helper repeats the check defensively because skipping it\n * would be a policy-bypass.\n *\n * The tool handler is NEVER invoked by this function \u2014 the write is\n * short-circuited and only runs from the Step 5.8 confirm route. See the\n * unit test `does not call the tool handler` for the guard.\n */\nexport async function prepareMutation(\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<PrepareMutationResult> {\n const { agent, tool } = input\n if (tool.isMutation !== true) {\n throw new AiMutationPreparationError(\n 'not_a_mutation_tool',\n `Tool \"${tool.name}\" is not a mutation tool; prepareMutation should not be invoked.`,\n )\n }\n const effectivePolicy = resolveEffectiveMutationPolicy(\n agent.mutationPolicy,\n input.mutationPolicyOverride ?? null,\n agent.id,\n )\n if (effectivePolicy === 'read-only') {\n throw new AiMutationPreparationError(\n 'read_only_agent',\n `Agent \"${agent.id}\" has effective mutationPolicy=read-only; mutation tool \"${tool.name}\" cannot be prepared.`,\n )\n }\n\n const tenantId = assertTenantScope(ctx)\n const em = resolveEm(ctx.container)\n const repo = new AiPendingActionRepository(em)\n\n const isBulk = tool.isBulk === true\n let fieldDiff: AiPendingActionFieldDiff[] = []\n let records: AiPendingActionRecordDiff[] | null = null\n let targetEntityType: string | null = null\n let targetRecordId: string | null = null\n let recordVersion: string | null = null\n let sideEffectsSummary: string | null = null\n\n if (isBulk) {\n const batch = await buildBatchRecords(tool, input, ctx)\n records = batch.records\n targetEntityType = batch.targetEntityType\n sideEffectsSummary = batch.sideEffectsSummary\n } else {\n const single = await buildSingleRecordDiff(tool, input, ctx)\n fieldDiff = single.fieldDiff\n targetEntityType = single.targetEntityType\n targetRecordId = single.targetRecordId\n recordVersion = single.recordVersion\n sideEffectsSummary = single.sideEffectsSummary\n }\n\n const normalizedInput = input.toolCallArgs ?? {}\n const conversationId = input.conversationId ?? null\n const idempotencyKey = computeMutationIdempotencyKey({\n tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: agent.id,\n conversationId,\n toolName: tool.name,\n normalizedInput,\n })\n\n const pendingAction = await repo.create(\n {\n agentId: agent.id,\n toolName: tool.name,\n idempotencyKey,\n createdByUserId: ctx.userId,\n normalizedInput,\n conversationId,\n targetEntityType,\n targetRecordId,\n fieldDiff,\n records,\n sideEffectsSummary,\n recordVersion,\n attachmentIds: normalizeAttachmentIds(normalizedInput),\n now: input.now,\n },\n {\n tenantId,\n organizationId: ctx.organizationId ?? null,\n userId: ctx.userId,\n },\n )\n\n const uiPart: AiUiPart = {\n componentId: MUTATION_PREVIEW_CARD_COMPONENT_ID,\n props: {\n pendingActionId: pendingAction.id,\n expiresAt: pendingAction.expiresAt.toISOString(),\n ...(records ? { records } : { fieldDiff }),\n ...(sideEffectsSummary ? { sideEffectsSummary } : {}),\n },\n }\n\n return { uiPart, pendingAction }\n}\n\nexport const MUTATION_PREVIEW_CARD_COMPONENT = MUTATION_PREVIEW_CARD_COMPONENT_ID\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,7 @@
1
+ function definePromptTemplate(template) {
2
+ return template;
3
+ }
4
+ export {
5
+ definePromptTemplate
6
+ };
7
+ //# sourceMappingURL=prompt-composition-types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/prompt-composition-types.ts"],
4
+ "sourcesContent": ["export type PromptSectionName =\n | 'role'\n | 'scope'\n | 'data'\n | 'tools'\n | 'attachments'\n | 'mutationPolicy'\n | 'responseStyle'\n | 'overrides'\n\nexport interface PromptSection {\n name: PromptSectionName\n content: string\n order?: number\n}\n\nexport interface PromptTemplate {\n id: string\n sections: PromptSection[]\n}\n\nexport function definePromptTemplate(template: PromptTemplate): PromptTemplate {\n return template\n}\n"],
5
+ "mappings": "AAqBO,SAAS,qBAAqB,UAA0C;AAC7E,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,175 @@
1
+ const CANONICAL_PROMPT_SECTIONS = [
2
+ "role",
3
+ "scope",
4
+ "data",
5
+ "tools",
6
+ "attachments",
7
+ "mutationPolicy",
8
+ "responseStyle",
9
+ "overrides"
10
+ ];
11
+ const RESERVED_OVERRIDE_KEYS = [
12
+ "mutationPolicy",
13
+ "readOnly",
14
+ "allowedTools",
15
+ "acceptedMediaTypes"
16
+ ];
17
+ const HEADER_MAP = {
18
+ role: "role",
19
+ scope: "scope",
20
+ data: "data",
21
+ tools: "tools",
22
+ attachments: "attachments",
23
+ "mutation policy": "mutationPolicy",
24
+ mutation_policy: "mutationPolicy",
25
+ mutationpolicy: "mutationPolicy",
26
+ "response style": "responseStyle",
27
+ response_style: "responseStyle",
28
+ responsestyle: "responseStyle",
29
+ overrides: "overrides"
30
+ };
31
+ function canonicalize(key) {
32
+ const normalized = key.trim().toLowerCase();
33
+ if (!normalized) return null;
34
+ return HEADER_MAP[normalized] ?? null;
35
+ }
36
+ function prettyHeader(name) {
37
+ switch (name) {
38
+ case "mutationPolicy":
39
+ return "MUTATION POLICY";
40
+ case "responseStyle":
41
+ return "RESPONSE STYLE";
42
+ default:
43
+ return name.toUpperCase();
44
+ }
45
+ }
46
+ function applyPromptOverride(template, override) {
47
+ const baseSections = [...template.sections];
48
+ const sections = override?.sections ?? null;
49
+ if (!sections || typeof sections !== "object") {
50
+ return {
51
+ sections: baseSections,
52
+ systemPrompt: renderPrompt(baseSections)
53
+ };
54
+ }
55
+ assertNoReservedKeys(sections);
56
+ const byCanonical = /* @__PURE__ */ new Map();
57
+ const unknownKeys = [];
58
+ for (const [rawKey, value] of Object.entries(sections)) {
59
+ if (typeof value !== "string") continue;
60
+ const trimmed = value.trim();
61
+ if (!trimmed) continue;
62
+ const canonical = canonicalize(rawKey);
63
+ if (canonical) {
64
+ const existing = byCanonical.get(canonical);
65
+ byCanonical.set(canonical, existing ? `${existing}
66
+
67
+ ${trimmed}` : trimmed);
68
+ } else {
69
+ unknownKeys.push({ rawKey, value: trimmed });
70
+ }
71
+ }
72
+ const appended = baseSections.map((section) => {
73
+ const addendum = byCanonical.get(section.name);
74
+ if (!addendum) return section;
75
+ const nextContent = section.content.trim().length === 0 ? addendum : `${section.content}
76
+
77
+ ${addendum}`;
78
+ return { ...section, content: nextContent };
79
+ });
80
+ const declaredNames = new Set(appended.map((s) => s.name));
81
+ for (const [canonical, addendum] of byCanonical.entries()) {
82
+ if (!declaredNames.has(canonical)) {
83
+ appended.push({ name: canonical, content: addendum });
84
+ declaredNames.add(canonical);
85
+ }
86
+ }
87
+ if (unknownKeys.length > 0) {
88
+ const newSections = unknownKeys.map(({ rawKey, value }) => ({
89
+ name: "overrides",
90
+ content: `[${rawKey.trim().toUpperCase()}]
91
+ ${value}`
92
+ }));
93
+ const responseStyleIndex = appended.findIndex((s) => s.name === "responseStyle");
94
+ const overridesIndex = appended.findIndex((s) => s.name === "overrides");
95
+ let insertAt;
96
+ if (overridesIndex >= 0) {
97
+ insertAt = overridesIndex;
98
+ } else if (responseStyleIndex >= 0) {
99
+ insertAt = responseStyleIndex + 1;
100
+ } else {
101
+ insertAt = appended.length;
102
+ }
103
+ appended.splice(insertAt, 0, ...newSections);
104
+ }
105
+ return {
106
+ sections: appended,
107
+ systemPrompt: renderPrompt(appended)
108
+ };
109
+ }
110
+ function renderPrompt(sections) {
111
+ const lines = [];
112
+ for (const section of sections) {
113
+ const content = section.content.trim();
114
+ if (!content) continue;
115
+ lines.push(`[${prettyHeader(section.name)}]
116
+ ${content}`);
117
+ }
118
+ return lines.join("\n\n");
119
+ }
120
+ function findReservedKeys(sections) {
121
+ if (!sections || typeof sections !== "object") return [];
122
+ const reservedSet = new Set(RESERVED_OVERRIDE_KEYS.map((key) => key.toLowerCase()));
123
+ const hits = [];
124
+ for (const key of Object.keys(sections)) {
125
+ if (reservedSet.has(key.trim().toLowerCase())) {
126
+ hits.push(key);
127
+ }
128
+ }
129
+ return hits;
130
+ }
131
+ function assertNoReservedKeys(sections) {
132
+ const hits = findReservedKeys(sections);
133
+ if (hits.length > 0) {
134
+ throw new PromptOverrideReservedKeyError(hits);
135
+ }
136
+ }
137
+ class PromptOverrideReservedKeyError extends Error {
138
+ constructor(keys) {
139
+ super(
140
+ `Prompt override includes reserved policy keys: ${keys.join(", ")}. Policy fields (${RESERVED_OVERRIDE_KEYS.join(", ")}) are never editable via prompt overrides.`
141
+ );
142
+ this.keys = keys;
143
+ this.code = "reserved_key";
144
+ this.name = "PromptOverrideReservedKeyError";
145
+ }
146
+ }
147
+ function composeSystemPromptWithOverride(baseSystemPrompt, override) {
148
+ if (!override?.sections || Object.keys(override.sections).length === 0) {
149
+ return baseSystemPrompt;
150
+ }
151
+ const template = {
152
+ id: "legacy-system-prompt",
153
+ sections: [
154
+ { name: "role", content: baseSystemPrompt },
155
+ { name: "scope", content: "" },
156
+ { name: "data", content: "" },
157
+ { name: "tools", content: "" },
158
+ { name: "attachments", content: "" },
159
+ { name: "mutationPolicy", content: "" },
160
+ { name: "responseStyle", content: "" }
161
+ ]
162
+ };
163
+ const applied = applyPromptOverride(template, override);
164
+ return applied.systemPrompt;
165
+ }
166
+ export {
167
+ CANONICAL_PROMPT_SECTIONS,
168
+ PromptOverrideReservedKeyError,
169
+ RESERVED_OVERRIDE_KEYS,
170
+ applyPromptOverride,
171
+ composeSystemPromptWithOverride,
172
+ findReservedKeys,
173
+ renderPrompt
174
+ };
175
+ //# sourceMappingURL=prompt-override-merge.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/prompt-override-merge.ts"],
4
+ "sourcesContent": ["import type {\n PromptSection,\n PromptSectionName,\n PromptTemplate,\n} from './prompt-composition-types'\n\n/**\n * Canonical prompt section names the built-in agent prompt templates ship.\n * These align with the 7 section headers the spec (\u00A78) mandates, plus the\n * free-form `overrides` bucket the template already reserves.\n */\nexport const CANONICAL_PROMPT_SECTIONS: readonly PromptSectionName[] = [\n 'role',\n 'scope',\n 'data',\n 'tools',\n 'attachments',\n 'mutationPolicy',\n 'responseStyle',\n 'overrides',\n] as const\n\n/**\n * Reserved keys that MUST NOT appear in a prompt override. These name policy\n * fields that live on the agent definition itself \u2014 allowing them to be\n * modified via the prompt-override layer would let a tenant silently escalate\n * an agent beyond the mutation / tool / attachment contract enforced by\n * `checkAgentPolicy` at dispatch time.\n */\nexport const RESERVED_OVERRIDE_KEYS: readonly string[] = [\n 'mutationPolicy',\n 'readOnly',\n 'allowedTools',\n 'acceptedMediaTypes',\n] as const\n\nconst HEADER_MAP: Readonly<Record<string, PromptSectionName>> = {\n role: 'role',\n scope: 'scope',\n data: 'data',\n tools: 'tools',\n attachments: 'attachments',\n 'mutation policy': 'mutationPolicy',\n mutation_policy: 'mutationPolicy',\n mutationpolicy: 'mutationPolicy',\n 'response style': 'responseStyle',\n response_style: 'responseStyle',\n responsestyle: 'responseStyle',\n overrides: 'overrides',\n}\n\nfunction canonicalize(key: string): PromptSectionName | null {\n const normalized = key.trim().toLowerCase()\n if (!normalized) return null\n return HEADER_MAP[normalized] ?? null\n}\n\nfunction prettyHeader(name: PromptSectionName): string {\n switch (name) {\n case 'mutationPolicy':\n return 'MUTATION POLICY'\n case 'responseStyle':\n return 'RESPONSE STYLE'\n default:\n return name.toUpperCase()\n }\n}\n\nexport interface AppliedPromptOverride {\n sections: PromptSection[]\n systemPrompt: string\n}\n\nexport interface PromptOverrideInput {\n /** Additive text keyed by canonical section id or free-form new header. */\n sections: Record<string, string> | null | undefined\n}\n\n/**\n * Applies an additive prompt override to a built-in template. Rules:\n *\n * - A canonical section key (`role`, `scope`, `data`, `tools`, `attachments`,\n * `MUTATION POLICY`, `RESPONSE STYLE`, `overrides`) APPENDS the override\n * text below the built-in section content with a blank line separator.\n * The built-in content is never removed or rewritten.\n * - A non-canonical key is treated as a brand-new section and inserted\n * after the canonical `RESPONSE STYLE` position (before `overrides`,\n * if any). Canonical section order is always preserved.\n * - Empty or whitespace-only override values are ignored.\n * - If any reserved policy key is present (`mutationPolicy`, `readOnly`,\n * `allowedTools`, `acceptedMediaTypes`), this function throws. Call sites\n * SHOULD validate via {@link validatePromptOverrideInput} first so the\n * error surfaces as a 400 to the API caller.\n */\nexport function applyPromptOverride(\n template: PromptTemplate,\n override: PromptOverrideInput | null | undefined,\n): AppliedPromptOverride {\n const baseSections = [...template.sections]\n const sections = override?.sections ?? null\n\n if (!sections || typeof sections !== 'object') {\n return {\n sections: baseSections,\n systemPrompt: renderPrompt(baseSections),\n }\n }\n\n assertNoReservedKeys(sections)\n\n // First pass: append to canonical sections.\n const byCanonical = new Map<PromptSectionName, string>()\n const unknownKeys: Array<{ rawKey: string; value: string }> = []\n for (const [rawKey, value] of Object.entries(sections)) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (!trimmed) continue\n const canonical = canonicalize(rawKey)\n if (canonical) {\n const existing = byCanonical.get(canonical)\n byCanonical.set(canonical, existing ? `${existing}\\n\\n${trimmed}` : trimmed)\n } else {\n unknownKeys.push({ rawKey, value: trimmed })\n }\n }\n\n const appended: PromptSection[] = baseSections.map((section) => {\n const addendum = byCanonical.get(section.name)\n if (!addendum) return section\n const nextContent = section.content.trim().length === 0\n ? addendum\n : `${section.content}\\n\\n${addendum}`\n return { ...section, content: nextContent }\n })\n\n // If a canonical section wasn't already present but an override targets it,\n // append it using its canonical header. Catalog / customer templates ship\n // all 7 canonical headers today, but we defend against future templates\n // that may omit some.\n const declaredNames = new Set(appended.map((s) => s.name))\n for (const [canonical, addendum] of byCanonical.entries()) {\n if (!declaredNames.has(canonical)) {\n appended.push({ name: canonical, content: addendum })\n declaredNames.add(canonical)\n }\n }\n\n // Brand-new sections inject after RESPONSE STYLE. We insert by splitting the\n // array at the index directly after `responseStyle`. If `overrides` is\n // already present, new sections land before it so `overrides` stays last.\n if (unknownKeys.length > 0) {\n const newSections: PromptSection[] = unknownKeys.map(({ rawKey, value }) => ({\n name: 'overrides',\n content: `[${rawKey.trim().toUpperCase()}]\\n${value}`,\n }))\n const responseStyleIndex = appended.findIndex((s) => s.name === 'responseStyle')\n const overridesIndex = appended.findIndex((s) => s.name === 'overrides')\n let insertAt: number\n if (overridesIndex >= 0) {\n insertAt = overridesIndex\n } else if (responseStyleIndex >= 0) {\n insertAt = responseStyleIndex + 1\n } else {\n insertAt = appended.length\n }\n appended.splice(insertAt, 0, ...newSections)\n }\n\n return {\n sections: appended,\n systemPrompt: renderPrompt(appended),\n }\n}\n\n/**\n * Produces a flat string representation of a prompt template. The runtime uses\n * this when the agent definition declares sections but the underlying call\n * site expects a single-string `systemPrompt` (today: both `runAiAgentText`\n * and `runAiAgentObject`).\n */\nexport function renderPrompt(sections: readonly PromptSection[]): string {\n const lines: string[] = []\n for (const section of sections) {\n const content = section.content.trim()\n if (!content) continue\n lines.push(`[${prettyHeader(section.name)}]\\n${content}`)\n }\n return lines.join('\\n\\n')\n}\n\n/**\n * Returns the list of reserved keys present in the supplied override payload.\n * Empty array means the body is safe for persistence. Call this before the\n * repository `save` so the API layer can reject with a 400 / `reserved_key`.\n */\nexport function findReservedKeys(sections: Record<string, unknown> | null | undefined): string[] {\n if (!sections || typeof sections !== 'object') return []\n const reservedSet = new Set(RESERVED_OVERRIDE_KEYS.map((key) => key.toLowerCase()))\n const hits: string[] = []\n for (const key of Object.keys(sections)) {\n if (reservedSet.has(key.trim().toLowerCase())) {\n hits.push(key)\n }\n }\n return hits\n}\n\nfunction assertNoReservedKeys(sections: Record<string, unknown>): void {\n const hits = findReservedKeys(sections)\n if (hits.length > 0) {\n throw new PromptOverrideReservedKeyError(hits)\n }\n}\n\nexport class PromptOverrideReservedKeyError extends Error {\n readonly code = 'reserved_key'\n constructor(public readonly keys: readonly string[]) {\n super(\n `Prompt override includes reserved policy keys: ${keys.join(', ')}. ` +\n `Policy fields (${RESERVED_OVERRIDE_KEYS.join(', ')}) are never editable via prompt overrides.`,\n )\n this.name = 'PromptOverrideReservedKeyError'\n }\n}\n\n/**\n * Convenience wrapper: composes the full system prompt for a legacy agent that\n * ships a single-string `systemPrompt`. Returns the base unchanged when no\n * override is present. When an override is present, the base is treated as the\n * `role` section and overrides are layered via {@link applyPromptOverride}.\n */\nexport function composeSystemPromptWithOverride(\n baseSystemPrompt: string,\n override: PromptOverrideInput | null | undefined,\n): string {\n if (!override?.sections || Object.keys(override.sections).length === 0) {\n return baseSystemPrompt\n }\n const template: PromptTemplate = {\n id: 'legacy-system-prompt',\n sections: [\n { name: 'role', content: baseSystemPrompt },\n { name: 'scope', content: '' },\n { name: 'data', content: '' },\n { name: 'tools', content: '' },\n { name: 'attachments', content: '' },\n { name: 'mutationPolicy', content: '' },\n { name: 'responseStyle', content: '' },\n ],\n }\n const applied = applyPromptOverride(template, override)\n return applied.systemPrompt\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -25,7 +25,8 @@ function jsonSchemaToZod(jsonSchema) {
25
25
  const properties = jsonSchema.properties;
26
26
  const required = jsonSchema.required || [];
27
27
  const additionalProperties = jsonSchema.additionalProperties;
28
- if (additionalProperties && (!properties || Object.keys(properties).length === 0)) {
28
+ const hasFixedProperties = properties && Object.keys(properties).length > 0;
29
+ if (additionalProperties && !hasFixedProperties && properties === void 0) {
29
30
  if (typeof additionalProperties === "object") {
30
31
  return z.record(z.string(), jsonSchemaToZod(additionalProperties));
31
32
  }
@@ -79,6 +80,9 @@ function toSafeZodSchema(schema) {
79
80
  }
80
81
  try {
81
82
  const jsonSchema = z.toJSONSchema(schema, { unrepresentable: "any" });
83
+ if (jsonSchema.type === "object" && !jsonSchema.properties) {
84
+ jsonSchema.properties = {};
85
+ }
82
86
  const safeSchema = jsonSchemaToZod(jsonSchema);
83
87
  safeSchemaCache.set(schema, safeSchema);
84
88
  return safeSchema;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/schema-utils.ts"],
4
- "sourcesContent": ["import { z, type ZodType } from 'zod'\n\n/**\n * Cache for converted safe schemas to avoid repeated conversions per request.\n */\nconst safeSchemaCache = new WeakMap<ZodType, ZodType>()\n\n/**\n * Convert a JSON Schema to a simple Zod schema.\n * This creates a schema that can be converted back to JSON Schema without errors.\n *\n * Supports:\n * - Basic types: string, number, integer, boolean, null\n * - Arrays with item types\n * - Objects with properties and required fields\n * - Records/dictionaries via additionalProperties\n * - Union types via anyOf/oneOf\n * - Enum values\n */\nexport function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {\n const type = jsonSchema.type as string | undefined\n\n if (type === 'string') {\n return z.string()\n }\n if (type === 'number' || type === 'integer') {\n return z.number()\n }\n if (type === 'boolean') {\n return z.boolean()\n }\n if (type === 'null') {\n return z.null()\n }\n if (type === 'array') {\n const items = jsonSchema.items as Record<string, unknown> | undefined\n if (items) {\n return z.array(jsonSchemaToZod(items))\n }\n return z.array(z.unknown())\n }\n if (type === 'object') {\n const properties = jsonSchema.properties as Record<string, Record<string, unknown>> | undefined\n const required = (jsonSchema.required as string[]) || []\n const additionalProperties = jsonSchema.additionalProperties\n\n // Handle z.record() - objects with additionalProperties but no fixed properties\n if (additionalProperties && (!properties || Object.keys(properties).length === 0)) {\n // This is a record/dictionary type - allow any properties\n if (typeof additionalProperties === 'object') {\n return z.record(z.string(), jsonSchemaToZod(additionalProperties as Record<string, unknown>))\n }\n // additionalProperties: true means any value\n return z.record(z.string(), z.unknown())\n }\n\n if (properties) {\n const shape: Record<string, ZodType> = {}\n for (const [key, propSchema] of Object.entries(properties)) {\n let fieldSchema = jsonSchemaToZod(propSchema)\n // Make field optional if not in required array\n if (!required.includes(key)) {\n fieldSchema = fieldSchema.optional()\n }\n shape[key] = fieldSchema\n }\n // If additionalProperties is allowed, use passthrough\n if (additionalProperties) {\n return z.object(shape).passthrough()\n }\n return z.object(shape)\n }\n\n // Empty object with additionalProperties - treat as record\n if (additionalProperties) {\n return z.record(z.string(), z.unknown())\n }\n return z.object({})\n }\n\n // Handle union types (anyOf, oneOf)\n const anyOf = jsonSchema.anyOf as Record<string, unknown>[] | undefined\n const oneOf = jsonSchema.oneOf as Record<string, unknown>[] | undefined\n const unionTypes = anyOf || oneOf\n if (unionTypes && unionTypes.length >= 2) {\n const schemas = unionTypes.map(s => jsonSchemaToZod(s))\n return z.union(schemas as [ZodType, ZodType, ...ZodType[]])\n }\n\n // Handle nullable via anyOf with null\n if (anyOf && anyOf.length === 2) {\n const types = anyOf.map((s) => s.type)\n if (types.includes('null')) {\n const nonNullSchema = anyOf.find((s) => s.type !== 'null')\n if (nonNullSchema) {\n return jsonSchemaToZod(nonNullSchema).nullable()\n }\n }\n }\n\n // Handle enum\n const enumValues = jsonSchema.enum as string[] | undefined\n if (enumValues && enumValues.length > 0) {\n return z.enum(enumValues as [string, ...string[]])\n }\n\n // Fallback for empty schemas (like Date converted with unrepresentable: 'any')\n return z.unknown()\n}\n\n/**\n * Convert a Zod schema to a safe Zod schema that has no Date types.\n * Uses JSON Schema as an intermediate format to handle all Zod v4 internal complexities.\n * Results are cached to avoid repeated conversions.\n *\n * @param schema - The original Zod schema\n * @returns A safe Zod schema without Date types\n */\nexport function toSafeZodSchema(schema: ZodType): ZodType {\n // Check cache first\n const cached = safeSchemaCache.get(schema)\n if (cached) {\n return cached\n }\n\n try {\n // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types\n const jsonSchema = z.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // Convert back to a simple Zod schema without Date types\n const safeSchema = jsonSchemaToZod(jsonSchema)\n\n // Cache the result\n safeSchemaCache.set(schema, safeSchema)\n\n return safeSchema\n } catch (error) {\n console.error('[Schema Utils] Error converting schema:', error)\n // Fallback to the original schema if conversion fails\n return schema\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z, type ZodType } from 'zod'\n\n/**\n * Cache for converted safe schemas to avoid repeated conversions per request.\n */\nconst safeSchemaCache = new WeakMap<ZodType, ZodType>()\n\n/**\n * Convert a JSON Schema to a simple Zod schema.\n * This creates a schema that can be converted back to JSON Schema without errors.\n *\n * Supports:\n * - Basic types: string, number, integer, boolean, null\n * - Arrays with item types\n * - Objects with properties and required fields\n * - Records/dictionaries via additionalProperties\n * - Union types via anyOf/oneOf\n * - Enum values\n */\nexport function jsonSchemaToZod(jsonSchema: Record<string, unknown>): ZodType {\n const type = jsonSchema.type as string | undefined\n\n if (type === 'string') {\n return z.string()\n }\n if (type === 'number' || type === 'integer') {\n return z.number()\n }\n if (type === 'boolean') {\n return z.boolean()\n }\n if (type === 'null') {\n return z.null()\n }\n if (type === 'array') {\n const items = jsonSchema.items as Record<string, unknown> | undefined\n if (items) {\n return z.array(jsonSchemaToZod(items))\n }\n return z.array(z.unknown())\n }\n if (type === 'object') {\n const properties = jsonSchema.properties as Record<string, Record<string, unknown>> | undefined\n const required = (jsonSchema.required as string[]) || []\n const additionalProperties = jsonSchema.additionalProperties\n\n // Handle z.record() - objects with additionalProperties but no fixed properties.\n // Skip the record path when properties is present but empty \u2014 that is a\n // no-arg object schema, not a dictionary. OpenAI requires `properties: {}`\n // in the JSON Schema, and `z.object({})` produces that, whereas\n // `z.record()` does not.\n const hasFixedProperties = properties && Object.keys(properties).length > 0\n if (additionalProperties && !hasFixedProperties && properties === undefined) {\n // This is a record/dictionary type - allow any properties\n if (typeof additionalProperties === 'object') {\n return z.record(z.string(), jsonSchemaToZod(additionalProperties as Record<string, unknown>))\n }\n // additionalProperties: true means any value\n return z.record(z.string(), z.unknown())\n }\n\n if (properties) {\n const shape: Record<string, ZodType> = {}\n for (const [key, propSchema] of Object.entries(properties)) {\n let fieldSchema = jsonSchemaToZod(propSchema)\n // Make field optional if not in required array\n if (!required.includes(key)) {\n fieldSchema = fieldSchema.optional()\n }\n shape[key] = fieldSchema\n }\n // If additionalProperties is allowed, use passthrough\n if (additionalProperties) {\n return z.object(shape).passthrough()\n }\n return z.object(shape)\n }\n\n // Empty object with additionalProperties - treat as record\n if (additionalProperties) {\n return z.record(z.string(), z.unknown())\n }\n return z.object({})\n }\n\n // Handle union types (anyOf, oneOf)\n const anyOf = jsonSchema.anyOf as Record<string, unknown>[] | undefined\n const oneOf = jsonSchema.oneOf as Record<string, unknown>[] | undefined\n const unionTypes = anyOf || oneOf\n if (unionTypes && unionTypes.length >= 2) {\n const schemas = unionTypes.map(s => jsonSchemaToZod(s))\n return z.union(schemas as [ZodType, ZodType, ...ZodType[]])\n }\n\n // Handle nullable via anyOf with null\n if (anyOf && anyOf.length === 2) {\n const types = anyOf.map((s) => s.type)\n if (types.includes('null')) {\n const nonNullSchema = anyOf.find((s) => s.type !== 'null')\n if (nonNullSchema) {\n return jsonSchemaToZod(nonNullSchema).nullable()\n }\n }\n }\n\n // Handle enum\n const enumValues = jsonSchema.enum as string[] | undefined\n if (enumValues && enumValues.length > 0) {\n return z.enum(enumValues as [string, ...string[]])\n }\n\n // Fallback for empty schemas (like Date converted with unrepresentable: 'any')\n return z.unknown()\n}\n\n/**\n * Convert a Zod schema to a safe Zod schema that has no Date types.\n * Uses JSON Schema as an intermediate format to handle all Zod v4 internal complexities.\n * Results are cached to avoid repeated conversions.\n *\n * @param schema - The original Zod schema\n * @returns A safe Zod schema without Date types\n */\nexport function toSafeZodSchema(schema: ZodType): ZodType {\n // Check cache first\n const cached = safeSchemaCache.get(schema)\n if (cached) {\n return cached\n }\n\n try {\n // Use Zod 4's toJSONSchema with unrepresentable: 'any' to handle Date types\n const jsonSchema = z.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<string, unknown>\n\n // OpenAI requires `properties` on object schemas. Empty passthrough objects\n // (tools that take no args) roundtrip as `{ type: \"object\", additionalProperties: true }`\n // without `properties`, which OpenAI rejects. Ensure `properties` is present.\n if (jsonSchema.type === 'object' && !jsonSchema.properties) {\n jsonSchema.properties = {}\n }\n\n // Convert back to a simple Zod schema without Date types\n const safeSchema = jsonSchemaToZod(jsonSchema)\n\n // Cache the result\n safeSchemaCache.set(schema, safeSchema)\n\n return safeSchema\n } catch (error) {\n console.error('[Schema Utils] Error converting schema:', error)\n // Fallback to the original schema if conversion fails\n return schema\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }