@k2b/cloud 0.6.0 → 0.8.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 (349) hide show
  1. package/README.md +2 -2
  2. package/package.json +22 -11
  3. package/scripts/README.md +10 -0
  4. package/scripts/browser-performance.ts +14 -0
  5. package/scripts/build-pdf-renderer.ts +39 -0
  6. package/scripts/build.ts +20 -35
  7. package/scripts/preload.ts +16 -12
  8. package/scripts/sync-recovery-smoke.ts +22 -6
  9. package/src/_internal/app-presentation.ts +14 -2
  10. package/src/_internal/build-metadata.ts +8 -1
  11. package/src/_internal/capabilities.ts +131 -7
  12. package/src/_internal/capability-streams.ts +81 -0
  13. package/src/_internal/define-app.ts +80 -37
  14. package/src/_internal/heartbeat.ts +4 -0
  15. package/src/_internal/help-catalog.ts +27 -172
  16. package/src/_internal/help.ts +13 -76
  17. package/src/_internal/page-responses.ts +2 -0
  18. package/src/_internal/postgres-application-name.ts +30 -0
  19. package/src/_internal/process-identity.ts +15 -0
  20. package/src/_internal/process-sync.ts +1 -1
  21. package/src/_internal/readiness.ts +4 -1
  22. package/src/_internal/registry-validation.ts +29 -25
  23. package/src/_internal/registry.ts +2 -53
  24. package/src/_internal/runtime-context.ts +5 -16
  25. package/src/_internal/server-timing.ts +13 -0
  26. package/src/_internal/status-preserving-ssr.ts +18 -6
  27. package/src/_internal/web-vitals-asset.ts +10 -0
  28. package/src/access/PermissionEditor.tsx +18 -151
  29. package/src/access/PrincipalPicker.tsx +110 -0
  30. package/src/access/ResourceApiKeys.tsx +1 -1
  31. package/src/access/ui.ts +1 -0
  32. package/src/ai/admin.ts +14 -13
  33. package/src/ai/assistant-models.ts +12 -1
  34. package/src/ai/audio-format.ts +34 -0
  35. package/src/ai/audio-tool.ts +90 -0
  36. package/src/ai/browser-code-contracts.ts +201 -0
  37. package/src/ai/browser.ts +4 -0
  38. package/src/ai/capabilities.ts +55 -36
  39. package/src/ai/capability-execution.ts +47 -12
  40. package/src/ai/chat/blocks.tsx +229 -90
  41. package/src/ai/chat/builtin-tools.tsx +2 -4
  42. package/src/ai/chat/capability-result.ts +15 -0
  43. package/src/ai/chat/capability-table.tsx +2 -10
  44. package/src/ai/chat/composer-adapter.ts +83 -3
  45. package/src/ai/chat/message-actions.tsx +8 -7
  46. package/src/ai/chat/message-utils.ts +25 -1
  47. package/src/ai/chat/messages.ts +2 -0
  48. package/src/ai/chat/presentation.tsx +31 -47
  49. package/src/ai/chat/primitives.tsx +43 -1
  50. package/src/ai/chat/tool-disclosure.tsx +21 -2
  51. package/src/ai/chat/tool-groups.ts +58 -0
  52. package/src/ai/chat-quotas.ts +25 -0
  53. package/src/ai/chat-task-contracts.ts +14 -10
  54. package/src/ai/chat-tasks.ts +297 -43
  55. package/src/ai/client/controller.ts +165 -39
  56. package/src/ai/client/file-source.ts +12 -6
  57. package/src/ai/client/live-connection.ts +1 -0
  58. package/src/ai/client/projection.ts +29 -11
  59. package/src/ai/client/transport.ts +6 -0
  60. package/src/ai/code-capability-routes.ts +105 -0
  61. package/src/ai/code-capability-transport.ts +39 -0
  62. package/src/ai/code-execution.ts +53 -0
  63. package/src/ai/code-mode-skill.ts +118 -0
  64. package/src/ai/code-runtime-tools.ts +179 -0
  65. package/src/ai/code-source-contracts.ts +270 -0
  66. package/src/ai/code-source-tools.ts +188 -0
  67. package/src/ai/compaction.ts +27 -6
  68. package/src/ai/data-analysis-skill.ts +10 -0
  69. package/src/ai/default-tools.ts +99 -3
  70. package/src/ai/dictation-runtime.ts +171 -0
  71. package/src/ai/dictations.ts +188 -0
  72. package/src/ai/draft-content.ts +49 -0
  73. package/src/ai/enrich.ts +6 -0
  74. package/src/ai/executor.ts +189 -33
  75. package/src/ai/fetch-file-tool.ts +2 -2
  76. package/src/ai/file-content-version.ts +27 -0
  77. package/src/ai/file-context.ts +2 -1
  78. package/src/ai/file-media-type.ts +12 -0
  79. package/src/ai/file-reference-contracts.ts +15 -0
  80. package/src/ai/file-tools.ts +8 -1
  81. package/src/ai/files-store.ts +212 -71
  82. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  83. package/src/ai/grids-skill.ts +42 -10
  84. package/src/ai/http.ts +26 -8
  85. package/src/ai/index.ts +26 -3
  86. package/src/ai/inference-calls.ts +197 -0
  87. package/src/ai/live-events.ts +5 -1
  88. package/src/ai/live.ts +1 -1
  89. package/src/ai/markdown-pdf-tool.ts +2 -2
  90. package/src/ai/memories.ts +30 -27
  91. package/src/ai/memory-learning-runs.ts +1 -1
  92. package/src/ai/memory-learning.ts +64 -32
  93. package/src/ai/memory-tool.ts +1 -5
  94. package/src/ai/memory-workflow-evidence.ts +9 -4
  95. package/src/ai/message-queue.ts +178 -0
  96. package/src/ai/migrate.ts +189 -11
  97. package/src/ai/model-pricing.ts +43 -0
  98. package/src/ai/pdf-render-worker.ts +75 -0
  99. package/src/ai/pdf-render.fixture.ts +28 -0
  100. package/src/ai/pdf-render.ts +77 -0
  101. package/src/ai/projects-routes.ts +18 -43
  102. package/src/ai/projects.ts +33 -14
  103. package/src/ai/protocol.ts +3 -0
  104. package/src/ai/provider.ts +1 -8
  105. package/src/ai/quota-provider.ts +192 -0
  106. package/src/ai/quota-report.ts +152 -0
  107. package/src/ai/quotas-migrate.ts +45 -0
  108. package/src/ai/quotas.ts +168 -0
  109. package/src/ai/routes.ts +241 -11
  110. package/src/ai/run-timeout.ts +12 -0
  111. package/src/ai/runtime-tools.ts +7 -5
  112. package/src/ai/runtime.ts +74 -17
  113. package/src/ai/settings.ts +36 -8
  114. package/src/ai/short-id.ts +4 -5
  115. package/src/ai/skill-catalog.ts +0 -10
  116. package/src/ai/skill-search.ts +58 -0
  117. package/src/ai/skill-seeds.ts +114 -20
  118. package/src/ai/skill-tool.ts +21 -5
  119. package/src/ai/skills-routes.ts +49 -4
  120. package/src/ai/skills.ts +358 -163
  121. package/src/ai/store.ts +416 -201
  122. package/src/ai/stream.ts +5 -0
  123. package/src/ai/structured-runs.ts +4 -3
  124. package/src/ai/structured.ts +12 -1
  125. package/src/ai/system-prompt.ts +14 -2
  126. package/src/ai/task-contracts.ts +84 -0
  127. package/src/ai/task-execution.ts +92 -0
  128. package/src/ai/timeline.ts +2 -5
  129. package/src/ai/todo-contracts.ts +32 -0
  130. package/src/ai/todo-tool.ts +19 -0
  131. package/src/ai/tool-audit.ts +30 -40
  132. package/src/ai/tools.ts +6 -0
  133. package/src/ai/transcription.ts +202 -0
  134. package/src/ai/turn-timing.ts +114 -0
  135. package/src/ai/types.ts +58 -10
  136. package/src/ai/ui.tsx +3 -0
  137. package/src/ai/usage.ts +118 -118
  138. package/src/ai/vision-tool.ts +60 -10
  139. package/src/api/admin-ai-projects.ts +2 -2
  140. package/src/api/admin-ai-quotas.ts +80 -0
  141. package/src/api/admin-ai-skills.ts +48 -5
  142. package/src/api/admin-ai-usage.ts +2 -2
  143. package/src/api/admin-core-settings.ts +25 -0
  144. package/src/api/admin-identity.ts +91 -51
  145. package/src/api/admin-linux-identities.ts +10 -4
  146. package/src/api/admin-rail.ts +58 -0
  147. package/src/api/announcements.ts +14 -0
  148. package/src/api/app-approval.ts +2 -2
  149. package/src/api/auth.ts +1 -1
  150. package/src/api/capabilities.ts +324 -43
  151. package/src/api/capability-streams.ts +262 -0
  152. package/src/api/help.ts +8 -44
  153. package/src/api/index.ts +8 -2
  154. package/src/api/mcp.ts +45 -34
  155. package/src/api/me-web-vitals.ts +35 -0
  156. package/src/api/me.ts +2 -0
  157. package/src/api/search/schemas.ts +26 -15
  158. package/src/api/search.bench.ts +1 -1
  159. package/src/api/search.ts +54 -17
  160. package/src/browser/CloudResourceSearch.tsx +640 -702
  161. package/src/browser/app-approval-vault.ts +19 -7
  162. package/src/browser/command-bridge.ts +109 -0
  163. package/src/browser/command-shortcuts.ts +76 -0
  164. package/src/browser/commands.ts +67 -0
  165. package/src/browser/navigation-search.ts +48 -0
  166. package/src/browser/notifications.ts +1 -1
  167. package/src/browser/resource-picker.tsx +5 -18
  168. package/src/browser/resource-search-dialog.ts +9 -0
  169. package/src/browser/resource-search-input.ts +46 -0
  170. package/src/browser/resource-search-messages.ts +76 -40
  171. package/src/browser/resource-search.ts +7 -0
  172. package/src/browser/search-bridge.ts +102 -0
  173. package/src/browser/search-commands.ts +59 -0
  174. package/src/browser/search.ts +13 -0
  175. package/src/browser/spotlight-position.ts +208 -0
  176. package/src/browser/testing.ts +6 -0
  177. package/src/browser/web-vitals.ts +25 -0
  178. package/src/capabilities/claims.ts +179 -0
  179. package/src/capabilities/client.ts +34 -0
  180. package/src/capabilities/command-link.ts +16 -0
  181. package/src/capabilities/executions.ts +298 -0
  182. package/src/capabilities/migrate.ts +81 -0
  183. package/src/capabilities/server.ts +116 -35
  184. package/src/capabilities/stream-body.ts +16 -0
  185. package/src/capabilities/streams.ts +28 -0
  186. package/src/cli/admin/ai-quotas.ts +161 -0
  187. package/src/cli/admin/ai-skills.ts +64 -0
  188. package/src/cli/admin/ai-usage.ts +53 -30
  189. package/src/cli/admin/app-credentials.ts +77 -0
  190. package/src/cli/admin/data.ts +17 -0
  191. package/src/cli/admin/index.ts +10 -1
  192. package/src/cli/admin/instance.ts +2 -2
  193. package/src/cli/admin/linux.ts +4 -2
  194. package/src/cli/admin/sync.ts +1 -1
  195. package/src/cli/capabilities.ts +42 -0
  196. package/src/cli/capability-streams.ts +25 -0
  197. package/src/cli/index.ts +3 -2
  198. package/src/config/define-env.ts +97 -0
  199. package/src/config/env.ts +147 -51
  200. package/src/config/index.ts +3 -1
  201. package/src/contracts/app.ts +23 -3
  202. package/src/contracts/capabilities.ts +75 -5
  203. package/src/contracts/capability-streams.ts +46 -0
  204. package/src/contracts/commands.ts +49 -0
  205. package/src/contracts/index.ts +5 -2
  206. package/src/contracts/rail-admin.ts +38 -0
  207. package/src/contracts/rail-preferences.ts +16 -0
  208. package/src/contracts/registry.ts +5 -27
  209. package/src/contracts/settings-types.ts +1 -1
  210. package/src/contracts/shared.ts +18 -5
  211. package/src/contracts/web-vitals.ts +27 -0
  212. package/src/desktop/index.ts +1 -0
  213. package/src/desktop/solid.tsx +1 -1
  214. package/src/index.ts +8 -6
  215. package/src/server/index.ts +3 -0
  216. package/src/server/middleware/auth.ts +3 -2
  217. package/src/server/middleware/route-template.ts +9 -3
  218. package/src/server/middleware/runtime.ts +2 -1
  219. package/src/server/middleware/settings.ts +17 -11
  220. package/src/server/ratelimit.ts +1 -1
  221. package/src/server/services/access-revision.ts +10 -0
  222. package/src/server/services/access.ts +3 -1
  223. package/src/server/services/index.ts +1 -0
  224. package/src/services/account-category-policy.ts +9 -0
  225. package/src/services/account-lifecycle/audit.ts +1 -1
  226. package/src/services/accounts/app.ts +12 -2
  227. package/src/services/accounts/email-write.ts +23 -0
  228. package/src/services/accounts/groups.ts +15 -4
  229. package/src/services/accounts/identities.ts +249 -0
  230. package/src/services/accounts/identity-reconciliation.ts +87 -0
  231. package/src/services/accounts/ipa-data.ts +2 -2
  232. package/src/services/accounts/local-groups.ts +2 -2
  233. package/src/services/accounts/notification-sender.ts +12 -2
  234. package/src/services/accounts/posix.ts +45 -29
  235. package/src/services/accounts/users.ts +2 -2
  236. package/src/services/announcements/index.ts +69 -8
  237. package/src/services/app-approval.ts +11 -10
  238. package/src/services/audit/index.ts +1 -1
  239. package/src/services/cache-fill.ts +35 -0
  240. package/src/services/help/index.ts +109 -0
  241. package/src/services/help/maintenance.ts +25 -0
  242. package/src/services/help/store.ts +180 -0
  243. package/src/services/help/types.ts +46 -0
  244. package/src/services/identity/invocation-actor.ts +2 -2
  245. package/src/services/identity/key-config.ts +8 -5
  246. package/src/services/identity/key-ring.ts +1 -1
  247. package/src/services/identity/runtime-config.ts +6 -5
  248. package/src/services/index.ts +15 -0
  249. package/src/services/ipa/posix.ts +1 -1
  250. package/src/services/ipa/sync.ts +1 -1
  251. package/src/services/ipa/users.ts +268 -221
  252. package/src/services/legal-consent.ts +1 -1
  253. package/src/services/logging/index.ts +15 -13
  254. package/src/services/logging/trace.ts +2 -1
  255. package/src/services/logging/web-vitals.ts +81 -0
  256. package/src/services/mandates/index.ts +21 -4
  257. package/src/services/mandates/policy.ts +165 -2
  258. package/src/services/notifications/batches.ts +125 -57
  259. package/src/services/notifications/browser.ts +12 -0
  260. package/src/services/notifications/index.ts +0 -2
  261. package/src/services/notifications/platform.ts +0 -4
  262. package/src/services/oauth-tokens.ts +3 -3
  263. package/src/services/pdf/gotenberg.ts +131 -72
  264. package/src/services/pdf/index.ts +11 -8
  265. package/src/services/providers/local/users.ts +25 -16
  266. package/src/services/public-http.ts +101 -0
  267. package/src/services/rail-shortcuts.ts +95 -0
  268. package/src/services/rail-snapshot.ts +53 -0
  269. package/src/services/request-cache-redis.ts +29 -0
  270. package/src/services/session/index.ts +15 -2
  271. package/src/services/session/user.ts +62 -37
  272. package/src/services/settings/app.ts +5 -1
  273. package/src/services/settings/core-settings.ts +26 -35
  274. package/src/services/settings/defaults.ts +5 -0
  275. package/src/services/settings/index.ts +3 -3
  276. package/src/services/settings/store.ts +58 -84
  277. package/src/services/webauthn.ts +1 -1
  278. package/src/shared/ai-costs.ts +42 -0
  279. package/src/shared/ai-platform-prompt.ts +1 -1
  280. package/src/shared/ai-quotas.ts +136 -0
  281. package/src/shared/ai-usage.ts +6 -16
  282. package/src/shared/app-presentation.ts +21 -15
  283. package/src/shared/capability-messages.ts +14 -0
  284. package/src/shared/help.ts +6 -42
  285. package/src/shared/index.ts +3 -0
  286. package/src/shared/markdown/extensions/info-blocks.ts +1 -1
  287. package/src/ssr/AdminLayout.tsx +7 -5
  288. package/src/ssr/AdminSidebar.tsx +30 -16
  289. package/src/ssr/AppLaunchpad.island.tsx +33 -136
  290. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  291. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  292. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  293. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  294. package/src/ssr/Layout.tsx +17 -6
  295. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  296. package/src/ssr/LayoutFooter.tsx +1 -1
  297. package/src/ssr/LayoutHeader.tsx +46 -14
  298. package/src/ssr/LayoutHelp.tsx +22 -23
  299. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  300. package/src/ssr/LayoutRail.tsx +17 -9
  301. package/src/ssr/MinimalLayout.tsx +1 -1
  302. package/src/ssr/MobileNavigation.tsx +71 -0
  303. package/src/ssr/MobileProfileActions.tsx +49 -0
  304. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  305. package/src/ssr/RailApps.island.tsx +2 -2
  306. package/src/ssr/RailEditor.tsx +17 -2
  307. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  308. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  309. package/src/ssr/admin-navigation.ts +11 -1
  310. package/src/ssr/help.ts +29 -0
  311. package/src/ssr/islands/SearchBar.tsx +67 -0
  312. package/src/ssr/islands/index.ts +3 -1
  313. package/src/ssr/layout-context.ts +2 -2
  314. package/src/ssr/layout-help-search.ts +15 -0
  315. package/src/ssr/mobile-menu-history.ts +70 -0
  316. package/src/ssr/platform-messages.ts +16 -8
  317. package/src/ssr/profile-actions.ts +42 -0
  318. package/src/ssr/rail-context.ts +8 -4
  319. package/src/ssr/rail-messages.ts +2 -0
  320. package/src/ssr/rail-navigation.ts +3 -3
  321. package/src/ssr/workspace-navigation.ts +75 -0
  322. package/src/styles/effects.css +6 -6
  323. package/src/styles/global.css +54 -0
  324. package/src/styles/input.css +43 -41
  325. package/src/styles/resource-search.css +505 -0
  326. package/src/styles/utilities-feedback.css +13 -0
  327. package/src/styles/utilities-layout.css +1 -5
  328. package/src/styles/utilities-navigation.css +3 -39
  329. package/src/types/ambient.d.ts +5 -0
  330. package/src/workflows/ai/index.ts +2 -2
  331. package/src/workflows/ai/runtime.ts +19 -90
  332. package/src/workflows/ai/store.ts +2 -0
  333. package/src/workflows/ai/types.ts +6 -82
  334. package/src/workflows/ai-actions.ts +16 -13
  335. package/src/workflows/definition.ts +10 -5
  336. package/src/workflows/store/actions.ts +44 -20
  337. package/src/workflows/store/index.ts +4 -2
  338. package/src/workflows/store/runs.ts +36 -0
  339. package/src/workflows/store/worker-pool.ts +55 -0
  340. package/src/workflows/store/worker-runtime.ts +90 -0
  341. package/src/ai/kit-skill.ts +0 -22
  342. package/src/ai/usage-migrate.ts +0 -81
  343. package/src/browser/notification-target.ts +0 -27
  344. package/src/contracts/notification-live.ts +0 -66
  345. package/src/server/services/freeipa/test-certificates.ts +0 -44
  346. package/src/services/notifications/live.ts +0 -50
  347. package/src/services/session/test-fixture.ts +0 -18
  348. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  349. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -2,7 +2,7 @@ import { Hono, type MiddlewareHandler } from "hono";
