@k2b/cloud 0.42.0 → 0.44.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 (352) hide show
  1. package/package.json +13 -5
  2. package/scripts/app-favicon.ts +7 -6
  3. package/scripts/build.ts +3 -0
  4. package/scripts/installable-assets.ts +61 -0
  5. package/scripts/preload.ts +3 -0
  6. package/src/_internal/app-access-path.ts +12 -0
  7. package/src/_internal/app-access-request.ts +13 -0
  8. package/src/_internal/app-access-resources.ts +141 -0
  9. package/src/_internal/app-event-catalog.ts +40 -0
  10. package/src/_internal/app-event-schemas.ts +127 -0
  11. package/src/_internal/application-package.ts +40 -0
  12. package/src/_internal/built-in-app-ids.json +27 -0
  13. package/src/_internal/capabilities.ts +185 -12
  14. package/src/_internal/capability-origin.ts +4 -0
  15. package/src/_internal/capability-streams.ts +10 -5
  16. package/src/_internal/define-app.ts +159 -45
  17. package/src/_internal/installable-assets.ts +90 -0
  18. package/src/_internal/installable-head.ts +22 -0
  19. package/src/_internal/object-store-area.ts +9 -0
  20. package/src/_internal/object-store-config.ts +101 -0
  21. package/src/_internal/object-store-key.ts +14 -0
  22. package/src/_internal/page-responses.ts +34 -3
  23. package/src/_internal/process-identity.ts +7 -2
  24. package/src/_internal/registry-validation.ts +15 -1
  25. package/src/_internal/registry.ts +44 -1
  26. package/src/_internal/runtime-context.ts +9 -1
  27. package/src/_internal/runtime-watcher.ts +8 -0
  28. package/src/_internal/status-preserving-ssr.ts +12 -1
  29. package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
  30. package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
  31. package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
  32. package/src/access/AppAccessEditor.tsx +380 -0
  33. package/src/access/PermissionEditor.browser-harness.tsx +22 -2
  34. package/src/access/PermissionEditor.tsx +386 -77
  35. package/src/access/PrincipalAvatar.tsx +63 -0
  36. package/src/access/PrincipalPicker.tsx +29 -8
  37. package/src/access/ResourceApiKeys.tsx +3 -4
  38. package/src/access/app-access-adapter.ts +51 -0
  39. package/src/access/app-access-messages.ts +172 -0
  40. package/src/access/messages.ts +14 -0
  41. package/src/access/ui.ts +4 -1
  42. package/src/account/Avatar.tsx +21 -3
  43. package/src/account/ProfilePictureDialog.tsx +699 -0
  44. package/src/account/profile-picture-messages.ts +269 -0
  45. package/src/account/ui.ts +8 -1
  46. package/src/ai/admin.ts +3 -0
  47. package/src/ai/app-frame.ts +12 -0
  48. package/src/ai/application-tasks.ts +101 -0
  49. package/src/ai/assistant-models.ts +126 -64
  50. package/src/ai/capabilities.ts +19 -4
  51. package/src/ai/capability-execution.ts +5 -2
  52. package/src/ai/chart-block.ts +63 -30
  53. package/src/ai/chat/chart-block.tsx +12 -3
  54. package/src/ai/chat/composer-adapter.ts +8 -7
  55. package/src/ai/chat/message-actions.tsx +1 -1
  56. package/src/ai/chat/message-utils.ts +2 -2
  57. package/src/ai/chat/presentation.tsx +22 -1
  58. package/src/ai/chat-quotas.ts +5 -4
  59. package/src/ai/chat-tasks.ts +2 -2
  60. package/src/ai/client/controller.ts +11 -10
  61. package/src/ai/client/projection.ts +3 -3
  62. package/src/ai/code-capability-routes.ts +1 -0
  63. package/src/ai/compaction.ts +3 -3
  64. package/src/ai/credentials.ts +13 -114
  65. package/src/ai/default-tool-contracts.ts +4 -3
  66. package/src/ai/dictation-runtime.ts +4 -4
  67. package/src/ai/enrich.ts +2 -2
  68. package/src/ai/executor.ts +38 -27
  69. package/src/ai/http.ts +17 -29
  70. package/src/ai/index.ts +11 -5
  71. package/src/ai/inference-calls.ts +6 -4
  72. package/src/ai/maintenance.ts +1 -1
  73. package/src/ai/memory-learning-runs.ts +5 -5
  74. package/src/ai/memory-learning.ts +3 -3
  75. package/src/ai/message-queue.ts +1 -2
  76. package/src/ai/migrate.ts +75 -42
  77. package/src/ai/model-access-migrate.ts +3 -17
  78. package/src/ai/model-access.ts +44 -85
  79. package/src/ai/model-card.tsx +134 -0
  80. package/src/ai/model-catalog.ts +359 -0
  81. package/src/ai/model-chooser-messages.ts +88 -0
  82. package/src/ai/model-chooser.tsx +577 -0
  83. package/src/ai/model-delete.ts +234 -0
  84. package/src/ai/model-fixture.ts +39 -0
  85. package/src/ai/model-icon.tsx +36 -0
  86. package/src/ai/model-overview.ts +53 -0
  87. package/src/ai/model-usage.ts +45 -0
  88. package/src/ai/openai-api-messages.ts +0 -4
  89. package/src/ai/openai-api-slots.ts +10 -2
  90. package/src/ai/openai-api.ts +83 -16
  91. package/src/ai/personal-agent.ts +1 -1
  92. package/src/ai/prefs.ts +12 -10
  93. package/src/ai/projects-routes.ts +2 -1
  94. package/src/ai/projects.ts +13 -12
  95. package/src/ai/protocol.ts +2 -2
  96. package/src/ai/provider-connections.ts +161 -0
  97. package/src/ai/provider-errors.ts +138 -0
  98. package/src/ai/provider-fetch.ts +4 -0
  99. package/src/ai/provider-retry.ts +1 -1
  100. package/src/ai/provider.ts +2 -1
  101. package/src/ai/quota-limits.ts +36 -0
  102. package/src/ai/quota-provider.ts +11 -2
  103. package/src/ai/quota-report.ts +7 -4
  104. package/src/ai/quotas-migrate.ts +1 -0
  105. package/src/ai/quotas.ts +19 -8
  106. package/src/ai/recent-resources.ts +6 -0
  107. package/src/ai/request-headers.ts +14 -87
  108. package/src/ai/resource-refs.ts +6 -2
  109. package/src/ai/routes.ts +17 -18
  110. package/src/ai/runtime.ts +50 -22
  111. package/src/ai/settings.ts +93 -580
  112. package/src/ai/store.ts +45 -23
  113. package/src/ai/stream.ts +1 -1
  114. package/src/ai/structured-runs.ts +2 -2
  115. package/src/ai/structured.ts +29 -46
  116. package/src/ai/task-contracts.ts +25 -5
  117. package/src/ai/task-execution.ts +15 -6
  118. package/src/ai/task-model.ts +9 -0
  119. package/src/ai/transcription.ts +37 -21
  120. package/src/ai/turn-failure.ts +24 -2
  121. package/src/ai/types.ts +50 -62
  122. package/src/ai/ui.tsx +13 -0
  123. package/src/ai/usage.ts +8 -8
  124. package/src/ai/validate.ts +8 -7
  125. package/src/ai/view-block.ts +2 -2
  126. package/src/api/accounts-entities.ts +40 -16
  127. package/src/api/admin-ai-quotas.ts +34 -56
  128. package/src/api/admin-ai-usage.ts +1 -1
  129. package/src/api/admin-ai.ts +173 -0
  130. package/src/api/admin-core-settings.ts +68 -244
  131. package/src/api/admin-directory-shares.ts +55 -0
  132. package/src/api/admin-identity.ts +81 -2
  133. package/src/api/app-access.ts +85 -0
  134. package/src/api/app-approval.ts +3 -1
  135. package/src/api/app-event-delivery.ts +51 -0
  136. package/src/api/app-events.ts +123 -0
  137. package/src/api/app-resource-access.ts +48 -0
  138. package/src/api/auth/account-mismatch.ts +24 -0
  139. package/src/api/auth.ts +26 -3
  140. package/src/api/capabilities.ts +127 -15
  141. package/src/api/capability-streams.ts +2 -5
  142. package/src/api/identity-child-invocation.ts +145 -0
  143. package/src/api/index.ts +11 -2
  144. package/src/api/mcp.ts +180 -5
  145. package/src/api/me.ts +38 -17
  146. package/src/api/pwa-phone.ts +87 -48
  147. package/src/api/pwa.ts +34 -8
  148. package/src/api/resource-links.ts +83 -6
  149. package/src/api/resource-resolve.ts +50 -0
  150. package/src/api/search/schemas.ts +3 -1
  151. package/src/api/search.ts +9 -7
  152. package/src/api/widgets.ts +1 -0
  153. package/src/app-pwa/authority.ts +75 -0
  154. package/src/app-pwa/contracts.ts +177 -0
  155. package/src/app-pwa/core-query-error.ts +20 -0
  156. package/src/app-pwa/index.ts +23 -0
  157. package/src/app-pwa/protocol.ts +27 -0
  158. package/src/app-pwa/schema.ts +59 -0
  159. package/src/app-pwa/service.ts +861 -0
  160. package/src/app-pwa/transaction.ts +338 -0
  161. package/src/app-pwa/transport-input.ts +57 -0
  162. package/src/app-pwa/transport.ts +235 -0
  163. package/src/approvals/messages.ts +2 -2
  164. package/src/browser/app-access-recovery.ts +44 -0
  165. package/src/browser/app-session.ts +14 -3
  166. package/src/browser/cloud-id.ts +86 -0
  167. package/src/browser/live-websocket.ts +5 -0
  168. package/src/browser/pwa-drafts.ts +57 -0
  169. package/src/browser/resource-clipboard.ts +4 -0
  170. package/src/browser/resource-links.ts +52 -0
  171. package/src/browser/resource-picker.tsx +3 -2
  172. package/src/capabilities/calendar-sources.ts +302 -0
  173. package/src/capabilities/executions.ts +10 -3
  174. package/src/capabilities/migrate.ts +4 -0
  175. package/src/capabilities/nested.ts +43 -0
  176. package/src/capabilities/server-invoke.ts +202 -0
  177. package/src/capabilities/server.ts +26 -187
  178. package/src/capabilities/testing.ts +5 -2
  179. package/src/cli/access.ts +6 -5
  180. package/src/cli/account.ts +57 -0
  181. package/src/cli/admin/agents.ts +66 -0
  182. package/src/cli/admin/ai-models.ts +199 -0
  183. package/src/cli/admin/ai-quotas.ts +6 -102
  184. package/src/cli/admin/ai-usage.ts +4 -4
  185. package/src/cli/admin/app-credentials.ts +5 -3
  186. package/src/cli/admin/index.ts +7 -3
  187. package/src/cli/app-access.ts +60 -0
  188. package/src/cli/apps.ts +89 -26
  189. package/src/cli/avatar.ts +64 -0
  190. package/src/cli/capabilities.ts +22 -1
  191. package/src/cli/events.ts +320 -0
  192. package/src/cli/index.ts +1 -1
  193. package/src/config/env.ts +40 -0
  194. package/src/contracts/app-access.ts +52 -5
  195. package/src/contracts/app-approval.ts +8 -1
  196. package/src/contracts/app-details.ts +89 -0
  197. package/src/contracts/app-events.ts +51 -0
  198. package/src/contracts/app.ts +24 -5
  199. package/src/contracts/calendar-source.ts +274 -0
  200. package/src/contracts/calendar-view.ts +35 -0
  201. package/src/contracts/capabilities.ts +164 -3
  202. package/src/contracts/geo-admin.ts +2 -0
  203. package/src/contracts/geo-route.ts +0 -11
  204. package/src/contracts/geo.ts +14 -2
  205. package/src/contracts/index.ts +6 -0
  206. package/src/contracts/installable.ts +40 -0
  207. package/src/contracts/notification-types.ts +36 -9
  208. package/src/contracts/profile.ts +32 -0
  209. package/src/contracts/pwa-builds.ts +14 -0
  210. package/src/contracts/pwa-location.ts +38 -0
  211. package/src/contracts/pwa.ts +24 -3
  212. package/src/contracts/registry.ts +8 -0
  213. package/src/contracts/resource-clipboard.ts +35 -0
  214. package/src/contracts/resource-pages.ts +140 -0
  215. package/src/contracts/resource-resolution.ts +13 -0
  216. package/src/contracts/shared.ts +4 -0
  217. package/src/contracts/user-notifications.ts +2 -0
  218. package/src/events/app-event-delivery.ts +100 -0
  219. package/src/events/app-events.testing.ts +65 -0
  220. package/src/events/app-events.ts +134 -0
  221. package/src/events/core-user-created.ts +24 -0
  222. package/src/events/index.ts +9 -0
  223. package/src/events/testing.ts +2 -0
  224. package/src/index.ts +2 -1
  225. package/src/server/api/respond.ts +8 -3
  226. package/src/server/index.ts +1 -0
  227. package/src/server/middleware/app-access.ts +7 -6
  228. package/src/server/middleware/auth.ts +168 -66
  229. package/src/server/middleware/index.ts +1 -0
  230. package/src/server/middleware/invocation.ts +4 -2
  231. package/src/server/middleware/workload.ts +3 -3
  232. package/src/server/oauth-scope.ts +10 -0
  233. package/src/server/public-storage.ts +106 -0
  234. package/src/server/services/access.ts +8 -0
  235. package/src/services/accounts/app.ts +35 -3
  236. package/src/services/accounts/avatar.ts +342 -35
  237. package/src/services/accounts/base-group.ts +4 -5
  238. package/src/services/accounts/directory-shares.ts +157 -0
  239. package/src/services/accounts/entities.ts +50 -6
  240. package/src/services/accounts/groups.ts +3 -1
  241. package/src/services/accounts/identities.ts +64 -12
  242. package/src/services/accounts/local-groups.ts +1 -1
  243. package/src/services/accounts/users.ts +23 -15
  244. package/src/services/app-access-management.ts +519 -0
  245. package/src/services/app-access.ts +25 -7
  246. package/src/services/app-approval.ts +15 -4
  247. package/src/services/app-details.ts +224 -0
  248. package/src/services/app-discovery.ts +2 -1
  249. package/src/services/app-event-shapes.ts +139 -0
  250. package/src/services/app-events/accept.ts +116 -0
  251. package/src/services/app-events/admin.ts +250 -0
  252. package/src/services/app-events/declarations.ts +187 -0
  253. package/src/services/app-events/deliver.ts +218 -0
  254. package/src/services/app-events.ts +92 -0
  255. package/src/services/config-conflict.ts +11 -0
  256. package/src/services/core-user-events.ts +1 -0
  257. package/src/services/freeipa-config.ts +9 -0
  258. package/src/services/geo/admin.ts +4 -1
  259. package/src/services/geo/ledger.ts +47 -14
  260. package/src/services/geo/route-chain.ts +40 -18
  261. package/src/services/geo/route-estimate.ts +19 -0
  262. package/src/services/geo/route-plan.ts +0 -2
  263. package/src/services/geo/route-service.ts +57 -39
  264. package/src/services/geo/service.ts +51 -25
  265. package/src/services/identity/core-origin.ts +12 -0
  266. package/src/services/identity/index.ts +2 -1
  267. package/src/services/identity/invocation-actor.ts +50 -2
  268. package/src/services/identity/invocation-authority.ts +38 -1
  269. package/src/services/identity/invocation-token.ts +255 -50
  270. package/src/services/identity/issue-invocation.ts +9 -1
  271. package/src/services/identity/models-only-auth.ts +13 -4
  272. package/src/services/identity/nested-invocation.ts +128 -0
  273. package/src/services/image-metadata.ts +65 -1
  274. package/src/services/index.ts +24 -1
  275. package/src/services/ipa/sync.ts +6 -2
  276. package/src/services/ipa/users.ts +4 -1
  277. package/src/services/mandates/policy.ts +19 -12
  278. package/src/services/notifications/browser.ts +4 -0
  279. package/src/services/notifications/catalog.ts +5 -2
  280. package/src/services/notifications/platform.ts +7 -2
  281. package/src/services/notifications/user.ts +18 -7
  282. package/src/services/oauth-tokens.ts +28 -2
  283. package/src/services/object-store.ts +217 -0
  284. package/src/services/providers/local/users.ts +5 -0
  285. package/src/services/pwa-devices.ts +147 -37
  286. package/src/services/service-account-credentials.ts +5 -2
  287. package/src/services/session/index.ts +14 -0
  288. package/src/services/settings/core-settings.ts +91 -47
  289. package/src/services/settings/index.ts +8 -0
  290. package/src/services/settings/versions.ts +26 -0
  291. package/src/shared/ai-models.ts +274 -0
  292. package/src/shared/ai-quotas.ts +2 -2
  293. package/src/shared/ai-usage.ts +2 -2
  294. package/src/shared/index.ts +2 -0
  295. package/src/shared-text/documents.ts +25 -8
  296. package/src/shared-text/log.ts +1 -1
  297. package/src/shared-text/text-diff.ts +75 -0
  298. package/src/ssr/AdminArea.tsx +131 -0
  299. package/src/ssr/AdminLayout.tsx +11 -3
  300. package/src/ssr/AdminSidebar.tsx +7 -7
  301. package/src/ssr/AppAccessDenied.tsx +114 -0
  302. package/src/ssr/AppBadge.tsx +14 -0
  303. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  304. package/src/ssr/AppPwaRuntime.island.tsx +82 -0
  305. package/src/ssr/CloudShell.island.tsx +25 -4
  306. package/src/ssr/Layout.tsx +15 -7
  307. package/src/ssr/PwaLayout.tsx +73 -8
  308. package/src/ssr/PwaPartCount.island.tsx +23 -0
  309. package/src/ssr/PwaRuntime.island.tsx +85 -6
  310. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  311. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  312. package/src/ssr/ShellRuntime.island.tsx +5 -0
  313. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  314. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
  315. package/src/ssr/admin-active-link.ts +16 -0
  316. package/src/ssr/admin-navigation.ts +26 -20
  317. package/src/ssr/app-badges.ts +31 -7
  318. package/src/ssr/app-pwa-document.tsx +16 -0
  319. package/src/ssr/app-pwa-messages.ts +17 -0
  320. package/src/ssr/index.ts +8 -0
  321. package/src/ssr/layout-context.ts +1 -1
  322. package/src/ssr/page-trail-runtime.ts +3 -1
  323. package/src/ssr/platform-messages.ts +9 -1
  324. package/src/ssr/pwa-messages.ts +10 -2
  325. package/src/ssr/rail-navigation.ts +5 -1
  326. package/src/ssr/settings-draft.ts +153 -0
  327. package/src/ssr/shell-data.tsx +2 -0
  328. package/src/styles/global.css +1 -0
  329. package/src/styles/profile-picture.css +270 -0
  330. package/src/styles/utilities-navigation.css +6 -0
  331. package/src/workflows/ai/policy.ts +49 -0
  332. package/src/workflows/ai/runtime.ts +131 -21
  333. package/src/workflows/ai/store.ts +283 -35
  334. package/src/workflows/ai/types.ts +8 -2
  335. package/src/workflows/ai-actions.ts +148 -120
  336. package/src/workflows/authoring.ts +31 -0
  337. package/src/workflows/contracts.ts +1 -0
  338. package/src/workflows/definition.ts +2 -0
  339. package/src/workflows/editor.ts +15 -0
  340. package/src/workflows/index.ts +2 -1
  341. package/src/workflows/language/compiler.ts +34 -3
  342. package/src/workflows/runtime/executor.ts +132 -33
  343. package/src/workflows/runtime/index.ts +2 -0
  344. package/src/workflows/runtime/ports.ts +5 -1
  345. package/src/workflows/store/actions.ts +1 -1
  346. package/src/workflows/store/definitions.ts +5 -0
  347. package/src/workflows/store/runs.ts +5 -3
  348. package/src/workflows/store/worker.ts +7 -2
  349. package/src/ai/model-pricing.ts +0 -43
  350. package/src/ai/model-request-settings.ts +0 -98
  351. package/src/ai/settings-messages.ts +0 -129
  352. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -1,14 +1,13 @@
