@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,207 @@
1
+ import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { UIMessage } from 'ai'
3
+ import { z } from 'zod'
4
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
5
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
7
+ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
8
+ import { loadAgentRegistry } from '../../../lib/agent-registry'
9
+ import { checkAgentPolicy, type AgentPolicyDenyCode } from '../../../lib/agent-policy'
10
+ import { runAiAgentText } from '../../../lib/agent-runtime'
11
+ import { AgentPolicyError } from '../../../lib/agent-tools'
12
+
13
+ const MAX_MESSAGES = 100
14
+
15
+ const agentIdPattern = /^[a-z0-9_]+\.[a-z0-9_]+$/
16
+
17
+ const chatMessageSchema = z.object({
18
+ role: z.enum(['user', 'assistant', 'system']),
19
+ content: z.string(),
20
+ })
21
+
22
+ const pageContextSchema = z
23
+ .object({
24
+ pageId: z.string().nullable().optional(),
25
+ entityType: z.string().nullable().optional(),
26
+ recordId: z.string().nullable().optional(),
27
+ })
28
+ .passthrough()
29
+
30
+ const chatRequestSchema = z.object({
31
+ messages: z
32
+ .array(chatMessageSchema)
33
+ .min(1, 'messages must contain at least one message')
34
+ .max(MAX_MESSAGES, `messages must contain at most ${MAX_MESSAGES} entries`),
35
+ attachmentIds: z.array(z.string()).optional(),
36
+ debug: z.boolean().optional(),
37
+ pageContext: pageContextSchema.optional(),
38
+ /**
39
+ * Optional stable conversation id forwarded from `<AiChat>`. Bridged into
40
+ * the Step 5.6 `prepareMutation` idempotency hash so repeated turns within
41
+ * the same chat collapse onto the same pending action. Additive; omitted
42
+ * bodies continue to work as before.
43
+ */
44
+ conversationId: z.string().min(1).max(128).optional(),
45
+ })
46
+
47
+ export type AiChatRequest = z.infer<typeof chatRequestSchema>
48
+
49
+ const agentQuerySchema = z.object({
50
+ agent: z
51
+ .string()
52
+ .regex(agentIdPattern, 'agent must match "<module>.<agent>" (lowercase, digits, underscores only)'),
53
+ })
54
+
55
+ export const openApi: OpenApiRouteDoc = {
56
+ tag: 'AI Assistant',
57
+ summary: 'AI agent dispatcher',
58
+ methods: {
59
+ POST: {
60
+ operationId: 'aiAssistantChatAgent',
61
+ summary: 'Stream a chat turn for a registered AI agent',
62
+ description:
63
+ 'Dispatches a chat turn to the focused AI agent identified by `?agent=<module>.<agent>`. ' +
64
+ 'Enforces agent-level `requiredFeatures`, tool whitelisting, read-only / mutationPolicy, ' +
65
+ 'execution-mode compatibility, and attachment media-type policy. The streaming response ' +
66
+ 'body uses an AI SDK-compatible `text/event-stream` transport.',
67
+ query: agentQuerySchema,
68
+ requestBody: {
69
+ contentType: 'application/json',
70
+ description: 'Chat turn payload. `messages` is required; `attachmentIds`, `debug`, and `pageContext` are optional.',
71
+ schema: chatRequestSchema,
72
+ },
73
+ responses: [
74
+ { status: 200, description: 'Streaming text/event-stream response compatible with AI SDK chat transports.', mediaType: 'text/event-stream' },
75
+ ],
76
+ errors: [
77
+ { status: 400, description: 'Invalid query param, malformed payload, or message count above the cap.' },
78
+ { status: 401, description: 'Unauthenticated caller.' },
79
+ { status: 403, description: 'Caller lacks agent-level or tool-level required features.' },
80
+ { status: 404, description: 'Unknown agent id.' },
81
+ { status: 409, description: 'Agent/tool/execution-mode policy violation.' },
82
+ { status: 500, description: 'Internal runtime failure.' },
83
+ ],
84
+ },
85
+ },
86
+ }
87
+
88
+ export const metadata = {
89
+ POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
90
+ }
91
+
92
+ function jsonError(
93
+ status: number,
94
+ message: string,
95
+ code: string,
96
+ extra?: Record<string, unknown>,
97
+ ): NextResponse {
98
+ return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })
99
+ }
100
+
101
+ function statusForDenyCode(code: AgentPolicyDenyCode): number {
102
+ switch (code) {
103
+ case 'agent_unknown':
104
+ return 404
105
+ case 'agent_features_denied':
106
+ case 'tool_features_denied':
107
+ return 403
108
+ case 'tool_not_whitelisted':
109
+ case 'tool_unknown':
110
+ case 'mutation_blocked_by_readonly':
111
+ case 'mutation_blocked_by_policy':
112
+ case 'execution_mode_not_supported':
113
+ return 409
114
+ case 'attachment_type_not_accepted':
115
+ return 400
116
+ default:
117
+ return 409
118
+ }
119
+ }
120
+
121
+ export async function POST(req: NextRequest): Promise<Response> {
122
+ const auth = await getAuthFromRequest(req)
123
+ if (!auth) {
124
+ return jsonError(401, 'Unauthorized', 'unauthenticated')
125
+ }
126
+
127
+ const requestUrl = new URL(req.url)
128
+ const queryResult = agentQuerySchema.safeParse({
129
+ agent: requestUrl.searchParams.get('agent') ?? undefined,
130
+ })
131
+ if (!queryResult.success) {
132
+ return jsonError(400, 'Invalid or missing "agent" query parameter.', 'validation_error', {
133
+ issues: queryResult.error.issues,
134
+ })
135
+ }
136
+ const agentId = queryResult.data.agent
137
+
138
+ let parsedBody: unknown
139
+ try {
140
+ parsedBody = await req.json()
141
+ } catch {
142
+ return jsonError(400, 'Request body must be valid JSON.', 'validation_error')
143
+ }
144
+
145
+ const bodyResult = chatRequestSchema.safeParse(parsedBody)
146
+ if (!bodyResult.success) {
147
+ return jsonError(400, 'Invalid request body.', 'validation_error', {
148
+ issues: bodyResult.error.issues,
149
+ })
150
+ }
151
+
152
+ try {
153
+ await loadAgentRegistry()
154
+
155
+ const container = await createRequestContainer()
156
+ const rbacService = container.resolve<RbacService>('rbacService')
157
+ const acl = await rbacService.loadAcl(auth.sub, {
158
+ tenantId: auth.tenantId,
159
+ organizationId: auth.orgId,
160
+ })
161
+
162
+ const decision = checkAgentPolicy({
163
+ agentId,
164
+ authContext: {
165
+ userFeatures: acl.features,
166
+ isSuperAdmin: acl.isSuperAdmin,
167
+ },
168
+ requestedExecutionMode: 'chat',
169
+ // TODO(step-3.7): resolve attachmentIds -> media types via attachment-bridge
170
+ // once the attachment-to-model conversion bridge lands. Until then the
171
+ // policy gate skips attachment-type validation because media types are
172
+ // not known at dispatch time.
173
+ attachmentMediaTypes: undefined,
174
+ })
175
+
176
+ if (!decision.ok) {
177
+ return jsonError(statusForDenyCode(decision.code), decision.message, decision.code)
178
+ }
179
+
180
+ return await runAiAgentText({
181
+ agentId,
182
+ messages: bodyResult.data.messages as unknown as UIMessage[],
183
+ attachmentIds: bodyResult.data.attachmentIds,
184
+ pageContext: bodyResult.data.pageContext,
185
+ debug: bodyResult.data.debug,
186
+ conversationId: bodyResult.data.conversationId ?? null,
187
+ authContext: {
188
+ tenantId: auth.tenantId ?? null,
189
+ organizationId: auth.orgId ?? null,
190
+ userId: auth.sub,
191
+ features: acl.features,
192
+ isSuperAdmin: acl.isSuperAdmin,
193
+ },
194
+ container,
195
+ })
196
+ } catch (error) {
197
+ if (error instanceof AgentPolicyError) {
198
+ return jsonError(statusForDenyCode(error.code), error.message, error.code)
199
+ }
200
+ console.error('[AI Chat Agent] Dispatch failure:', error)
201
+ return jsonError(
202
+ 500,
203
+ error instanceof Error ? error.message : 'Agent dispatch failed.',
204
+ 'internal_error',
205
+ )
206
+ }
207
+ }
@@ -0,0 +1,282 @@
1
+ import { z } from 'zod'
2
+ import type { AiAgentDefinition } from '../../../../lib/ai-agent-definition'
3
+ import type { AiToolDefinition } from '../../../../lib/types'
4
+ import {
5
+ resetAgentRegistryForTests,
6
+ seedAgentRegistryForTests,
7
+ } from '../../../../lib/agent-registry'
8
+ import { toolRegistry, registerMcpTool } from '../../../../lib/tool-registry'
9
+
10
+ const authMock = jest.fn()
11
+ const loadAclMock = jest.fn()
12
+ const createRequestContainerMock = jest.fn()
13
+ const runAiAgentObjectMock = jest.fn()
14
+
15
+ jest.mock('@open-mercato/shared/lib/auth/server', () => ({
16
+ getAuthFromRequest: (...args: unknown[]) => authMock(...args),
17
+ }))
18
+
19
+ jest.mock('@open-mercato/shared/lib/di/container', () => ({
20
+ createRequestContainer: (...args: unknown[]) => createRequestContainerMock(...args),
21
+ }))
22
+
23
+ jest.mock('../../../../lib/agent-runtime', () => ({
24
+ runAiAgentObject: (...args: unknown[]) => runAiAgentObjectMock(...args),
25
+ }))
26
+
27
+ import { POST } from '../route'
28
+
29
+ function makeAgent(
30
+ overrides: Partial<AiAgentDefinition> & Pick<AiAgentDefinition, 'id' | 'moduleId'>,
31
+ ): AiAgentDefinition {
32
+ return {
33
+ label: `${overrides.id} label`,
34
+ description: `${overrides.id} description`,
35
+ systemPrompt: 'You are a test agent.',
36
+ allowedTools: [],
37
+ ...overrides,
38
+ }
39
+ }
40
+
41
+ function makeTool(
42
+ overrides: Partial<AiToolDefinition> & Pick<AiToolDefinition, 'name'>,
43
+ ): AiToolDefinition {
44
+ return {
45
+ description: `${overrides.name} description`,
46
+ inputSchema: z.object({}),
47
+ handler: async () => ({ ok: true }),
48
+ ...overrides,
49
+ }
50
+ }
51
+
52
+ function buildRequest(body: unknown): Request {
53
+ return new Request('http://localhost/api/ai_assistant/ai/run-object', {
54
+ method: 'POST',
55
+ headers: { 'content-type': 'application/json' },
56
+ body: JSON.stringify(body),
57
+ })
58
+ }
59
+
60
+ describe('POST /api/ai_assistant/ai/run-object', () => {
61
+ let consoleErrorSpy: jest.SpyInstance
62
+
63
+ beforeEach(() => {
64
+ jest.clearAllMocks()
65
+ resetAgentRegistryForTests()
66
+ toolRegistry.clear()
67
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
68
+ authMock.mockResolvedValue({
69
+ sub: 'user-1',
70
+ tenantId: 'tenant-1',
71
+ orgId: 'org-1',
72
+ })
73
+ loadAclMock.mockResolvedValue({ features: ['ai_assistant.view'], isSuperAdmin: false })
74
+ createRequestContainerMock.mockResolvedValue({
75
+ resolve: (name: string) => {
76
+ if (name === 'rbacService') return { loadAcl: loadAclMock }
77
+ return null
78
+ },
79
+ })
80
+ runAiAgentObjectMock.mockResolvedValue({
81
+ mode: 'generate',
82
+ object: { title: 'hello' },
83
+ finishReason: 'stop',
84
+ usage: { inputTokens: 10, outputTokens: 20 },
85
+ })
86
+ })
87
+
88
+ afterEach(() => {
89
+ consoleErrorSpy.mockRestore()
90
+ })
91
+
92
+ afterAll(() => {
93
+ resetAgentRegistryForTests()
94
+ toolRegistry.clear()
95
+ })
96
+
97
+ it('returns 401 when unauthenticated', async () => {
98
+ authMock.mockResolvedValueOnce(null)
99
+
100
+ const response = await POST(
101
+ buildRequest({
102
+ agent: 'catalog.extract',
103
+ messages: [{ role: 'user', content: 'hi' }],
104
+ }) as any,
105
+ )
106
+
107
+ expect(response.status).toBe(401)
108
+ const json = await response.json()
109
+ expect(json.code).toBe('unauthenticated')
110
+ })
111
+
112
+ it('returns 400 when the body fails zod validation (missing messages)', async () => {
113
+ seedAgentRegistryForTests([
114
+ makeAgent({
115
+ id: 'catalog.extract',
116
+ moduleId: 'catalog',
117
+ executionMode: 'object',
118
+ output: { schemaName: 'Extract', schema: z.object({ title: z.string() }) },
119
+ }),
120
+ ])
121
+
122
+ const response = await POST(buildRequest({ agent: 'catalog.extract' }) as any)
123
+
124
+ expect(response.status).toBe(400)
125
+ const json = await response.json()
126
+ expect(json.code).toBe('validation_error')
127
+ })
128
+
129
+ it('returns 404 for an unknown agent', async () => {
130
+ const response = await POST(
131
+ buildRequest({
132
+ agent: 'catalog.missing',
133
+ messages: [{ role: 'user', content: 'hi' }],
134
+ }) as any,
135
+ )
136
+
137
+ expect(response.status).toBe(404)
138
+ const json = await response.json()
139
+ expect(json.code).toBe('agent_unknown')
140
+ })
141
+
142
+ it('returns 403 when the agent requires features the user lacks', async () => {
143
+ seedAgentRegistryForTests([
144
+ makeAgent({
145
+ id: 'catalog.extract',
146
+ moduleId: 'catalog',
147
+ executionMode: 'object',
148
+ requiredFeatures: ['catalog.extract.use'],
149
+ output: { schemaName: 'Extract', schema: z.object({ title: z.string() }) },
150
+ }),
151
+ ])
152
+
153
+ const response = await POST(
154
+ buildRequest({
155
+ agent: 'catalog.extract',
156
+ messages: [{ role: 'user', content: 'hi' }],
157
+ }) as any,
158
+ )
159
+
160
+ expect(response.status).toBe(403)
161
+ const json = await response.json()
162
+ expect(json.code).toBe('agent_features_denied')
163
+ })
164
+
165
+ it('returns 422 when a chat-mode agent is invoked via run-object', async () => {
166
+ seedAgentRegistryForTests([
167
+ makeAgent({
168
+ id: 'customers.assistant',
169
+ moduleId: 'customers',
170
+ }),
171
+ ])
172
+
173
+ const response = await POST(
174
+ buildRequest({
175
+ agent: 'customers.assistant',
176
+ messages: [{ role: 'user', content: 'hi' }],
177
+ }) as any,
178
+ )
179
+
180
+ expect(response.status).toBe(422)
181
+ const json = await response.json()
182
+ expect(json.code).toBe('execution_mode_not_supported')
183
+ })
184
+
185
+ it('returns 200 and delegates to runAiAgentObject on success', async () => {
186
+ registerMcpTool(
187
+ makeTool({ name: 'catalog.read_product', requiredFeatures: ['catalog.products.view'] }),
188
+ { moduleId: 'catalog' },
189
+ )
190
+ seedAgentRegistryForTests([
191
+ makeAgent({
192
+ id: 'catalog.extract',
193
+ moduleId: 'catalog',
194
+ executionMode: 'object',
195
+ allowedTools: ['catalog.read_product'],
196
+ output: { schemaName: 'Extract', schema: z.object({ title: z.string() }) },
197
+ }),
198
+ ])
199
+
200
+ const response = await POST(
201
+ buildRequest({
202
+ agent: 'catalog.extract',
203
+ messages: [{ role: 'user', content: 'Generate a product title' }],
204
+ pageContext: { pageId: 'ai_assistant.playground' },
205
+ }) as any,
206
+ )
207
+
208
+ expect(response.status).toBe(200)
209
+ const json = await response.json()
210
+ expect(json).toEqual({
211
+ object: { title: 'hello' },
212
+ finishReason: 'stop',
213
+ usage: { inputTokens: 10, outputTokens: 20 },
214
+ })
215
+ expect(runAiAgentObjectMock).toHaveBeenCalledTimes(1)
216
+ const callArg = runAiAgentObjectMock.mock.calls[0][0] as {
217
+ agentId: string
218
+ input: unknown
219
+ pageContext?: { pageId?: string }
220
+ authContext: { userId: string; tenantId: string | null; organizationId: string | null }
221
+ }
222
+ expect(callArg.agentId).toBe('catalog.extract')
223
+ expect(callArg.pageContext).toEqual({ pageId: 'ai_assistant.playground' })
224
+ expect(callArg.authContext.userId).toBe('user-1')
225
+ expect(callArg.authContext.tenantId).toBe('tenant-1')
226
+ expect(callArg.authContext.organizationId).toBe('org-1')
227
+ })
228
+
229
+ it('returns 422 when the helper resolves to stream mode', async () => {
230
+ seedAgentRegistryForTests([
231
+ makeAgent({
232
+ id: 'catalog.extract',
233
+ moduleId: 'catalog',
234
+ executionMode: 'object',
235
+ output: { schemaName: 'Extract', schema: z.object({ title: z.string() }) },
236
+ }),
237
+ ])
238
+ runAiAgentObjectMock.mockResolvedValueOnce({
239
+ mode: 'stream',
240
+ object: Promise.resolve({ title: 'hello' }),
241
+ partialObjectStream: (async function* () {})(),
242
+ textStream: (async function* () {})(),
243
+ })
244
+
245
+ const response = await POST(
246
+ buildRequest({
247
+ agent: 'catalog.extract',
248
+ messages: [{ role: 'user', content: 'hi' }],
249
+ }) as any,
250
+ )
251
+
252
+ expect(response.status).toBe(422)
253
+ const json = await response.json()
254
+ expect(json.code).toBe('execution_mode_not_supported')
255
+ })
256
+
257
+ it('maps AgentPolicyError thrown by the runtime to the canonical HTTP status', async () => {
258
+ const { AgentPolicyError } = await import('../../../../lib/agent-tools')
259
+ seedAgentRegistryForTests([
260
+ makeAgent({
261
+ id: 'catalog.extract',
262
+ moduleId: 'catalog',
263
+ executionMode: 'object',
264
+ output: { schemaName: 'Extract', schema: z.object({ title: z.string() }) },
265
+ }),
266
+ ])
267
+ runAiAgentObjectMock.mockRejectedValueOnce(
268
+ new AgentPolicyError('tool_not_whitelisted', 'Tool not whitelisted'),
269
+ )
270
+
271
+ const response = await POST(
272
+ buildRequest({
273
+ agent: 'catalog.extract',
274
+ messages: [{ role: 'user', content: 'hi' }],
275
+ }) as any,
276
+ )
277
+
278
+ expect(response.status).toBe(409)
279
+ const json = await response.json()
280
+ expect(json.code).toBe('tool_not_whitelisted')
281
+ })
282
+ })
@@ -0,0 +1,204 @@
1
+ import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { UIMessage } from 'ai'
3
+ import { z } from 'zod'
4
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
5
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
7
+ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
8
+ import { loadAgentRegistry } from '../../../lib/agent-registry'
9
+ import { checkAgentPolicy, type AgentPolicyDenyCode } from '../../../lib/agent-policy'
10
+ import { runAiAgentObject } from '../../../lib/agent-runtime'
11
+ import { AgentPolicyError } from '../../../lib/agent-tools'
12
+
13
+ const MAX_MESSAGES = 100
14
+
15
+ const agentIdPattern = /^[a-z0-9_]+\.[a-z0-9_]+$/
16
+
17
+ const agentIdSchema = z
18
+ .string()
19
+ .regex(agentIdPattern, 'agent must match "<module>.<agent>" (lowercase, digits, underscores only)')
20
+
21
+ const chatMessageSchema = z.object({
22
+ role: z.enum(['user', 'assistant', 'system']),
23
+ content: z.string(),
24
+ })
25
+
26
+ const pageContextSchema = z
27
+ .object({
28
+ pageId: z.string().optional(),
29
+ entityType: z.string().optional(),
30
+ recordId: z.string().optional(),
31
+ })
32
+ .passthrough()
33
+
34
+ const runObjectRequestSchema = z.object({
35
+ agent: agentIdSchema,
36
+ messages: z
37
+ .array(chatMessageSchema)
38
+ .min(1, 'messages must contain at least one message')
39
+ .max(MAX_MESSAGES, `messages must contain at most ${MAX_MESSAGES} entries`),
40
+ attachmentIds: z.array(z.string()).optional(),
41
+ pageContext: pageContextSchema.optional(),
42
+ modelOverride: z.string().optional(),
43
+ })
44
+
45
+ export type AiRunObjectRequest = z.infer<typeof runObjectRequestSchema>
46
+
47
+ export const openApi: OpenApiRouteDoc = {
48
+ tag: 'AI Assistant',
49
+ summary: 'Run an AI agent in structured-output (object) mode',
50
+ methods: {
51
+ POST: {
52
+ operationId: 'aiAssistantRunObject',
53
+ summary: 'Run an object-mode AI agent and return the generated object',
54
+ description:
55
+ 'Invokes `runAiAgentObject` server-side for the registered AI agent identified by `agent` ' +
56
+ '(matching "<module>.<agent>"). Enforces the same `requiredFeatures`, tool whitelisting, ' +
57
+ 'mutationPolicy, and attachment media-type policy as the chat dispatcher, but additionally ' +
58
+ 'requires the agent to declare `executionMode: "object"`. Returns the generated object in ' +
59
+ 'a single JSON response (no streaming).',
60
+ requestBody: {
61
+ contentType: 'application/json',
62
+ description:
63
+ 'Object-mode dispatch payload. `agent` and `messages` are required; `attachmentIds`, `pageContext`, and `modelOverride` are optional.',
64
+ schema: runObjectRequestSchema,
65
+ },
66
+ responses: [
67
+ {
68
+ status: 200,
69
+ description: 'Object-mode run completed; response body contains `{ object, usage?, finishReason? }`.',
70
+ mediaType: 'application/json',
71
+ },
72
+ ],
73
+ errors: [
74
+ { status: 400, description: 'Malformed payload or message cap exceeded.' },
75
+ { status: 401, description: 'Unauthenticated caller.' },
76
+ { status: 403, description: 'Caller lacks agent-level or tool-level required features.' },
77
+ { status: 404, description: 'Unknown agent id.' },
78
+ { status: 409, description: 'Agent/tool/execution-mode policy violation.' },
79
+ { status: 422, description: 'Agent does not support object-mode execution.' },
80
+ { status: 500, description: 'Internal runtime failure.' },
81
+ ],
82
+ },
83
+ },
84
+ }
85
+
86
+ export const metadata = {
87
+ POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
88
+ }
89
+
90
+ function jsonError(
91
+ status: number,
92
+ message: string,
93
+ code: string,
94
+ extra?: Record<string, unknown>,
95
+ ): NextResponse {
96
+ return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })
97
+ }
98
+
99
+ function statusForDenyCode(code: AgentPolicyDenyCode): number {
100
+ switch (code) {
101
+ case 'agent_unknown':
102
+ return 404
103
+ case 'agent_features_denied':
104
+ case 'tool_features_denied':
105
+ return 403
106
+ case 'execution_mode_not_supported':
107
+ return 422
108
+ case 'tool_not_whitelisted':
109
+ case 'tool_unknown':
110
+ case 'mutation_blocked_by_readonly':
111
+ case 'mutation_blocked_by_policy':
112
+ return 409
113
+ case 'attachment_type_not_accepted':
114
+ return 400
115
+ default:
116
+ return 409
117
+ }
118
+ }
119
+
120
+ export async function POST(req: NextRequest): Promise<Response> {
121
+ const auth = await getAuthFromRequest(req)
122
+ if (!auth) {
123
+ return jsonError(401, 'Unauthorized', 'unauthenticated')
124
+ }
125
+
126
+ let parsedBody: unknown
127
+ try {
128
+ parsedBody = await req.json()
129
+ } catch {
130
+ return jsonError(400, 'Request body must be valid JSON.', 'validation_error')
131
+ }
132
+
133
+ const bodyResult = runObjectRequestSchema.safeParse(parsedBody)
134
+ if (!bodyResult.success) {
135
+ return jsonError(400, 'Invalid request body.', 'validation_error', {
136
+ issues: bodyResult.error.issues,
137
+ })
138
+ }
139
+
140
+ try {
141
+ await loadAgentRegistry()
142
+
143
+ const container = await createRequestContainer()
144
+ const rbacService = container.resolve<RbacService>('rbacService')
145
+ const acl = await rbacService.loadAcl(auth.sub, {
146
+ tenantId: auth.tenantId,
147
+ organizationId: auth.orgId,
148
+ })
149
+
150
+ const decision = checkAgentPolicy({
151
+ agentId: bodyResult.data.agent,
152
+ authContext: {
153
+ userFeatures: acl.features,
154
+ isSuperAdmin: acl.isSuperAdmin,
155
+ },
156
+ requestedExecutionMode: 'object',
157
+ attachmentMediaTypes: undefined,
158
+ })
159
+
160
+ if (!decision.ok) {
161
+ return jsonError(statusForDenyCode(decision.code), decision.message, decision.code)
162
+ }
163
+
164
+ const result = await runAiAgentObject({
165
+ agentId: bodyResult.data.agent,
166
+ input: bodyResult.data.messages as unknown as UIMessage[],
167
+ attachmentIds: bodyResult.data.attachmentIds,
168
+ pageContext: bodyResult.data.pageContext,
169
+ modelOverride: bodyResult.data.modelOverride,
170
+ authContext: {
171
+ tenantId: auth.tenantId ?? null,
172
+ organizationId: auth.orgId ?? null,
173
+ userId: auth.sub,
174
+ features: acl.features,
175
+ isSuperAdmin: acl.isSuperAdmin,
176
+ },
177
+ container,
178
+ })
179
+
180
+ if (result.mode !== 'generate') {
181
+ return jsonError(
182
+ 422,
183
+ 'Streaming object-mode is not supported by the run-object HTTP route; agent must use generate mode.',
184
+ 'execution_mode_not_supported',
185
+ )
186
+ }
187
+
188
+ return NextResponse.json({
189
+ object: result.object,
190
+ finishReason: result.finishReason,
191
+ usage: result.usage,
192
+ })
193
+ } catch (error) {
194
+ if (error instanceof AgentPolicyError) {
195
+ return jsonError(statusForDenyCode(error.code), error.message, error.code)
196
+ }
197
+ console.error('[AI Run Object] Dispatch failure:', error)
198
+ return jsonError(
199
+ 500,
200
+ error instanceof Error ? error.message : 'Agent object dispatch failed.',
201
+ 'internal_error',
202
+ )
203
+ }
204
+ }