@k2b/cloud 0.43.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 (267) hide show
  1. package/package.json +7 -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/PermissionEditor.tsx +9 -3
  21. package/src/access/PrincipalPicker.tsx +14 -4
  22. package/src/access/ResourceApiKeys.tsx +3 -4
  23. package/src/access/app-access-messages.ts +6 -0
  24. package/src/account/ProfilePictureDialog.tsx +699 -0
  25. package/src/account/profile-picture-messages.ts +269 -0
  26. package/src/account/ui.ts +7 -0
  27. package/src/ai/admin.ts +3 -0
  28. package/src/ai/app-frame.ts +12 -0
  29. package/src/ai/application-tasks.ts +101 -0
  30. package/src/ai/assistant-models.ts +126 -64
  31. package/src/ai/capabilities.ts +19 -4
  32. package/src/ai/chart-block.ts +63 -30
  33. package/src/ai/chat/chart-block.tsx +12 -3
  34. package/src/ai/chat/composer-adapter.ts +8 -7
  35. package/src/ai/chat/message-actions.tsx +1 -1
  36. package/src/ai/chat/message-utils.ts +2 -2
  37. package/src/ai/chat/presentation.tsx +22 -1
  38. package/src/ai/chat-quotas.ts +5 -4
  39. package/src/ai/chat-tasks.ts +2 -2
  40. package/src/ai/client/controller.ts +11 -10
  41. package/src/ai/client/projection.ts +3 -3
  42. package/src/ai/compaction.ts +3 -3
  43. package/src/ai/credentials.ts +13 -114
  44. package/src/ai/default-tool-contracts.ts +4 -3
  45. package/src/ai/dictation-runtime.ts +4 -4
  46. package/src/ai/enrich.ts +2 -2
  47. package/src/ai/executor.ts +31 -22
  48. package/src/ai/http.ts +17 -29
  49. package/src/ai/index.ts +11 -5
  50. package/src/ai/inference-calls.ts +5 -4
  51. package/src/ai/maintenance.ts +1 -1
  52. package/src/ai/memory-learning-runs.ts +5 -5
  53. package/src/ai/memory-learning.ts +3 -3
  54. package/src/ai/message-queue.ts +1 -2
  55. package/src/ai/migrate.ts +75 -42
  56. package/src/ai/model-access-migrate.ts +3 -17
  57. package/src/ai/model-access.ts +44 -85
  58. package/src/ai/model-card.tsx +134 -0
  59. package/src/ai/model-catalog.ts +359 -0
  60. package/src/ai/model-chooser-messages.ts +88 -0
  61. package/src/ai/model-chooser.tsx +577 -0
  62. package/src/ai/model-delete.ts +234 -0
  63. package/src/ai/model-fixture.ts +39 -0
  64. package/src/ai/model-icon.tsx +36 -0
  65. package/src/ai/model-overview.ts +53 -0
  66. package/src/ai/model-usage.ts +45 -0
  67. package/src/ai/openai-api-messages.ts +0 -4
  68. package/src/ai/openai-api.ts +26 -9
  69. package/src/ai/personal-agent.ts +1 -1
  70. package/src/ai/prefs.ts +12 -10
  71. package/src/ai/projects-routes.ts +2 -1
  72. package/src/ai/projects.ts +13 -12
  73. package/src/ai/protocol.ts +2 -2
  74. package/src/ai/provider-connections.ts +161 -0
  75. package/src/ai/provider-errors.ts +55 -6
  76. package/src/ai/provider-fetch.ts +4 -0
  77. package/src/ai/provider.ts +1 -1
  78. package/src/ai/quota-limits.ts +36 -0
  79. package/src/ai/quota-provider.ts +4 -2
  80. package/src/ai/quota-report.ts +7 -4
  81. package/src/ai/quotas.ts +19 -8
  82. package/src/ai/recent-resources.ts +6 -0
  83. package/src/ai/request-headers.ts +14 -87
  84. package/src/ai/resource-refs.ts +6 -2
  85. package/src/ai/routes.ts +17 -18
  86. package/src/ai/runtime.ts +50 -22
  87. package/src/ai/settings.ts +93 -580
  88. package/src/ai/store.ts +45 -23
  89. package/src/ai/stream.ts +1 -1
  90. package/src/ai/structured-runs.ts +2 -2
  91. package/src/ai/structured.ts +21 -44
  92. package/src/ai/task-contracts.ts +25 -5
  93. package/src/ai/task-execution.ts +15 -6
  94. package/src/ai/task-model.ts +9 -0
  95. package/src/ai/transcription.ts +27 -22
  96. package/src/ai/types.ts +50 -62
  97. package/src/ai/ui.tsx +13 -0
  98. package/src/ai/usage.ts +8 -8
  99. package/src/ai/validate.ts +8 -7
  100. package/src/ai/view-block.ts +2 -2
  101. package/src/api/accounts-entities.ts +16 -0
  102. package/src/api/admin-ai-quotas.ts +34 -56
  103. package/src/api/admin-ai-usage.ts +1 -1
  104. package/src/api/admin-ai.ts +173 -0
  105. package/src/api/admin-core-settings.ts +68 -244
  106. package/src/api/app-approval.ts +3 -1
  107. package/src/api/app-event-delivery.ts +51 -0
  108. package/src/api/app-events.ts +123 -0
  109. package/src/api/app-resource-access.ts +48 -0
  110. package/src/api/auth/account-mismatch.ts +24 -0
  111. package/src/api/auth.ts +26 -3
  112. package/src/api/capabilities.ts +28 -4
  113. package/src/api/index.ts +7 -1
  114. package/src/api/mcp.ts +175 -2
  115. package/src/api/me.ts +6 -4
  116. package/src/api/pwa-phone.ts +75 -26
  117. package/src/api/pwa.ts +34 -8
  118. package/src/api/resource-links.ts +83 -6
  119. package/src/api/resource-resolve.ts +50 -0
  120. package/src/api/search/schemas.ts +3 -1
  121. package/src/api/search.ts +9 -7
  122. package/src/app-pwa/contracts.ts +4 -0
  123. package/src/app-pwa/core-query-error.ts +20 -0
  124. package/src/app-pwa/index.ts +1 -0
  125. package/src/app-pwa/service.ts +91 -0
  126. package/src/app-pwa/transport.ts +22 -0
  127. package/src/approvals/messages.ts +2 -2
  128. package/src/browser/cloud-id.ts +86 -0
  129. package/src/browser/resource-clipboard.ts +4 -0
  130. package/src/browser/resource-links.ts +52 -0
  131. package/src/capabilities/server-invoke.ts +2 -11
  132. package/src/capabilities/testing.ts +5 -2
  133. package/src/cli/access.ts +6 -5
  134. package/src/cli/admin/ai-models.ts +199 -0
  135. package/src/cli/admin/ai-quotas.ts +6 -102
  136. package/src/cli/admin/ai-usage.ts +4 -4
  137. package/src/cli/admin/app-credentials.ts +5 -3
  138. package/src/cli/admin/index.ts +5 -2
  139. package/src/cli/apps.ts +89 -26
  140. package/src/cli/capabilities.ts +22 -1
  141. package/src/cli/events.ts +320 -0
  142. package/src/config/env.ts +40 -0
  143. package/src/contracts/app-access.ts +26 -2
  144. package/src/contracts/app-details.ts +89 -0
  145. package/src/contracts/app-events.ts +51 -0
  146. package/src/contracts/app.ts +21 -5
  147. package/src/contracts/capabilities.ts +143 -3
  148. package/src/contracts/index.ts +2 -0
  149. package/src/contracts/notification-types.ts +36 -9
  150. package/src/contracts/pwa.ts +17 -1
  151. package/src/contracts/registry.ts +6 -0
  152. package/src/contracts/resource-clipboard.ts +35 -0
  153. package/src/contracts/resource-pages.ts +140 -0
  154. package/src/contracts/resource-resolution.ts +13 -0
  155. package/src/contracts/shared.ts +4 -0
  156. package/src/contracts/user-notifications.ts +2 -0
  157. package/src/events/app-event-delivery.ts +100 -0
  158. package/src/events/app-events.testing.ts +65 -0
  159. package/src/events/app-events.ts +134 -0
  160. package/src/events/core-user-created.ts +24 -0
  161. package/src/events/index.ts +9 -0
  162. package/src/events/testing.ts +2 -0
  163. package/src/index.ts +1 -1
  164. package/src/server/api/respond.ts +8 -3
  165. package/src/server/index.ts +1 -0
  166. package/src/server/middleware/app-access.ts +1 -1
  167. package/src/server/middleware/auth.ts +21 -7
  168. package/src/server/middleware/index.ts +1 -0
  169. package/src/server/middleware/invocation.ts +2 -0
  170. package/src/server/public-storage.ts +106 -0
  171. package/src/server/services/access.ts +8 -0
  172. package/src/services/accounts/app.ts +6 -0
  173. package/src/services/accounts/entities.ts +14 -3
  174. package/src/services/accounts/groups.ts +3 -1
  175. package/src/services/accounts/identities.ts +62 -10
  176. package/src/services/accounts/users.ts +7 -6
  177. package/src/services/app-access.ts +25 -7
  178. package/src/services/app-details.ts +224 -0
  179. package/src/services/app-discovery.ts +2 -1
  180. package/src/services/app-event-shapes.ts +139 -0
  181. package/src/services/app-events/accept.ts +116 -0
  182. package/src/services/app-events/admin.ts +250 -0
  183. package/src/services/app-events/declarations.ts +187 -0
  184. package/src/services/app-events/deliver.ts +218 -0
  185. package/src/services/app-events.ts +92 -0
  186. package/src/services/config-conflict.ts +11 -0
  187. package/src/services/core-user-events.ts +1 -0
  188. package/src/services/freeipa-config.ts +9 -0
  189. package/src/services/geo/ledger.ts +28 -10
  190. package/src/services/geo/service.ts +43 -24
  191. package/src/services/identity/core-origin.ts +12 -0
  192. package/src/services/identity/invocation-actor.ts +20 -2
  193. package/src/services/identity/invocation-authority.ts +4 -0
  194. package/src/services/identity/invocation-token.ts +238 -56
  195. package/src/services/identity/issue-invocation.ts +6 -1
  196. package/src/services/image-metadata.ts +65 -1
  197. package/src/services/index.ts +13 -1
  198. package/src/services/ipa/sync.ts +6 -2
  199. package/src/services/ipa/users.ts +4 -1
  200. package/src/services/mandates/policy.ts +19 -12
  201. package/src/services/notifications/browser.ts +4 -0
  202. package/src/services/notifications/catalog.ts +5 -2
  203. package/src/services/notifications/platform.ts +7 -2
  204. package/src/services/notifications/user.ts +18 -7
  205. package/src/services/oauth-tokens.ts +28 -2
  206. package/src/services/object-store.ts +217 -0
  207. package/src/services/providers/local/users.ts +5 -0
  208. package/src/services/pwa-devices.ts +147 -37
  209. package/src/services/session/index.ts +14 -0
  210. package/src/services/settings/core-settings.ts +71 -43
  211. package/src/services/settings/index.ts +8 -0
  212. package/src/services/settings/versions.ts +26 -0
  213. package/src/shared/ai-models.ts +274 -0
  214. package/src/shared/ai-quotas.ts +2 -2
  215. package/src/shared/ai-usage.ts +2 -2
  216. package/src/shared/index.ts +2 -0
  217. package/src/shared-text/documents.ts +25 -8
  218. package/src/shared-text/log.ts +1 -1
  219. package/src/shared-text/text-diff.ts +75 -0
  220. package/src/ssr/AdminArea.tsx +131 -0
  221. package/src/ssr/AdminLayout.tsx +9 -3
  222. package/src/ssr/AdminSidebar.tsx +7 -7
  223. package/src/ssr/AppAccessDenied.tsx +2 -0
  224. package/src/ssr/AppBadge.tsx +14 -0
  225. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  226. package/src/ssr/CloudShell.island.tsx +25 -4
  227. package/src/ssr/Layout.tsx +8 -6
  228. package/src/ssr/PwaLayout.tsx +16 -1
  229. package/src/ssr/PwaPartCount.island.tsx +23 -0
  230. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  231. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  232. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  233. package/src/ssr/admin-active-link.ts +16 -0
  234. package/src/ssr/admin-navigation.ts +25 -20
  235. package/src/ssr/app-badges.ts +31 -7
  236. package/src/ssr/index.ts +8 -0
  237. package/src/ssr/page-trail-runtime.ts +3 -1
  238. package/src/ssr/platform-messages.ts +9 -1
  239. package/src/ssr/pwa-messages.ts +2 -2
  240. package/src/ssr/rail-navigation.ts +5 -1
  241. package/src/ssr/settings-draft.ts +153 -0
  242. package/src/ssr/shell-data.tsx +2 -0
  243. package/src/styles/global.css +1 -0
  244. package/src/styles/profile-picture.css +270 -0
  245. package/src/styles/utilities-navigation.css +6 -0
  246. package/src/workflows/ai/policy.ts +49 -0
  247. package/src/workflows/ai/runtime.ts +131 -21
  248. package/src/workflows/ai/store.ts +283 -35
  249. package/src/workflows/ai/types.ts +8 -2
  250. package/src/workflows/ai-actions.ts +148 -120
  251. package/src/workflows/authoring.ts +31 -0
  252. package/src/workflows/contracts.ts +1 -0
  253. package/src/workflows/definition.ts +2 -0
  254. package/src/workflows/editor.ts +15 -0
  255. package/src/workflows/index.ts +2 -1
  256. package/src/workflows/language/compiler.ts +34 -3
  257. package/src/workflows/runtime/executor.ts +132 -33
  258. package/src/workflows/runtime/index.ts +2 -0
  259. package/src/workflows/runtime/ports.ts +5 -1
  260. package/src/workflows/store/actions.ts +1 -1
  261. package/src/workflows/store/definitions.ts +5 -0
  262. package/src/workflows/store/runs.ts +5 -3
  263. package/src/workflows/store/worker.ts +7 -2
  264. package/src/ai/model-pricing.ts +0 -43
  265. package/src/ai/model-request-settings.ts +0 -98
  266. package/src/ai/settings-messages.ts +0 -129
  267. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -1,15 +1,11 @@
