@k2b/cloud 0.6.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,308 @@
1
+ import { lazySync } from "../_internal/process-sync";
2
+ import { logger } from "../services/logging";
3
+ import { latestTopicCursor } from "../services/topic-cursor";
4
+ import {
5
+ type AiStreamEvent,
6
+ type AiTurnSnapshot,
7
+ type AiWireEvent,
8
+ isNewerWireEvent,
9
+ reconcileResolvedTurnActions,
10
+ steerAppliedBlockId,
11
+ steerMessageBlockId,
12
+ } from "./protocol";
13
+ import { projectPublicAiStoredMessages, publicAiStoredMessages } from "./public-projection";
14
+ import { aiConversations } from "./store";
15
+ import type { AiConversation } from "./types";
16
+
17
+ const log = logger("ai:stream");
18
+
19
+ /**
20
+ * Live fanout for wire events. Events carry their full payload so the SSE hot
21
+ * path never touches Postgres; durable state lives in ai.messages plus the
22
+ * throttled ai.turns.live_blocks snapshot.
23
+ */
24
+ export const aiStreamTopic = lazySync((sync) =>
25
+ sync.topic<AiWireEvent>({
26
+ id: "cloud-ai-stream",
27
+ owner: "cloud",
28
+ retention: { maxAgeMs: 15 * 60 * 1000, maxBytes: 256 * 1024 * 1024 },
29
+ maxPayloadBytes: 257 * 1024,
30
+ }),
31
+ );
32
+
33
+ export type AiTurnControlEvent = { type: "abort"; conversationId: string; turnId: string };
34
+
35
+ export const aiTurnControlsTopic = lazySync((sync) =>
36
+ sync.topic<AiTurnControlEvent>({
37
+ id: "cloud-ai-turn-controls",
38
+ owner: "cloud",
39
+ // Abort signals are rare and tiny (5 KiB cap): 4 MiB keeps ~800 of them
40
+ // across the 15-minute window.
41
+ retention: { maxAgeMs: 15 * 60 * 1000, maxBytes: 4 * 1024 * 1024 },
42
+ maxPayloadBytes: 5 * 1024,
43
+ }),
44
+ );
45
+
46
+ export const publishAiWireEvent = async (event: AiWireEvent): Promise<void> => {
47
+ await aiStreamTopic().publish({
48
+ tenantId: event.conversationId,
49
+ orderingKey: event.turnId,
50
+ data: event,
51
+ idempotencyKey: `wire:${event.turnId}:${event.attempt}:${event.seq}`,
52
+ });
53
+ };
54
+
55
+ export const publishAiTurnAbort = async (input: { conversationId: string; turnId: string }): Promise<void> => {
56
+ await aiTurnControlsTopic().publish({
57
+ tenantId: input.conversationId,
58
+ orderingKey: input.turnId,
59
+ data: { type: "abort", conversationId: input.conversationId, turnId: input.turnId },
60
+ idempotencyKey: `turn-abort:${input.turnId}`,
61
+ });
62
+ };
63
+
64
+ const encoder = new TextEncoder();
65
+ const DEFAULT_HEARTBEAT_MS = 5_000;
66
+
67
+ export const sseHeaders = {
68
+ "Content-Type": "text/event-stream; charset=utf-8",
69
+ "Cache-Control": "no-cache, no-transform",
70
+ Connection: "keep-alive",
71
+ "X-Accel-Buffering": "no",
72
+ } as const;
73
+
74
+ export const encodeSseEvent = (event: AiStreamEvent): Uint8Array =>
75
+ encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
76
+
77
+ export const encodeSseHeartbeat = (): Uint8Array => encoder.encode(": heartbeat\n\n");
78
+
79
+ const turnSnapshotFromActive = (active: NonNullable<Awaited<ReturnType<typeof aiConversations.getActiveTurn>>>): AiTurnSnapshot => ({
80
+ turnId: active.turn.shortId,
81
+ attempt: active.turn.attempt,
82
+ status: active.turn.status,
83
+ seq: active.liveSeq,
84
+ blocks: [...active.liveBlocks],
85
+ modelProfileId: active.turn.modelProfileId,
86
+ createdAt: active.turn.createdAt,
87
+ });
88
+
89
+ /** Initial history window; older messages load on demand while scrolling up. */
90
+ export const AI_STREAM_INITIAL_MESSAGE_LIMIT = 100;
91
+
92
+ export const loadAiStreamState = async (conversation: AiConversation): Promise<Extract<AiStreamEvent, { type: "state" }>> => {
93
+ const [page, active] = await Promise.all([
94
+ aiConversations.listMessagesPage({ conversationId: conversation.id, limit: AI_STREAM_INITIAL_MESSAGE_LIMIT }),
95
+ aiConversations.getActiveTurn({ conversationId: conversation.id }),
96
+ ]);
97
+ const snapshot = active ? turnSnapshotFromActive(active) : null;
98
+ if (snapshot) {
99
+ const [steers, resolvedActions] = await Promise.all([
100
+ aiConversations.listTurnSteers({ conversationId: conversation.id, turnId: active!.turn.id }),
101
+ aiConversations.listResolvedPendingActions({ conversationId: conversation.id, turnId: active!.turn.id }),
102
+ ]);
103
+ snapshot.blocks = reconcileResolvedTurnActions(snapshot.blocks, resolvedActions);
104
+ const known = new Set(snapshot.blocks.map((block) => block.id));
105
+ for (const steer of steers) {
106
+ if (steer.status === "discarded" || known.has(steerMessageBlockId(steer.id))) continue;
107
+ snapshot.blocks.push({
108
+ id: steerMessageBlockId(steer.id),
109
+ kind: "steer_message",
110
+ steerId: steer.id,
111
+ text: steer.text,
112
+ status: steer.status === "pending" ? "pending" : "consumed",
113
+ });
114
+ if (steer.status === "consumed" && !known.has(steerAppliedBlockId(steer.id))) {
115
+ snapshot.blocks.push({ id: steerAppliedBlockId(steer.id), kind: "steer_applied", steerId: steer.id });
116
+ }
117
+ }
118
+ }
119
+ return {
120
+ type: "state",
121
+ conversation: { ...conversation, id: conversation.shortId },
122
+ messages: await publicAiStoredMessages(page.messages, conversation),
123
+ hasMoreMessages: page.hasMore,
124
+ activeTurn: snapshot,
125
+ };
126
+ };
127
+
128
+ type SnapshotTailItem<TSnapshot, TEvent> = { kind: "snapshot"; value: TSnapshot } | { kind: "event"; value: TEvent };
129
+
130
+ async function* streamSnapshotThenTail<TCursor, TSnapshot, TEvent>(input: {
131
+ captureCursor: () => Promise<TCursor>;
132
+ loadSnapshot: () => Promise<TSnapshot>;
133
+ tail: (cursor: TCursor) => AsyncIterable<TEvent>;
134
+ }): AsyncGenerator<SnapshotTailItem<TSnapshot, TEvent>> {
135
+ const cursor = await input.captureCursor();
136
+ yield { kind: "snapshot", value: await input.loadSnapshot() };
137
+ for await (const event of input.tail(cursor)) yield { kind: "event", value: event };
138
+ }
139
+
140
+ /**
141
+ * Transport-neutral conversation stream: one `state` snapshot, then the live
142
+ * tail. SSE and WebSocket adapters must both consume this feed.
143
+ *
144
+ * The topic cursor is grabbed before the snapshot is loaded, so every event
145
+ * that races the snapshot is replayed from the tail and deduplicated via
146
+ * (attempt, seq). Events of unknown turns are dropped until their
147
+ * `turn_started` arrives, which makes stale retention entries harmless.
148
+ * A memoized per-conversation hub preserves this snapshot race guarantee. While
149
+ * a conversation has subscribers it costs one full-topic follower per process
150
+ * (Sync filters tenants locally); the hub retires when the last subscriber
151
+ * leaves. live() has no subscription-ready barrier.
152
+ */
153
+ export async function* streamAiConversationEvents(input: {
154
+ conversation: AiConversation;
155
+ signal: AbortSignal;
156
+ }): AsyncGenerator<AiStreamEvent> {
157
+ let current: { turnId: string; publicTurnId: string; attempt: number; seq: number } | null = null;
158
+ for await (const item of streamSnapshotThenTail({
159
+ captureCursor: () => latestTopicCursor({ topic: aiStreamTopic(), resourceId: "cloud-ai-stream", tenantId: input.conversation.id }),
160
+ loadSnapshot: () => loadAiStreamState(input.conversation),
161
+ tail: (after) => aiStreamTopic().hub({ tenantId: input.conversation.id }).subscribe({ after, signal: input.signal }),
162
+ })) {
163
+ if (item.kind === "snapshot") {
164
+ const state = item.value;
165
+ yield state;
166
+ const activeTurn = state.activeTurn
167
+ ? await aiConversations.getTurnByShortId({
168
+ conversationId: input.conversation.id,
169
+ shortId: state.activeTurn.turnId,
170
+ })
171
+ : null;
172
+ current =
173
+ state.activeTurn && activeTurn
174
+ ? {
175
+ turnId: activeTurn.id,
176
+ publicTurnId: state.activeTurn.turnId,
177
+ attempt: state.activeTurn.attempt,
178
+ seq: state.activeTurn.seq,
179
+ }
180
+ : null;
181
+ continue;
182
+ }
183
+
184
+ const received = item.value;
185
+ const event = received.data;
186
+ if (current?.turnId === event.turnId) {
187
+ if (!isNewerWireEvent(event, current)) continue;
188
+ } else if (event.type !== "turn_started") {
189
+ continue;
190
+ }
191
+ const publicTurnId =
192
+ current?.turnId === event.turnId
193
+ ? current.publicTurnId
194
+ : (await aiConversations.getTurn({ conversationId: input.conversation.id, turnId: event.turnId }))?.shortId;
195
+ if (!publicTurnId) continue;
196
+ current = { turnId: event.turnId, publicTurnId, attempt: event.attempt, seq: event.seq };
197
+ if (event.type === "turn_finished") {
198
+ const messages = await aiConversations
199
+ .listTurnMessages({ conversationId: event.conversationId, loopId: event.turnId })
200
+ .catch(() => []);
201
+ yield {
202
+ ...event,
203
+ conversationId: input.conversation.shortId,
204
+ turnId: publicTurnId,
205
+ messages: projectPublicAiStoredMessages(messages, input.conversation.shortId, new Map([[event.turnId, publicTurnId]])),
206
+ };
207
+ continue;
208
+ }
209
+
210
+ yield { ...event, conversationId: input.conversation.shortId, turnId: publicTurnId };
211
+ }
212
+ }
213
+
214
+ export const __aiStreamTest = { streamSnapshotThenTail };
215
+
216
+ /** Conversation-scoped SSE adapter for the shared event feed. */
217
+ export const createAiConversationStreamResponse = (input: {
218
+ conversation: AiConversation;
219
+ signal?: AbortSignal;
220
+ heartbeatMs?: number;
221
+ /**
222
+ * Re-checked on every heartbeat. A stream can outlive the grant that opened
223
+ * it by hours, and authorizing once at connect time means a withdrawn grant
224
+ * keeps delivering model output until the client happens to disconnect.
225
+ * Returning false closes the stream; the client's reconnect then meets the
226
+ * ordinary 403.
227
+ */
228
+ revalidate?: () => Promise<boolean>;
229
+ }): Response => {
230
+ const heartbeatMs = input.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
231
+ const liveAbort = new AbortController();
232
+ let heartbeat: ReturnType<typeof setInterval> | undefined;
233
+ let closed = false;
234
+
235
+ const abortLive = () => liveAbort.abort();
236
+ if (input.signal?.aborted) abortLive();
237
+ else input.signal?.addEventListener("abort", abortLive, { once: true });
238
+
239
+ const stream = new ReadableStream<Uint8Array>({
240
+ async start(controller) {
241
+ const enqueue = (chunk: Uint8Array): boolean => {
242
+ if (closed) return false;
243
+ try {
244
+ controller.enqueue(chunk);
245
+ return true;
246
+ } catch {
247
+ close();
248
+ return false;
249
+ }
250
+ };
251
+ const close = () => {
252
+ if (closed) return;
253
+ closed = true;
254
+ if (heartbeat) clearInterval(heartbeat);
255
+ heartbeat = undefined;
256
+ input.signal?.removeEventListener("abort", abortLive);
257
+ abortLive();
258
+ try {
259
+ controller.close();
260
+ } catch {
261
+ // The client may already have cancelled the stream.
262
+ }
263
+ };
264
+
265
+ if (heartbeatMs > 0) {
266
+ heartbeat = setInterval(() => {
267
+ if (!enqueue(encodeSseHeartbeat())) return;
268
+ if (!input.revalidate) return;
269
+ // Fail closed: a revalidation that throws is not a pass.
270
+ void input
271
+ .revalidate()
272
+ .catch((error) => {
273
+ log.warn("AI conversation stream revalidation failed", {
274
+ conversationId: input.conversation.id,
275
+ error: error instanceof Error ? error.message : "revalidation failed",
276
+ });
277
+ return false;
278
+ })
279
+ .then((allowed) => {
280
+ if (!allowed) close();
281
+ });
282
+ }, heartbeatMs);
283
+ }
284
+
285
+ try {
286
+ for await (const event of streamAiConversationEvents({ conversation: input.conversation, signal: liveAbort.signal })) {
287
+ if (!enqueue(encodeSseEvent(event))) return;
288
+ }
289
+ } catch (error) {
290
+ if (!liveAbort.signal.aborted) {
291
+ log.warn("AI conversation stream failed", {
292
+ conversationId: input.conversation.id,
293
+ error: error instanceof Error ? error.message : "AI conversation stream failed",
294
+ });
295
+ }
296
+ } finally {
297
+ close();
298
+ }
299
+ },
300
+ cancel() {
301
+ closed = true;
302
+ if (heartbeat) clearInterval(heartbeat);
303
+ abortLive();
304
+ },
305
+ });
306
+
307
+ return new Response(stream, { headers: sseHeaders });
308
+ };
@@ -0,0 +1,66 @@
1
+ import type { Usage } from "@k2b/nessi";
2
+ import { sql } from "bun";
3
+ import { logger } from "../services/logging";
4
+
5
+ export type AiUsageAttribution = {
6
+ userId?: string;
7
+ conversationId?: string;
8
+ turnId?: string;
9
+ workflowRunId?: string;
10
+ };
11
+
12
+ export type AiStructuredRunRecord = {
13
+ attribution?: AiUsageAttribution;
14
+ traceId?: string;
15
+ task: string;
16
+ appId?: string;
17
+ modelProfileId?: string;
18
+ providerModel?: string;
19
+ status: "ok" | "failed";
20
+ durationMs: number;
21
+ usage?: Usage;
22
+ mode?: string;
23
+ repaired?: boolean;
24
+ attempts?: number;
25
+ errorCode?: string;
26
+ error?: string;
27
+ };
28
+
29
+ const usageNumber = (usage: Usage | undefined, key: "input" | "output" | "total" | "creditsUsed") => {
30
+ const value = (usage as Partial<Record<typeof key, unknown>> | undefined)?.[key];
31
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
32
+ };
33
+
34
+ /** Metadata-only durable accounting. Prompt, input, and output content never enter this ledger. */
35
+ export const recordAiStructuredRun = async (record: AiStructuredRunRecord): Promise<void> => {
36
+ await sql`
37
+ INSERT INTO ai.structured_runs (
38
+ task, app_id, model_profile_id, provider_model, status, duration_ms,
39
+ input_tokens, output_tokens, total_tokens, credits_used,
40
+ mode, repaired, attempts, error_code, error, user_id, conversation_id, turn_id, workflow_run_id, trace_id
41
+ ) VALUES (
42
+ ${record.task}, ${record.appId ?? null}, ${record.modelProfileId ?? null}, ${record.providerModel ?? null},
43
+ ${record.status}, ${Math.max(0, Math.round(record.durationMs))},
44
+ ${usageNumber(record.usage, "input")}, ${usageNumber(record.usage, "output")},
45
+ ${usageNumber(record.usage, "total")}, ${usageNumber(record.usage, "creditsUsed")},
46
+ ${record.mode ?? null}, ${record.repaired ?? null}, ${record.attempts ?? null},
47
+ ${record.errorCode ?? null}, ${record.error?.slice(0, 2_000) ?? null},
48
+ COALESCE(${record.attribution?.userId ?? null}::uuid,
49
+ (SELECT created_by_user_id FROM ai.conversations WHERE id = ${record.attribution?.conversationId ?? null}::uuid)),
50
+ ${record.attribution?.conversationId ?? null}::uuid, ${record.attribution?.turnId ?? null}::uuid,
51
+ ${record.attribution?.workflowRunId ?? null}::uuid, ${record.traceId ?? null}
52
+ )
53
+ `;
54
+ };
55
+
56
+ /** Accounting failure must not turn a completed inference into a retry. */
57
+ export const safelyRecordStructuredRun = async (record: AiStructuredRunRecord): Promise<void> => {
58
+ try {
59
+ await recordAiStructuredRun(record);
60
+ } catch (error) {
61
+ logger("ai.structured").error("Could not persist AI structured-run usage", {
62
+ task: record.task,
63
+ error: error instanceof Error ? error.message : String(error),
64
+ });
65
+ }
66
+ };
@@ -0,0 +1,186 @@
1
+ import type { Input, LoopAggregate, StructuredMeta, Usage } from "@k2b/nessi";
2
+ import { nessi, StructuredOutputError } from "@k2b/nessi";
3
+ import type { z } from "zod";
4
+ import { coreSettings } from "../services";
5
+ import type { TraceContext } from "../services/logging";
6
+ import { trace } from "../services/logging";
7
+ import { resolveAiModel } from "./settings";
8
+ import { type AiUsageAttribution, safelyRecordStructuredRun } from "./structured-runs";
9
+ import type { AiResolvedModel } from "./types";
10
+
11
+ export const AI_BACKGROUND_MODEL_SETTING_KEY = "ai.background_model_id";
12
+ export const AI_WORKFLOW_MODEL_SETTING_KEY = "ai.workflow_model_id";
13
+
14
+ export const selectAiWorkflowModelId = (input: {
15
+ requestedModelId?: string;
16
+ workflowModelId?: string;
17
+ backgroundModelId?: string;
18
+ }): string | undefined => input.requestedModelId?.trim() || input.workflowModelId?.trim() || input.backgroundModelId?.trim() || undefined;
19
+
20
+ /**
21
+ * Resolve the model for background inference: explicit request →
22
+ * `ai.background_model_id` setting → platform default. Throws when AI is
23
+ * disabled or the model is unavailable — callers skip their work then.
24
+ */
25
+ export const resolveAiBackgroundModel = async (requestedModelId?: string): Promise<AiResolvedModel> => {
26
+ const backgroundModelId = String((await coreSettings.get<string>(AI_BACKGROUND_MODEL_SETTING_KEY)) ?? "").trim();
27
+ return resolveAiModel({ kind: "selectable" }, requestedModelId?.trim() || backgroundModelId || undefined);
28
+ };
29
+
30
+ /** Resolve a workflow model: action override → workflow setting → background setting → platform default. */
31
+ export const resolveAiWorkflowModel = async (requestedModelId?: string): Promise<AiResolvedModel> => {
32
+ const [workflowModelId, backgroundModelId] = await Promise.all([
33
+ coreSettings.get<string>(AI_WORKFLOW_MODEL_SETTING_KEY),
34
+ coreSettings.get<string>(AI_BACKGROUND_MODEL_SETTING_KEY),
35
+ ]);
36
+ return resolveAiModel(
37
+ { kind: "selectable" },
38
+ selectAiWorkflowModelId({
39
+ requestedModelId,
40
+ workflowModelId: String(workflowModelId ?? ""),
41
+ backgroundModelId: String(backgroundModelId ?? ""),
42
+ }),
43
+ );
44
+ };
45
+
46
+ export type RunAiStructuredInput<TOutput extends z.ZodType> = {
47
+ /** Short machine name for tracing, e.g. "chat-enrich". */
48
+ task: string;
49
+ input: Input;
50
+ output: TOutput;
51
+ outputName?: string;
52
+ systemPrompt?: string;
53
+ requestedModelId?: string;
54
+ temperature?: number;
55
+ maxOutputTokens?: number;
56
+ signal?: AbortSignal;
57
+ /** Parent trace span when the caller already runs inside one (e.g. a sync job). */
58
+ traceParent?: TraceContext;
59
+ appId?: string;
60
+ /** Metadata-only attribution, supplied by the authorized caller. */
61
+ attribution?: AiUsageAttribution;
62
+ /** Model resolution seam — tests inject a fake so they never touch shared settings. */
63
+ resolveModel?: (requestedModelId?: string) => Promise<AiResolvedModel>;
64
+ };
65
+
66
+ export type RunAiStructuredResult<TOutput extends z.ZodType> = {
67
+ output: z.infer<TOutput>;
68
+ modelProfileId: string;
69
+ usage?: Usage;
70
+ structuredMeta: StructuredMeta;
71
+ };
72
+
73
+ /**
74
+ * One schema-valid background inference via nessi.structured, wrapped in a
75
+ * trace span (events: model.resolved, llm.completed — metadata only, never
76
+ * prompt or output content).
77
+ */
78
+ export const runAiStructured = async <TOutput extends z.ZodType>(
79
+ input: RunAiStructuredInput<TOutput>,
80
+ ): Promise<RunAiStructuredResult<TOutput>> => {
81
+ return trace.withSpan(
82
+ {
83
+ name: `ai.structured.${input.task}`,
84
+ source: `ai:structured:${input.task}`,
85
+ appId: input.appId,
86
+ category: "ai",
87
+ parent: input.traceParent,
88
+ },
89
+ async (span) => {
90
+ const startedAt = Date.now();
91
+ let resolved: AiResolvedModel | undefined;
92
+ try {
93
+ resolved = await (input.resolveModel ?? resolveAiBackgroundModel)(input.requestedModelId);
94
+ await trace.record({
95
+ context: span,
96
+ event: "model.resolved",
97
+ attributes: { model: resolved.profile.id, providerModel: resolved.profile.model, provider: resolved.profile.provider },
98
+ });
99
+
100
+ const result = await nessi.structured({
101
+ agentId: "cloud-bg",
102
+ provider: resolved.provider,
103
+ systemPrompt: input.systemPrompt,
104
+ input: input.input,
105
+ output: input.output,
106
+ outputName: input.outputName,
107
+ temperature: input.temperature ?? 0,
108
+ maxOutputTokens: input.maxOutputTokens ?? resolved.profile.maxOutputTokens,
109
+ disableReasoning: true,
110
+ signal: input.signal,
111
+ });
112
+ const durationMs = Date.now() - startedAt;
113
+
114
+ await trace.record({
115
+ context: span,
116
+ event: "llm.completed",
117
+ attributes: {
118
+ model: resolved.profile.id,
119
+ durationMs,
120
+ mode: result.structuredMeta.mode,
121
+ repaired: result.structuredMeta.repaired,
122
+ attempts: result.structuredMeta.attempts,
123
+ inputTokens: result.usage?.input,
124
+ outputTokens: result.usage?.output,
125
+ },
126
+ });
127
+ await safelyRecordStructuredRun({
128
+ task: input.task,
129
+ attribution: input.attribution,
130
+ traceId: span.traceId,
131
+ appId: input.appId,
132
+ modelProfileId: resolved.profile.id,
133
+ providerModel: resolved.profile.model,
134
+ status: "ok",
135
+ durationMs,
136
+ usage: result.usage,
137
+ mode: result.structuredMeta.mode,
138
+ repaired: result.structuredMeta.repaired,
139
+ attempts: result.structuredMeta.attempts,
140
+ });
141
+
142
+ return {
143
+ output: result.output,
144
+ modelProfileId: resolved.profile.id,
145
+ usage: result.usage,
146
+ structuredMeta: result.structuredMeta,
147
+ };
148
+ } catch (error) {
149
+ const details =
150
+ error instanceof StructuredOutputError
151
+ ? (error.details as { attempts?: number; aggregate?: LoopAggregate } | undefined)
152
+ : undefined;
153
+ await safelyRecordStructuredRun({
154
+ task: input.task,
155
+ attribution: input.attribution,
156
+ traceId: span.traceId,
157
+ appId: input.appId,
158
+ modelProfileId: resolved?.profile.id,
159
+ providerModel: resolved?.profile.model,
160
+ status: "failed",
161
+ durationMs: Date.now() - startedAt,
162
+ usage: details?.aggregate?.usage,
163
+ attempts: details?.attempts,
164
+ errorCode: error instanceof StructuredOutputError ? error.code : error instanceof Error ? error.name : "unknown",
165
+ error: error instanceof Error ? error.message : String(error),
166
+ });
167
+ throw error;
168
+ }
169
+ },
170
+ {
171
+ summarize: (result) => ({ model: result.modelProfileId, mode: result.structuredMeta.mode, repaired: result.structuredMeta.repaired }),
172
+ onError: (error) => (error instanceof StructuredOutputError ? structuredFailureSummary(error) : undefined),
173
+ },
174
+ );
175
+ };
176
+
177
+ /** Metadata-only failure diagnostics: error code, attempts, and per-attempt stop reasons (catches max_tokens truncation). */
178
+ const structuredFailureSummary = (error: StructuredOutputError): Record<string, unknown> => {
179
+ const details = error.details as { attempts?: number; aggregate?: LoopAggregate } | undefined;
180
+ return {
181
+ code: error.code,
182
+ attempts: details?.attempts,
183
+ stopReasons: details?.aggregate?.turns?.map((turn) => turn.stopReason ?? "unknown").join(","),
184
+ outputTokens: details?.aggregate?.usage?.output,
185
+ };
186
+ };