@k2b/cloud 0.43.0 → 0.45.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 (308) hide show
  1. package/package.json +11 -2
  2. package/src/_internal/app-access-request.ts +13 -0
  3. package/src/_internal/app-access-resources.ts +141 -0
  4. package/src/_internal/app-event-catalog.ts +40 -0
  5. package/src/_internal/app-event-schemas.ts +127 -0
  6. package/src/_internal/application-package.ts +40 -0
  7. package/src/_internal/built-in-app-ids.json +27 -0
  8. package/src/_internal/capabilities.ts +158 -12
  9. package/src/_internal/capability-origin.ts +4 -0
  10. package/src/_internal/capability-streams.ts +7 -5
  11. package/src/_internal/define-app.ts +57 -6
  12. package/src/_internal/object-store-area.ts +9 -0
  13. package/src/_internal/object-store-config.ts +101 -0
  14. package/src/_internal/object-store-key.ts +14 -0
  15. package/src/_internal/process-identity.ts +7 -2
  16. package/src/_internal/registry-validation.ts +11 -1
  17. package/src/_internal/registry.ts +8 -1
  18. package/src/_internal/runtime-context.ts +8 -1
  19. package/src/access/AppAccessEditor.tsx +2 -0
  20. package/src/access/GroupCoverage.tsx +20 -5
  21. package/src/access/PermissionEditor.tsx +20 -5
  22. package/src/access/PrincipalAvatar.tsx +30 -21
  23. package/src/access/PrincipalPicker.tsx +14 -4
  24. package/src/access/ResourceApiKeys.tsx +3 -4
  25. package/src/access/app-access-messages.ts +6 -0
  26. package/src/access/messages.ts +4 -4
  27. package/src/account/EntitySearch.tsx +12 -0
  28. package/src/account/ProfilePictureDialog.tsx +699 -0
  29. package/src/account/profile-picture-messages.ts +269 -0
  30. package/src/account/ui.ts +7 -0
  31. package/src/ai/admin.ts +3 -0
  32. package/src/ai/app-frame.ts +12 -0
  33. package/src/ai/application-tasks.ts +101 -0
  34. package/src/ai/assistant-models.ts +126 -64
  35. package/src/ai/capabilities.ts +19 -4
  36. package/src/ai/chart-block.ts +63 -30
  37. package/src/ai/chat/chart-block.tsx +12 -3
  38. package/src/ai/chat/composer-adapter.ts +8 -7
  39. package/src/ai/chat/message-actions.tsx +1 -1
  40. package/src/ai/chat/message-utils.ts +2 -2
  41. package/src/ai/chat/presentation.tsx +22 -1
  42. package/src/ai/chat-quotas.ts +5 -4
  43. package/src/ai/chat-tasks.ts +2 -2
  44. package/src/ai/client/controller.ts +11 -10
  45. package/src/ai/client/projection.ts +3 -3
  46. package/src/ai/compaction.ts +3 -3
  47. package/src/ai/credentials.ts +13 -114
  48. package/src/ai/default-tool-contracts.ts +4 -3
  49. package/src/ai/dictation-runtime.ts +10 -6
  50. package/src/ai/enrich.ts +2 -2
  51. package/src/ai/executor.ts +40 -25
  52. package/src/ai/http.ts +17 -29
  53. package/src/ai/index.ts +12 -5
  54. package/src/ai/inference-calls.ts +5 -4
  55. package/src/ai/maintenance.ts +1 -1
  56. package/src/ai/memory-learning-runs.ts +5 -5
  57. package/src/ai/memory-learning.ts +3 -3
  58. package/src/ai/message-queue.ts +1 -2
  59. package/src/ai/migrate.ts +75 -42
  60. package/src/ai/model-access-migrate.ts +3 -17
  61. package/src/ai/model-access.ts +44 -85
  62. package/src/ai/model-card.tsx +134 -0
  63. package/src/ai/model-catalog.ts +359 -0
  64. package/src/ai/model-chooser-messages.ts +88 -0
  65. package/src/ai/model-chooser.tsx +577 -0
  66. package/src/ai/model-delete.ts +234 -0
  67. package/src/ai/model-fixture.ts +39 -0
  68. package/src/ai/model-icon.tsx +36 -0
  69. package/src/ai/model-overview.ts +53 -0
  70. package/src/ai/model-usage.ts +45 -0
  71. package/src/ai/openai-api-messages.ts +0 -4
  72. package/src/ai/openai-api.ts +26 -9
  73. package/src/ai/personal-agent.ts +1 -1
  74. package/src/ai/prefs.ts +12 -10
  75. package/src/ai/project-tool.ts +4 -4
  76. package/src/ai/projects-authorization.ts +53 -0
  77. package/src/ai/projects-routes.ts +58 -47
  78. package/src/ai/projects.ts +110 -88
  79. package/src/ai/protocol.ts +2 -2
  80. package/src/ai/provider-connections.ts +161 -0
  81. package/src/ai/provider-errors.ts +55 -6
  82. package/src/ai/provider-fetch.ts +4 -0
  83. package/src/ai/provider.ts +1 -1
  84. package/src/ai/quota-limits.ts +36 -0
  85. package/src/ai/quota-provider.ts +4 -2
  86. package/src/ai/quota-report.ts +7 -4
  87. package/src/ai/quotas.ts +19 -8
  88. package/src/ai/recent-resources.ts +9 -3
  89. package/src/ai/request-headers.ts +14 -87
  90. package/src/ai/resource-refs.ts +6 -2
  91. package/src/ai/routes.ts +47 -48
  92. package/src/ai/runtime.ts +50 -22
  93. package/src/ai/settings.ts +93 -580
  94. package/src/ai/skill-tool.ts +4 -4
  95. package/src/ai/skills-routes.ts +27 -26
  96. package/src/ai/skills.ts +120 -81
  97. package/src/ai/store.ts +45 -23
  98. package/src/ai/stream.ts +1 -1
  99. package/src/ai/structured-runs.ts +2 -2
  100. package/src/ai/structured.ts +21 -44
  101. package/src/ai/task-contracts.ts +25 -5
  102. package/src/ai/task-execution.ts +15 -6
  103. package/src/ai/task-model.ts +9 -0
  104. package/src/ai/transcription.ts +27 -22
  105. package/src/ai/types.ts +50 -62
  106. package/src/ai/ui.tsx +13 -0
  107. package/src/ai/usage.ts +8 -8
  108. package/src/ai/validate.ts +8 -7
  109. package/src/ai/view-block.ts +2 -2
  110. package/src/api/accounts-entities.ts +22 -3
  111. package/src/api/admin-ai-quotas.ts +34 -56
  112. package/src/api/admin-ai-skills.ts +4 -1
  113. package/src/api/admin-ai-usage.ts +1 -1
  114. package/src/api/admin-ai.ts +173 -0
  115. package/src/api/admin-core-settings.ts +68 -244
  116. package/src/api/app-approval.ts +3 -1
  117. package/src/api/app-event-delivery.ts +51 -0
  118. package/src/api/app-events.ts +123 -0
  119. package/src/api/app-resource-access.ts +48 -0
  120. package/src/api/auth/account-mismatch.ts +24 -0
  121. package/src/api/auth.ts +35 -5
  122. package/src/api/capabilities.ts +28 -4
  123. package/src/api/index.ts +7 -1
  124. package/src/api/mcp.ts +175 -2
  125. package/src/api/me.ts +6 -4
  126. package/src/api/pwa-phone.ts +75 -26
  127. package/src/api/pwa.ts +34 -8
  128. package/src/api/resource-links.ts +83 -6
  129. package/src/api/resource-resolve.ts +50 -0
  130. package/src/api/search/schemas.ts +3 -1
  131. package/src/api/search.ts +9 -7
  132. package/src/app-pwa/contracts.ts +4 -0
  133. package/src/app-pwa/core-query-error.ts +20 -0
  134. package/src/app-pwa/index.ts +1 -0
  135. package/src/app-pwa/service.ts +91 -0
  136. package/src/app-pwa/transport.ts +22 -0
  137. package/src/approvals/messages.ts +2 -2
  138. package/src/browser/cloud-id.ts +86 -0
  139. package/src/browser/resource-clipboard.ts +4 -0
  140. package/src/browser/resource-links.ts +52 -0
  141. package/src/browser/session.ts +39 -0
  142. package/src/browser/shared-text-history-validation.ts +151 -0
  143. package/src/browser/shared-text-history.ts +464 -0
  144. package/src/browser/shared-text-offline-authorized.ts +29 -0
  145. package/src/browser/shared-text-offline.ts +351 -0
  146. package/src/browser/shared-text-provider.ts +714 -0
  147. package/src/browser/shared-text-table-contract.ts +150 -0
  148. package/src/browser/shared-text.ts +19 -0
  149. package/src/capabilities/server-invoke.ts +2 -11
  150. package/src/capabilities/testing.ts +5 -2
  151. package/src/cli/access.ts +6 -5
  152. package/src/cli/admin/ai-models.ts +199 -0
  153. package/src/cli/admin/ai-quotas.ts +6 -102
  154. package/src/cli/admin/ai-usage.ts +4 -4
  155. package/src/cli/admin/app-credentials.ts +5 -3
  156. package/src/cli/admin/index.ts +5 -2
  157. package/src/cli/apps.ts +89 -26
  158. package/src/cli/capabilities.ts +22 -1
  159. package/src/cli/events.ts +320 -0
  160. package/src/config/env.ts +40 -0
  161. package/src/contracts/app-access.ts +26 -2
  162. package/src/contracts/app-details.ts +89 -0
  163. package/src/contracts/app-events.ts +51 -0
  164. package/src/contracts/app.ts +21 -5
  165. package/src/contracts/capabilities.ts +143 -3
  166. package/src/contracts/index.ts +2 -0
  167. package/src/contracts/notification-types.ts +36 -9
  168. package/src/contracts/pwa.ts +17 -1
  169. package/src/contracts/registry.ts +6 -0
  170. package/src/contracts/resource-clipboard.ts +35 -0
  171. package/src/contracts/resource-pages.ts +140 -0
  172. package/src/contracts/resource-resolution.ts +13 -0
  173. package/src/contracts/shared.ts +6 -1
  174. package/src/contracts/user-notifications.ts +2 -0
  175. package/src/events/app-event-delivery.ts +100 -0
  176. package/src/events/app-events.testing.ts +65 -0
  177. package/src/events/app-events.ts +134 -0
  178. package/src/events/core-user-created.ts +24 -0
  179. package/src/events/index.ts +9 -0
  180. package/src/events/testing.ts +2 -0
  181. package/src/index.ts +1 -1
  182. package/src/server/api/respond.ts +17 -4
  183. package/src/server/index.ts +2 -0
  184. package/src/server/middleware/app-access.ts +1 -1
  185. package/src/server/middleware/auth.ts +21 -7
  186. package/src/server/middleware/index.ts +1 -0
  187. package/src/server/middleware/invocation.ts +2 -0
  188. package/src/server/public-storage.ts +106 -0
  189. package/src/server/services/access.ts +31 -10
  190. package/src/server/services/index.ts +1 -0
  191. package/src/services/accounts/app.ts +36 -13
  192. package/src/services/accounts/entities.ts +43 -19
  193. package/src/services/accounts/group-sql.ts +23 -0
  194. package/src/services/accounts/groups.ts +19 -14
  195. package/src/services/accounts/identities.ts +70 -11
  196. package/src/services/accounts/identity-reconciliation.ts +3 -1
  197. package/src/services/accounts/local-groups.ts +69 -4
  198. package/src/services/accounts/users.ts +45 -14
  199. package/src/services/app-access.ts +25 -7
  200. package/src/services/app-details.ts +224 -0
  201. package/src/services/app-discovery.ts +2 -1
  202. package/src/services/app-event-shapes.ts +139 -0
  203. package/src/services/app-events/accept.ts +116 -0
  204. package/src/services/app-events/admin.ts +250 -0
  205. package/src/services/app-events/declarations.ts +187 -0
  206. package/src/services/app-events/deliver.ts +218 -0
  207. package/src/services/app-events.ts +92 -0
  208. package/src/services/config-conflict.ts +11 -0
  209. package/src/services/core-user-events.ts +1 -0
  210. package/src/services/freeipa-config.ts +9 -0
  211. package/src/services/geo/ledger.ts +28 -10
  212. package/src/services/geo/service.ts +43 -24
  213. package/src/services/identity/core-origin.ts +12 -0
  214. package/src/services/identity/invocation-actor.ts +20 -2
  215. package/src/services/identity/invocation-authority.ts +4 -0
  216. package/src/services/identity/invocation-token.ts +238 -56
  217. package/src/services/identity/issue-invocation.ts +6 -1
  218. package/src/services/image-metadata.ts +65 -1
  219. package/src/services/index.ts +13 -1
  220. package/src/services/ipa/sync.ts +6 -2
  221. package/src/services/ipa/users.ts +4 -1
  222. package/src/services/mandates/index.ts +2 -2
  223. package/src/services/mandates/policy.ts +20 -12
  224. package/src/services/notifications/browser.ts +4 -0
  225. package/src/services/notifications/catalog.ts +5 -2
  226. package/src/services/notifications/platform.ts +7 -2
  227. package/src/services/notifications/user.ts +18 -7
  228. package/src/services/oauth-tokens.ts +28 -2
  229. package/src/services/object-store.ts +217 -0
  230. package/src/services/providers/local/users.ts +5 -0
  231. package/src/services/pwa-devices.ts +147 -37
  232. package/src/services/session/index.ts +26 -3
  233. package/src/services/settings/core-settings.ts +71 -43
  234. package/src/services/settings/index.ts +8 -0
  235. package/src/services/settings/versions.ts +26 -0
  236. package/src/shared/ai-models.ts +274 -0
  237. package/src/shared/ai-quotas.ts +2 -2
  238. package/src/shared/ai-usage.ts +2 -2
  239. package/src/shared/email-html.ts +1 -0
  240. package/src/shared/index.ts +2 -0
  241. package/src/shared/markdown/extensions/katex.ts +8 -1
  242. package/src/shared/markdown/extensions/tables.ts +14 -1
  243. package/src/shared/markdown/index.ts +22 -11
  244. package/src/shared-text/anchors.ts +29 -0
  245. package/src/shared-text/awareness.ts +87 -0
  246. package/src/shared-text/changes.ts +27 -0
  247. package/src/shared-text/documents.ts +433 -65
  248. package/src/shared-text/events.ts +12 -2
  249. package/src/shared-text/index.ts +16 -1
  250. package/src/shared-text/log.ts +24 -5
  251. package/src/shared-text/snapshots.ts +69 -23
  252. package/src/shared-text/structural-history.ts +377 -0
  253. package/src/shared-text/table-actions.ts +432 -0
  254. package/src/shared-text/text-diff.ts +75 -0
  255. package/src/shared-text/types.ts +24 -5
  256. package/src/ssr/AdminArea.tsx +131 -0
  257. package/src/ssr/AdminLayout.tsx +9 -3
  258. package/src/ssr/AdminSidebar.tsx +7 -7
  259. package/src/ssr/AppAccessDenied.tsx +2 -0
  260. package/src/ssr/AppBadge.tsx +14 -0
  261. package/src/ssr/AppLaunchpadPanel.tsx +2 -2
  262. package/src/ssr/CloudShell.island.tsx +44 -7
  263. package/src/ssr/Layout.tsx +9 -7
  264. package/src/ssr/MobileProfileActions.tsx +2 -2
  265. package/src/ssr/ProfilePreferences.tsx +2 -1
  266. package/src/ssr/PwaLayout.tsx +16 -1
  267. package/src/ssr/PwaPartCount.island.tsx +23 -0
  268. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  269. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  270. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  271. package/src/ssr/admin-active-link.ts +16 -0
  272. package/src/ssr/admin-navigation.ts +26 -31
  273. package/src/ssr/app-badges.ts +31 -7
  274. package/src/ssr/app-launchpad.tsx +6 -1
  275. package/src/ssr/index.ts +8 -0
  276. package/src/ssr/page-trail-runtime.ts +3 -1
  277. package/src/ssr/platform-messages.ts +11 -1
  278. package/src/ssr/profile-actions.ts +3 -6
  279. package/src/ssr/pwa-messages.ts +2 -2
  280. package/src/ssr/rail-navigation.ts +5 -1
  281. package/src/ssr/settings-draft.ts +153 -0
  282. package/src/ssr/shell-data.tsx +3 -1
  283. package/src/styles/global.css +1 -0
  284. package/src/styles/profile-picture.css +270 -0
  285. package/src/styles/utilities-navigation.css +6 -0
  286. package/src/workflows/ai/policy.ts +49 -0
  287. package/src/workflows/ai/runtime.ts +131 -21
  288. package/src/workflows/ai/store.ts +283 -35
  289. package/src/workflows/ai/types.ts +8 -2
  290. package/src/workflows/ai-actions.ts +148 -120
  291. package/src/workflows/authoring.ts +31 -0
  292. package/src/workflows/contracts.ts +1 -0
  293. package/src/workflows/definition.ts +2 -0
  294. package/src/workflows/editor.ts +15 -0
  295. package/src/workflows/index.ts +2 -1
  296. package/src/workflows/language/compiler.ts +34 -3
  297. package/src/workflows/language/expressions.ts +3 -2
  298. package/src/workflows/runtime/executor.ts +134 -34
  299. package/src/workflows/runtime/index.ts +2 -0
  300. package/src/workflows/runtime/ports.ts +5 -1
  301. package/src/workflows/store/actions.ts +1 -1
  302. package/src/workflows/store/definitions.ts +5 -0
  303. package/src/workflows/store/runs.ts +5 -3
  304. package/src/workflows/store/worker.ts +7 -2
  305. package/src/ai/model-pricing.ts +0 -43
  306. package/src/ai/model-request-settings.ts +0 -98
  307. package/src/ai/settings-messages.ts +0 -129
  308. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -13,7 +13,7 @@ import type { AiConversationResourceObservation } from "./types";