1
+ import { sql } from "bun";
1
2
  import { Hono, type MiddlewareHandler } from "hono";
2
- import { describeRoute } from "hono-openapi";
3
- import { z } from "zod";
4
3
  import { AiBackgroundCostError, backgroundCostState, releaseBackgroundCostStop } from "../ai/inference-calls";
5
- import { setAiModelPricing } from "../ai/model-pricing";
6
- import { AiModelRequestSettingsInvalid, getAiModelRequestSettings, setAiModelRequestSettings } from "../ai/model-request-settings";
7
4
  import { quotaAdminConfig, quotaReport } from "../ai/quota-report";
8
5
  import { AiQuotaError, aiQuotas } from "../ai/quotas";
9
6
  import { readAiSettingsState } from "../ai/settings";
10
- import { type AuthContext, auth, jsonResponse, requiresAdmin, v } from "../server";
11
- import { AiModelPricingSchema, hasBillableAiPricing } from "../shared/ai-costs";
12
- import { AiModelRequestSettingsSchema, AiModelRequestSettingsUpdateSchema } from "../shared/ai-model-request-settings";
7
+ import { type AuthContext, auth, v } from "../server";
8
+ import { hasBillableAiPricing } from "../shared/ai-costs";
13
9
  import {
14
10
  AiQuotaConfigSchema,
15
11
  AiQuotaIdentitySchema,
@@ -23,8 +19,22 @@ const subject = (p: { type: "user" | "service_account"; id: string }) =>
23
19
  export const createAdminAiQuotaRoutes = (authenticate: MiddlewareHandler<AuthContext> = auth.requireRole("admin")) =>
24
20
  new Hono<AuthContext>()
25
21
  .use("*", authenticate)
26
- .onError((error, c) => {
27
- if (error instanceof AiModelRequestSettingsInvalid) return c.json({ message: error.message }, 400);
22
+ .onError(async (error, c) => {
23
+ if (error instanceof AiQuotaError && error.code === "quota_conflict") {
24
+ const current = await quotaAdminConfig();
25
+ const [change] = await sql<
26
+ { id: string | null; name: string | null; changedAt: string }[]
27
+ >`SELECT u.id,COALESCE(NULLIF(u.display_name,''),u.uid) AS name,to_char(c.created_at AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS "changedAt" FROM ai.cost_changes c LEFT JOIN auth.users u ON u.id=c.actor_id WHERE c.revision=${current.revision} ORDER BY c.created_at DESC LIMIT 1`;
28
+ return c.json(
29
+ {
30
+ code: "conflict",
31
+ current,
32
+ changedBy: change?.id ? { id: change.id, name: change.name ?? change.id } : null,
33
+ changedAt: change?.changedAt ?? new Date(0).toISOString(),
34
+ },
35
+ 409,
36
+ );
37
+ }
28
38
  if (error instanceof AiQuotaError || error instanceof AiBackgroundCostError)
29
39
  return c.json({ error: error.code, message: error.message }, 409);
30
40
  throw error;
@@ -33,54 +43,22 @@ export const createAdminAiQuotaRoutes = (authenticate: MiddlewareHandler<AuthCon
33
43
  .get("/models", async (c) =>
34
44
  c.json({
35
45
  unit: (await aiQuotas.config()).unit ?? "EUR",
36
- models: (await readAiSettingsState()).profiles.map((m) => ({
37
- id: m.id,
38
- label: m.label,
39
- pricing: m.pricing,
40
- enabled: m.enabled,
41
- capabilities: m.capabilities,
42
- })),
46
+ models: (await readAiSettingsState()).profiles
47
+ .filter((m, index, profiles) => {
48
+ const representative =
49
+ profiles.find((candidate) => candidate.modelId === m.modelId && hasBillableAiPricing(candidate.pricing)) ??
50
+ profiles.find((candidate) => candidate.modelId === m.modelId);
51
+ return profiles.indexOf(representative!) === index;
52
+ })
53
+ .map((m) => ({
54
+ id: m.modelId,
55
+ label: m.label,
56
+ pricing: m.pricing,
57
+ enabled: m.enabled,
58
+ capabilities: m.capabilities,
59
+ })),
43
60
  }),
44
61
  )
45
- .put(
46
- "/models/:id/pricing",
47
- v("json", z.object({ pricing: AiModelPricingSchema.nullable(), expected: AiModelPricingSchema.nullable() }).strict()),
48
- async (c) => {
49
- const data = c.req.valid("json");
50
- return c.json(await setAiModelPricing(c.req.param("id")!, data.pricing, data.expected, c.get("user")!.id));
51
- },
52
- )
53
- .get(
54
- "/models/:id/settings",
55
- describeRoute({
56
- tags: ["Administration"],
57
- summary: "Read masked model request settings",
58
- ...requiresAdmin,
59
- responses: { 200: jsonResponse(AiModelRequestSettingsSchema, "Model request settings (header names only)") },
60
- }),
61
- async (c) => {
62
- c.header("Cache-Control", "no-store");
63
- return c.json(await getAiModelRequestSettings(c.req.param("id")!));
64
- },
65
- )
66
- .put(
67
- "/models/:id/settings",
68
- describeRoute({
69
- tags: ["Administration"],
70
- summary: "Patch model request settings with a revision guard",
71
- ...requiresAdmin,
72
- responses: {
73
- 200: jsonResponse(AiModelRequestSettingsSchema, "Updated model request settings (header names only)"),
74
- 400: jsonResponse(z.object({ message: z.string() }), "Invalid model request settings"),
75
- 409: jsonResponse(z.object({ message: z.string(), error: z.string() }), "Model request settings changed"),
76
- },
77
- }),
78
- v("json", AiModelRequestSettingsUpdateSchema),
79
- async (c) => {
80
- c.header("Cache-Control", "no-store");
81
- return c.json(await setAiModelRequestSettings(c.req.param("id")!, c.req.valid("json"), c.get("user")!.id));
82
- },
83
- )
84
62
  .get("/background", async (c) => c.json(await backgroundCostState()))
85
63
  .post("/background/release", async (c) => c.json(await releaseBackgroundCostStop(c.get("user")!.id)))
86
64
  .get("/report", v("query", AiQuotaReportQuerySchema), async (c) => c.json(await quotaReport(c.req.valid("query"))))
@@ -92,7 +70,7 @@ export const createAdminAiQuotaRoutes = (authenticate: MiddlewareHandler<AuthCon
92
70
  previous = await aiQuotas.config();
93
71
  if (
94
72
  config.rules.some(
95
- (r) => r.scope !== "*" && !models.some((m) => m.id === r.scope) && !previous.rules.some((old) => old.scope === r.scope),
73
+ (r) => r.scope !== "*" && !models.some((m) => m.modelId === r.scope) && !previous.rules.some((old) => old.scope === r.scope),
96
74
  )
97
75
  )
98
76
  return c.json({ message: "Select an enabled chat model with nonzero prices or all chat models." }, 400);
@@ -25,7 +25,7 @@ export const createAdminAiUsageRoutes = (
25
25
  .get(
26
26
  "/facets",
27
27
  describeRoute({ summary: "Find AI usage filter values", tags: ["Admin AI usage"] }),
28
- v("query", AiUsageQuerySchema.extend({ field: z.enum(["userId", "modelProfileId", "providerModel", "appId", "task"]) }).strict()),
28
+ v("query", AiUsageQuerySchema.extend({ field: z.enum(["userId", "modelId", "providerModel", "appId", "task"]) }).strict()),
29
29
  async (c) => {
30
30
  const q = c.req.valid("query");
31
31
  return respond(c, ok({ items: await service.facets(q.field, q.search ?? "", q) }));
@@ -0,0 +1,173 @@
1
+ import { sql } from "bun";
2
+ import { Hono, type MiddlewareHandler } from "hono";
3
+ import { z } from "zod";
4
+ import { setAiCredential } from "../ai/credentials";
5
+ import { AiModelAccessDraftSchema } from "../ai/model-access";
6
+ import { AiConfigError, aiModelCatalog, assertAiRevision, lockAiModelCatalog } from "../ai/model-catalog";
7
+ import { deleteAiCatalogEntry } from "../ai/model-delete";
8
+ import { readAiAdminOverview } from "../ai/model-overview";
9
+ import { aiModelUsage } from "../ai/model-usage";
10
+ import {
11
+ AiProviderListError,
12
+ listProviderModels,
13
+ recordAiProviderStatus,
14
+ storedProviderConnection,
15
+ testAiProvider,
16
+ } from "../ai/provider-connections";
17
+ import { getAiRequestHeaders, setAiRequestHeaders } from "../ai/request-headers";
18
+ import { type AuthContext, auth, v } from "../server";
19
+ import { ConfigConflict } from "../services/config-conflict";
20
+ import { ImageMetadataError } from "../services/image-metadata";
21
+ import {
22
+ AI_MODEL_SLOTS,
23
+ AiDeleteModelSchema,
24
+ AiModelInputSchema,
25
+ AiModelPatchSchema,
26
+ AiProviderInputSchema,
27
+ AiProviderPatchSchema,
28
+ AiVariantInputSchema,
29
+ AiVariantPatchSchema,
30
+ } from "../shared/ai-models";
31
+ import { AiRequestHeadersSchema, patchAiRequestHeaders, providerSupportsRequestHeaders } from "../shared/ai-request-options";
32
+
33
+ const revision = { revision: z.number().int().positive() };
34
+ const providerPatch = AiProviderPatchSchema.extend(revision).strict();
35
+ const modelPatch = AiModelPatchSchema.extend({ ...revision, access: AiModelAccessDraftSchema.optional() }).strict();
36
+ const variantPatch = AiVariantPatchSchema.extend(revision).strict();
37
+ const draftConnection = AiProviderInputSchema.omit({ id: true, label: true, dataBoundary: true, enabled: true, logo: true })
38
+ .extend({ apiKey: z.string().optional(), headers: AiRequestHeadersSchema.optional() })
39
+ .strict();
40
+ export const createAdminAiRoutes = (authenticate: MiddlewareHandler<AuthContext> = auth.requireRole("admin")) =>
41
+ new Hono<AuthContext>()
42
+ .use("*", authenticate)
43
+ .onError((error, c) => {
44
+ if (error instanceof ImageMetadataError) return c.json({ code: error.code, message: error.message }, 422);
45
+ if (error instanceof ConfigConflict)
46
+ return c.json({ code: error.code, current: error.current, changedBy: error.changedBy, changedAt: error.changedAt }, 409);
47
+ if (error instanceof AiConfigError)
48
+ return c.json({ code: error.code, message: error.message }, error.code === "model_not_found" ? 404 : 400);
49
+ if (error instanceof AiProviderListError) return c.json({ code: error.code, message: error.message }, 502);
50
+ if (error instanceof z.ZodError) return c.json({ code: "invalid_configuration", message: z.prettifyError(error) }, 400);
51
+ throw error;
52
+ })
53
+ .get("/overview", async (c) => c.json(await readAiAdminOverview()))
54
+ .get("/providers", async (c) => c.json({ providers: await aiModelCatalog.providers() }))
55
+ .post("/providers", v("json", AiProviderInputSchema), async (c) =>
56
+ c.json(await aiModelCatalog.createProvider(c.req.valid("json"), c.get("user").id), 201),
57
+ )
58
+ .post("/providers/test", v("json", draftConnection), async (c) => {
59
+ const data = c.req.valid("json");
60
+ return c.json(await testAiProvider({ ...data, headers: patchAiRequestHeaders({}, data.headers ?? {}) }, c.req.raw.signal));
61
+ })
62
+ .get("/providers/:id", async (c) => {
63
+ return c.json(await aiModelCatalog.providerDetail(c.req.param("id")!));
64
+ })
65
+ .patch("/providers/:id", v("json", providerPatch), async (c) => {
66
+ const { revision, ...patch } = c.req.valid("json");
67
+ return c.json(await aiModelCatalog.updateProvider(c.req.param("id")!, patch, revision, c.get("user").id));
68
+ })
69
+ .put("/providers/:id/credential", v("json", z.object({ ...revision, apiKey: z.string() }).strict()), async (c) => {
70
+ const id = c.req.param("id")!,
71
+ data = c.req.valid("json");
72
+ const saved = await sql.begin(async (db) => {
73
+ await lockAiModelCatalog(db);
74
+ const current = await aiModelCatalog.providerDetail(id, db);
75
+ await assertAiRevision(current, data.revision, db);
76
+ await setAiCredential(id, data.apiKey, db);
77
+ await db`UPDATE ai.providers SET revision=revision+1,updated_by=${c.get("user").id}::uuid,updated_at=clock_timestamp(),status='unknown',status_code=NULL,status_message=NULL,status_checked_at=NULL WHERE id=${id}`;
78
+ return aiModelCatalog.provider(id, db);
79
+ });
80
+ return c.json(saved);
81
+ })
82
+ .put("/providers/:id/headers", v("json", z.object({ ...revision, headers: AiRequestHeadersSchema }).strict()), async (c) => {
83
+ const id = c.req.param("id")!,
84
+ data = c.req.valid("json");
85
+ return c.json(
86
+ await sql.begin(async (db) => {
87
+ await lockAiModelCatalog(db);
88
+ const current = await aiModelCatalog.providerDetail(id, db);
89
+ await assertAiRevision(current, data.revision, db);
90
+ if (!providerSupportsRequestHeaders(current.type))
91
+ throw new AiConfigError("invalid_configuration", "Extra headers require vLLM or an OpenAI-compatible provider.");
92
+ await setAiRequestHeaders(id, patchAiRequestHeaders(await getAiRequestHeaders(id, db), data.headers), db);
93
+ await db`UPDATE ai.providers SET revision=revision+1,updated_by=${c.get("user").id}::uuid,updated_at=clock_timestamp(),status='unknown',status_code=NULL,status_message=NULL,status_checked_at=NULL WHERE id=${id}`;
94
+ return aiModelCatalog.provider(id, db);
95
+ }),
96
+ );
97
+ })
98
+ .post("/providers/:id/test", async (c) => {
99
+ const provider = await aiModelCatalog.provider(c.req.param("id")!);
100
+ const result = await testAiProvider(await storedProviderConnection(provider), c.req.raw.signal);
101
+ await recordAiProviderStatus(provider.id, result, true);
102
+ return c.json(result);
103
+ })
104
+ .get("/providers/:id/available-models", async (c) =>
105
+ c.json({
106
+ models: await listProviderModels(
107
+ await storedProviderConnection(await aiModelCatalog.provider(c.req.param("id")!)),
108
+ c.req.raw.signal,
109
+ ),
110
+ }),
111
+ )
112
+ .post("/providers/:id/models", v("json", z.union([AiModelInputSchema, z.array(AiModelInputSchema).min(1)])), async (c) => {
113
+ const data = c.req.valid("json");
114
+ return c.json(
115
+ { models: await aiModelCatalog.createModels(c.req.param("id")!, Array.isArray(data) ? data : [data], c.get("user").id) },
116
+ 201,
117
+ );
118
+ })
119
+ .get("/models", async (c) => c.json({ models: await aiModelCatalog.models() }))
120
+ .get("/models/:id", async (c) => {
121
+ return c.json(await aiModelCatalog.modelDetail(c.req.param("id")!));
122
+ })
123
+ .patch("/models/:id", v("json", modelPatch), async (c) => {
124
+ const { revision, access, ...patch } = c.req.valid("json");
125
+ const model = await aiModelCatalog.updateModel(c.req.param("id")!, patch, revision, c.get("user").id, access);
126
+ return c.json(model);
127
+ })
128
+ .get("/models/:id/usage", async (c) => {
129
+ const model = await aiModelCatalog.model(c.req.param("id")!);
130
+ return c.json(
131
+ await aiModelUsage(
132
+ (await aiModelCatalog.variants()).filter((v) => v.modelId === model.id).map((v) => v.id),
133
+ [model.id],
134
+ ),
135
+ );
136
+ })
137
+ .post("/models/:id/variants", v("json", AiVariantInputSchema), async (c) =>
138
+ c.json(await aiModelCatalog.createVariant(c.req.param("id")!, c.req.valid("json"), c.get("user").id), 201),
139
+ )
140
+ .patch("/variants/:id", v("json", variantPatch), async (c) => {
141
+ const { revision, ...patch } = c.req.valid("json");
142
+ return c.json(await aiModelCatalog.updateVariant(c.req.param("id")!, patch, revision, c.get("user").id));
143
+ })
144
+ .get("/variants/:id/usage", async (c) => {
145
+ const variant = await aiModelCatalog.variant(c.req.param("id")!);
146
+ return c.json(await aiModelUsage([variant.id], [variant.modelId]));
147
+ })
148
+ .delete("/providers/:id", v("json", AiDeleteModelSchema), async (c) =>
149
+ c.json(await deleteAiCatalogEntry("provider", c.req.param("id")!, c.req.valid("json"), c.get("user").id)),
150
+ )
151
+ .delete("/models/:id", v("json", AiDeleteModelSchema), async (c) =>
152
+ c.json(await deleteAiCatalogEntry("model", c.req.param("id")!, c.req.valid("json"), c.get("user").id)),
153
+ )
154
+ .delete("/variants/:id", v("json", AiDeleteModelSchema), async (c) =>
155
+ c.json(await deleteAiCatalogEntry("variant", c.req.param("id")!, c.req.valid("json"), c.get("user").id)),
156
+ )
157
+ .get("/assignments", async (c) => c.json({ assignments: await aiModelCatalog.assignmentsForAdmin() }))
158
+ .put(
159
+ "/assignments/:slot",
160
+ v("param", z.object({ slot: z.enum(AI_MODEL_SLOTS) })),
161
+ v("json", z.object({ ...revision, variantId: z.string().nullable() }).strict()),
162
+ async (c) =>
163
+ c.json(
164
+ await aiModelCatalog.assign(
165
+ c.req.valid("param").slot,
166
+ c.req.valid("json").variantId,
167
+ c.req.valid("json").revision,
168
+ c.get("user").id,
169
+ ),
170
+ ),
171
+ );
172
+ export default createAdminAiRoutes();
173
+ export type AdminAiApi = ReturnType<typeof createAdminAiRoutes>;