2
2
  import { z } from "zod";
3
3
  import { getCapability } from "../_internal/registry";
4
4
  import { CloudResourceRefSchema, cloudResourceRefAppId, resolveCapabilityResourceReader } from "../contracts/capabilities";
5
- import { PrincipalSchema } from "../contracts/shared";
5
+ import { AuthenticatedPrincipalSchema } from "../contracts/shared";
6
6
  import { type AuthContext, auth, err, fail, ok, rateLimit, respond, v } from "../server";
7
7
  import { decodeAiFileContent } from "./files-store";
8
8
  import {
@@ -28,7 +28,7 @@ const ProjectFieldsSchema = z.object({
28
28
  });
29
29
 
30
30
  const UpdateProjectSchema = ProjectFieldsSchema.partial().refine((input) => Object.keys(input).length > 0, "No changes supplied.");
31
- const ProjectAccessSchema = z.object({ principal: PrincipalSchema, permission: z.enum(["read", "write", "admin"]) });
31
+ const ProjectAccessSchema = z.object({ principal: AuthenticatedPrincipalSchema, permission: z.enum(["read", "write", "admin"]) });
32
32
  const ProjectAccessUpdateSchema = z.object({ permission: z.enum(["read", "write", "admin"]) });
33
33
  const KnowledgeSchema = z.object({
34
34
  title: z.string().trim().min(1).max(200),
@@ -70,17 +70,13 @@ type AiProjectsRouteDependencies = {
70
70
  const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =>
71
71
  new Hono<AuthContext>()
72
72
  .use(dependencies.limit ?? rateLimit())
73
- .use("*", dependencies.authenticate ?? auth.requireRole("*"))
74
- .get("/", async (c) =>
75
- respond(c, ok({ projects: (await aiProjects.list(c.get("accessSubject") ?? null)).map(publicProject) })),
76
- )
73
+ .use("*", dependencies.authenticate ?? auth.requireRole("authenticated"))
74
+ .get("/", async (c) => respond(c, ok({ projects: (await aiProjects.list(c.get("accessSubject") ?? null)).map(publicProject) })))
77
75
  .post("/", dependencies.authenticate ?? auth.requireRole("authenticated"), v("json", ProjectFieldsSchema), async (c) =>
78
76
  respond(
79
77
  c,
80
78
  ok({
81
- project: publicProject(
82
- await aiProjects.create({ subject: c.get("accessSubject"), ...c.req.valid("json") }),
83
- ),
79
+ project: publicProject(await aiProjects.create({ subject: c.get("accessSubject"), ...c.req.valid("json") })),
84
80
  }),
85
81
  201,
86
82
  ),
@@ -108,9 +104,7 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
108
104
  })
109
105
  .post("/:projectId/access", v("json", ProjectAccessSchema), async (c) => {
110
106
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "admin");
111
- const access = project
112
- ? await aiProjects.grantAccess(project.id, c.get("accessSubject") ?? null, c.req.valid("json"))
113
- : null;
107
+ const access = project ? await aiProjects.grantAccess(project.id, c.get("accessSubject") ?? null, c.req.valid("json")) : null;
114
108
  return access ? respond(c, ok({ access }), 201) : notFound(c);
115
109
  })
116
110
  .patch("/:projectId/access/:accessId", v("json", ProjectAccessUpdateSchema), async (c) => {
@@ -133,8 +127,7 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
133
127
  .delete("/:projectId/access/:accessId", async (c) => {
134
128
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "admin");
135
129
  try {
136
- return project &&
137
- (await aiProjects.revokeAccess(project.id, c.req.param("accessId")!, c.get("accessSubject") ?? null))
130
+ return project && (await aiProjects.revokeAccess(project.id, c.req.param("accessId")!, c.get("accessSubject") ?? null))
138
131
  ? respond(c, ok({ deleted: true }))
139
132
  : notFound(c, "Access entry");
140
133
  } catch (error) {
@@ -148,35 +141,27 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
148
141
  return respond(
149
142
  c,
150
143
  ok({
151
- knowledge: (
152
- await aiProjects.listKnowledge(project.id, c.get("accessSubject") ?? null, c.req.valid("query").q)
153
- ).map((item) => publicKnowledge(item, project.shortId)),
144
+ knowledge: (await aiProjects.listKnowledge(project.id, c.get("accessSubject") ?? null, c.req.valid("query").q)).map((item) =>
145
+ publicKnowledge(item, project.shortId),
146
+ ),
154
147
  }),
155
148
  );
156
149
  })
157
150
  .post("/:projectId/knowledge", v("json", KnowledgeSchema), async (c) => {
158
151
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "write");
159
- const knowledge = project
160
- ? await aiProjects.createKnowledge(project.id, c.get("accessSubject") ?? null, c.req.valid("json"))
161
- : null;
152
+ const knowledge = project ? await aiProjects.createKnowledge(project.id, c.get("accessSubject") ?? null, c.req.valid("json")) : null;
162
153
  return knowledge ? respond(c, ok({ knowledge: publicKnowledge(knowledge, project!.shortId) }), 201) : notFound(c);
163
154
  })
164
155
  .patch("/:projectId/knowledge/:knowledgeId", v("json", UpdateKnowledgeSchema), async (c) => {
165
156
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "write");
166
157
  const knowledge =
167
158
  project &&
168
- (await aiProjects.updateKnowledge(
169
- project.id,
170
- c.req.param("knowledgeId")!,
171
- c.get("accessSubject") ?? null,
172
- c.req.valid("json"),
173
- ));
159
+ (await aiProjects.updateKnowledge(project.id, c.req.param("knowledgeId")!, c.get("accessSubject") ?? null, c.req.valid("json")));
174
160
  return knowledge ? respond(c, ok({ knowledge: publicKnowledge(knowledge, project!.shortId) })) : notFound(c, "Knowledge entry");
175
161
  })
176
162
  .delete("/:projectId/knowledge/:knowledgeId", async (c) => {
177
163
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "write");
178
- return project &&
179
- (await aiProjects.deleteKnowledge(project.id, c.req.param("knowledgeId")!, c.get("accessSubject") ?? null))
164
+ return project && (await aiProjects.deleteKnowledge(project.id, c.req.param("knowledgeId")!, c.get("accessSubject") ?? null))
180
165
  ? respond(c, ok({ deleted: true }))
181
166
  : notFound(c, "Knowledge entry");
182
167
  })
@@ -186,20 +171,14 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
186
171
  return respond(
187
172
  c,
188
173
  ok({
189
- files: (await aiProjects.listFiles(project.id, c.get("accessSubject") ?? null)).map((file) =>
190
- publicFile(file, project.shortId),
191
- ),
174
+ files: (await aiProjects.listFiles(project.id, c.get("accessSubject") ?? null)).map((file) => publicFile(file, project.shortId)),
192
175
  }),
193
176
  );
194
177
  })