1
1
  import { openAICompatibleTranscription, type TranscriptionProvider } from "@k2b/nessi/ai";
2
- import { coreSettings } from "../services";
3
2
  import { logger, type TraceContext, trace } from "../services/logging";
4
3
  import { AI_AUDIO_MAX_BYTES, inspectAiAudio } from "./audio-format";
5
4
  import { getAiCredential } from "./credentials";
6
5
  import { beginAiCall, finishAiCall } from "./inference-calls";
7
- import { readAiSettingsState, selectAiModelProfile } from "./settings";
6
+ import { classifyProviderFailure, recordAiProviderStatus } from "./provider-connections";
7
+ import { readAiSettingsState, resolveAiModelRef, selectAiModelProfile } from "./settings";
8
8
  import { type AiUsageAttribution, safelyRecordStructuredRun } from "./structured-runs";
9
9
  import type { AiDataBoundary, AiModelProfile } from "./types";
10
10
 
11
- export const AI_AUDIO_MODEL_SETTING_KEY = "ai.audio_model_id";
12
11
  /** Matches the existing chat turn execution budget; callers may cancel sooner. */
13
12
  export const AI_TRANSCRIPTION_TIMEOUT_MS = 10 * 60_000;
14
13
 
@@ -31,31 +30,48 @@ export const createAiTranscriptionProvider = (profile: AiModelProfile, apiKey?:
31
30
  "transcription_configuration_failed",
32
31
  "Audio transcription is not set up: the audio model has no base URL. An administrator can set the audio provider URL in the AI settings.",
33
32
  );