13
13
 
14
14
  const LinkedResourceReferenceSchema = CloudResourceReferenceSchema.extend({
15
15
  href: CloudResourceHrefSchema.optional(),
16
- });
16
+ }).strip();
17
17
 
18
18
  export const addCloudResourceHrefs = <T>(
19
19
  value: T,
@@ -67,12 +67,14 @@ export const collectConversationResourceObservations = (...values: unknown[]): A
67
67
  return;
68
68
  }
69
69
 
70
- const view = CloudResourceViewSchema.safeParse(value);
70
+ const view = CloudResourceViewSchema.strip().safeParse(value);
71
71
  if (view.success) {
72
72
  const resource: CloudResourceView = view.data;
73
73
  observations.set(observationKey(resource.ref), {
74
74
  ref: resource.ref,
75
75
  title: resource.title,
76
+ context: resource.context,
77
+ status: resource.status,
76
78
  preview: resource.preview,
77
79
  icon: resource.icon,
78
80
  href: resource.links.find((link) => link.rel === "open")?.href,
@@ -87,6 +89,8 @@ export const collectConversationResourceObservations = (...values: unknown[]): A
87
89
  ...previous,
88
90
  ref,
89
91
  ...(resource.title !== undefined ? { title: resource.title } : {}),
92
+ ...(resource.context !== undefined ? { context: resource.context } : {}),
93
+ ...(resource.status !== undefined ? { status: resource.status } : {}),
90
94
  ...(resource.preview !== undefined ? { preview: resource.preview } : {}),
91
95
  ...(resource.icon !== undefined ? { icon: resource.icon } : {}),
92
96
  ...(resource.href !== undefined ? { href: resource.href } : {}),
package/src/ai/routes.ts CHANGED
@@ -8,7 +8,6 @@ import { ErrorResponseSchema } from "../contracts";
8
8
  import { APPROVAL_CLIENT_HEADER } from "../contracts/approvals";
9
9
  import { capabilityActionPolicy } from "../contracts/capabilities";
10
10
  import {
11
- type AccessSubject,
12
11
  type AuthContext,
13
12
  auth,
14
13
  err,
@@ -37,7 +36,7 @@ import { coreSettings } from "../services/settings/api";
37
36
  import { resolveAppIdentityPresentation } from "../shared/app-presentation";
38
37
  import { readThemeFromCookieHeader } from "../shared/theme";
39
38
  import { type AiStoredActionReview, isLegacyActionReview, isUnknownActionOutcome, storedActionReview } from "./action-review";
40
- import { assistantAiSettingsState, listAssistantAiModels, resolveAssistantAudioModel, selectAssistantAiModelId } from "./assistant-models";
39
+ import { assistantAiSettingsState, peopleAiModels, resolveAssistantAudioModel, selectAssistantAiModel } from "./assistant-models";
41
40
  import { AI_AUDIO_MAX_BYTES } from "./audio-format";
42
41
  import { buildAiCapabilityCatalog } from "./capabilities";
43
42
  import { checkAiCapability, resolveAiCapabilityActor } from "./capability-execution";
@@ -80,6 +79,7 @@ import {
80
79
  } from "./message-queue";
81
80
  import { aiActorUser, aiPrefsUserId, aiUserPrefs } from "./prefs";
82
81
  import { aiProjects } from "./projects";
82
+ import type { AiResourceContext } from "./projects-authorization";
83
83
  import { projectPublicAiStoredMessages, publicAiStoredMessages } from "./public-projection";
84
84
  import { AiRecentResourceSchema, listAiRecentResources } from "./recent-resources";
85
85
  import { RECENT_SENT_REF_LIMIT } from "./recent-sent-refs";
@@ -224,6 +224,8 @@ const MemoryCreateSchema = z.object({
224
224
  const MemoryUpdateSchema = MemoryCreateSchema.partial().refine((value) => Object.keys(value).length > 0, "Pass a changed field.");
225
225
  const MemoryIdSchema = z.string().regex(AI_SHORT_ID_PATTERN);
226
226
 
227
+ const resourceContext = (c: Context<AuthContext>): AiResourceContext => ({ actor: c.get("actor"), accessSubject: c.get("accessSubject") });
228
+
227
229
  const notFound = (c: Context<AuthContext>) => respond(c, fail(err.notFound("Conversation")));
228
230
  const fileNotFound = (c: Context<AuthContext>) => respond(c, fail(err.notFound("File")));
229
231
 
@@ -232,14 +234,14 @@ const publicConversation = (conversation: AiConversation, projectId: string | nu
232
234
  id: conversation.shortId,
233
235
  projectId: conversation.projectId ? projectId : null,
234
236
  });
235
- const publicConversations = async (conversations: AiConversation[], subject: AccessSubject) => {
237
+ const publicConversations = async (conversations: AiConversation[], subject: AiResourceContext) => {
236
238
  const projectIds = conversations.flatMap((conversation) => (conversation.projectId ? [conversation.projectId] : []));
237
239
  const shortIds = await aiProjects.resolveShortIds(projectIds, subject);
238
240
  return conversations.map((conversation) =>
239
241
  publicConversation(conversation, conversation.projectId ? (shortIds.get(conversation.projectId) ?? null) : null),
240
242
  );
241
243
  };
242
- const publicConversationFor = async (conversation: AiConversation, subject: AccessSubject) =>
244
+ const publicConversationFor = async (conversation: AiConversation, subject: AiResourceContext) =>
243
245
  (await publicConversations([conversation], subject))[0]!;
244
246
  const publicMemories = async (userId: string, memories: Awaited<ReturnType<typeof aiMemories.list>>) => {
245
247
  const sourceIds = memories.flatMap((memory) => (memory.sourceConversationId ? [memory.sourceConversationId] : []));
@@ -254,13 +256,13 @@ const publicMemory = async (userId: string, memory: Awaited<ReturnType<typeof ai
254
256
  (await publicMemories(userId, [memory]))[0]!;
255
257
 
256
258
  /** Fire-and-forget: remember the model this user actually ran a turn with (preselected for new chats). */
257
- const rememberLastUsedModel = (actor: RequestActor, modelProfileId: string | null | undefined): void => {
259
+ const rememberLastUsedModel = (actor: RequestActor, model: import("../shared/ai-models").AiModelRequest | undefined): void => {
258
260
  const userId = aiPrefsUserId(actor);
259
- if (!userId || !modelProfileId) return;
260
- void aiUserPrefs.update(userId, { lastModelId: modelProfileId }).catch(() => undefined);
261
+ if (!userId || !model) return;
262
+ void aiUserPrefs.update(userId, { selection: model }).catch(() => undefined);
261
263
  };
262
264
 
263
- const conversationDetail = async (conversation: AiConversation, subject: AccessSubject) => {
265
+ const conversationDetail = async (conversation: AiConversation, subject: AiResourceContext) => {
264
266
  const [state, timeline, publicView] = await Promise.all([
265
267
  loadAiStreamState(conversation),
266
268
  aiConversations.listConversationTimeline({ conversationId: conversation.id }),
@@ -297,7 +299,7 @@ const prepareConversationForMessageRetry = async (conversationId: string): Promi
297
299
 
298
300
  const prepareLaunchDraft = async (
299
301
  body: z.infer<typeof AiCreateConversationInputSchema>,
300
- subject: AccessSubject | null,
302
+ subject: AiResourceContext | null,
301
303
  authority: import("../server").RequestAuthority | null = null,
302
304
  ) => {
303
305
  const content = [...(body.draft?.content ?? [])];
@@ -348,7 +350,7 @@ export const aiRoutes = (() => {
348
350
  return c.json(await getAiChatQuotas(c.get("accessSubject")));
349
351
  })
350
352
  .get("/models", async (c) => {
351
- return respond(c, ok(await listAssistantAiModels(c.get("accessSubject"))));
353
+ return respond(c, ok(await peopleAiModels(c.get("accessSubject"))));
352
354
  })
353
355
  .get("/prefs", async (c) => {
354
356
  const ctx = await resolveContext(c);
@@ -438,16 +440,15 @@ export const aiRoutes = (() => {
438
440
  const state = await readAiSettingsState();
439
441
  let previewProfile: ReturnType<typeof selectAiModelProfile> | null = null;
440
442
  if (state.ok && state.enabled) {
441
- const models = await listAssistantAiModels(c.get("accessSubject"), ctx.modelPolicy);
442
- const preferredId =
443
- models.find((model) => model.id === prefs?.lastModelId)?.id ??
444
- models.find((model) => model.id === state.defaultModelId)?.id ??
445
- models[0]?.id;
446
- if (preferredId) previewProfile = selectAiModelProfile(state, ctx.modelPolicy, preferredId);
443
+ try {
444
+ previewProfile = selectAiModelProfile(state, ctx.modelPolicy, prefs.selection ?? undefined);
445
+ } catch {
446
+ /* The prompt preview remains available without a usable model. */
447
+ }
447
448
  }
448
449
  const toolsSupported = Boolean(previewProfile?.capabilities.includes("tools"));
449
450
  const availableSkills = toolsSupported
450
- ? (await aiSkills.list(c.get("accessSubject"), { authority: appAccessAuthority(c) })).filter((skill) => skill.enabled)
451
+ ? (await aiSkills.list(resourceContext(c), { authority: appAccessAuthority(c) })).filter((skill) => skill.enabled)
451
452
  : [];
452
453
  const skillCatalog = selectAiSkillCatalog(
453
454
  availableSkills.filter((skill) => !skill.manualOnly).map(({ name, description }) => ({ name, description })),
@@ -462,8 +463,8 @@ export const aiRoutes = (() => {
462
463
  })),
463
464
  ...(client?.localTools === "ask" ? [createCloudAiLocalBashTool(), ...createCloudAiLocalTools()] : []),
464
465
  ...(memoryEnabled ? [createCloudAiMemoryTool()] : []),
465
- ...(availableSkills.length ? [createCloudAiLoadSkillTool(c.get("accessSubject"), appAccessAuthority(c))] : []),
466
- ...(skillCatalog.omitted > 0 ? [createCloudAiSearchSkillsTool(c.get("accessSubject"), [], appAccessAuthority(c))] : []),
466
+ ...(availableSkills.length ? [createCloudAiLoadSkillTool(resourceContext(c), appAccessAuthority(c))] : []),
467
+ ...(skillCatalog.omitted > 0 ? [createCloudAiSearchSkillsTool(resourceContext(c), [], appAccessAuthority(c))] : []),
467
468
  ]
468
469
  : [];
469
470
  const memoryToolEnabled = tools.some((tool) => tool.def.name === "memory");
@@ -546,7 +547,7 @@ export const aiRoutes = (() => {
546
547
  const ctx = await resolveContext(c);
547
548
  if (ctx instanceof Response) return ctx;
548
549
  const query = c.req.valid("query");
549
- const project = query.projectId ? await aiProjects.getByShortId(query.projectId, c.get("accessSubject"), "read") : null;
550
+ const project = query.projectId ? await aiProjects.getByShortId(query.projectId, resourceContext(c), "read") : null;
550
551
  if (query.projectId && !project) return respond(c, fail(err.notFound("Project")));
551
552
  return respond(
552
553
  c,
@@ -562,7 +563,7 @@ export const aiRoutes = (() => {
562
563
  unassigned: query.unassigned,
563
564
  limit: query.limit,
564
565
  }),
565
- c.get("accessSubject"),
566
+ resourceContext(c),
566
567
  ),
567
568
  ),
568
569
  );
@@ -571,7 +572,7 @@ export const aiRoutes = (() => {
571
572
  const ctx = await resolveContext(c);
572
573
  if (ctx instanceof Response) return ctx;
573
574
  const query = c.req.valid("query");
574
- const project = query.projectId ? await aiProjects.getByShortId(query.projectId, c.get("accessSubject"), "read") : null;
575
+ const project = query.projectId ? await aiProjects.getByShortId(query.projectId, resourceContext(c), "read") : null;
575
576
  if (query.projectId && !project) return respond(c, fail(err.notFound("Project")));
576
577
  const page = await aiConversations.listConversationsPage({
577
578
  ownerUserId: ctx.ownerUserId,
@@ -584,15 +585,15 @@ export const aiRoutes = (() => {
584
585
  page: query.page ?? 1,
585
586
  perPage: query.perPage ?? 20,
586
587
  });
587
- return respond(c, ok({ ...page, items: await publicConversations(page.items, c.get("accessSubject")) }));
588
+ return respond(c, ok({ ...page, items: await publicConversations(page.items, resourceContext(c)) }));
588
589
  })
589
590
  .post("/conversations", v("json", AiCreateConversationInputSchema), async (c) => {
590
591
  const ctx = await resolveContext(c);
591
592
  if (ctx instanceof Response) return ctx;
592
593
  const body = c.req.valid("json");
593
- const project = body.projectId ? await aiProjects.getByShortId(body.projectId, c.get("accessSubject"), "read") : null;
594
+ const project = body.projectId ? await aiProjects.getByShortId(body.projectId, resourceContext(c), "read") : null;
594
595
  if (body.projectId && !project) return respond(c, fail(err.notFound("Project")));
595
- const draft = await prepareLaunchDraft(body, c.get("accessSubject"), appAccessAuthority(c));
596
+ const draft = await prepareLaunchDraft(body, resourceContext(c), appAccessAuthority(c));
596
597
  if (!draft.ok) return respond(c, draft);
597
598
  const requestedCapabilityTools = (body.preloadTools ?? []).filter(
598
599
  (requested): requested is { appId: string; kind: "query" | "action"; id: string } => "appId" in requested,
@@ -643,9 +644,7 @@ export const aiRoutes = (() => {
643
644
  launchedByAppId: body.launchedByAppId,
644
645
  });
645
646
  // A replay may return a conversation assigned to a Project since creation.
646
- const currentProject = conversation.projectId
647
- ? await aiProjects.get(conversation.projectId, c.get("accessSubject"), "read")
648
- : null;
647
+ const currentProject = conversation.projectId ? await aiProjects.get(conversation.projectId, resourceContext(c), "read") : null;
649
648
  if (conversation.projectId && !currentProject) return respond(c, fail(err.notFound("Project")));
650
649
  return respond(c, ok(publicConversation(conversation, currentProject?.shortId ?? null)), 201);
651
650
  } catch (error) {
@@ -660,7 +659,7 @@ export const aiRoutes = (() => {
660
659
  if (ctx instanceof Response) return ctx;
661
660
  const conversation = await loadConversation(c, ctx);
662
661
  if (!conversation) return notFound(c);
663
- return respond(c, ok(await conversationDetail(conversation, c.get("accessSubject"))));
662
+ return respond(c, ok(await conversationDetail(conversation, resourceContext(c))));
664
663
  })
665
664
  .get(
666
665
  "/conversations/:conversationId/submission/:draftRevision",
@@ -670,7 +669,7 @@ export const aiRoutes = (() => {
670
669
  if (ctx instanceof Response) return ctx;
671
670
  const conversation = await loadConversation(c, ctx);
672
671
  if (!conversation) return notFound(c);
673
- if (conversation.projectId && !(await aiProjects.get(conversation.projectId, c.get("accessSubject"), "read"))) return notFound(c);
672
+ if (conversation.projectId && !(await aiProjects.get(conversation.projectId, resourceContext(c), "read"))) return notFound(c);
674
673
  const submission = await aiConversations.getDraftSubmission({
675
674
  conversationId: conversation.id,
676
675
  ownerUserId: ctx.ownerUserId,
@@ -798,7 +797,7 @@ export const aiRoutes = (() => {
798
797
  pinned: body.pinned,
799
798
  });
800
799
  if (!updated) return notFound(c);
801
- return respond(c, ok(await publicConversationFor(updated, c.get("accessSubject"))));
800
+ return respond(c, ok(await publicConversationFor(updated, resourceContext(c))));
802
801
  })
803
802
  .put("/conversations/:conversationId/project", v("json", ConversationProjectInputSchema), async (c) => {
804
803
  const ctx = await resolveContext(c);
@@ -806,7 +805,7 @@ export const aiRoutes = (() => {
806
805
  const conversation = await loadConversation(c, ctx);
807
806
  if (!conversation) return notFound(c);
808
807
  const body = c.req.valid("json");
809
- const project = body.projectId ? await aiProjects.getByShortId(body.projectId, c.get("accessSubject"), "read") : null;
808
+ const project = body.projectId ? await aiProjects.getByShortId(body.projectId, resourceContext(c), "read") : null;
810
809
  if (body.projectId && !project) return respond(c, fail(err.notFound("Project")));
811
810
  const result = await aiConversations.setConversationProject({
812
811
  conversationId: conversation.id,
@@ -821,7 +820,7 @@ export const aiRoutes = (() => {
821
820
  }
822
821
  return notFound(c);
823
822
  }
824
- return respond(c, ok(await publicConversationFor(result.conversation, c.get("accessSubject"))));
823
+ return respond(c, ok(await publicConversationFor(result.conversation, resourceContext(c))));
825
824
  })
826
825
  .post("/conversations/:conversationId/pin", async (c) => {
827
826
  const ctx = await resolveContext(c);
@@ -833,7 +832,7 @@ export const aiRoutes = (() => {
833
832
  ownerUserId: ctx.ownerUserId,
834
833
  pinned: true,
835
834
  });
836
- return updated ? respond(c, ok(await publicConversationFor(updated, c.get("accessSubject")))) : notFound(c);
835
+ return updated ? respond(c, ok(await publicConversationFor(updated, resourceContext(c)))) : notFound(c);
837
836
  })
838
837
  .delete("/conversations/:conversationId/pin", async (c) => {
839
838
  const ctx = await resolveContext(c);
@@ -845,7 +844,7 @@ export const aiRoutes = (() => {
845
844
  ownerUserId: ctx.ownerUserId,
846
845
  pinned: false,
847
846
  });
848
- return updated ? respond(c, ok(await publicConversationFor(updated, c.get("accessSubject")))) : notFound(c);
847
+ return updated ? respond(c, ok(await publicConversationFor(updated, resourceContext(c)))) : notFound(c);
849
848
  })
850
849
  .put("/conversations/:conversationId/done", v("json", z.object({ done: z.boolean().nullable() })), async (c) => {
851
850
  const ctx = await resolveContext(c);
@@ -861,7 +860,7 @@ export const aiRoutes = (() => {
861
860
  return result.reason === "active_turn"
862
861
  ? respond(c, fail(err.conflict("Stop the current response before marking this chat done.")))
863
862
  : notFound(c);
864
- return respond(c, ok(await publicConversationFor(result.conversation, c.get("accessSubject"))));
863
+ return respond(c, ok(await publicConversationFor(result.conversation, resourceContext(c))));
865
864
  })
866
865
  .post("/conversations/:conversationId/archive", async (c) => {
867
866
  const ctx = await resolveContext(c);
@@ -911,7 +910,7 @@ export const aiRoutes = (() => {
911
910
  error: error instanceof Error ? error.message : String(error),
912
911
  }),
913
912
  );
914
- return restored ? respond(c, ok(await publicConversationFor(restored, c.get("accessSubject")))) : notFound(c);
913
+ return restored ? respond(c, ok(await publicConversationFor(restored, resourceContext(c)))) : notFound(c);
915
914
  })
916
915
  .post("/conversations/:conversationId/viewed", async (c) => {
917
916
  const ctx = await resolveContext(c);
@@ -1008,7 +1007,7 @@ export const aiRoutes = (() => {
1008
1007
  };
1009
1008
  });
1010
1009
  const { input, message } = aiInputToUserMessage(aiTurnInputToContent({ content: turnContent }));
1011
- const project = conversation.projectId ? await aiProjects.snapshot(conversation.projectId, c.get("accessSubject")) : null;
1010
+ const project = conversation.projectId ? await aiProjects.snapshot(conversation.projectId, resourceContext(c)) : null;
1012
1011
  if (conversation.projectId && !project) return respond(c, fail(err.notFound("Project")));
1013
1012
  try {
1014
1013
  const submission = {
@@ -1022,7 +1021,7 @@ export const aiRoutes = (() => {
1022
1021
  locale: getLocale(c),
1023
1022
  theme: readThemeFromCookieHeader(c.req.header("cookie")),
1024
1023
  timeZone: getTimeZone(c),
1025
- requestedModelId: body.modelProfileId ?? project?.defaultModelProfileId ?? undefined,
1024
+ model: body.model ?? project?.defaultModel ?? undefined,
1026
1025
  modelPolicy: ctx.modelPolicy,
1027
1026
  project: project ?? undefined,
1028
1027
  clientToolIds: body.clientToolIds,
@@ -1043,7 +1042,7 @@ export const aiRoutes = (() => {
1043
1042
  return respond(c, ok({ queued: true }));
1044
1043
  }
1045
1044
  const result = await submitAiChatTurn(submission);
1046
- rememberLastUsedModel(ctx.actor, result.turn.modelProfileId);
1045
+ rememberLastUsedModel(ctx.actor, body.model);
1047
1046
  return respond(
1048
1047
  c,
1049
1048
  ok({
@@ -1111,13 +1110,13 @@ export const aiRoutes = (() => {
1111
1110
  ? await aiConversations.getTurnRunConfig({ conversationId: conversation.id, turnId: target.loopId })
1112
1111
  : null;
1113
1112
  const originalProject = originalRunConfig?.kind !== "compact" ? originalRunConfig?.project : undefined;
1114
- const currentProject = originalProject?.id ? await aiProjects.getByShortId(originalProject.id, c.get("accessSubject")) : null;
1113
+ const currentProject = originalProject?.id ? await aiProjects.getByShortId(originalProject.id, resourceContext(c)) : null;
1115
1114
  if (originalProject && !currentProject) return respond(c, fail(err.notFound("Project")));
1116
1115
 
1117
1116
  try {
1118
- const requestedModelId = await selectAssistantAiModelId(
1117
+ const model = await selectAssistantAiModel(
1119
1118
  c.get("accessSubject"),
1120
- body.modelProfileId ?? originalProject?.defaultModelProfileId ?? undefined,
1119
+ body.model ?? originalProject?.defaultModel ?? undefined,
1121
1120
  ctx.modelPolicy,
1122
1121
  );
1123
1122
  if ((await prepareConversationForMessageRetry(conversation.id)) === "busy") {
@@ -1134,7 +1133,7 @@ export const aiRoutes = (() => {
1134
1133
  locale: getLocale(c),
1135
1134
  theme: readThemeFromCookieHeader(c.req.header("cookie")),
1136
1135
  timeZone: getTimeZone(c),
1137
- requestedModelId,
1136
+ model,
1138
1137
  modelPolicy: ctx.modelPolicy,
1139
1138
  systemPrompt,
1140
1139
  project: originalProject,
@@ -1158,7 +1157,7 @@ export const aiRoutes = (() => {
1158
1157
  }
1159
1158
  : undefined,
1160
1159
  });
1161
- rememberLastUsedModel(ctx.actor, result.turn.modelProfileId);
1160
+ rememberLastUsedModel(ctx.actor, body.model);
1162
1161
  return respond(
1163
1162
  c,
1164
1163
  ok({
@@ -1190,7 +1189,7 @@ export const aiRoutes = (() => {
1190
1189
  }
1191
1190
 
1192
1191
  const body = c.req.valid("json");
1193
- if (conversation.projectId && !(await aiProjects.get(conversation.projectId, c.get("accessSubject"), "read"))) {
1192
+ if (conversation.projectId && !(await aiProjects.get(conversation.projectId, resourceContext(c), "read"))) {
1194
1193
  return respond(c, fail(err.notFound("Project")));
1195
1194
  }
1196
1195
  const forked = await aiConversations.forkConversation({
@@ -1199,7 +1198,7 @@ export const aiRoutes = (() => {
1199
1198
  ownerUserId: ctx.ownerUserId,
1200
1199
  title: body.title ?? conversation.title,
1201
1200
  });
1202
- return respond(c, ok(await conversationDetail(forked, c.get("accessSubject"))));
1201
+ return respond(c, ok(await conversationDetail(forked, resourceContext(c))));
1203
1202
  })
1204
1203
  .get("/conversations/:conversationId/enrichment", async (c) => {
1205
1204
  const ctx = await resolveContext(c);
@@ -1230,7 +1229,7 @@ export const aiRoutes = (() => {
1230
1229
  const result = await submitAiCompaction({
1231
1230
  conversationId: conversation.id,
1232
1231
  actor: ctx.actor,
1233
- requestedModelId: body.modelProfileId,
1232
+ model: body.model,
1234
1233
  modelPolicy: ctx.modelPolicy,
1235
1234
  });
1236
1235
  return respond(c, ok({ turn: publicTurn(result.turn, conversation.shortId) }), 201);
package/src/ai/runtime.ts CHANGED
@@ -8,6 +8,7 @@ import { type ApprovalDecisionChannel, approvals } from "../services/approvals";
8
8
  import { logger } from "../services/logging";
9
9
  import { superviseRuntimeTask } from "../services/runtime-lifecycle";
10
10
  import { coreSettings } from "../services/settings/api";
11
+ import type { AiModelRequest } from "../shared/ai-models";
11
12
  import {
12
13
  AI_WEBSITE_APPROVAL_TOOL,
13
14
  type AiToolApprovalChangeContext,
@@ -15,16 +16,17 @@ import {
15
16
  aiTurnAllowsRememberedApprovals,
16
17
  rememberAiToolApproval,
17
18
  } from "./approvals";
18
- import { aiChatAccessSubject, selectAssistantAiModelId } from "./assistant-models";
19
+ import { aiChatAccessSubject, selectAssistantAiModel } from "./assistant-models";
19
20
  import { startAiDictationRuntime } from "./dictation-runtime";
20
21
  import { AiTurnExecutor, type ExecutorConfig } from "./executor";
21
22
  import { canonicalizeAiConversationAttachments, snapshotAiConversationFiles } from "./file-context";
22
23
  import type { AiLocalClient, AiLocalContext } from "./local-tool-contracts";
23
24
  import { drainQueuedMessages } from "./message-queue";
25
+ import { aiUserPrefs } from "./prefs";
24
26
  import { AI_TURN_LEASE_MS } from "./protocol";
25
27
  import { aiQuotas } from "./quotas";
26
28
  import { parseAiResourceMarker } from "./resource-markers";
27
- import { isAiVisionModelConfigured } from "./settings";
29
+ import { isAiVisionModelConfigured, readAiSettingsState } from "./settings";
28
30
  import { aiConversations } from "./store";
29
31
  import { publishAiWireEvent } from "./stream";
30
32
  import { aiToolAudit } from "./tool-audit";
@@ -110,7 +112,7 @@ export type SubmitAiChatTurnInput = {
110
112
  theme?: "light" | "dark";
111
113
  timeZone?: string;
112
114
  modelPolicy?: AiModelPolicy;
113
- requestedModelId?: string;
115
+ model?: AiModelRequest;
114
116
  /** Optional instructions that apply only to this turn. */
115
117
  systemPrompt?: string;
116
118
  project?: AiChatTurnRunConfig["project"];
@@ -143,21 +145,43 @@ export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
143
145
  );
144
146
  const canonicalInput = files?.attached.length ? inputMessage.content : input.input;
145
147
  const hasImageAttachments = Boolean(files?.attached.some((file) => isAiImageMediaType(file.mediaType)));
148
+ const state = await readAiSettingsState();
146
149
  const canInspectAttachedImages =
147
150
  input.canInspectAttachedImages ??
148
- (input.toolSource?.kind === "default" && (await isAiVisionModelConfigured(input.modelPolicy?.allowedDataBoundaries)));
149
- const requestedModelId = input.assistantChat
150
- ? await selectAssistantAiModelId(aiChatAccessSubject(input.actor), input.requestedModelId, input.modelPolicy)
151
- : input.requestedModelId;
152
- const { resolved } = await validateAiTurnRequest({
153
- input: canonicalInput,
154
- hasImageAttachments,
155
- canInspectAttachedImages,
156
- modelPolicy: input.modelPolicy,
157
- requestedModelId,
158
- });
151
+ (input.toolSource?.kind === "default" && (await isAiVisionModelConfigured(input.modelPolicy?.allowedDataBoundaries, state)));
152
+ let requested = input.model;
153
+ let previousLabel: string | undefined;
154
+ if (input.assistantChat) {
155
+ const latest = await aiConversations.getLatestTurn({ conversationId: input.conversationId, kind: "chat" });
156
+ const previous = latest ? await aiConversations.getTurnRunConfig({ conversationId: input.conversationId, turnId: latest.id }) : null;
157
+ const inherited = previous?.kind === "chat" ? previous.model : undefined;
158
+ requested ??= inherited;
159
+ if (inherited && "id" in inherited && requested && "id" in requested && requested.id === inherited.id) {
160
+ if (!state.profiles.some((profile) => profile.id === inherited.id)) {
161
+ previousLabel = previous?.kind === "chat" ? (previous.modelLabel ?? inherited.id) : inherited.id;
162
+ const subject = aiChatAccessSubject(input.actor);
163
+ const prefs = subject?.type === "user" ? await aiUserPrefs.get(subject.userId) : null;
164
+ requested = prefs?.selection && "class" in prefs.selection ? prefs.selection : { class: "balanced" };
165
+ }
166
+ }
167
+ }
168
+ const model = input.assistantChat
169
+ ? await selectAssistantAiModel(aiChatAccessSubject(input.actor), requested, input.modelPolicy, state)
170
+ : requested;
171
+ const { resolved } = await validateAiTurnRequest(
172
+ {
173
+ input: canonicalInput,
174
+ hasImageAttachments,
175
+ canInspectAttachedImages,
176
+ modelPolicy: input.modelPolicy,
177
+ model,
178
+ },
179
+ state,
180
+ );
159
181
  const quotaSubject = aiChatAccessSubject(input.actor);
160
- if (input.assistantChat && quotaSubject) await aiQuotas.assertAllowed(quotaSubject, resolved.profile.id);
182
+ if (input.assistantChat && quotaSubject) await aiQuotas.assertAllowed(quotaSubject, resolved.profile.id, state);
183
+ const modelLabel =
184
+ resolved.profile.label + (resolved.profile.id !== resolved.profile.modelId ? ` · ${resolved.profile.variantLabel}` : "");
161
185
  const runConfig: AiChatTurnRunConfig = {
162
186
  kind: "chat",
163
187
  selectedSkillIds: [
@@ -177,7 +201,10 @@ export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
177
201
  ...(input.theme ? { theme: input.theme } : {}),
178
202
  ...(input.timeZone ? { timeZone: input.timeZone } : {}),
179
203
  modelPolicy: input.modelPolicy,
180
- requestedModelId,
204
+ model,
205
+ modelResolved: resolved.model,
206
+ modelLabel,
207
+ ...(previousLabel ? { modelReplacement: { kind: "model_replaced", previousLabel, nextLabel: modelLabel } } : {}),
181
208
  systemPrompt: input.systemPrompt,
182
209
  project: input.project,
183
210
  files,
@@ -191,7 +218,7 @@ export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
191
218
 
192
219
  return {
193
220
  conversationId: input.conversationId,
194
- modelProfileId: resolved.profile.id,
221
+ modelId: resolved.profile.id,
195
222
  runConfig,
196
223
  userMessage,
197
224
  truncateFromSeq: input.truncateFromSeq,
@@ -244,7 +271,7 @@ export const deliverAiInterChatMessage = async (input: {
244
271
  };
245
272
  const delivered = await aiConversations.deliverInterChatMessage({
246
273
  messageId: input.message.id,
247
- modelProfileId: resolved.profile.id,
274
+ modelId: resolved.profile.id,
248
275
  runConfig,
249
276
  userMessage: { role: "user", content: [{ type: "text", text }] },
250
277
  sourceHref: input.sourceHref,
@@ -257,20 +284,21 @@ export type SubmitAiCompactionInput = {
257
284
  conversationId: string;
258
285
  actor?: RequestActor;
259
286
  modelPolicy?: AiModelPolicy;
260
- requestedModelId?: string;
287
+ model?: AiModelRequest;
261
288
  };
262
289
 
263
290
  export const submitAiCompaction = async (input: SubmitAiCompactionInput): Promise<{ turn: AiTurn }> => {
264
- const { resolved } = await validateAiTurnRequest({ input: "", modelPolicy: input.modelPolicy, requestedModelId: input.requestedModelId });
291
+ const { resolved } = await validateAiTurnRequest({ input: "", modelPolicy: input.modelPolicy, model: input.model });
265
292
  const runConfig: AiCompactionTurnRunConfig = {
266
293
  kind: "compact",
267
294
  actor: input.actor,
268
295
  modelPolicy: input.modelPolicy,
269
- requestedModelId: input.requestedModelId,
296
+ model: { id: resolved.profile.id },
297
+ modelResolved: resolved.model,
270
298
  };
271
299
  const turn = await aiConversations.createCompactionTurn({
272
300
  conversationId: input.conversationId,
273
- modelProfileId: resolved.profile.id,
301
+ modelId: resolved.profile.id,
274
302
  runConfig,
275
303
  });
276
304
  await enqueueAiTurn({ conversationId: input.conversationId, turnId: turn.id });