195
178
  .post("/:projectId/files", v("json", ProjectFileSchema), async (c) => {
196
179
  const body = c.req.valid("json");
197
180
  try {
198
- const project = await aiProjects.getByShortId(
199
- c.req.param("projectId")!,
200
- c.get("accessSubject") ?? null,
201
- "write",
202
- );
181
+ const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "write");
203
182
  const file = project
204
183
  ? await aiProjects.writeFile(project.id, c.get("accessSubject") ?? null, {
205
184
  path: body.path,
@@ -214,9 +193,7 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
214
193
  })
215
194
  .get("/:projectId/files/:fileId", async (c) => {
216
195
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null);
217
- const file = project
218
- ? await aiProjects.readFile(project.id, c.req.param("fileId")!, c.get("accessSubject") ?? null)
219
- : null;
196
+ const file = project ? await aiProjects.readFile(project.id, c.req.param("fileId")!, c.get("accessSubject") ?? null) : null;
220
197
  if (!file) return notFound(c, "Project file");
221
198
  return respond(
222
199
  c,
@@ -229,8 +206,7 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
229
206
  })
230
207
  .delete("/:projectId/files/:fileId", async (c) => {
231
208
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "write");
232
- return project &&
233
- (await aiProjects.deleteFile(project.id, c.req.param("fileId")!, c.get("accessSubject") ?? null))
209
+ return project && (await aiProjects.deleteFile(project.id, c.req.param("fileId")!, c.get("accessSubject") ?? null))
234
210
  ? respond(c, ok({ deleted: true }))
235
211
  : notFound(c, "Project file");
236
212
  })
@@ -265,8 +241,7 @@ const buildAiProjectsRoutes = (dependencies: AiProjectsRouteDependencies = {}) =
265
241
  })
