@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.
- package/package.json +11 -2
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/GroupCoverage.tsx +20 -5
- package/src/access/PermissionEditor.tsx +20 -5
- package/src/access/PrincipalAvatar.tsx +30 -21
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/access/messages.ts +4 -4
- package/src/account/EntitySearch.tsx +12 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +10 -6
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +40 -25
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +12 -5
- package/src/ai/inference-calls.ts +5 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api.ts +26 -9
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/project-tool.ts +4 -4
- package/src/ai/projects-authorization.ts +53 -0
- package/src/ai/projects-routes.ts +58 -47
- package/src/ai/projects.ts +110 -88
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +9 -3
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +47 -48
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/skill-tool.ts +4 -4
- package/src/ai/skills-routes.ts +27 -26
- package/src/ai/skills.ts +120 -81
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +21 -44
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +27 -22
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +22 -3
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-skills.ts +4 -1
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +35 -5
- package/src/api/capabilities.ts +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/session.ts +39 -0
- package/src/browser/shared-text-history-validation.ts +151 -0
- package/src/browser/shared-text-history.ts +464 -0
- package/src/browser/shared-text-offline-authorized.ts +29 -0
- package/src/browser/shared-text-offline.ts +351 -0
- package/src/browser/shared-text-provider.ts +714 -0
- package/src/browser/shared-text-table-contract.ts +150 -0
- package/src/browser/shared-text.ts +19 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +6 -1
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +1 -1
- package/src/server/api/respond.ts +17 -4
- package/src/server/index.ts +2 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +31 -10
- package/src/server/services/index.ts +1 -0
- package/src/services/accounts/app.ts +36 -13
- package/src/services/accounts/entities.ts +43 -19
- package/src/services/accounts/group-sql.ts +23 -0
- package/src/services/accounts/groups.ts +19 -14
- package/src/services/accounts/identities.ts +70 -11
- package/src/services/accounts/identity-reconciliation.ts +3 -1
- package/src/services/accounts/local-groups.ts +69 -4
- package/src/services/accounts/users.ts +45 -14
- package/src/services/app-access.ts +25 -7
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/index.ts +2 -2
- package/src/services/mandates/policy.ts +20 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/session/index.ts +26 -3
- package/src/services/settings/core-settings.ts +71 -43
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/email-html.ts +1 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/markdown/extensions/katex.ts +8 -1
- package/src/shared/markdown/extensions/tables.ts +14 -1
- package/src/shared/markdown/index.ts +22 -11
- package/src/shared-text/anchors.ts +29 -0
- package/src/shared-text/awareness.ts +87 -0
- package/src/shared-text/changes.ts +27 -0
- package/src/shared-text/documents.ts +433 -65
- package/src/shared-text/events.ts +12 -2
- package/src/shared-text/index.ts +16 -1
- package/src/shared-text/log.ts +24 -5
- package/src/shared-text/snapshots.ts +69 -23
- package/src/shared-text/structural-history.ts +377 -0
- package/src/shared-text/table-actions.ts +432 -0
- package/src/shared-text/text-diff.ts +75 -0
- package/src/shared-text/types.ts +24 -5
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +44 -7
- package/src/ssr/Layout.tsx +9 -7
- package/src/ssr/MobileProfileActions.tsx +2 -2
- package/src/ssr/ProfilePreferences.tsx +2 -1
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -31
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-launchpad.tsx +6 -1
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +11 -1
- package/src/ssr/profile-actions.ts +3 -6
- package/src/ssr/pwa-messages.ts +2 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +3 -1
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/language/expressions.ts +3 -2
- package/src/workflows/runtime/executor.ts +134 -34
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- 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,
|
|
11
|
-
import {
|
|
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
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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);
|
|
@@ -94,7 +94,10 @@ export const createAdminAiSkillsRoutes = (authenticate: MiddlewareHandler<AuthCo
|
|
|
94
94
|
.get("/:skillId/projects", async (c) => {
|
|
95
95
|
const skill = await aiSkills.admin.getByShortId(c.req.param("skillId")!);
|
|
96
96
|
if (!skill) return notFound(c, "Skill");
|
|
97
|
-
const projects = await aiSkills.admin.linkedProjects(skill.id,
|
|
97
|
+
const projects = await aiSkills.admin.linkedProjects(skill.id, {
|
|
98
|
+
actor: c.get("actor"),
|
|
99
|
+
accessSubject: c.get("accessSubject"),
|
|
100
|
+
});
|
|
98
101
|
return respond(c, ok({ projects: projects.map((project) => ({ id: project.shortId, name: project.name })) }));
|
|
99
102
|
})
|
|
100
103
|
.get("/:skillId/access", async (c) => {
|
|
@@ -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", "
|
|
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>;
|