@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
@@ -10,12 +10,15 @@ import {
10
10
  PaginationQuerySchema,
11
11
  PaginationResponseSchema,
12
12
  parsePagination,
13
+ ServiceAccountKindSchema,
14
+ ServiceAccountStatusSchema,
13
15
  UserProfileSchema,
14
16
  UserProviderSchema,
15
17
  } from "../contracts";
16
- import { type AuthContext, auth, expectUserBackedActor, jsonResponse, requiresAuth, respond, v } from "../server";
18
+ import { type AuthContext, auth, jsonResponse, requiresAuth, respond, userFromActor, v } from "../server";
17
19
  import { noTransform } from "../server/middleware/no-transform";
18
20
  import { accountsAppService as accountsService } from "../services";
21
+ import { directoryShares } from "../services/accounts/directory-shares";
19
22
 
20
23
  const EntitiesListResponseSchema = z.object({
21
24
  items: z.array(EntityListItemSchema),
@@ -33,6 +36,8 @@ const QuerySchema = z
33
36
  profile: UserProfileSchema.optional(),
34
37
  user_ids: z.string().optional(),
35
38
  group_ids: z.string().optional(),
39
+ service_account_kinds: z.string().optional(),
40
+ service_account_status: ServiceAccountStatusSchema.optional(),
36
41
  exclude_user_ids: z.string().optional(),
37
42
  exclude_group_ids: z.string().optional(),
38
43
  exclude_service_account_ids: z.string().optional(),
@@ -72,6 +77,14 @@ const parseKinds = (value?: string) => {
72
77
  return { ok: false as const, message: "Invalid kinds query parameter." };
73
78
  };
74
79
 
80
+ const parseServiceAccountKinds = (value?: string) => {
81
+ const kinds = parseCsv(value);
82
+ if (kinds.length === 0) return { ok: true as const, value: undefined };
83
+ const parsed = z.array(ServiceAccountKindSchema).safeParse([...new Set(kinds)]);
84
+ if (parsed.success) return { ok: true as const, value: parsed.data };
85
+ return { ok: false as const, message: "Invalid service_account_kinds query parameter." };
86
+ };
87
+
75
88
  const parseUuidList = (value: string | undefined, label: string) => {
76
89
  const ids = parseCsv(value);
77
90
  if (ids.length === 0) return { ok: true as const, value: undefined };
@@ -87,30 +100,30 @@ const parseUuidList = (value: string | undefined, label: string) => {
87
100
 
88
101
  type AccountsEntitiesRouteDependencies = {
89
102
  authenticate?: MiddlewareHandler<AuthContext>;
90
- requireUser?: MiddlewareHandler<AuthContext>;
91
103
  listEntities?: typeof accountsService.entity.list;
104
+ searchShares?: typeof directoryShares.search;
92
105
  };
93
106
 
94
107
  export const createAccountsEntitiesRoutes = (dependencies: AccountsEntitiesRouteDependencies = {}) =>
95
108
  new Hono<AuthContext>().use("/entities/*", noTransform()).get(
96
109
  "/entities",
97
110
  dependencies.authenticate ?? auth.requireRole("authenticated"),
98
- dependencies.requireUser ?? auth.requireUser(),
99
111
  describeRoute({
100
112
  tags: ["Accounts"],
101
- summary: "List mixed users and groups",
113
+ summary: "List visible directory entries",
102
114
  description:
103
- "List visible users and groups with SQL-backed filtering and pagination. Guest accounts see only themselves and their effective groups; relation filters require a full user account. Personal Linux groups (a user's private primary group, flagged with `personalOwner`) are left out unless `include_personal=true`; exact `group_ids` lookups and relation filters always return them.",
115
+ "List visible users and groups with SQL-backed filtering and pagination. Guest accounts see only themselves and their effective groups; relation filters require a full user account. Standalone and agent accounts with read scope see only explicitly shared directory entries and cannot query relationships. Personal Linux groups (a user's private primary group, flagged with `personalOwner`) are left out unless `include_personal=true`; exact `group_ids` lookups and relation filters always return them.",
104
116
  ...requiresAuth,
105
117
  responses: {
106
118
  200: jsonResponse(EntitiesListResponseSchema, "Paginated mixed entity list"),
107
119
  401: jsonResponse(ErrorResponseSchema, "Authentication required"),
108
- 403: jsonResponse(ErrorResponseSchema, "User-backed account required; relation filters require a full account"),
120
+ 403: jsonResponse(ErrorResponseSchema, "Directory read access required; relation filters require a full user account"),
109
121
  },
110
122
  }),
111
123
  v("query", QuerySchema),
112
124
  async (c) => {
113
- const user = expectUserBackedActor(c);
125
+ const actor = c.get("actor");
126
+ const user = userFromActor(actor);
114
127
  const query = c.req.valid("query");
115
128
  const usesRelationFilter = Boolean(
116
129
  query.user_member_of_group_ids ||
@@ -119,12 +132,14 @@ export const createAccountsEntitiesRoutes = (dependencies: AccountsEntitiesRoute
119
132
  query.parent_group_id ||
120
133
  query.managed_by_user_id,
121
134
  );
122
- if (!user.roles.includes("user") && usesRelationFilter) {
135
+ if (user && !user.roles.includes("user") && usesRelationFilter) {
123
136
  return respond(c, fail(err.forbidden("Guest accounts cannot use entity relation filters")));
124
137
  }
125
138
  const params = parsePagination(query);
126
139
  const kinds = parseKinds(query.kinds);
127
140
  if (!kinds.ok) return respond(c, fail(err.badInput(kinds.message)));
141
+ const serviceAccountKinds = parseServiceAccountKinds(query.service_account_kinds);
142
+ if (!serviceAccountKinds.ok) return respond(c, fail(err.badInput(serviceAccountKinds.message)));
128
143
  const excludeUserIds = parseUuidList(query.exclude_user_ids, "exclude_user_ids");
129
144
  if (!excludeUserIds.ok) return respond(c, fail(err.badInput(excludeUserIds.message)));
130
145
  const excludeGroupIds = parseUuidList(query.exclude_group_ids, "exclude_group_ids");
@@ -138,20 +153,15 @@ export const createAccountsEntitiesRoutes = (dependencies: AccountsEntitiesRoute
138
153
  const groupIds = parseUuidList(query.group_ids, "group_ids");
139
154
  if (!groupIds.ok) return respond(c, fail(err.badInput(groupIds.message)));
140
155
 
141
- const result = await (dependencies.listEntities ?? accountsService.entity.list)({
142
- actor: {
143
- userId: user.id,
144
- uid: user.uid,
145
- roles: user.roles,
146
- provider: user.provider,
147
- },
148
- pagination: { page: params.page, perPage: params.perPage },
156
+ const filters = {
149
157
  search: query.search,
150
158
  kinds: kinds.value,
151
159
  provider: query.provider,
152
160
  profile: query.profile,
153
161
  userIds: userIds.value,
154
162
  groupIds: groupIds.value,
163
+ serviceAccountKinds: serviceAccountKinds.value,
164
+ serviceAccountStatus: query.service_account_status,
155
165
  excludeUserIds: excludeUserIds.value,
156
166
  excludeGroupIds: excludeGroupIds.value,
157
167
  excludeServiceAccountIds: excludeServiceAccountIds.value,
@@ -162,6 +172,20 @@ export const createAccountsEntitiesRoutes = (dependencies: AccountsEntitiesRoute
162
172
  managedByUserId: query.managed_by_user_id,
163
173
  recursive: query.recursive === "true",
164
174
  includePersonal: query.include_personal === "true",
175
+ };
176
+ if (!user) {
177
+ const result = await (dependencies.searchShares ?? directoryShares.search)({
178
+ actor,
179
+ requestId: c.req.header("x-request-id"),
180
+ query: { ...filters, page: params.page, perPage: params.perPage },
181
+ });
182
+ if (!result.ok) return respond(c, result);
183
+ return c.json({ items: result.data.items, pagination: createPagination(params, result.data.total) });
184
+ }
185
+ const result = await (dependencies.listEntities ?? accountsService.entity.list)({
186
+ actor: { userId: user.id, uid: user.uid, roles: user.roles, provider: user.provider },
187
+ pagination: { page: params.page, perPage: params.perPage },
188
+ ...filters,
165
189
  });
166
190
 
167
191
  return respond(c, {
@@ -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>;