266
242
  .delete("/:projectId/references/:referenceId", async (c) => {
267
243
  const project = await aiProjects.getByShortId(c.req.param("projectId")!, c.get("accessSubject") ?? null, "write");
268
- return project &&
269
- (await aiProjects.deleteReference(project.id, c.req.param("referenceId")!, c.get("accessSubject") ?? null))
244
+ return project && (await aiProjects.deleteReference(project.id, c.req.param("referenceId")!, c.get("accessSubject") ?? null))
270
245
  ? respond(c, ok({ deleted: true }))
271
246
  : notFound(c, "Project reference");
272
247
  });
@@ -1,5 +1,6 @@
1
1
  import { type SQL, type SQLQuery, sql } from "bun";
2
2
  import type { CloudResourceRef } from "../contracts/capabilities";
3
+ import { AuthenticatedPrincipalSchema } from "../contracts/shared";
3
4
  import type { AccessSubject } from "../server";
4
5
  import {
5
6
  buildAccessPrincipalCondition,
@@ -10,6 +11,7 @@ import {
10
11
  type Principal,
11
12
  } from "../server/services/access";
12
13
  import { toPgUuidArray } from "../services/postgres";
14
+ import { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
13
15
  import { mountAiProjectFilePath } from "./file-mount";
14
16
  import { withAiShortIdForDb } from "./short-id";
15
17
  import type { AiProjectPromptSnapshot } from "./types";
@@ -168,15 +170,17 @@ const principalForSubject = (subject: AccessSubject): Principal =>
168
170
  : { type: "service_account", serviceAccountId: subject.serviceAccountId };
169
171
 
170
172
  const accessMatch = (subject: AccessSubject | null): SQLQuery =>
171
- buildAccessPrincipalCondition({
172
- subject,
173
- columns: {
174
- userId: sql`access.user_id`,
175
- groupId: sql`access.group_id`,
176
- serviceAccountId: sql`access.service_account_id`,
177
- authenticatedOnly: sql`access.authenticated_only`,
178
- },
179
- });
173
+ subject === null
174
+ ? sql`FALSE`
175
+ : buildAccessPrincipalCondition({
176
+ subject,
177
+ columns: {
178
+ userId: sql`access.user_id`,
179
+ groupId: sql`access.group_id`,
180
+ serviceAccountId: sql`access.service_account_id`,
181
+ authenticatedOnly: sql`access.authenticated_only`,
182
+ },
183
+ });
180
184
 
181
185
  const permissionFor = async (row: ProjectRow, subject: AccessSubject | null, db: SQL = sql): Promise<AiProjectPermission | "none"> => {
182
186
  const match = accessMatch(subject);
@@ -299,6 +303,7 @@ const createProjectAccess = async (
299
303
  input: { principal: Principal; permission: AiProjectPermission },
300
304
  db: SQL,
301
305
  ): Promise<AiProjectAccess> => {
306
+ AuthenticatedPrincipalSchema.parse(input.principal);
302
307
  const created = await createAccess(input, db);
303
308
  if (!created.ok) throw new Error(created.error.message);
304
309
  let shortId = "";
@@ -439,7 +444,6 @@ export const aiProjects = {
439
444
  JOIN auth.access access ON access.id = project_access.access_id
440
445
  WHERE access.permission <> 'none' AND ${match}
441
446
  ORDER BY lower(project.name), project.id
442
- LIMIT 200
443
447
  `;
444
448
  return (await Promise.all(rows.map((row) => toProject(row, subject)))).filter((project): project is AiProject => Boolean(project));
445
449
  },
@@ -787,24 +791,36 @@ export const aiProjects = {
787
791
  });
788
792
  },
789
793
 
790
- async listFiles(projectId: string, subject: AccessSubject | null): Promise<AiProjectFile[]> {
794
+ async listFiles(projectId: string, subject: AccessSubject | null, page?: { after?: string; limit: number }): Promise<AiProjectFile[]> {
791
795
  if (!(await requireProject(projectId, subject, "read"))) return [];
796
+ if (page && (!Number.isInteger(page.limit) || page.limit < 1 || page.limit > 1000)) throw new Error("Invalid file page limit");
792
797
  return (
793
798
  await sql<
794
799
  FileRow[]
795
- >`SELECT id, short_id, project_id, path, media_type, size, updated_at FROM ai.project_files WHERE project_id = ${projectId}::uuid ORDER BY path LIMIT 500`
800
+ >`SELECT id, short_id, project_id, path, media_type, size, updated_at FROM ai.project_files WHERE project_id = ${projectId}::uuid AND path > ${page?.after ?? ""} ORDER BY path LIMIT ${page?.limit ?? 500}`
796
801
  ).map(toFile);
797
802
  },
798
803
 
799
804
  async writeFile(
800
805
  projectId: string,
801
806
  subject: AccessSubject | null,
802
- input: { path: string; mediaType: string; bytes: Uint8Array },
807
+ input: { path: string; mediaType: string; bytes: Uint8Array; expectedVersion?: string | null },
803
808
  ): Promise<AiProjectFile | null> {
804
809
  if (!(await requireProject(projectId, subject, "write"))) return null;
805
- if (input.bytes.byteLength > AI_PROJECT_FILE_MAX_BYTES) throw new Error("Project file exceeds the size limit.");
810
+ if (input.bytes.byteLength > AI_PROJECT_FILE_MAX_BYTES)
811
+ throw new AiFileWriteError("STORAGE_FULL", "Project file exceeds the size limit; nothing was written.");
806
812
  const path = normalizeProjectPath(input.path);
807
813
  return sql.begin(async (tx) => {
814
+ const [row] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id=${projectId}::uuid FOR UPDATE`;
815
+ const project = row ? await toProject(row, subject, tx) : null;
816
+ if (!project || !hasPermission(project.permission, "write")) return null;
817
+ if (input.expectedVersion !== undefined) {
818
+ const [existing] = await tx<
819
+ (FileRow & { bytes: Uint8Array })[]
820
+ >`SELECT id,short_id,project_id,path,media_type,size,updated_at,bytes FROM ai.project_files WHERE project_id=${projectId}::uuid AND path=${path}`;
821
+ const version = existing ? aiFileContentVersion({ ...toFile(existing), bytes: existing.bytes }) : null;
822
+ if (version !== input.expectedVersion) throw new AiFileVersionConflict();
823
+ }
808
824
  const rows = await withAiShortIdForDb(
809
825
  tx,
810
826
  "idx_ai_project_files_short_id",
@@ -851,6 +867,9 @@ export const aiProjects = {
851
867
  async deleteFile(projectId: string, fileId: string, subject: AccessSubject | null): Promise<boolean> {
852
868
  if (!(await requireProject(projectId, subject, "write"))) return false;
853
869
  return sql.begin(async (tx) => {
870
+ const [row] = await tx<ProjectRow[]>`SELECT * FROM ai.projects WHERE id=${projectId}::uuid FOR UPDATE`;
871
+ const project = row ? await toProject(row, subject, tx) : null;
872
+ if (!project || !hasPermission(project.permission, "write")) return false;
854
873
  const rows = await tx<{ id: string }[]>`
855
874
  DELETE FROM ai.project_files WHERE short_id = ${fileId} AND project_id = ${projectId}::uuid RETURNING id
856
875
  `;
@@ -33,6 +33,8 @@ export type AiTurnBlock =
33
33
  name: string;
34
34
  args?: unknown;
35
35
  status: AiToolBlockStatus;
36
+ /** Localized transient host status, without tool payload data. */
37
+ progress?: string;
36
38
  result?: unknown;
37
39
  isError?: boolean;
38
40
  /** Present while status is awaiting_approval. */
@@ -62,6 +64,7 @@ export type AiWireEvent =
62
64
  /** Authoritative ordered baseline for this attempt. Optional for older senders. */
63
65
  blocks?: AiTurnBlock[];
64
66
  })
67
+ | (AiWireEventBase & { type: "message_saved"; message: AiStoredMessage })
65
68
  | (AiWireEventBase & { type: "block_set"; block: AiTurnBlock })
66
69
  | (AiWireEventBase & { type: "block_delta"; blockId: string; blockKind: "text" | "thinking"; delta: string })
67
70
  | (AiWireEventBase & {
@@ -10,12 +10,11 @@ const commonOptions = (profile: AiModelProfile, apiKey?: string) => ({
10
10
  baseURL: profile.baseURL,
11
11
  contextWindow: profile.contextWindow,
12
12
  temperature: profile.temperature,
13
- creditsPerInputToken: profile.creditsPerInputToken,
14
- creditsPerOutputToken: profile.creditsPerOutputToken,
15
13
  timeouts: PROVIDER_TIMEOUTS,
16
14
  });
17
15
 
18
16
  export const createAiProvider = (profile: AiModelProfile, apiKey?: string): Provider => {
17
+ if (profile.capabilities.includes("transcription")) throw new Error("Transcription profiles cannot be used for chat generation.");
19
18
  switch (profile.provider) {
20
19
  case "openai":
21
20
  return openai(profile.model, commonOptions(profile, apiKey));
@@ -32,8 +31,6 @@ export const createAiProvider = (profile: AiModelProfile, apiKey?: string): Prov
32
31
  baseURL: profile.baseURL,
33
32
  contextWindow: profile.contextWindow,
34
33
  temperature: profile.temperature,
35
- creditsPerInputToken: profile.creditsPerInputToken,
36
- creditsPerOutputToken: profile.creditsPerOutputToken,
37
34
  timeouts: PROVIDER_TIMEOUTS,
38
35
  });
39
36
  case "vllm":
@@ -44,8 +41,6 @@ export const createAiProvider = (profile: AiModelProfile, apiKey?: string): Prov
44
41
  apiKey,
45
42
  contextWindow: profile.contextWindow,
46
43
  temperature: profile.temperature,
47
- creditsPerInputToken: profile.creditsPerInputToken,
48
- creditsPerOutputToken: profile.creditsPerOutputToken,
49
44
  timeouts: PROVIDER_TIMEOUTS,
50
45
  compat: {
51
46
  toolCallIdPolicy: "passthrough",
@@ -65,8 +60,6 @@ export const createAiProvider = (profile: AiModelProfile, apiKey?: string): Prov
65
60
  apiKey,
66
61
  contextWindow: profile.contextWindow,
67
62
  temperature: profile.temperature,
68
- creditsPerInputToken: profile.creditsPerInputToken,
69
- creditsPerOutputToken: profile.creditsPerOutputToken,
70
63
  timeouts: PROVIDER_TIMEOUTS,
71
64
  });
72
65
  }
@@ -0,0 +1,192 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { estimateTokens, type Provider } from "@k2b/nessi";
3
+ import { sql } from "bun";
4
+ import type { AccessSubject } from "../server/services/access";
5
+ import { logger } from "../services/logging";
6
+ import { isAssistantChatTurn } from "./assistant-models";
7
+ import { AiBackgroundAdmissionError, type AiCallContext, beginAiCall, finishAiCall } from "./inference-calls";
8
+ import type { AiModelProfile } from "./types";
9
+
10
+ const log = logger("ai:quotas");
11
+
12
+ /** One wrapper owns call accounting. complete() can have a separate compaction purpose. */
13
+ const callLifecycle = {
14
+ begin: beginAiCall,
15
+ finish: finishAiCall,
16
+ heartbeat: async (id: string) => {
17
+ await sql`UPDATE ai.inference_calls SET lease_expires_at=now()+interval '2 minutes' WHERE id=${id}::uuid AND finished_at IS NULL`;
18
+ },
19
+ };
20
+ export function inferenceProvider(
21
+ provider: Provider,
22
+ profile: AiModelProfile,
23
+ context: AiCallContext,
24
+ completeContext = context,
25
+ lifecycle = callLifecycle,
26
+ ): Provider {
27
+ const begin = async (request: Parameters<Provider["complete"]>[0], ctx: AiCallContext) => {
28
+ const messages = request.messages.map((message) =>
29
+ message.role === "user"
30
+ ? {
31
+ ...message,
32
+ content: message.content.filter((part) => typeof part === "string" || part.type !== "file"),
33
+ }
34
+ : message,
35
+ );
36
+ const inputTokens =
37
+ estimateTokens(messages) +
38
+ Math.ceil(
39
+ ((request.systemPrompt?.length ?? 0) +
40
+ JSON.stringify(request.tools ?? []).length +
41
+ JSON.stringify(request.responseFormat ?? {}).length) /
42
+ 4,
43
+ );
44
+ // Wait up to one reservation lease before delegating to the caller's retry policy.
45
+ // No provider request or new reservation exists while waiting; cancellation stays active.
46
+ const deadline = Date.now() + 120_000;
47
+ const admit = async () => {
48
+ for (;;) {
49
+ request.signal?.throwIfAborted();
50
+ try {
51
+ return await lifecycle.begin(
52
+ profile,
53
+ ctx,
54
+ inputTokens,
55
+ request.maxOutputTokens ?? profile.maxOutputTokens,
56
+ // Nessi's Anthropic adapter defaults to 1024; other adapters use the model default.
57
+ provider.family === "anthropic" ? 1024 : provider.contextWindow,
58
+ );
59
+ } catch (error) {
60
+ if (!(error instanceof AiBackgroundAdmissionError) || !error.retryable || Date.now() >= deadline) throw error;
61
+ await delay(1_000, undefined, { signal: request.signal });
62
+ }
63
+ }
64
+ };
65
+ const call = await admit();
66
+ const heartbeat = setInterval(() => {
67
+ void lifecycle.heartbeat(call.id).catch(() => log.warn("AI accounting heartbeat failed", { callId: call.id }));
68
+ }, 30_000);
69
+ return { ...call, inputTokens, stop: () => clearInterval(heartbeat) };
70
+ };
71
+ const finish = async (id: string, usage: Parameters<typeof finishAiCall>[1], status: "ok" | "failed") => {
72
+ try {
73
+ await lifecycle.finish(id, usage, status);
74
+ } catch {
75
+ log.error("AI cost booking failed", { callId: id, code: "ai_cost_booking_failed" });
76
+ }
77
+ };
78
+ return {
79
+ name: provider.name,
80
+ family: provider.family,
81
+ model: provider.model,
82
+ contextWindow: provider.contextWindow,
83
+ capabilities: provider.capabilities,
84
+ complete: async (request) => {
85
+ const call = await begin(request, completeContext);
86
+ let usage: Parameters<typeof finishAiCall>[1];
87
+ let status: "ok" | "failed" = "failed";
88
+ try {
89
+ const result = await provider.complete({ ...request, maxOutputTokens: call.maxOutputTokens });
90
+ if (
91
+ result.usage &&
92
+ [result.usage.input, result.usage.output].every((n) => Number.isSafeInteger(n) && n >= 0) &&
93
+ result.usage.input + result.usage.output > 0
94
+ )
95
+ usage = { input: result.usage.input, output: result.usage.output };
96
+ status = ["error", "aborted", "interrupted"].includes(result.finishReason) ? "failed" : "ok";
97
+ return result;
98
+ } finally {
99
+ call.stop();
100
+ if (!usage && status === "failed") usage = { input: call.inputTokens, output: 0, estimated: true };
101
+ await finish(call.id, usage, status);
102
+ }
103
+ },
104
+ stream: async function* (request) {
105
+ const call = await begin(request, context);
106
+ const id = call.id;
107
+ let usage: { input: number; output: number; estimated?: boolean } | undefined;
108
+ let completed = false;
109
+ let failed = false;
110
+ const outputBlocks = new Map<string, number>();
111
+ let generated = false;
112
+ try {
113
+ for await (const event of provider.stream({ ...request, maxOutputTokens: call.maxOutputTokens })) {
114
+ if (event.type === "issue" && event.issue.kind === "provider_error") failed = true;
115
+ if (event.type === "block_delta") outputBlocks.set(event.blockId, (outputBlocks.get(event.blockId) ?? 0) + event.delta.length);
116
+ if (event.type === "block_end") {
117
+ const block = event.block;
118
+ const size =
119
+ block.type === "text"
120
+ ? block.text.length
121
+ : block.type === "thinking"
122
+ ? block.thinking.length
123
+ : block.name.length + JSON.stringify(block.args).length;
124
+ outputBlocks.set(event.blockId, size);
125
+ }
126
+ if (event.type === "block_start" || event.type === "block_delta" || event.type === "block_end") generated = true;
127
+ if (event.type === "issue" && event.issue.kind === "provider_error" && event.issue.contextOverflow && !generated && !usage)
128
+ usage = { input: 0, output: 0 };
129
+ if (event.type === "usage") {
130
+ if (event.finishReason === "aborted" || event.finishReason === "interrupted" || event.finishReason === "error") failed = true;
131
+ const { input, output } = event.usage;
132
+ // Some adapters synthesize all-zero usage when the endpoint reports none.
133
+ if (Number.isSafeInteger(input) && Number.isSafeInteger(output) && input >= 0 && output >= 0 && input + output > 0)
134
+ usage = { input, output };
135
+ }
136
+ yield event;
137
+ }
138
+ completed = true;
139
+ } finally {
140
+ call.stop();
141
+ if (id) {
142
+ try {
143
+ // Interrupted adapters commonly omit their final usage event. Charge an
144
+ // explicitly labelled estimate, never invent a measured zero.
145
+ const interrupted = !completed || failed || request.signal?.aborted;
146
+ if (!usage && interrupted) {
147
+ // Encoded files are not text tokens. Their provider-specific costs
148
+ // cannot be reconstructed after interruption.
149
+ const messages = request.messages.map((message) =>
150
+ message.role === "user"
151
+ ? {
152
+ ...message,
153
+ content: message.content.filter((part) => typeof part === "string" || part.type !== "file"),
154
+ }
155
+ : message,
156
+ );
157
+ const instructionSize =
158
+ (request.systemPrompt?.length ?? 0) +
159
+ JSON.stringify(request.tools ?? []).length +
160
+ JSON.stringify(request.responseFormat ?? {}).length;
161
+ usage = {
162
+ input: estimateTokens(messages) + Math.ceil(instructionSize / 4),
163
+ output: Math.ceil(Array.from(outputBlocks.values()).reduce((sum, size) => sum + size, 0) / 4),
164
+ estimated: true,
165
+ };
166
+ }
167
+ await finish(id, usage, completed && !failed && !request.signal?.aborted ? "ok" : "failed");
168
+ } catch (error) {
169
+ log.warn("Chat usage booking failed", { code: "quota_booking_failed", turnId: context.turnId, callId: id });
170
+ }
171
+ }
172
+ }
173
+ },
174
+ };
175
+ }
176
+
177
+ export function assistantQuotaProvider(
178
+ provider: Provider,
179
+ config: Parameters<typeof isAssistantChatTurn>[0],
180
+ subject: AccessSubject | null,
181
+ profile: AiModelProfile,
182
+ turnId: string,
183
+ conversationId: string,
184
+ ) {
185
+ const common = { subject, turnId, conversationId };
186
+ return inferenceProvider(
187
+ provider,
188
+ profile,
189
+ { ...common, kind: isAssistantChatTurn(config) ? "chat" : "background", task: isAssistantChatTurn(config) ? "chat" : "agent-turn" },
190
+ { ...common, kind: "background", task: "chat-compaction" },
191
+ );
192
+ }