34
- return openAICompatibleTranscription(profile.model, { name: profile.provider, baseURL, apiKey });
33
+ const provider = openAICompatibleTranscription(profile.model, { name: profile.provider, baseURL, apiKey });
34
+ return {
35
+ name: provider.name,
36
+ model: provider.model,
37
+ transcribe: async (request) => {
38
+ try {
39
+ const result = await provider.transcribe(request);
40
+ await recordAiProviderStatus(profile.providerId, { status: "ok", message: "The provider connection works." });
41
+ return result;
42
+ } catch (error) {
43
+ const failure = describeTranscriptionFailure(error, "provider", request.signal?.aborted);
44
+ if (!request.signal?.aborted)
45
+ await recordAiProviderStatus(
46
+ profile.providerId,
47
+ failure.httpStatus
48
+ ? classifyProviderFailure({ status: failure.httpStatus })
49
+ : failure.code === "transcription_connection_failed"
50
+ ? classifyProviderFailure(new TypeError())
51
+ : classifyProviderFailure(error),
52
+ );
53
+ throw failure;
54
+ }
55
+ },
56
+ };
35
57
  };
36
58
 
37
59
  export const resolveAiAudioModel = async (
38
- input: { requestedModelId?: string; allowedDataBoundaries?: AiDataBoundary[] } = {},
60
+ input: { model?: import("../shared/ai-models").AiModelRequest; allowedDataBoundaries?: AiDataBoundary[] } = {},
39
61
  ): Promise<AiResolvedAudioModel> => {
40
62
  const state = await readAiSettingsState();
41
- if (!state.ok)
42
- throw Object.assign(
43
- new AiTranscriptionError(
44
- "transcription_configuration_failed",
45
- `Audio transcription is not available: ${state.error.message} An administrator can fix this in the AI settings.`,
46
- ),
47
- { aiError: state.error },
48
- );
49
63
  if (!state.enabled)
50
64
  throw new AiTranscriptionError(
51
65
  "transcription_configuration_failed",
52
66
  "Audio transcription is not available: AI is disabled. An administrator can enable AI in the AI settings.",
53
67
  );
54
- const modelId = input.requestedModelId ?? String((await coreSettings.get<string>(AI_AUDIO_MODEL_SETTING_KEY)) ?? "").trim();
68
+ const modelId = input.model
69
+ ? resolveAiModelRef(state, input.model).id
70
+ : state.assignments.find((assignment) => assignment.slot === "audio")?.variantId;
55
71
  if (!modelId)
56
72
  throw new AiTranscriptionError(
57
73
  "transcription_configuration_failed",
58
- "Audio transcription is not set up: no audio model is configured. An administrator can configure an audio model in the AI settings.",
74
+ "Audio transcription is not set up: no audio model is assigned. An administrator can configure an audio model in the AI settings.",
59
75
  );
60
76
  let profile: AiModelProfile;
61
77
  try {
@@ -82,7 +98,7 @@ export const resolveAiAudioModel = async (
82
98
  error instanceof Error && "aiError" in error ? { aiError: error.aiError } : {},
83
99
  );
84
100
  }
85
- const credential = await getAiCredential(profile.id);
101
+ const credential = await getAiCredential(profile.providerId);
86
102
  if (profile.provider === "openai" && !credential?.trim())
87
103
  throw new AiTranscriptionError(
88
104
  "transcription_configuration_failed",
@@ -97,7 +113,7 @@ export type RunAiTranscriptionInput = {
97
113
  filename: string;
98
114
  language?: string;
99
115
  prompt?: string;
100
- requestedModelId?: string;
116
+ model?: import("../shared/ai-models").AiModelRequest;
101
117
  allowedDataBoundaries?: AiDataBoundary[];
102
118
  signal?: AbortSignal;
103
119
  appId?: string;
@@ -108,7 +124,7 @@ export type RunAiTranscriptionInput = {
108
124
  };
109
125
 
110
126
  /** One provider call. Durable execution and input authorization belong to the caller. */
111
- export const runAiTranscription = async (input: RunAiTranscriptionInput): Promise<{ text: string; modelProfileId: string }> => {
127
+ export const runAiTranscription = async (input: RunAiTranscriptionInput): Promise<{ text: string; modelId: string }> => {
112
128
  return trace.withSpan(
113
129
  {
114
130
  name: `ai.transcription.${input.task}`,
@@ -154,13 +170,13 @@ export const runAiTranscription = async (input: RunAiTranscriptionInput): Promis
154
170
  });
155
171
  signal.throwIfAborted();
156
172
  succeeded = true;
157
- return { text: result.text, modelProfileId: resolved.profile.id };
173
+ return { text: result.text, modelId: resolved.profile.id };
158
174
  } catch (error) {
159
175
  failure = describeTranscriptionFailure(error, stage, signal.aborted);
160
176
  logger("ai:transcription")[input.signal?.aborted ? "info" : "error"](failure.message, {
161
177
  traceId: span.traceId,
162
178
  task: input.task,
163
- modelProfileId: resolved?.profile.id ?? input.requestedModelId,
179
+ modelId: resolved?.profile.id ?? input.model,
164
180
  providerModel: resolved?.profile.model,
165
181
  stage,
166
182
  errorCode: failure.code,
@@ -180,7 +196,7 @@ export const runAiTranscription = async (input: RunAiTranscriptionInput): Promis
180
196
  task: input.task,
181
197
  appId: input.appId,
182
198
  attribution: input.attribution,
183
- modelProfileId: resolved?.profile.id,
199
+ modelId: resolved?.profile.id,
184
200
  providerModel: resolved?.profile.model,
185
201
  mode: "transcription",
186
202
  attempts: resolved ? 1 : 0,
@@ -13,8 +13,9 @@ export class AiTurnFailure extends Error {
13
13
  }
14
14
 
15
15
  /**
16
- * Why a turn failed. `error` is what a person reads, worded by the reader's client; `detail` is the raw cause, which
17
- * only the log keeps. `message`, when set, is Cloud's own wording for the stored error instead of the worded reason.
16
+ * Why a turn failed. `error` is what a person reads, worded by the reader's client; `detail` is the diagnostic cause, which
17
+ * only the log keeps. Provider diagnostics have already been reduced to fixed text. `message`, when set, is Cloud's
18
+ * own wording for the stored error instead of the worded reason.
18
19
  */
19
20
  export type AiTurnFailureInfo = { error: AiTurnError; detail: string; message?: string };
20
21
 
@@ -102,3 +103,24 @@ export const rememberProviderErrors = (provider: Provider, remember: (reason: Ai
102
103
  },
103
104
  };
104
105
  };
106
+
107
+ /** nessi may construct issue messages from model tool arguments or names after the adapter returns. */
108
+ export const aiIssueDiagnostic = (issue: NessiIssue): { code: string; message: string } => {
109
+ switch (issue.kind) {
110
+ case "provider_error":
111
+ return { code: "provider_error", message: "AI provider request failed." };
112
+ case "timeout":
113
+ return issue.scope === "tool"
114
+ ? { code: "tool_timeout", message: "AI tool execution timed out." }
115
+ : { code: "provider_timeout", message: "AI provider request timed out." };
116
+ case "malformed_tool_call":
117
+ case "cancelled_tool_call":
118
+ return { code: issue.kind, message: "AI provider returned an incomplete or invalid tool call." };
119
+ case "tool_execution_error":
120
+ return { code: "tool_execution_error", message: "AI tool execution failed." };
121
+ case "tool_historical_result_error":
122
+ return { code: "tool_historical_result_error", message: "AI tool history could not be prepared." };
123
+ default:
124
+ return { code: "runtime_error", message: "AI execution failed." };
125
+ }
126
+ };
package/src/ai/types.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  CloudResourceRef,
21
21
  } from "../contracts/capabilities";
22
22
  import type { RequestActor } from "../server";
23
+ import type { AiModelRequest, AiResolvedModelRef } from "../shared/ai-models";
23
24
  import type { AiActionCheck, AiStoredActionReview } from "./action-review";
24
25
  import type { AiLocalClient, AiLocalContext, AiLocalToolName } from "./local-tool-contracts";
25
26
  import type { AiTurnBlock } from "./protocol";
@@ -27,6 +28,8 @@ import type { AiTurnBlock } from "./protocol";
27
28
  /** Current reader-confirmed metadata for a recently sent resource. */
28
29
  export type AiRecentResource = {
29
30
  ref: CloudResourceRef;
31
+ context?: string;
32
+ status?: string;
30
33
  title: string;
31
34
  icon?: string;
32
35
  href?: string;
@@ -44,7 +47,7 @@ export type AiRecentSentResourceRef = {
44
47
  conversationId: string;
45
48
  };
46
49
 
47
- export const AI_MODEL_CAPABILITIES = ["streaming", "tools", "vision", "transcription"] as const;
50
+ export const AI_MODEL_CAPABILITIES = ["tools", "vision", "transcription"] as const;
48
51
  export type AiModelCapability = (typeof AI_MODEL_CAPABILITIES)[number];
49
52
 
50
53
  export const AI_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"] as const;
@@ -62,11 +65,15 @@ export type AiDataPolicy = AiDataBoundary;
62
65
  export type AiModelProfile = {
63
66
  id: string;
64
67
  label: string;
68
+ modelId: string;
69
+ providerId: string;
70
+ variantLabel: string;
71
+ description: string;
72
+ icon: import("../shared/ai-models").AiModelIcon;
73
+ showInPicker: boolean;
65
74
  provider: AiProviderId;
66
75
  model: string;
67
76
  enabled: boolean;
68
- /** Small logo (data URL, hard-compressed) shown in the admin card and the composer model picker. */
69
- image?: string;
70
77
  capabilities: AiModelCapability[];
71
78
  dataBoundary: AiDataBoundary;
72
79
  baseURL?: string;
@@ -82,67 +89,34 @@ export type AiModelProfile = {
82
89
  pricing?: import("../shared/ai-costs").AiModelPricing;
83
90
  };
84
91
 
85
- export type AiPublicModelProfile = Pick<
86
- AiModelProfile,
87
- "id" | "label" | "provider" | "model" | "image" | "capabilities" | "dataBoundary" | "contextWindow"
88
- >;
89
-
90
92
  export type AiUserContentPart = ContentPart;
91
93
 
92
- export type AiSettingsErrorCode =
93
- | "ai_disabled"
94
- | "invalid_model_profiles"
95
- | "missing_default_model"
96
- | "default_model_disabled"
97
- | "missing_provider_credential"
98
- | "model_policy_mismatch"
99
- | "model_access_denied";
100
-
101
- export type AiSettingsError = {
102
- code: AiSettingsErrorCode;
103
- message: string;
104
- fields?: Record<string, string>;
94
+ export type AiSettingsErrorCode = "ai_disabled" | "ai_no_model" | "model_policy_mismatch";
95
+ export type AiSettingsError = { code: AiSettingsErrorCode; message: string; fields?: Record<string, string> };
96
+ export type AiSettingsState = {
97
+ ok: true;
98
+ enabled: boolean;
99
+ assignments: import("../shared/ai-models").AiModelAssignment[];
100
+ globalInstructions: string;
101
+ localClientInstructions: string;
102
+ compactionInstructions: string;
103
+ maxToolResultChars: number;
104
+ firecrawlConfigured: boolean;
105
+ profiles: AiModelProfile[];
105
106
  };
106
107
 
107
- export type AiSettingsState =
108
- | {
109
- ok: true;
110
- enabled: boolean;
111
- defaultModelId: string;
112
- visionModelId?: string;
113
- globalInstructions: string;
114
- localClientInstructions: string;
115
- compactionInstructions: string;
116
- maxToolResultChars: number;
117
- firecrawlConfigured: boolean;
118
- profiles: AiModelProfile[];
119
- }
120
- | {
121
- ok: false;
122
- enabled: boolean;
123
- defaultModelId: string;
124
- visionModelId?: string;
125
- globalInstructions: string;
126
- localClientInstructions: string;
127
- compactionInstructions: string;
128
- maxToolResultChars: number;
129
- firecrawlConfigured: boolean;
130
- profiles: AiModelProfile[];
131
- error: AiSettingsError;
132
- };
133
-
134
108
  export type AiModelPolicy =
135
109
  | { kind: "platform-default"; allowedDataBoundaries?: AiDataBoundary[]; requiredCapabilities?: AiModelCapability[] }
136
110
  | { kind: "locked"; modelId: string; allowedDataBoundaries?: AiDataBoundary[]; requiredCapabilities?: AiModelCapability[] }
137
111
  | {
138
112
  kind: "selectable";
139
- defaultModelId?: string;
140
113
  allowedModelIds?: string[];
141
114
  allowedDataBoundaries?: AiDataBoundary[];
142
115
  requiredCapabilities?: AiModelCapability[];
143
116
  };
144
117
 
145
118
  export type AiResolvedModel = {
119
+ model: import("../shared/ai-models").AiResolvedModelRef;
146
120
  profile: AiModelProfile;
147
121
  provider: Provider;
148
122
  };
@@ -230,7 +204,7 @@ export type AiEnrichmentRun = {
230
204
  conversationId: string;
231
205
  status: AiEnrichmentRunStatus;
232
206
  trigger: AiEnrichmentTrigger;
233
- modelProfileId: string | null;
207
+ modelId: string | null;
234
208
  /** nessi structured mode of the successful call: native | fallback | repair. */
235
209
  mode: string | null;
236
210
  durationMs: number | null;
@@ -298,7 +272,7 @@ export type AiStoredMessage = {
298
272
  kind: "message" | "summary";
299
273
  message: Message;
300
274
  loopId: string | null;
301
- modelProfileId: string | null;
275
+ modelId: string | null;
302
276
  providerModel: string | null;
303
277
  usage: Usage | null;
304
278
  stopReason: string | null;
@@ -313,6 +287,9 @@ export type AiStoredMessage = {
313
287
  meta: {
314
288
  /** The atomically consumed composer revision, used to confirm a local send. */
315
289
  submittedDraftRevision?: number;
290
+ modelRequest?: AiModelRequest;
291
+ modelResolved?: AiResolvedModelRef;
292
+ modelReplacement?: { kind: "model_replaced"; previousLabel: string; nextLabel: string };
316
293
  compactedCount?: number;
317
294
  todoPlan?: import("./todo-contracts").AiTodoPlan;
318
295
  steerId?: string;
@@ -353,6 +330,7 @@ export type AiConversationTimelineEntry = {
353
330
  id: string;
354
331
  seq: number;
355
332
  loopId: string | null;
333
+ modelReplacement?: { kind: "model_replaced"; previousLabel: string; nextLabel: string };
356
334
  userPreview: string;
357
335
  assistantPreview: string;
358
336
  isSteer: boolean;
@@ -418,7 +396,7 @@ export type AiTurn = {
418
396
  conversationId: string;
419
397
  status: AiTurnStatus;
420
398
  attempt: number;
421
- modelProfileId: string | null;
399
+ modelId: string | null;
422
400
  createdAt: string;
423
401
  completedAt: string | null;
424
402
  error: string | null;
@@ -534,7 +512,7 @@ export type AiProjectPromptSnapshot = {
534
512
  instructions: string;
535
513
  context: string;
536
514
  references: CloudResourceRef[];
537
- defaultModelProfileId: string | null;
515
+ defaultModel: import("../shared/ai-models").AiModelRequest | null;
538
516
  };
539
517
 
540
518
  export type AiProjectFileToolStat = {
@@ -569,6 +547,8 @@ export type AiSkillFileToolSource = {
569
547
 
570
548
  export type AiConversationResourceRef = {
571
549
  ref: CloudResourceRef;
550
+ context?: string | null;
551
+ status?: string | null;
572
552
  title: string | null;
573
553
  preview: string | null;
574
554
  icon: string | null;
@@ -581,6 +561,8 @@ export type AiConversationResourceRef = {
581
561
 
582
562
  export type AiConversationResourceObservation = {
583
563
  ref: CloudResourceRef;
564
+ context?: string;
565
+ status?: string;
584
566
  title?: string;
585
567
  preview?: string;
586
568
  icon?: string;
@@ -601,6 +583,8 @@ export type AiConversationResourceOccurrence = AiConversationResourceRef & {
601
583
  export type AiConversationSourceKind = "result" | "web" | "file" | "resource" | "activity";
602
584
 
603
585
  export type AiConversationSource = {
586
+ context?: string | null;
587
+ status?: string | null;
604
588
  kind: AiConversationSourceKind;
605
589
  key: string;
606
590
  title: string;
@@ -683,7 +667,10 @@ export type AiChatTurnRunConfig = {
683
667
  /** Request locale persisted with the turn so async execution keeps the caller preference. */
684
668
  locale?: string;
685
669
  modelPolicy?: AiModelPolicy;
686
- requestedModelId?: string;
670
+ model?: AiModelRequest;
671
+ modelResolved?: AiResolvedModelRef;
672
+ modelLabel?: string;
673
+ modelReplacement?: { kind: "model_replaced"; previousLabel: string; nextLabel: string };
687
674
  /** Optional instructions that apply only to this turn. */
688
675
  systemPrompt?: string;
689
676
  project?: AiProjectPromptSnapshot;
@@ -708,7 +695,8 @@ export type AiCompactionTurnRunConfig = {
708
695
  kind: "compact";
709
696
  actor?: RequestActor;
710
697
  modelPolicy?: AiModelPolicy;
711
- requestedModelId?: string;
698
+ model?: AiModelRequest;
699
+ modelResolved?: AiResolvedModelRef;
712
700
  };
713
701
 
714
702
  export type AiTurnRunConfig = AiChatTurnRunConfig | AiCompactionTurnRunConfig;
@@ -851,7 +839,7 @@ export type AiConversationService = {
851
839
  failInterChatMessage(input: { messageId: string; error: string }): Promise<boolean>;
852
840
  deliverInterChatMessage(input: {
853
841
  messageId: string;
854
- modelProfileId: string;
842
+ modelId: string;
855
843
  runConfig: AiChatTurnRunConfig;
856
844
  userMessage: Message;
857
845
  sourceHref?: string;
@@ -909,7 +897,7 @@ export type AiConversationService = {
909
897
  conversationId: string;
910
898
  status: AiEnrichmentRunStatus;
911
899
  trigger: AiEnrichmentTrigger;
912
- modelProfileId?: string;
900
+ modelId?: string;
913
901
  mode?: string;
914
902
  durationMs?: number;
915
903
  titleUpdated?: boolean;
@@ -954,7 +942,7 @@ export type AiConversationService = {
954
942
  turnId?: string;
955
943
  checkpointSeq: number;
956
944
  summary: Message;
957
- modelProfileId?: string | null;
945
+ modelId?: string | null;
958
946
  }): Promise<void>;
959
947
  listTurnMessages(input: { conversationId: string; loopId: string; includeCompacted?: boolean }): Promise<AiStoredMessage[]>;
960
948
  setMessageFeedback(input: {
@@ -964,12 +952,12 @@ export type AiConversationService = {
964
952
  }): Promise<AiMessageFeedback | null>;
965
953
  clearMessageFeedback(input: { conversationId: string; messageShortId: string }): Promise<boolean>;
966
954
  /** Create a queued compaction turn. Chat turns must use submitChatTurn. */
967
- createCompactionTurn(input: { conversationId: string; modelProfileId: string; runConfig: AiCompactionTurnRunConfig }): Promise<AiTurn>;
955
+ createCompactionTurn(input: { conversationId: string; modelId: string; runConfig: AiCompactionTurnRunConfig }): Promise<AiTurn>;
968
956
  /** Persist the user message and create its turn in one transaction. */
969
957
  submitChatTurn(input: {
970
958
  queuedMessageId?: string;
971
959
  conversationId: string;
972
- modelProfileId: string;
960
+ modelId: string;
973
961
  runConfig: AiChatTurnRunConfig;
974
962
  userMessage: Message;
975
963
  /** Atomically consume this saved draft revision when present. */
@@ -984,7 +972,7 @@ export type AiConversationService = {
984
972
  retrySourceTurnId?: string;
985
973
  }): Promise<{ turn: AiTurn; message: AiStoredMessage }>;
986
974
  getTurnRunConfig(input: { conversationId: string; turnId: string }): Promise<AiTurnRunConfig | null>;
987
- getLatestTurn(input: { conversationId: string }): Promise<AiTurn | null>;
975
+ getLatestTurn(input: { conversationId: string; kind?: AiTurnRunConfig["kind"] }): Promise<AiTurn | null>;
988
976
  getTurn(input: { conversationId: string; turnId: string }): Promise<AiTurn | null>;
989
977
  getTurnByShortId(input: { conversationId: string; shortId: string }): Promise<AiTurn | null>;
990
978
  getActiveTurn(input: { conversationId: string }): Promise<{
@@ -1076,7 +1064,7 @@ export type AiConversationService = {
1076
1064
  /** Called after a durable append, for incremental usage/history projection. */
1077
1065
  onMessage?: (message: AiStoredMessage) => Promise<void>;
1078
1066
  conversationId: string;
1079
- modelProfileId?: string | null;
1067
+ modelId?: string | null;
1080
1068
  turnId?: string | null;
1081
1069
  leaseOwner?: string | null;
1082
1070
  /** Ephemeral provider input for this turn; user-message persistence remains reference-only. */
package/src/ai/ui.tsx CHANGED
@@ -1,4 +1,17 @@
1
1
  export { AiTurnBlockView } from "./chat/blocks";
2
+ export { AiModelCard, type AiModelCardStop, aiModelClassLabel, aiModelClassSubtitle } from "./model-card";
3
+ export {
4
+ AiModelChooser,
5
+ type AiModelChooserState,
6
+ AiModelField,
7
+ AiModelNote,
8
+ aiModelBlocksSending,
9
+ aiModelRequestFor,
10
+ aiModelUnavailableText,
11
+ resolveAiModelChoice,
12
+ } from "./model-chooser";
13
+ export { aiModelChooserMessages } from "./model-chooser-messages";
14
+ export { AiModelIcon, aiModelDisplayIcon } from "./model-icon";
2
15
 
3
16
  import { latestLoopUsage, latestUsage, latestUsageSnapshot, textFromMessage } from "./chat/message-utils";
4
17
 
package/src/ai/usage.ts CHANGED
@@ -12,7 +12,7 @@ export type AiUsageRun = {
12
12
  createdAt: string;
13
13
  userId: string | null;
14
14
  userLabel: string | null;
15
- modelProfileId: string | null;
15
+ modelId: string | null;
16
16
  providerModel: string | null;
17
17
  appId: string | null;
18
18
  conversationId: string | null;
@@ -42,7 +42,7 @@ export type AiUsageFeedback = {
42
42
  conversationTitle: string;
43
43
  userId: string | null;
44
44
  userLabel: string | null;
45
- modelProfileId: string | null;
45
+ modelId: string | null;
46
46
  providerModel: string | null;
47
47
  rating: "up" | "down";
48
48
  reasons: string[];
@@ -137,7 +137,7 @@ const events = (since: Date, until: Date) => sql`
137
137
  `;
138
138
  const globalFilter = (q: AiUsageQuery) => sql`
139
139
  (${q.userId ?? null}::text IS NULL OR (${q.userId === "unassigned"} AND e.user_id IS NULL) OR e.user_id::text=${q.userId ?? null})
140
- AND (${q.modelProfileId ?? null}::text IS NULL OR e.model_profile_id=${q.modelProfileId ?? null})
140
+ AND (${q.modelId ?? null}::text IS NULL OR e.model_profile_id=${q.modelId ?? null})
141
141
  AND (${q.providerModel ?? null}::text IS NULL OR e.provider_model=${q.providerModel ?? null})
142
142
  AND (${q.workflowId ?? null}::uuid IS NULL OR e.workflow_id=${q.workflowId ?? null}::uuid)
143
143
  AND (${q.workflowRunId ?? null}::uuid IS NULL OR e.workflow_run_id=${q.workflowRunId ?? null}::uuid)
@@ -187,7 +187,7 @@ const pageOf = async <T>(projection: ReturnType<typeof events>, q: AiUsageQuery)
187
187
  return { items, total, page, perPage: q.perPage };
188
188
  };
189
189
  const runColumns = () => sql`e.id, e.kind, e.task, e.status, e.created_at::text AS "createdAt", e.user_id::text AS "userId",
190
- COALESCE(NULLIF(u.display_name,''),u.uid,sa.name) AS "userLabel",e.model_profile_id AS "modelProfileId",e.provider_model AS "providerModel",
190
+ COALESCE(NULLIF(u.display_name,''),u.uid,sa.name) AS "userLabel",e.model_profile_id AS "modelId",e.provider_model AS "providerModel",
191
191
  e.app_id AS "appId",e.conversation_id::text AS "conversationId",e.turn_id::text AS "turnId",e.workflow_run_id::text AS "workflowRunId",
192
192
  e.workflow_id::text AS "workflowId",e.workflow_name AS "workflowName",e.estimated,e.trace_id AS "traceId",e.tokens,e.cost::float8 AS cost,e.duration_ms AS "durationMs",e.error_code AS "errorCode",e.error,e.attempts,
193
193
  e.request_started_at::text AS "requestStartedAt",e.generation_ms AS "generationMs",e.headers_ms AS "headersMs",e.first_byte_ms AS "firstByteMs",
@@ -235,7 +235,7 @@ export const aiUsage = {
235
235
  // The response period is authoritative for both summary and feedback. Editing a rating never moves its response into a different period.
236
236
  const feedbackRows = sql`SELECT m.id::text AS id,c.id::text AS "conversationId",c.title AS "conversationTitle",
237
237
  e.user_id::text AS "userId",COALESCE(NULLIF(u.display_name,''),u.uid,sa.name) AS "userLabel",
238
- e.model_profile_id AS "modelProfileId",e.provider_model AS "providerModel",
238
+ e.model_profile_id AS "modelId",e.provider_model AS "providerModel",
239
239
  CASE WHEN m.feedback_rating=1 THEN 'up' ELSE 'down' END AS rating,
240
240
  m.feedback_reasons AS reasons,m.feedback_comment AS comment,m.created_at::text AS "createdAt",m.feedback_updated_at::text AS "updatedAt"
241
241
  FROM (${filtered}) e JOIN ai.messages m ON m.loop_id=e.turn_id::text AND m.conversation_id=e.conversation_id
@@ -266,7 +266,7 @@ export const aiUsage = {
266
266
  FROM ai.conversations c WHERE c.created_at >= ${since} AND c.created_at <= ${until} AND c.launched_by_app_id IS NOT NULL
267
267
  AND (${q.userId ?? null}::text IS NULL OR (${q.userId === "unassigned"} AND c.created_by_user_id IS NULL) OR c.created_by_user_id::text=${q.userId ?? null})
268
268
  AND (${q.appId ?? null}::text IS NULL OR c.launched_by_app_id=${q.appId ?? null})
269
- AND ((${q.modelProfileId ?? null}::text IS NULL AND ${q.providerModel ?? null}::text IS NULL) OR EXISTS (
269
+ AND ((${q.modelId ?? null}::text IS NULL AND ${q.providerModel ?? null}::text IS NULL) OR EXISTS (
270
270
  SELECT 1 FROM (${filtered}) e WHERE e.kind='chat' AND e.conversation_id=c.id
271
271
  )) GROUP BY c.launched_by_app_id ORDER BY chats DESC,c.launched_by_app_id`,
272
272
  q,
@@ -305,12 +305,12 @@ export const aiUsage = {
305
305
  LEFT JOIN auth.users u ON u.id=e.user_id LEFT JOIN auth.service_accounts sa ON sa.id=e.user_id WHERE e.kind=${kind} AND e.id=${id}`;
306
306
  return row ?? null;
307
307
  },
308
- facets: async (field: "userId" | "modelProfileId" | "providerModel" | "appId" | "task", search: string, input: Partial<AiUsageQuery>) => {
308
+ facets: async (field: "userId" | "modelId" | "providerModel" | "appId" | "task", search: string, input: Partial<AiUsageQuery>) => {
309
309
  const { query, since, until } = period(input);
310
310
  const key =
311
311
  field === "userId"
312
312
  ? sql`e.user_id::text`
313
- : field === "modelProfileId"
313
+ : field === "modelId"
314
314
  ? sql`e.model_profile_id`
315
315
  : field === "providerModel"
316
316
  ? sql`e.provider_model`
@@ -1,9 +1,10 @@
1
1
  import type { Input } from "@k2b/nessi";
2
+ import type { AiModelRequest } from "../shared/ai-models";
2
3
  import { readAiSettingsState, resolveAiModelFromState } from "./settings";
3
4
  import type { AiModelPolicy, AiSettingsError, AiSettingsState } from "./types";
4
5
 
5
6
  export const isAiSettingsError = (error: unknown): error is Error & { aiError: AiSettingsError } =>
6
- error instanceof Error && typeof (error as Error & { aiError?: unknown }).aiError === "object";
7
+ error instanceof Error && "aiError" in error && typeof error.aiError === "object";
7
8
 
8
9
  const inputIncludesFiles = (input: Input): boolean =>
9
10
  Array.isArray(input) && input.some((part) => typeof part === "object" && part.type === "file");
@@ -13,27 +14,27 @@ export type ValidateAiTurnInput = {
13
14
  hasImageAttachments?: boolean;
14
15
  canInspectAttachedImages?: boolean;
15
16
  modelPolicy?: AiModelPolicy;
16
- requestedModelId?: string;
17
+ model?: AiModelRequest;
17
18
  };
18
19
 
19
20
  export const validateAiTurnRequest = async (
20
21
  input: ValidateAiTurnInput,
22
+ providedState?: AiSettingsState,
21
23
  ): Promise<{ settings: Extract<AiSettingsState, { ok: true }>; resolved: Awaited<ReturnType<typeof resolveAiModelFromState>> }> => {
22
- const settings = await readAiSettingsState();
23
- if (!settings.ok) throw Object.assign(new Error(settings.error.message), { aiError: settings.error });
24
+ const settings = providedState ?? (await readAiSettingsState());
24
25
  if (!settings.enabled) {
25
26
  throw Object.assign(new Error("AI is disabled."), {
26
27
  aiError: { code: "ai_disabled", message: "AI is disabled." } satisfies AiSettingsError,
27
28
  });
28
29
  }
29
30
 
30
- const resolved = await resolveAiModelFromState(settings, input.modelPolicy ?? { kind: "platform-default" }, input.requestedModelId);
31
+ const resolved = await resolveAiModelFromState(settings, input.modelPolicy ?? { kind: "platform-default" }, input.model);
31
32
  if (inputIncludesFiles(input.input) && !resolved.profile.capabilities.includes("vision")) {
32
33
  throw Object.assign(new Error(`AI model "${resolved.profile.id}" does not support image input.`), {
33
34
  aiError: {
34
35
  code: "model_policy_mismatch",
35
36
  message: `AI model "${resolved.profile.label}" does not support image input.`,
36
- fields: { modelProfileId: "Choose a model with vision support." },
37
+ fields: { modelId: "Choose a model with vision support." },
37
38
  } satisfies AiSettingsError,
38
39
  });
39
40
  }
@@ -44,7 +45,7 @@ export const validateAiTurnRequest = async (
44
45
  aiError: {
45
46
  code: "model_policy_mismatch",
46
47
  message: `AI model "${resolved.profile.label}" needs Vision support or Tools with a configured Vision tool model.`,
47
- fields: { modelProfileId: "Choose a Vision model, or configure the view_image fallback." },
48
+ fields: { modelId: "Choose a Vision model, or configure the view_image fallback." },
48
49
  } satisfies AiSettingsError,
49
50
  });
50
51
  }
@@ -41,7 +41,7 @@ export const CLOUD_AI_DETAILS_VIEWPORT_ITEMS = 12;
41
41
  // the viewport at any width and its length counts no further.
42
42
  export const CLOUD_AI_DETAILS_MAX_CHARS = 4096;
43
43
 
44
- const referenceSchema = () => CloudResourceReferenceSchema.pick({ type: true, id: true, title: true });
44
+ const referenceSchema = () => CloudResourceReferenceSchema.pick({ type: true, id: true, title: true, context: true, status: true });
45
45
  const ReferenceSchema = /* @__PURE__ */ referenceSchema();
46
46
  export type CloudAiViewReference = z.output<typeof ReferenceSchema>;
47
47
  const cellTypeSchema = () => z.enum(["text", "number", "date", "datetime", "ref"]);
@@ -371,7 +371,7 @@ const ExplorerViewSchema = ExplorerDataSchema.superRefine((value, ctx) => {
371
371
  };
372
372
  }
373
373
  const domains = [
374
- ...("xAxis" in input && input.xAxis?.domain ? [input.xAxis.domain] : []),
374
+ ...("xAxis" in input && input.kind !== "bar" && input.xAxis?.domain ? [input.xAxis.domain] : []),
375
375
  ...("yAxis" in input && input.yAxis?.domain ? [input.yAxis.domain] : []),
376
376
  ];
377
377
  const stepping = domains.some(