@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
@@ -1,138 +1,37 @@
1
1
  import { sql } from "bun";
2
- import { toPgTextArray } from "../services/postgres";
3
2
  import { decryptValue, encryptValue } from "../services/settings/crypto";
4
- import type { AiRequestHeaderPatch } from "./request-headers";
5
3
 
6
4
  type SqlClient = typeof sql;
7
5
 
8
- /**
9
- * Provider API keys, one per model profile.
10
- *
11
- * They used to live on the profile inside the `ai.model_profiles_json` setting.
12
- * Settings are encrypted at rest but a `text` setting is handed to the admin UI
13
- * in full, so every key travelled to the browser in the page payload — and the
14
- * form had to send them back to avoid dropping them while editing a label.
15
- * Only `kind: "secret"` settings are redacted, and the registry is static, so
16
- * one setting per profile was never an option.
17
- *
18
- * These rows are the answer: encrypted with the same `APP_SECRET` helper the
19
- * settings store uses, addressed by profile id, and never serialised into
20
- * anything the client receives.
21
- */
22
-
23
- /** The key for a profile, or null when none is stored. */
24
- export const getAiCredential = async (profileId: string, db: SqlClient = sql): Promise<string | null> => {
6
+ /** Provider keys are encrypted at rest and never returned by an API. */
7
+ export const getAiCredential = async (providerId: string, db: SqlClient = sql): Promise<string | null> => {
25
8
  const [row] = await db<{ secret: string }[]>`
26
- SELECT secret FROM ai.model_credentials WHERE profile_id = ${profileId}
9
+ SELECT secret FROM ai.provider_credentials WHERE provider_id = ${providerId}
27
10
  `;
28
11
  if (!row) return null;
29
12
  try {
30
13
  const value = await decryptValue(row.secret);
31
14
  return typeof value === "string" && value.trim() ? value : null;
32
15
  } catch {
33
- console.warn(`[ai] ignoring unreadable provider credential for profile ${JSON.stringify(profileId)}`);
16
+ console.warn(`[ai] ignoring unreadable provider credential for provider ${JSON.stringify(providerId)}`);
34
17
  return null;
35
18
  }
36
19
  };
37
20
 
38
- /** Store or replace a profile's key. */
39
- export const setAiCredential = async (profileId: string, secret: string, db: SqlClient = sql): Promise<void> => {
21
+ /** Store or replace a provider's key. */
22
+ export const setAiCredential = async (providerId: string, secret: string, db: SqlClient = sql): Promise<void> => {
40
23
  const encrypted = await encryptValue(secret);
41
24
  await db`
42
- INSERT INTO ai.model_credentials (profile_id, secret)
43
- VALUES (${profileId}, ${encrypted})
44
- ON CONFLICT (profile_id) DO UPDATE
25
+ INSERT INTO ai.provider_credentials (provider_id, secret)
26
+ VALUES (${providerId}, ${encrypted})
27
+ ON CONFLICT (provider_id) DO UPDATE
45
28
  SET secret = EXCLUDED.secret,
46
29
  updated_at = now()
47
30
  `;
48
31
  };
49
32
 
50
- /** Profile ids whose stored key can currently be decrypted and used. */
51
- export const listAiCredentialProfileIds = async (db: SqlClient = sql): Promise<string[]> => {
52
- const rows = await db<{ profile_id: string; secret: string }[]>`SELECT profile_id, secret FROM ai.model_credentials`;
53
- const usable: string[] = [];
54
- for (const row of rows) {
55
- try {
56
- const value = await decryptValue(row.secret);
57
- if (typeof value === "string" && value.trim()) usable.push(row.profile_id);
58
- } catch {
59
- console.warn(`[ai] ignoring unreadable provider credential for profile ${JSON.stringify(row.profile_id)}`);
60
- }
61
- }
62
- return usable;
63
- };
64
-
65
- /**
66
- * Drop keys for profiles that no longer exist.
67
- *
68
- * Profiles live in a JSON setting rather than a table, so there is no foreign
69
- * key to cascade from. Pruning on save keeps a deleted profile from leaving its
70
- * key behind, and makes recreating an id a clean slate rather than a silent
71
- * inheritance.
72
- */
73
- export const pruneAiCredentials = async (keepProfileIds: readonly string[], db: SqlClient = sql): Promise<void> => {
74
- if (keepProfileIds.length === 0) {
75
- await db`DELETE FROM ai.model_credentials`;
76
- return;
77
- }
78
- await db`DELETE FROM ai.model_credentials WHERE profile_id <> ALL(${toPgTextArray([...keepProfileIds])}::text[])`;
79
- };
80
-
81
- /**
82
- * Separate provider keys from the profiles blob.
83
- *
84
- * Pure on purpose: the admin route and the one-time migration both need the
85
- * exact same split, and getting it wrong either leaks a key into a setting or
86
- * drops one on save. `profilesJson` is what may be stored; `credentials` is
87
- * what must go into the table; `profileIds` is what survives pruning.
88
- *
89
- * Unparseable input is handed back untouched — the settings validator owns that
90
- * error, and rewriting it here would replace the admin's parse message.
91
- */
92
- export const splitAiProfileCredentials = (
93
- rawValue: unknown,
94
- ): {
95
- profilesJson: string;
96
- credentials: Array<{ profileId: string; secret: string }>;
97
- profileIds: string[];
98
- requestHeaders: AiRequestHeaderPatch[];
99
- } | null => {
100
- if (typeof rawValue !== "string") return null;
101
- const text = rawValue;
102
- let parsed: unknown;
103
- try {
104
- parsed = JSON.parse(text || "[]");
105
- } catch {
106
- return null;
107
- }
108
- if (!Array.isArray(parsed)) return null;
109
-
110
- const profiles: unknown[] = [];
111
- const credentials: Array<{ profileId: string; secret: string }> = [];
112
- const profileIds: string[] = [];
113
- const requestHeaders: AiRequestHeaderPatch[] = [];
114
-
115
- for (const entry of parsed) {
116
- if (!entry || typeof entry !== "object") {
117
- profiles.push(entry);
118
- continue;
119
- }
120
- const {
121
- apiKey,
122
- requestHeaders: headerPatch,
123
- requestHeaderNames: _headerNames,
124
- credentialSetting: _legacy,
125
- ...profile
126
- } = entry as Record<string, unknown>;
127
- const profileId = typeof profile.id === "string" ? profile.id.trim() : "";
128
- if (profileId) {
129
- profile.id = profileId;
130
- profileIds.push(profileId);
131
- if (headerPatch !== undefined) requestHeaders.push({ profileId, patch: headerPatch });
132
- if (typeof apiKey === "string" && apiKey.trim()) credentials.push({ profileId, secret: apiKey.trim() });
133
- }
134
- profiles.push(profile);
135
- }
136
-
137
- return { profilesJson: JSON.stringify(profiles), credentials, profileIds, requestHeaders };
33
+ /** Provider IDs with a stored key; decrypt only the selected provider at execution. */
34
+ export const listAiCredentialProviderIds = async (db: SqlClient = sql): Promise<string[]> => {
35
+ const rows = await db<{ provider_id: string }[]>`SELECT provider_id FROM ai.provider_credentials`;
36
+ return rows.map((row) => row.provider_id);
138
37
  };
@@ -171,6 +171,7 @@ const chartKinds = (limits = true) => {
171
171
  kind: z.literal("bar"),
172
172
  ...ChartKindCommon,
173
173
  data: list(ChartCategory, 1, CLOUD_AI_CHART_MAX_VALUES),
174
+ xAxis: ChartAxis.pick({ label: true }).strict().optional(),
174
175
  yAxis: ChartAxis.optional(),
175
176
  colorByBar: z.boolean().optional(),
176
177
  showValues: z.boolean().optional(),
@@ -387,14 +388,14 @@ const flatChartSchema = () => {
387
388
  .max(CLOUD_AI_CHART_MAX_SERIES)
388
389
  .optional()
389
390
  .describe("line, scatter: label/data, optional reference role; line: style. Line needs 2 points per series."),
390
- xAxis: ChartAxis.optional().describe("line, scatter: x axis."),
391
- yAxis: ChartAxis.optional().describe("bar, line, scatter, boxplot: y axis."),
391
+ xAxis: ChartAxis.optional().describe("line, scatter: x axis. bar: only label, the category axis title."),
392
+ yAxis: ChartAxis.optional().describe("bar, line, scatter, boxplot: y axis; label names the value, format sets units."),
392
393
  legend: z
393
394
  .boolean()
394
395
  .optional()
395
396
  .describe("bar (colorByBar), line, scatter, pie, donut: legend; defaults to several series or unlabeled slices."),
396
397
  colorByBar: z.boolean().optional().describe("bar: one palette color per bar."),
397
- showValues: z.boolean().optional().describe("bar, heatmap: print values."),
398
+ showValues: z.boolean().optional().describe("bar: print values, by default when they stay readable. heatmap: print values."),
398
399
  area: z.boolean().optional().describe("line, sparkline: fill under the line."),
399
400
  smooth: z.boolean().optional().describe("line: smooth curves."),
400
401
  showLabels: z.boolean().optional().describe("pie, donut: name slices next to them."),
@@ -1,5 +1,6 @@
1
1
  import { sql } from "bun";
2
2
  import { lazySync } from "../_internal/process-sync";
3
+ import { get as getUser } from "../services/accounts/users";
3
4
  import { logger, trace } from "../services/logging";
4
5
  import { superviseRuntimeTask } from "../services/runtime-lifecycle";
5
6
  import { aiModelAccess } from "./model-access";
@@ -43,7 +44,7 @@ export const isRetryableDictationError = (error: unknown): boolean => {
43
44
  export const processAiDictation = async (
44
45
  id: string,
45
46
  signal: AbortSignal,
46
- run: (input: RunAiTranscriptionInput) => Promise<{ text: string; modelProfileId: string }> = runAiTranscription,
47
+ run: (input: RunAiTranscriptionInput) => Promise<{ text: string; modelId: string }> = runAiTranscription,
47
48
  ): Promise<void> => {
48
49
  const token = crypto.randomUUID();
49
50
  const [task] = await sql<ClaimedDictation[]>`
@@ -57,7 +58,7 @@ export const processAiDictation = async (
57
58
  name: "ai.dictation",
58
59
  source: "ai:dictations",
59
60
  category: "ai",
60
- attributes: { dictationId: id, conversationId: task.conversation_id, modelProfileId: task.model_profile_id, attempt: task.attempts },
61
+ attributes: { dictationId: id, conversationId: task.conversation_id, modelId: task.model_profile_id, attempt: task.attempts },
61
62
  });
62
63
  let failure: AiTranscriptionError | undefined;
63
64
  const canceled = new AbortController();
@@ -85,14 +86,17 @@ export const processAiDictation = async (
85
86
  if (!conversation) throw new Error("Conversation unavailable.");
86
87
  const subject = { type: "user" as const, userId: task.user_id };
87
88
  await aiModelAccess.assertAllowed(task.model_profile_id, subject);
88
- if (conversation.project_id && !(await aiProjects.snapshot(conversation.project_id, subject)))
89
- throw new Error("Project access unavailable.");
89
+ if (conversation.project_id) {
90
+ const user = await getUser({ id: task.user_id });
91
+ if (!user || !(await aiProjects.snapshot(conversation.project_id, { actor: { kind: "user", user }, accessSubject: subject })))
92
+ throw new Error("Project access unavailable.");
93
+ }
90
94
  const result = await run({
91
95
  task: "dictation",
92
96
  traceParent: span,
93
97
  file: new Blob([new Uint8Array(task.source_bytes)]),
94
98
  filename: task.source_path,
95
- requestedModelId: task.model_profile_id,
99
+ model: { id: task.model_profile_id },
96
100
  language: task.language ?? undefined,
97
101
  signal: combined,
98
102
  attribution: { userId: task.user_id, conversationId: task.conversation_id },
@@ -109,7 +113,7 @@ export const processAiDictation = async (
109
113
  log[combined.aborted ? "info" : retry ? "warn" : "error"](failure.message, {
110
114
  dictationId: id,
111
115
  traceId: span.traceId,
112
- modelProfileId: task.model_profile_id,
116
+ modelId: task.model_profile_id,
113
117
  attempt: task.attempts,
114
118
  retry,
115
119
  errorCode: failure.code,
package/src/ai/enrich.ts CHANGED
@@ -151,7 +151,7 @@ export const enrichDirtyAiConversations = async (input: {
151
151
  // Resolve the background model once per run; AI disabled / no model = quiet no-op.
152
152
  let resolved: AiResolvedModel;
153
153
  try {
154
- resolved = await (input.deps?.resolveModel ?? resolveAiBackgroundModel)();
154
+ resolved = await (input.deps?.resolveModel ?? resolveAiBackgroundModel)({ class: "fast" });
155
155
  } catch (error) {
156
156
  log.info("Enrichment skipped: no background model available", {
157
157
  error: error instanceof Error ? error.message : String(error),
@@ -224,7 +224,7 @@ export const enrichDirtyAiConversations = async (input: {
224
224
  conversationId: conversation.id,
225
225
  status: "ok",
226
226
  trigger,
227
- modelProfileId: result.modelProfileId,
227
+ modelId: result.model.id,
228
228
  mode: result.structuredMeta.mode,
229
229
  durationMs: Date.now() - startedAt,
230
230
  titleUpdated: applyTitle,
@@ -7,10 +7,11 @@ import { CapabilityActionReviewDataSchema, capabilityActionPolicy } from "../con
7
7
  import type { AccessSubject, RequestActor } from "../server";
8
8
  import { approvalAssurance, approvalScopeFromToolName, approvals } from "../services/approvals";
9
9
  import { createHelpReader } from "../services/help";
10
- import { logger } from "../services/logging";
10
+ import { logger, trace } from "../services/logging";
11
11
  import { getMandate } from "../services/mandates";
12
12
  import type { MandatePolicyV1 } from "../services/mandates/policy";
13
13
  import { coreSettings } from "../services/settings/api";
14
+ import type { AiModelRequest } from "../shared/ai-models";
14
15
  import { normalizeLocale } from "../shared/locale";
15
16
  import { type AiActionCheck, type AiStoredActionReview, storedActionReview } from "./action-review";
16
17
  import {
@@ -56,6 +57,7 @@ import { answerOpenToolCalls } from "./open-tool-calls";
56
57
  import { type AiUserPrefs, aiActorUser, aiUserPrefs } from "./prefs";
57
58
  import { createCloudAiReadProjectKnowledgeTool, createCloudAiSearchProjectTool } from "./project-tool";
58
59
  import { aiProjects } from "./projects";
60
+ import type { AiResourceContext } from "./projects-authorization";
59
61
  import {
60
62
  AI_TURN_LEASE_MS,
61
63
  type AiTurnBlock,
@@ -305,6 +307,11 @@ const accessSubjectForActor = (actor: RequestActor | undefined): AccessSubject |
305
307
  return { type: "service_account", serviceAccountId: actor.serviceAccount.id };
306
308
  };
307
309
 
310
+ const resourceContextForActor = (actor: RequestActor | undefined): AiResourceContext | null => {
311
+ const accessSubject = accessSubjectForActor(actor);
312
+ return actor && accessSubject ? { actor, accessSubject } : null;
313
+ };
314
+
308
315
  export type ExecutorConfig = {
309
316
  leaseOwner: string;
310
317
  heartbeatMs: number;
@@ -703,7 +710,7 @@ type MaterializedChatConfig = {
703
710
  tools: AiRuntimeTool[];
704
711
  toolApprovalContext?: AiToolApprovalContext;
705
712
  modelPolicy: AiChatTurnRunConfig["modelPolicy"];
706
- requestedModelId?: string;
713
+ model?: AiModelRequest;
707
714
  };
708
715
 
709
716
  const materializeChatConfig = async (config: AiChatTurnRunConfig, signal: AbortSignal, turnId: string): Promise<MaterializedChatConfig> => {
@@ -723,7 +730,7 @@ const materializeChatConfig = async (config: AiChatTurnRunConfig, signal: AbortS
723
730
  : [],
724
731
  toolApprovalContext: config.toolApprovalContext,
725
732
  modelPolicy: config.modelPolicy,
726
- requestedModelId: config.requestedModelId,
733
+ model: config.model,
727
734
  };
728
735
  };
729
736
 
@@ -753,7 +760,7 @@ export class AiTurnExecutor {
753
760
  const runConfig = claim.runConfig;
754
761
  if (!runConfig) {
755
762
  pipeline.seedBaseline(claim.liveBlocks ?? []);
756
- await pipeline.emitTurnStarted(claim.turn.modelProfileId ?? "");
763
+ await pipeline.emitTurnStarted(claim.turn.modelId ?? "");
757
764
  await pipeline.emitBaseline();
758
765
  await this.finalize(
759
766
  conversationId,
@@ -768,7 +775,7 @@ export class AiTurnExecutor {
768
775
 
769
776
  if (runConfig.kind === "compact") {
770
777
  pipeline.seedBaseline(claim.liveBlocks ?? []);
771
- await pipeline.emitTurnStarted(claim.turn.modelProfileId ?? "");
778
+ await pipeline.emitTurnStarted(claim.turn.modelId ?? "");
772
779
  await pipeline.emitBaseline();
773
780
  await this.runCompaction(conversationId, turnId, pipeline, runConfig, signal);
774
781
  return;
@@ -779,7 +786,7 @@ export class AiTurnExecutor {
779
786
  attemptState = await loadChatAttemptState(conversationId, turnId);
780
787
  } catch (error) {
781
788
  pipeline.seedBaseline(claim.liveBlocks ?? []);
782
- await pipeline.emitTurnStarted(claim.turn.modelProfileId ?? "");
789
+ await pipeline.emitTurnStarted(claim.turn.modelId ?? "");
783
790
  await pipeline.emitBaseline();
784
791
  await this.finalize(
785
792
  conversationId,
@@ -793,7 +800,7 @@ export class AiTurnExecutor {
793
800
  return;
794
801
  }
795
802
  pipeline.seedBaseline(rebuildAttemptBaseline(attemptState));
796
- await pipeline.emitTurnStarted(claim.turn.modelProfileId ?? "");
803
+ await pipeline.emitTurnStarted(claim.turn.modelId ?? "");
797
804
  await pipeline.emitBaseline();
798
805
  await this.runChat(conversationId, turnId, claim, runConfig, pipeline, signal, false, attemptState);
799
806
  }
@@ -882,16 +889,13 @@ export class AiTurnExecutor {
882
889
  if (allowed) material.tools = material.tools.filter((tool) => allowed.has(tool.def.name));
883
890
  chatId ||= conversation?.shortId ?? "";
884
891
  if (config.project) {
885
- const subject = accessSubjectForActor(material.actor);
892
+ const subject = resourceContextForActor(material.actor);
886
893
  const project = subject ? await aiProjects.getByShortId(config.project.id, subject, "read") : null;
887
894
  if (!project) {
888
895
  throw new AiTurnFailure("not_allowed", "Project access is no longer available.");
889
896
  }
890
897
  resolvedProjectId = project.id;
891
898
  }
892
- if (isAssistantChatTurn(config) && claim.turn.modelProfileId) {
893
- await aiModelAccess.assertAllowed(claim.turn.modelProfileId, accessSubjectForActor(material.actor));
894
- }
895
899
  validated = await (this.config.validateTurn ?? validateAiTurnRequest)({
896
900
  input: config.input,
897
901
  hasImageAttachments: config.files?.attached.some((file) => isAiImageMediaType(file.mediaType)),
@@ -899,12 +903,10 @@ export class AiTurnExecutor {
899
903
  material.tools.some((tool) => tool.def.name === "view_image") &&
900
904
  (await isAiVisionModelConfigured(material.modelPolicy?.allowedDataBoundaries)),
901
905
  modelPolicy: material.modelPolicy,
902
- requestedModelId: isAssistantChatTurn(config)
903
- ? (claim.turn.modelProfileId ?? material.requestedModelId)
904
- : material.requestedModelId,
906
+ model: claim.turn.modelId ? { id: claim.turn.modelId } : material.model,
905
907
  });
906
908
  if (isAssistantChatTurn(config)) {
907
- await aiModelAccess.assertAllowed(validated.resolved.profile.id, accessSubjectForActor(material.actor));
909
+ await aiModelAccess.assertAllowed(validated.resolved.profile.modelId, accessSubjectForActor(material.actor));
908
910
  }
909
911
  } catch (error) {
910
912
  signal.removeEventListener("abort", onSignal);
@@ -920,6 +922,11 @@ export class AiTurnExecutor {
920
922
  return;
921
923
  }
922
924
  const { settings, resolved } = validated;
925
+ if (config.modelResolved?.id === resolved.profile.id) resolved.model = config.modelResolved;
926
+ await trace.record({
927
+ event: "model.resolved",
928
+ attributes: { ...resolved.model, resolvedClass: resolved.model.class, turnId, conversationId },
929
+ });
923
930
 
924
931
  const defaultToolSource = config.toolSource?.kind === "default" ? config.toolSource : null;
925
932
  const toolActor =
@@ -965,11 +972,11 @@ export class AiTurnExecutor {
965
972
  const timeZone = String((await coreSettings.get<string>("app.timezone")) || "").trim() || "UTC";
966
973
  const promptLocale = normalizeLocale(config.locale ?? (await coreSettings.get<string>("app.locale")));
967
974
  const project = config.project;
968
- const projectSubject = project ? accessSubjectForActor(material.actor) : null;
975
+ const projectSubject = project ? resourceContextForActor(material.actor) : null;
969
976
  // Skills are an Assistant/default-tool capability. Custom and structured
970
977
  // executions must not gain an implicit database dependency or extra tools.
971
978
  const skillSubject =
972
- defaultToolSource && resolved.profile.capabilities.includes("tools") ? accessSubjectForActor(material.actor) : null;
979
+ defaultToolSource && resolved.profile.capabilities.includes("tools") ? resourceContextForActor(material.actor) : null;
973
980
  const availableSkills = skillSubject
974
981
  ? (await aiSkills.list(skillSubject, { authority: toolAuthority })).filter((skill) => skill.enabled)
975
982
  : [];
@@ -1187,7 +1194,7 @@ export class AiTurnExecutor {
1187
1194
  await pipeline.emitMessage(message);
1188
1195
  },
1189
1196
  conversationId,
1190
- modelProfileId: resolved.profile.id,
1197
+ modelId: resolved.profile.id,
1191
1198
  turnId,
1192
1199
  leaseOwner: this.config.leaseOwner,
1193
1200
  turnInput,
@@ -1591,7 +1598,9 @@ export class AiTurnExecutor {
1591
1598
  provider: acceptCanonicalToolNames(
1592
1599
  retryTransientProviderErrors(
1593
1600
  rememberProviderErrors(
1594
- answerOpenToolCalls(assistantQuotaProvider(resolved.provider, config, quotaSubject, resolved.profile, turnId, conversationId)),
1601
+ answerOpenToolCalls(
1602
+ assistantQuotaProvider(resolved.provider, config, quotaSubject, resolved.profile, turnId, conversationId, resolved.model),
1603
+ ),
1595
1604
  remember,
1596
1605
  ),
1597
1606
  {
@@ -1659,7 +1668,7 @@ export class AiTurnExecutor {
1659
1668
  : createCloudCompactFn({
1660
1669
  conversationId,
1661
1670
  turnId,
1662
- modelProfileId: resolved.profile.id,
1671
+ modelId: resolved.profile.id,
1663
1672
  additionalInstructions: settings.compactionInstructions,
1664
1673
  maxOutputTokens: resolved.profile.maxOutputTokens,
1665
1674
  signal: abortController.signal,
@@ -2220,7 +2229,7 @@ export class AiTurnExecutor {
2220
2229
  validated = await (this.config.validateTurn ?? validateAiTurnRequest)({
2221
2230
  input: "",
2222
2231
  modelPolicy: config.modelPolicy,
2223
- requestedModelId: config.requestedModelId,
2232
+ model: config.model,
2224
2233
  });
2225
2234
  } catch (error) {
2226
2235
  signal.removeEventListener("abort", onSignal);
@@ -2228,10 +2237,15 @@ export class AiTurnExecutor {
2228
2237
  return;
2229
2238
  }
2230
2239
  const { settings, resolved } = validated;
2240
+ if (config.modelResolved?.id === resolved.profile.id) resolved.model = config.modelResolved;
2241
+ await trace.record({
2242
+ event: "model.resolved",
2243
+ attributes: { ...resolved.model, resolvedClass: resolved.model.class, turnId, conversationId },
2244
+ });
2231
2245
 
2232
2246
  const store = aiConversations.createSessionStore({
2233
2247
  conversationId,
2234
- modelProfileId: resolved.profile.id,
2248
+ modelId: resolved.profile.id,
2235
2249
  turnId,
2236
2250
  leaseOwner: this.config.leaseOwner,
2237
2251
  });
@@ -2244,6 +2258,7 @@ export class AiTurnExecutor {
2244
2258
  inferenceProvider(resolved.provider, resolved.profile, {
2245
2259
  kind: "background",
2246
2260
  task: "chat-compaction",
2261
+ modelRef: resolved.model,
2247
2262
  conversationId,
2248
2263
  turnId,
2249
2264
  appId: "core",
@@ -2257,7 +2272,7 @@ export class AiTurnExecutor {
2257
2272
  compact: createCloudCompactFn({
2258
2273
  conversationId,
2259
2274
  turnId,
2260
- modelProfileId: resolved.profile.id,
2275
+ modelId: resolved.profile.id,
2261
2276
  additionalInstructions: settings.compactionInstructions,
2262
2277
  maxOutputTokens: resolved.profile.maxOutputTokens,
2263
2278
  signal: abortController.signal,
@@ -2430,10 +2445,10 @@ class StreamPipeline {
2430
2445
  await this.maybeSnapshot();
2431
2446
  }
2432
2447
 
2433
- async emitTurnStarted(modelProfileId: string): Promise<void> {
2448
+ async emitTurnStarted(modelId: string): Promise<void> {
2434
2449
  const seq = this.nextSeq();
2435
2450
  await this.publish(
2436
- this.envelope({ type: "turn_started" as const, seq, modelProfileId, providerModel: "", blocks: this.blocks }) as AiWireEvent,
2451
+ this.envelope({ type: "turn_started" as const, seq, modelId, providerModel: "", blocks: this.blocks }) as AiWireEvent,
2437
2452
  );
2438
2453
  }
2439
2454
 
package/src/ai/http.ts CHANGED
@@ -3,6 +3,7 @@ import type { Context } from "hono";
3
3
  import { z } from "zod";
4
4
  import { CapabilityAppIdSchema } from "../contracts/capabilities";
5
5
  import { type AuthContext, err, fail, respond } from "../server";
6
+ import { AiModelRequestSchema } from "../shared/ai-models";
6
7
  import { aiAttachmentMarker } from "./attachments";
7
8
  import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
8
9
  import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
@@ -11,13 +12,7 @@ import { AiQuotaError } from "./quotas";
11
12
  import { AiResourceMarkerSchema } from "./resource-markers";
12
13
  import { AI_SHORT_ID_PATTERN } from "./short-id";
13
14
  import { AI_SKILL_NAME_MAX_CHARS, AI_SKILL_NAME_PATTERN } from "./skill-format";
14
- import {
15
- AI_MESSAGE_FEEDBACK_REASONS,
16
- type AiClientToolId,
17
- type AiDraftContentPart,
18
- type AiSettingsError,
19
- type AiUserContentPart,
20
- } from "./types";
15
+ import { AI_MESSAGE_FEEDBACK_REASONS, type AiClientToolId, type AiDraftContentPart, type AiUserContentPart } from "./types";
21
16
  import { isAiSettingsError } from "./validate";
22
17
 
23
18
  export const AiDraftContentPartSchema = z.discriminatedUnion("type", [
@@ -144,9 +139,10 @@ export const AiTurnInputSchema = z
144
139
  .min(1)
145
140
  .max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
146
141
  .optional(),
147
- modelProfileId: z.string().trim().min(1).optional(),
142
+ model: AiModelRequestSchema.optional(),
148
143
  clientToolIds: AiClientToolIdsSchema.optional(),
149
144
  })
145
+ .strict()
150
146
  .refine((input) => Boolean(input.message?.trim() || input.content?.length), {
151
147
  message: "Message or content is required.",
152
148
  path: ["message"],
@@ -166,13 +162,14 @@ export const AiSubmitConversationDraftInputSchema = z
166
162
  .object({
167
163
  queueId: z.uuid().optional(),
168
164
  draftRevision: z.number().int().min(1),
169
- modelProfileId: z.string().trim().min(1).optional(),
165
+ model: AiModelRequestSchema.optional(),
170
166
  clientToolIds: AiClientToolIdsSchema.optional(),
171
167
  /** Set by a local client such as cld; the web never sends it. */
172
168
  client: AiLocalClientSchema.optional(),
173
169
  /** AGENTS.md files and local Skills a local client found; only valid together with client. */
174
170
  localContext: AiLocalContextSchema.optional(),
175
171
  })
172
+ .strict()
176
173
  .refine((input) => !input.localContext || Boolean(input.client), {
177
174
  message: "Local context requires a local client.",
178
175
  path: ["localContext"],
@@ -183,9 +180,11 @@ export const AiSteerInputSchema = z.object({
183
180
  clientRequestId: z.string().trim().min(1).max(120),
184
181
  });
185
182
 
186
- export const AiCompactionInputSchema = z.object({
187
- modelProfileId: z.string().trim().min(1).optional(),
188
- });
183
+ export const AiCompactionInputSchema = z
184
+ .object({
185
+ model: AiModelRequestSchema.optional(),
186
+ })
187
+ .strict();
189
188
 
190
189
  export type AiCompactionInput = z.infer<typeof AiCompactionInputSchema>;
191
190
 
@@ -200,11 +199,12 @@ export const AiMessageRetryInputSchema = z
200
199
  .min(1)
201
200
  .max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
202
201
  .optional(),
203
- modelProfileId: z.string().trim().min(1).optional(),
202
+ model: AiModelRequestSchema.optional(),
204
203
  clientToolIds: AiClientToolIdsSchema.optional(),
205
204
  client: AiLocalClientSchema.optional(),
206
205
  localContext: AiLocalContextSchema.omit({ selectedSkills: true }).optional(),
207
206
  })
207
+ .strict()
208
208
  .refine((input) => !input.localContext || Boolean(input.client), {
209
209
  message: "Local context requires a local client.",
210
210
  path: ["localContext"],
@@ -268,25 +268,13 @@ export const AiApiErrorSchema = z.object({
268
268
 
269
269
  const conflict = (message: string) => ({ code: "CONFLICT" as const, message, status: 409 as const });
270
270
 
271
- const aiSettingsServiceError = (error: AiSettingsError) => {
272
- switch (error.code) {
273
- case "model_access_denied":
274
- return err.forbidden(error.message);
275
- case "invalid_model_profiles":
276
- case "model_policy_mismatch":
277
- return err.badInput(error.message);
278
- case "ai_disabled":
279
- case "missing_default_model":
280
- case "default_model_disabled":
281
- case "missing_provider_credential":
282
- return conflict(error.message);
283
- }
284
- };
285
-
286
271
  export const toAiErrorResponse = (c: Context<AuthContext>, error: unknown) => {
287
272
  if (error instanceof AiQuotaError) return c.json({ ok: false, error: error.message, message: error.message, code: error.code }, 429);
288
273
  if (isAiSettingsError(error)) {
289
- return respond(c, fail(aiSettingsServiceError(error.aiError)));
274
+ return c.json(
275
+ { ok: false, code: error.aiError.code, message: error.aiError.message, error: error.aiError.message },
276
+ error.aiError.code === "model_policy_mismatch" ? 400 : 409,
277
+ );
290
278
  }
291
279
  const message = error instanceof Error ? error.message : "AI request failed";
292
280
  if (message.includes("idx_ai_turns_one_active")) return respond(c, fail(err.conflict("Running turn")));
package/src/ai/index.ts CHANGED
@@ -1,4 +1,13 @@
1
+ export * from "../shared/ai-models";
1
2
  export { type AiAppSkillIssue, aiAppSkills, appSkillsRegistryState, reconcileAppSkills } from "./app-skill-store";
3
+ export {
4
+ type ApplicationAiTaskInput,
5
+ type ApplicationAiTaskKey,
6
+ deleteApplicationAiTasks,
7
+ listUnreleasedApplicationAiTaskSources,
8
+ releaseApplicationAiTask,
9
+ runApplicationAiTask,
10
+ } from "./application-tasks";
2
11
  export { type AiApprovalPreferenceRoutes, type AiApprovalPreferenceView, createAiApprovalPreferenceRoutes } from "./approval-routes";
3
12
  export {
4
13
  AI_WEBSITE_APPROVAL_TOOL,
@@ -19,7 +28,7 @@ export {
19
28
  rememberAiToolApproval,
20
29
  revokeAiToolApprovalPreference,
21
30
  } from "./approvals";
22
- export { assistantAiSettingsState, listAssistantAiModels, selectAssistantAiModelId } from "./assistant-models";
31
+ export { assistantAiSettingsState, listAssistantAiModels, peopleAiModels, selectAssistantAiModel } from "./assistant-models";
23
32
  export { type AiAttachmentRef, aiAttachmentMarker, formatAiFileSize, parseAiAttachmentMarkers } from "./attachments";
24
33
  export { CloudAiTranscribeAudioInputSchema, CloudAiTranscribeAudioOutputSchema, createCloudAiTranscribeAudioTool } from "./audio-tool";
25
34
  export { AiBackgroundApprovalRequiredError, authorizeBackgroundAction } from "./background-review";
@@ -65,7 +74,7 @@ export { parseAiSse } from "./client/transport";
65
74
  export { CODE_CAPABILITY_TOKEN_HEADER, createCodeCapabilityTransport } from "./code-capability-transport";
66
75
  export { authorizeCodeExecution } from "./code-execution";
67
76
  export { CODE_SOURCE_TOOLS, type CodeSourceToolName } from "./code-source-contracts";
68
- export { listAiCredentialProfileIds } from "./credentials";
77
+ export { listAiCredentialProviderIds } from "./credentials";
69
78
  export {
70
79
  CLOUD_AI_CHART_MAX_SERIES,
71
80
  CLOUD_AI_CHART_MAX_VALUES,
@@ -281,6 +290,7 @@ export {
281
290
  type AiProjectReference,
282
291
  aiProjects,
283
292
  } from "./projects";
293
+ export { type AiResourceContext, AiResourceCredentialError } from "./projects-authorization";
284
294
  export {
285
295
  type AiProjectsRoutes,
286
296
  aiProjectsRoutes,
@@ -306,7 +316,6 @@ export {
306
316
  listAiModels,
307
317
  readAiSettingsState,
308
318
  resolveAiModel,
309
- resolveAiSettingsStateFromRaw,
310
319
  resolveAiVisionModel,
311
320
  selectAiModelProfile,
312
321
  toPublicAiSettingsState,
@@ -365,7 +374,6 @@ export {
365
374
  streamAiConversationEvents,
366
375
  } from "./stream";
367
376
  export {
368
- AI_BACKGROUND_MODEL_SETTING_KEY,
369
377
  type RunAiStructuredInput,
370
378
  type RunAiStructuredResult,
371
379
  resolveAiBackgroundModel,
@@ -420,7 +428,6 @@ export type {
420
428
  AiPendingTurnAction,
421
429
  AiProjectPromptSnapshot,
422
430
  AiProviderId,
423
- AiPublicModelProfile,
424
431
  AiRecentResource,
425
432
  AiResolvedModel,
426
433
  AiRuntimeTool,