@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.
- package/package.json +7 -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/PermissionEditor.tsx +9 -3
- 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/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 +4 -4
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +31 -22
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -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/projects-routes.ts +2 -1
- package/src/ai/projects.ts +13 -12
- 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 +6 -0
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +17 -18
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- 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 +16 -0
- package/src/api/admin-ai-quotas.ts +34 -56
- 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 +26 -3
- 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/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 +4 -0
- 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 +8 -3
- package/src/server/index.ts +1 -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 +8 -0
- package/src/services/accounts/app.ts +6 -0
- package/src/services/accounts/entities.ts +14 -3
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +62 -10
- package/src/services/accounts/users.ts +7 -6
- 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/policy.ts +19 -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 +14 -0
- 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/index.ts +2 -0
- package/src/shared-text/documents.ts +25 -8
- package/src/shared-text/log.ts +1 -1
- package/src/shared-text/text-diff.ts +75 -0
- 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 +1 -1
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +8 -6
- 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 +25 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- 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 +2 -0
- 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/runtime/executor.ts +132 -33
- 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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { i18n } from "@k2b/stdlib";
|
|
2
|
+
import { type DropdownItem, toast, useLocale } from "@k2b/ui";
|
|
3
|
+
import { createMemo, onMount } from "solid-js";
|
|
4
|
+
import type { CloudResourceRef } from "../contracts/capabilities";
|
|
5
|
+
import { cloudResourceHref } from "../contracts/resource-href";
|
|
6
|
+
import { cloudOrigin } from "./resource-links";
|
|
7
|
+
|
|
8
|
+
export const cloudIdMessages = i18n.define({
|
|
9
|
+
baseLocale: "en",
|
|
10
|
+
messages: {
|
|
11
|
+
en: {
|
|
12
|
+
copy: "Copy Cloud ID",
|
|
13
|
+
copied: "Cloud ID copied",
|
|
14
|
+
copyFailed: "Could not copy the Cloud ID",
|
|
15
|
+
},
|
|
16
|
+
de: {
|
|
17
|
+
copy: "Cloud-ID kopieren",
|
|
18
|
+
copied: "Cloud-ID kopiert",
|
|
19
|
+
copyFailed: "Cloud-ID konnte nicht kopiert werden",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type ClipboardModule = typeof import("./resource-clipboard");
|
|
25
|
+
// The clipboard contract validates with Zod, which stays out of an island's first load. It loads once the page is
|
|
26
|
+
// idle, so a copy usually writes the structured reference inside the click that asked for it.
|
|
27
|
+
let clipboardModule: ClipboardModule | undefined;
|
|
28
|
+
const loadClipboard = () => import("./resource-clipboard").then((module) => (clipboardModule = module));
|
|
29
|
+
|
|
30
|
+
/** One entry of the menu; `label` names the resource when a menu offers several, such as a message and its conversation. */
|
|
31
|
+
export type CloudIdTarget = { ref: CloudResourceRef; label?: string };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* "Copy Cloud ID" for the "…" menu of a resource. It copies the structured reference and its `/open` permalink as
|
|
35
|
+
* text: Cloud paste surfaces recognize the resource, and anywhere else the text opens it after sign-in. Without a
|
|
36
|
+
* configured Cloud URL the menu shows nothing.
|
|
37
|
+
*/
|
|
38
|
+
export const useCloudIdCopy = () => {
|
|
39
|
+
const locale = useLocale();
|
|
40
|
+
const t = createMemo(() => cloudIdMessages.resolve([locale()]).t);
|
|
41
|
+
onMount(() => {
|
|
42
|
+
if (clipboardModule || !cloudOrigin()) return;
|
|
43
|
+
const warm = () => void loadClipboard().catch(() => undefined);
|
|
44
|
+
if (typeof requestIdleCallback === "function") requestIdleCallback(warm, { timeout: 2000 });
|
|
45
|
+
else setTimeout(warm, 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const copy = (ref: CloudResourceRef) => {
|
|
49
|
+
const cloudUrl = cloudOrigin();
|
|
50
|
+
if (!cloudUrl) return;
|
|
51
|
+
const failed = () => void toast.error(t().copyFailed);
|
|
52
|
+
const write = (module: ClipboardModule) => {
|
|
53
|
+
try {
|
|
54
|
+
const { cloudResourceClipboard } = module;
|
|
55
|
+
return cloudResourceClipboard
|
|
56
|
+
.write({ cloudUrl, ref, fallbackText: cloudResourceClipboard.url(ref, cloudUrl) })
|
|
57
|
+
.then(() => void toast.success(t().copied), failed);
|
|
58
|
+
} catch {
|
|
59
|
+
failed();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (clipboardModule) return void write(clipboardModule);
|
|
63
|
+
// Before the clipboard code loads, copy the permalink alone: WebKit allows a write only while the click is handled.
|
|
64
|
+
try {
|
|
65
|
+
void navigator.clipboard.writeText(new URL(cloudResourceHref(ref), cloudUrl).href).then(() => void toast.success(t().copied), failed);
|
|
66
|
+
} catch {
|
|
67
|
+
failed();
|
|
68
|
+
}
|
|
69
|
+
void loadClipboard().catch(() => undefined);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
available: () => Boolean(cloudOrigin()),
|
|
74
|
+
copy,
|
|
75
|
+
/** The last section of a resource's "…" menu; empty without a resource or a configured Cloud URL. */
|
|
76
|
+
section: (...targets: readonly (CloudResourceRef | CloudIdTarget | null | undefined | false)[]): DropdownItem[] => {
|
|
77
|
+
if (!cloudOrigin()) return [];
|
|
78
|
+
const items = targets.flatMap((target) => {
|
|
79
|
+
if (!target) return [];
|
|
80
|
+
const { ref, label } = "ref" in target ? target : { ref: target, label: undefined };
|
|
81
|
+
return [{ label: label ?? t().copy, icon: "ti ti-id", immediate: true, action: () => copy(ref) }];
|
|
82
|
+
});
|
|
83
|
+
return items.length > 0 ? [{ items }] : [];
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
CLOUD_RESOURCE_CLIPBOARD_MAX_BYTES,
|
|
4
4
|
CLOUD_RESOURCE_CLIPBOARD_MIME_TYPE,
|
|
5
5
|
CLOUD_RESOURCE_CLIPBOARD_WEB_FORMAT,
|
|
6
|
+
formatCloudResourceUrl,
|
|
6
7
|
parseCloudResourceClipboard,
|
|
8
|
+
parseCloudResourceUrl,
|
|
7
9
|
serializeCloudResourceClipboard,
|
|
8
10
|
} from "../contracts/resource-clipboard";
|
|
9
11
|
|
|
@@ -65,6 +67,8 @@ export const cloudResourceClipboard = {
|
|
|
65
67
|
webFormat: CLOUD_RESOURCE_CLIPBOARD_WEB_FORMAT,
|
|
66
68
|
parse: parseCloudResourceClipboard,
|
|
67
69
|
serialize: serializeCloudResourceClipboard,
|
|
70
|
+
url: formatCloudResourceUrl,
|
|
71
|
+
parseUrl: parseCloudResourceUrl,
|
|
68
72
|
write: writeCloudResourceClipboard,
|
|
69
73
|
read: readCloudResourceClipboard,
|
|
70
74
|
} as const;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CoreApiType } from "../api";
|
|
2
|
+
import { localCloudResourceUrl } from "../contracts/resource-pages";
|
|
3
|
+
import type { ResolvedCloudResourceUrl } from "../contracts/resource-resolution";
|
|
4
|
+
import { createApiClient } from "../server/api-client";
|
|
5
|
+
import { readShellData } from "../ssr/shell-data";
|
|
6
|
+
|
|
7
|
+
export type { ResolvedCloudResourceUrl } from "../contracts/resource-resolution";
|
|
8
|
+
|
|
9
|
+
/** Available on Layout and AdminLayout pages; MinimalLayout does not install shell data. */
|
|
10
|
+
export const cloudOrigin = (): string | null => readShellData()?.cloudOrigin ?? null;
|
|
11
|
+
|
|
12
|
+
/** Recognize one pasted Cloud link. Unsupported, unreadable, and unavailable links all return null. */
|
|
13
|
+
export const resolveCloudUrl = async (text: string, options: { signal?: AbortSignal } = {}): Promise<ResolvedCloudResourceUrl | null> => {
|
|
14
|
+
const url = text.trim();
|
|
15
|
+
if (!localCloudResourceUrl(url, cloudOrigin())) return null;
|
|
16
|
+
try {
|
|
17
|
+
const response = await createApiClient<CoreApiType>().resources.resolve.$post({ json: { url } }, { init: { signal: options.signal } });
|
|
18
|
+
if (!response.ok) return null;
|
|
19
|
+
// Validate only after a successful response; Zod stays out of the initial browser graph.
|
|
20
|
+
const { ResolvedCloudResourceUrlSchema } = await import("../contracts/resource-resolution");
|
|
21
|
+
const result = ResolvedCloudResourceUrlSchema.safeParse(await response.json());
|
|
22
|
+
return result.success ? result.data : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Ctrl+Shift+V (Cmd+Shift+V, also with Option, which changes the key's character) asks for plain text; that paste
|
|
29
|
+
// keeps the link as it is.
|
|
30
|
+
let plainPasteAt = -Infinity;
|
|
31
|
+
if (typeof window !== "undefined")
|
|
32
|
+
window.addEventListener(
|
|
33
|
+
"keydown",
|
|
34
|
+
(event) => {
|
|
35
|
+
if (event.shiftKey && (event.ctrlKey || event.metaKey) && (event.code === "KeyV" || event.key.toLowerCase() === "v"))
|
|
36
|
+
plainPasteAt = event.timeStamp;
|
|
37
|
+
},
|
|
38
|
+
true,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Cloud link a paste carries: one absolute URL on this Cloud, as copied from the address bar or "Copy Cloud ID",
|
|
43
|
+
* and nothing else. Returns null for other text, other origins, and a paste as plain text. Recognize the link with
|
|
44
|
+
* `resolveCloudUrl()`.
|
|
45
|
+
*/
|
|
46
|
+
export const pastedCloudLink = (event: ClipboardEvent): string | null => {
|
|
47
|
+
const plain = event.timeStamp - plainPasteAt < 1_000;
|
|
48
|
+
plainPasteAt = -Infinity;
|
|
49
|
+
const text = (event.clipboardData?.getData("text/plain") ?? "").trim();
|
|
50
|
+
if (plain || text.startsWith("/")) return null;
|
|
51
|
+
return localCloudResourceUrl(text, cloudOrigin()) ? text : null;
|
|
52
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { dispatchCapability, loadCapabilityCatalogPage } from "../api/capabilities";
|
|
3
|
-
import { env } from "../config/env";
|
|
4
3
|
import {
|
|
5
4
|
CAPABILITY_FRAMEWORK_ERROR_CODES,
|
|
6
5
|
CAPABILITY_MAX_CATALOG_BYTES,
|
|
@@ -9,8 +8,7 @@ import {
|
|
|
9
8
|
capabilityResultSchema,
|
|
10
9
|
} from "../contracts/capabilities";
|
|
11
10
|
import type { RequestAuthority } from "../server";
|
|
12
|
-
import {
|
|
13
|
-
import { publicCloudOrigin } from "../shared/app-url";
|
|
11
|
+
import { coreOrigin } from "../services/identity/core-origin";
|
|
14
12
|
import { capabilityMessages } from "../shared/capability-messages";
|
|
15
13
|
import { LOCALE_HEADER } from "../shared/locale";
|
|
16
14
|
import { readCapabilityResponse } from "./response";
|
|
@@ -41,14 +39,7 @@ export type CapabilityCaller = {
|
|
|
41
39
|
mandate?: { id: string; revision: number; callingAppId: string };
|
|
42
40
|
};
|
|
43
41
|
|
|
44
|
-
export
|
|
45
|
-
const configured = env.CLOUD_CORE_INTERNAL_ORIGIN;
|
|
46
|
-
if (internal && !configured) throw new Error("CLOUD_CORE_INTERNAL_ORIGIN is required for mandate-backed invocation");
|
|
47
|
-
const origin = configured || publicCloudOrigin(await get<string>("app.url"));
|
|
48
|
-
const url = new URL(origin);
|
|
49
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("CLOUD_CORE_INTERNAL_ORIGIN must use http or https");
|
|
50
|
-
return url.origin;
|
|
51
|
-
};
|
|
42
|
+
export { coreOrigin } from "../services/identity/core-origin";
|
|
52
43
|
|
|
53
44
|
export const callerRequest = async (
|
|
54
45
|
caller: CapabilityCaller,
|
|
@@ -17,8 +17,11 @@ import {
|
|
|
17
17
|
} from "../contracts/capabilities";
|
|
18
18
|
|
|
19
19
|
/** Compile and validate the public manifest an app would register at startup. */
|
|
20
|
-
export const compileCapabilityManifest = (
|
|
21
|
-
|
|
20
|
+
export const compileCapabilityManifest = (
|
|
21
|
+
appId: string,
|
|
22
|
+
definitions: CapabilityDefinitions,
|
|
23
|
+
routes?: readonly string[],
|
|
24
|
+
): CapabilityManifest => compileCapabilities(appId, definitions, routes).manifest;
|
|
22
25
|
|
|
23
26
|
/** Fail a provider compatibility test when a published local id changed incompatibly. */
|
|
24
27
|
export const assertCapabilityManifestEvolution = (previous: CapabilityManifest, next: CapabilityManifest): void => {
|
package/src/cli/access.ts
CHANGED
|
@@ -49,6 +49,7 @@ export type AccessCommandAdapter<TResource extends AccessResource> = {
|
|
|
49
49
|
scopes?: readonly [string, ...string[]];
|
|
50
50
|
allowPublic?: boolean;
|
|
51
51
|
allowAuthenticated?: boolean;
|
|
52
|
+
/** Defaults to true; false preserves an explicitly restricted resource contract. */
|
|
52
53
|
allowServiceAccounts?: boolean;
|
|
53
54
|
examples?: {
|
|
54
55
|
list?: readonly string[];
|
|
@@ -217,7 +218,7 @@ export const resolveAccessPrincipal = async (
|
|
|
217
218
|
"--group",
|
|
218
219
|
...(options.allowAuthenticated === false ? [] : ["--authenticated"]),
|
|
219
220
|
...(options.allowPublic === true ? ["--public"] : []),
|
|
220
|
-
...(options.allowServiceAccounts
|
|
221
|
+
...(options.allowServiceAccounts !== false ? ["--service-account"] : []),
|
|
221
222
|
];
|
|
222
223
|
throw new Error(`Pass exactly one principal flag: ${allowedFlags.join(", ")}.`);
|
|
223
224
|
}
|
|
@@ -225,8 +226,8 @@ export const resolveAccessPrincipal = async (
|
|
|
225
226
|
if (flags.authenticated && options.allowAuthenticated === false) {
|
|
226
227
|
throw new Error("This resource does not allow authenticated-audience grants.");
|
|
227
228
|
}
|
|
228
|
-
if (flags.serviceAccount && options.allowServiceAccounts
|
|
229
|
-
throw new Error("
|
|
229
|
+
if (flags.serviceAccount && options.allowServiceAccounts === false) {
|
|
230
|
+
throw new Error("This resource does not allow service-account access grants.");
|
|
230
231
|
}
|
|
231
232
|
if (flags.authenticated) return { type: "authenticated" };
|
|
232
233
|
if (flags.public) return { type: "public" };
|
|
@@ -348,11 +349,11 @@ const accessIdFlag = {
|
|
|
348
349
|
|
|
349
350
|
export const createAccessCommands = <TResource extends AccessResource>(adapter: AccessCommandAdapter<TResource>) => {
|
|
350
351
|
const allowed = normalizeAllowedPermissions(adapter.allowedPermissions);
|
|
351
|
-
const searchableKinds = adapter.allowServiceAccounts ? PRINCIPAL_KINDS : (["user", "group"] as const);
|
|
352
|
+
const searchableKinds = adapter.allowServiceAccounts !== false ? PRINCIPAL_KINDS : (["user", "group"] as const);
|
|
352
353
|
const principalFlagOptions = {
|
|
353
354
|
includePublicFlag: adapter.allowPublic === true,
|
|
354
355
|
includeAuthenticatedFlag: adapter.allowAuthenticated !== false,
|
|
355
|
-
includeServiceAccountFlag: adapter.allowServiceAccounts
|
|
356
|
+
includeServiceAccountFlag: adapter.allowServiceAccounts !== false,
|
|
356
357
|
};
|
|
357
358
|
const resolve = (ctx: CloudCliContext, args: string[]) => adapter.resolveResource(ctx, args);
|
|
358
359
|
const scopeFlag: { scope?: ReturnType<typeof flag.string> } = adapter.scopes
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
AI_MODEL_SLOTS,
|
|
4
|
+
AiDeleteModelSchema,
|
|
5
|
+
AiModelAccessDraftSchema,
|
|
6
|
+
AiModelInputSchema,
|
|
7
|
+
AiModelPatchSchema,
|
|
8
|
+
AiProviderInputSchema,
|
|
9
|
+
AiProviderPatchSchema,
|
|
10
|
+
AiVariantInputSchema,
|
|
11
|
+
AiVariantPatchSchema,
|
|
12
|
+
} from "../../shared/ai-models";
|
|
13
|
+
import { arg, type CloudCliContext, command, confirmFlag, flag, printStructured, readCliInput } from "../index";
|
|
14
|
+
import { apiGet, apiJson, readJsonInput } from "./shared";
|
|
15
|
+
|
|
16
|
+
const base = "/api/admin/core/ai";
|
|
17
|
+
const print = (ctx: CloudCliContext, result: unknown) => {
|
|
18
|
+
if (!printStructured(ctx, result)) ctx.print(JSON.stringify(result, null, 2));
|
|
19
|
+
};
|
|
20
|
+
const confirm = (yes: boolean) => {
|
|
21
|
+
if (!yes) throw new Error("AI configuration changes require --yes.");
|
|
22
|
+
};
|
|
23
|
+
const id = { id: arg.required({ valueLabel: "id" }) };
|
|
24
|
+
const input = flag.input({ required: true, description: "JSON input: --input, --input-file or --stdin" });
|
|
25
|
+
const yes = confirmFlag("Confirm AI configuration change");
|
|
26
|
+
const endpoint = (area: string, id: string) => `${base}/${area}/${encodeURIComponent(id)}`;
|
|
27
|
+
export const aiModelCommands = [
|
|
28
|
+
command("ai providers list", {
|
|
29
|
+
summary: "List provider connections and their status",
|
|
30
|
+
async run({ ctx }) {
|
|
31
|
+
print(ctx, await apiGet(ctx, `${base}/providers`));
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
command("ai providers show", {
|
|
35
|
+
summary: "Show a provider; credential and header values stay private",
|
|
36
|
+
args: id,
|
|
37
|
+
async run({ ctx, args }) {
|
|
38
|
+
print(ctx, await apiGet(ctx, endpoint("providers", args.id)));
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
command("ai providers add", {
|
|
42
|
+
summary: "Create a provider connection",
|
|
43
|
+
flags: { input, yes },
|
|
44
|
+
async run({ ctx, flags }) {
|
|
45
|
+
confirm(flags.yes);
|
|
46
|
+
print(
|
|
47
|
+
ctx,
|
|
48
|
+
await apiJson(ctx, "POST", `${base}/providers`, AiProviderInputSchema.parse(await readJsonInput<unknown>(flags.input, "provider"))),
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
command("ai providers update", {
|
|
53
|
+
summary: "Update a provider with optimistic concurrency",
|
|
54
|
+
args: id,
|
|
55
|
+
flags: { input, yes },
|
|
56
|
+
async run({ ctx, args, flags }) {
|
|
57
|
+
confirm(flags.yes);
|
|
58
|
+
const patch = AiProviderPatchSchema.parse(await readJsonInput<unknown>(flags.input, "provider patch"));
|
|
59
|
+
const current = await apiGet<{ revision: number }>(ctx, endpoint("providers", args.id));
|
|
60
|
+
print(ctx, await apiJson(ctx, "PATCH", endpoint("providers", args.id), { ...patch, revision: current.revision }));
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
command("ai providers test", {
|
|
64
|
+
summary: "Test a saved provider connection (10 second timeout)",
|
|
65
|
+
args: id,
|
|
66
|
+
async run({ ctx, args }) {
|
|
67
|
+
print(ctx, await apiJson(ctx, "POST", `${endpoint("providers", args.id)}/test`, {}));
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
command("ai providers key", {
|
|
71
|
+
summary: "Replace a provider key from protected input",
|
|
72
|
+
args: id,
|
|
73
|
+
flags: { key: flag.input({ required: true, description: "API key: use --key-file or --stdin" }), yes },
|
|
74
|
+
async run({ ctx, args, flags }) {
|
|
75
|
+
confirm(flags.yes);
|
|
76
|
+
const apiKey = await readCliInput(flags.key, { required: true, label: "API key" });
|
|
77
|
+
const current = await apiGet<{ revision: number }>(ctx, endpoint("providers", args.id));
|
|
78
|
+
print(
|
|
79
|
+
ctx,
|
|
80
|
+
await apiJson(ctx, "PUT", `${endpoint("providers", args.id)}/credential`, {
|
|
81
|
+
apiKey: apiKey?.trim() ?? "",
|
|
82
|
+
revision: current.revision,
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
command("ai providers models", {
|
|
88
|
+
summary: "List the models advertised by a provider",
|
|
89
|
+
args: id,
|
|
90
|
+
async run({ ctx, args }) {
|
|
91
|
+
print(ctx, await apiGet(ctx, `${endpoint("providers", args.id)}/available-models`));
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
command("ai models list", {
|
|
95
|
+
summary: "List configured models",
|
|
96
|
+
async run({ ctx }) {
|
|
97
|
+
print(ctx, await apiGet(ctx, `${base}/models`));
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
command("ai models show", {
|
|
101
|
+
summary: "Show a model, variants and access",
|
|
102
|
+
args: id,
|
|
103
|
+
async run({ ctx, args }) {
|
|
104
|
+
print(ctx, await apiGet(ctx, endpoint("models", args.id)));
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
command("ai models add", {
|
|
108
|
+
summary: "Add a model to a provider",
|
|
109
|
+
args: { provider: arg.required() },
|
|
110
|
+
flags: { input, yes },
|
|
111
|
+
async run({ ctx, args, flags }) {
|
|
112
|
+
confirm(flags.yes);
|
|
113
|
+
const data = z
|
|
114
|
+
.union([AiModelInputSchema, z.array(AiModelInputSchema).min(1)])
|
|
115
|
+
.parse(await readJsonInput<unknown>(flags.input, "models"));
|
|
116
|
+
print(ctx, await apiJson(ctx, "POST", `${endpoint("providers", args.provider)}/models`, data));
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
command("ai models update", {
|
|
120
|
+
summary: "Update one model and its shared prices",
|
|
121
|
+
args: id,
|
|
122
|
+
flags: { input, yes },
|
|
123
|
+
async run({ ctx, args, flags }) {
|
|
124
|
+
confirm(flags.yes);
|
|
125
|
+
const data = AiModelPatchSchema.extend({ access: AiModelAccessDraftSchema.optional() }).parse(
|
|
126
|
+
await readJsonInput<unknown>(flags.input, "model patch"),
|
|
127
|
+
);
|
|
128
|
+
const current = await apiGet<{ revision: number }>(ctx, endpoint("models", args.id));
|
|
129
|
+
print(ctx, await apiJson(ctx, "PATCH", endpoint("models", args.id), { ...data, revision: current.revision }));
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
command("ai variants add", {
|
|
133
|
+
summary: "Create a named model variant",
|
|
134
|
+
args: { model: arg.required() },
|
|
135
|
+
flags: { input, yes },
|
|
136
|
+
async run({ ctx, args, flags }) {
|
|
137
|
+
confirm(flags.yes);
|
|
138
|
+
print(
|
|
139
|
+
ctx,
|
|
140
|
+
await apiJson(
|
|
141
|
+
ctx,
|
|
142
|
+
"POST",
|
|
143
|
+
`${endpoint("models", args.model)}/variants`,
|
|
144
|
+
AiVariantInputSchema.parse(await readJsonInput<unknown>(flags.input, "variant")),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
command("ai variants update", {
|
|
150
|
+
summary: "Update a variant's overrides",
|
|
151
|
+
args: id,
|
|
152
|
+
flags: { input, yes },
|
|
153
|
+
async run({ ctx, args, flags }) {
|
|
154
|
+
confirm(flags.yes);
|
|
155
|
+
const data = AiVariantPatchSchema.parse(await readJsonInput<unknown>(flags.input, "variant patch"));
|
|
156
|
+
const current = await apiGet<{ variants: { id: string; revision: number }[] }>(ctx, endpoint("models", args.id.split(":")[0]!));
|
|
157
|
+
const variant = current.variants.find((v) => v.id === args.id);
|
|
158
|
+
if (!variant) throw new Error("Variant not found.");
|
|
159
|
+
print(ctx, await apiJson(ctx, "PATCH", endpoint("variants", args.id), { ...data, revision: variant.revision }));
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
...(["providers", "models", "variants"] as const).map((area) =>
|
|
163
|
+
command(`ai ${area} delete`, {
|
|
164
|
+
summary: "Delete with explicit assignment replacements and retargeting choice",
|
|
165
|
+
args: id,
|
|
166
|
+
flags: { input, yes },
|
|
167
|
+
async run({ ctx, args, flags }) {
|
|
168
|
+
confirm(flags.yes);
|
|
169
|
+
const data = AiDeleteModelSchema.parse(await readJsonInput<unknown>(flags.input, "delete consequences"));
|
|
170
|
+
print(ctx, await apiJson(ctx, "DELETE", endpoint(area, args.id), data));
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
),
|
|
174
|
+
command("ai classes show", {
|
|
175
|
+
summary: "Show class and tool assignments, fallback chains and recent callers",
|
|
176
|
+
async run({ ctx }) {
|
|
177
|
+
print(ctx, await apiGet(ctx, `${base}/assignments`));
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
command("ai classes set", {
|
|
181
|
+
summary: "Assign a class or tool slot to a variant or none",
|
|
182
|
+
args: { slot: arg.required(), variant: arg.required() },
|
|
183
|
+
flags: { yes },
|
|
184
|
+
async run({ ctx, args, flags }) {
|
|
185
|
+
confirm(flags.yes);
|
|
186
|
+
const slot = z.enum(AI_MODEL_SLOTS).parse(args.slot);
|
|
187
|
+
const current = await apiGet<{ assignments: { slot: string; revision: number }[] }>(ctx, `${base}/assignments`);
|
|
188
|
+
const assignment = current.assignments.find((a) => a.slot === slot);
|
|
189
|
+
if (!assignment) throw new Error("Assignment missing.");
|
|
190
|
+
print(
|
|
191
|
+
ctx,
|
|
192
|
+
await apiJson(ctx, "PUT", `${base}/assignments/${slot}`, {
|
|
193
|
+
variantId: args.variant === "none" ? null : args.variant,
|
|
194
|
+
revision: assignment.revision,
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
}),
|
|
199
|
+
];
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type AiModelRequestSettings,
|
|
4
|
-
AiModelRequestSettingsSchema,
|
|
5
|
-
AiModelRequestSettingsUpdateSchema,
|
|
6
|
-
} from "../../shared/ai-model-request-settings";
|
|
1
|
+
import type { AiModelPricing } from "../../shared/ai-costs";
|
|
2
|
+
|
|
7
3
|
import {
|
|
8
4
|
type AiQuotaConfig,
|
|
9
5
|
AiQuotaConfigSchema,
|
|
@@ -12,7 +8,7 @@ import {
|
|
|
12
8
|
AiQuotaReportQuerySchema,
|
|
13
9
|
AiQuotaResetSchema,
|
|
14
10
|
} from "../../shared/ai-quotas";
|
|
15
|
-
import { type CloudCliContext, cliText, command, confirmFlag, flag, printRows, printStructured
|
|
11
|
+
import { type CloudCliContext, cliText, command, confirmFlag, flag, printRows, printStructured } from "../index";
|
|
16
12
|
import { apiGet, apiJson, queryString, readJsonInput } from "./shared";
|
|
17
13
|
|
|
18
14
|
const path = "/api/admin/core/ai-quotas";
|
|
@@ -28,98 +24,6 @@ const confirm = (ctx: CloudCliContext, yes: boolean) => {
|
|
|
28
24
|
};
|
|
29
25
|
|
|
30
26
|
export const aiQuotaCommands = [
|
|
31
|
-
command("ai models settings get", {
|
|
32
|
-
summary: "Read model thinking level, extra parameters and configured header names",
|
|
33
|
-
flags: { id: flag.string({ required: true, description: "Model profile ID" }) },
|
|
34
|
-
async run({ ctx, flags }) {
|
|
35
|
-
print(ctx, AiModelRequestSettingsSchema.parse(await apiGet(ctx, `${path}/models/${encodeURIComponent(flags.id!)}/settings`)));
|
|
36
|
-
},
|
|
37
|
-
}),
|
|
38
|
-
command("ai models settings set", {
|
|
39
|
-
summary: "Patch one model's request settings; header values are write-only",
|
|
40
|
-
flags: {
|
|
41
|
-
id: flag.string({ required: true, description: "Model profile ID" }),
|
|
42
|
-
thinkingLevel: flag.string({ name: "thinking-level", description: "Provider-specific thinking level" }),
|
|
43
|
-
clearThinkingLevel: flag.boolean({ name: "clear-thinking-level", description: "Use the model's default thinking level" }),
|
|
44
|
-
extraBody: flag.input({ name: "extra-body", description: "Extra parameters JSON: --extra-body, --extra-body-file or --stdin" }),
|
|
45
|
-
clearExtraBody: flag.boolean({ name: "clear-extra-body", description: "Remove extra parameters" }),
|
|
46
|
-
headers: flag.input({
|
|
47
|
-
stdinName: "headers-stdin",
|
|
48
|
-
description: "Header patch JSON; use --headers-file or --headers-stdin for secrets",
|
|
49
|
-
}),
|
|
50
|
-
clearHeaders: flag.boolean({ name: "clear-headers", description: "Remove all configured extra headers before applying the patch" }),
|
|
51
|
-
yes: confirmFlag("Confirm model request settings change"),
|
|
52
|
-
},
|
|
53
|
-
async run({ ctx, flags }) {
|
|
54
|
-
if (!flags.yes) throw new Error("Model request settings changes require --yes.");
|
|
55
|
-
if (flags.thinkingLevel !== undefined && flags.clearThinkingLevel)
|
|
56
|
-
throw new Error("Choose --thinking-level or --clear-thinking-level.");
|
|
57
|
-
if (flags.extraBody.provided && flags.clearExtraBody) throw new Error("Choose --extra-body or --clear-extra-body.");
|
|
58
|
-
if (
|
|
59
|
-
!flags.extraBody.provided &&
|
|
60
|
-
!flags.headers.provided &&
|
|
61
|
-
flags.thinkingLevel === undefined &&
|
|
62
|
-
!flags.clearThinkingLevel &&
|
|
63
|
-
!flags.clearExtraBody &&
|
|
64
|
-
!flags.clearHeaders
|
|
65
|
-
)
|
|
66
|
-
throw new Error("Select at least one model request setting to change.");
|
|
67
|
-
if (flags.extraBody.source === "stdin" && flags.headers.source === "stdin")
|
|
68
|
-
throw new Error("Read one JSON input from stdin and supply the other from a file.");
|
|
69
|
-
const parseInput = async (input: typeof flags.headers, label: string): Promise<unknown> => {
|
|
70
|
-
const raw = await readCliInput(input, { required: true, label });
|
|
71
|
-
try {
|
|
72
|
-
return JSON.parse(raw!);
|
|
73
|
-
} catch {
|
|
74
|
-
throw new Error(`Invalid ${label} JSON.`);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
const patch: Record<string, unknown> = {};
|
|
78
|
-
if (flags.thinkingLevel !== undefined || flags.clearThinkingLevel)
|
|
79
|
-
patch.reasoningEffort = flags.clearThinkingLevel ? null : flags.thinkingLevel;
|
|
80
|
-
if (flags.extraBody.provided || flags.clearExtraBody)
|
|
81
|
-
patch.extraBody = flags.clearExtraBody ? null : await parseInput(flags.extraBody, "extra parameters");
|
|
82
|
-
if (flags.headers.provided) patch.requestHeaders = await parseInput(flags.headers, "extra headers");
|
|
83
|
-
if (flags.clearHeaders) patch.clearRequestHeaders = true;
|
|
84
|
-
// Validate locally before reading the current revision or sending secrets.
|
|
85
|
-
const valid = AiModelRequestSettingsUpdateSchema.parse({ ...patch, expected: "pending" });
|
|
86
|
-
const current = await apiGet<AiModelRequestSettings>(ctx, `${path}/models/${encodeURIComponent(flags.id!)}/settings`);
|
|
87
|
-
print(
|
|
88
|
-
ctx,
|
|
89
|
-
AiModelRequestSettingsSchema.parse(
|
|
90
|
-
await apiJson(ctx, "PUT", `${path}/models/${encodeURIComponent(flags.id!)}/settings`, { ...valid, expected: current.revision }),
|
|
91
|
-
),
|
|
92
|
-
);
|
|
93
|
-
},
|
|
94
|
-
}),
|
|
95
|
-
command("ai models pricing get", {
|
|
96
|
-
summary: "List configured model input/output reference prices per million tokens",
|
|
97
|
-
async run({ ctx }) {
|
|
98
|
-
print(ctx, await apiGet(ctx, `${path}/models`));
|
|
99
|
-
},
|
|
100
|
-
}),
|
|
101
|
-
command("ai models pricing set", {
|
|
102
|
-
summary: "Set one model's reference prices; JSON null removes prices and makes it unlimited",
|
|
103
|
-
flags: {
|
|
104
|
-
id: flag.string({ required: true, description: "Model profile ID" }),
|
|
105
|
-
pricing: flag.input({
|
|
106
|
-
required: true,
|
|
107
|
-
description: "{inputPerMillion,outputPerMillion} or null: --pricing, --pricing-file or --stdin",
|
|
108
|
-
}),
|
|
109
|
-
yes: confirmFlag("Confirm model price change"),
|
|
110
|
-
},
|
|
111
|
-
async run({ ctx, flags }) {
|
|
112
|
-
confirm(ctx, flags.yes);
|
|
113
|
-
const pricing = AiModelPricingSchema.nullable().parse(await readJsonInput<unknown>(flags.pricing, "model pricing"));
|
|
114
|
-
const current = await apiGet<{ models: { id: string; pricing?: AiModelPricing }[] }>(ctx, `${path}/models`);
|
|
115
|
-
const model = current.models.find((model) => model.id === flags.id);
|
|
116
|
-
if (!model) throw new Error("Model not found.");
|
|
117
|
-
print(
|
|
118
|
-
ctx,
|
|
119
|
-
await apiJson(ctx, "PUT", `${path}/models/${encodeURIComponent(flags.id!)}/pricing`, { pricing, expected: model.pricing ?? null }),
|
|
120
|
-
);
|
|
121
|
-
},
|
|
122
|
-
}),
|
|
123
27
|
command("ai quotas background status", {
|
|
124
28
|
summary: "Show rolling 24-hour background costs and emergency-stop state",
|
|
125
29
|
async run({ ctx }) {
|
|
@@ -153,7 +57,7 @@ export const aiQuotaCommands = [
|
|
|
153
57
|
},
|
|
154
58
|
}),
|
|
155
59
|
command("ai quotas models", {
|
|
156
|
-
summary: "List configured
|
|
60
|
+
summary: "List configured models; only priced chat models support cost rules",
|
|
157
61
|
async run({ ctx }) {
|
|
158
62
|
const result = await apiGet<{ models: { id: string; label: string; pricing?: AiModelPricing }[] }>(ctx, `${path}/models`);
|
|
159
63
|
printRows(
|
|
@@ -170,7 +74,7 @@ export const aiQuotaCommands = [
|
|
|
170
74
|
range: flag.enum(["24h", "7d", "30d", "90d"] as const, { default: "30d" }),
|
|
171
75
|
until: flag.string({ description: "ISO period end; reuse query.until for consistent pagination" }),
|
|
172
76
|
search: flag.string({ description: "Search identity labels" }),
|
|
173
|
-
model: flag.string({ description: "Model
|
|
77
|
+
model: flag.string({ description: "Model ID" }),
|
|
174
78
|
status: flag.enum(["all", "available", "exhausted", "unknown", "unlimited", "disabled"] as const, { default: "all" }),
|
|
175
79
|
sort: flag.enum(["label", "cost", "lastUsed"] as const, { default: "lastUsed" }),
|
|
176
80
|
direction: flag.enum(["asc", "desc"] as const, { default: "desc" }),
|
|
@@ -216,7 +120,7 @@ export const aiQuotaCommands = [
|
|
|
216
120
|
summary: "Reset one identity and quota scope; reuse request ID when retrying",
|
|
217
121
|
flags: {
|
|
218
122
|
...identity,
|
|
219
|
-
scope: flag.string({ required: true, description: "Model
|
|
123
|
+
scope: flag.string({ required: true, description: "Model ID or * (quote the wildcard)" }),
|
|
220
124
|
requestId: flag.string({ required: true, description: "UUID for this reset; reuse for retries, use a new UUID for a new reset" }),
|
|
221
125
|
yes: confirmFlag("Confirm reset of this identity and quota scope"),
|
|
222
126
|
},
|
|
@@ -7,7 +7,7 @@ const filters = {
|
|
|
7
7
|
range: flag.enum(AI_USAGE_RANGES, { default: "30d", description: "Response/run period" }),
|
|
8
8
|
until: flag.string({ description: "Fixed ISO end time; reuse report.query.until across pages" }),
|
|
9
9
|
userId: flag.string({ name: "user", description: "User UUID or unassigned" }),
|
|
10
|
-
|
|
10
|
+
modelId: flag.string({ name: "model", description: "Model profile ID" }),
|
|
11
11
|
providerModel: flag.string({ name: "provider-model", description: "Actual provider model" }),
|
|
12
12
|
workflowId: flag.string({ name: "workflow", description: "Workflow definition UUID" }),
|
|
13
13
|
workflowRunId: flag.string({ name: "workflow-run", description: "Workflow run UUID" }),
|
|
@@ -75,7 +75,7 @@ export const aiUsageCommands = [
|
|
|
75
75
|
{ key: "task" },
|
|
76
76
|
{ key: "status" },
|
|
77
77
|
{ key: "userLabel" },
|
|
78
|
-
{ key: "
|
|
78
|
+
{ key: "modelId" },
|
|
79
79
|
{ key: "cost" },
|
|
80
80
|
{ key: "unit" },
|
|
81
81
|
{ key: "error" },
|
|
@@ -85,7 +85,7 @@ export const aiUsageCommands = [
|
|
|
85
85
|
ctx.table(report.feedback.items, [
|
|
86
86
|
{ key: "id" },
|
|
87
87
|
{ key: "userLabel" },
|
|
88
|
-
{ key: "
|
|
88
|
+
{ key: "modelId" },
|
|
89
89
|
{ key: "rating" },
|
|
90
90
|
{ key: "comment" },
|
|
91
91
|
]);
|
|
@@ -125,7 +125,7 @@ export const aiUsageCommands = [
|
|
|
125
125
|
command("ai usage facets", {
|
|
126
126
|
summary: "Find user IDs, model profiles, provider models, apps or tasks for filtering",
|
|
127
127
|
flags: {
|
|
128
|
-
field: flag.enum(["userId", "
|
|
128
|
+
field: flag.enum(["userId", "modelId", "providerModel", "appId", "task"] as const, { required: true }),
|
|
129
129
|
search: flag.string(),
|
|
130
130
|
range: filters.range,
|
|
131
131
|
},
|
|
@@ -51,7 +51,7 @@ export const appCredentialCommands = [
|
|
|
51
51
|
},
|
|
52
52
|
async run({ ctx, args, flags }) {
|
|
53
53
|
if (!flags.yes)
|
|
54
|
-
throw new Error(cliText(ctx, { en: "Creating an app credential requires --yes.", de: "App-
|
|
54
|
+
throw new Error(cliText(ctx, { en: "Creating an app credential requires --yes.", de: "App-Schlüssel erstellen erfordert --yes." }));
|
|
55
55
|
const result = z.object({ credential: metadata, token: z.string() }).parse(
|
|
56
56
|
await apiJson(ctx, "POST", path(args.app), {
|
|
57
57
|
name: flags.name,
|
|
@@ -68,10 +68,12 @@ export const appCredentialCommands = [
|
|
|
68
68
|
flags: { yes: confirmFlag("Revoke this app credential") },
|
|
69
69
|
async run({ ctx, args, flags }) {
|
|
70
70
|
if (!flags.yes)
|
|
71
|
-
throw new Error(
|
|
71
|
+
throw new Error(
|
|
72
|
+
cliText(ctx, { en: "Revoking an app credential requires --yes.", de: "App-Schlüssel widerrufen erfordert --yes." }),
|
|
73
|
+
);
|
|
72
74
|
const credentialId = z.uuid().parse(args.credential);
|
|
73
75
|
const result = await apiJson(ctx, "DELETE", `${path(args.app)}/${credentialId}`);
|
|
74
|
-
if (!printStructured(ctx, result)) ctx.print(cliText(ctx, { en: "Credential revoked.", de: "
|
|
76
|
+
if (!printStructured(ctx, result)) ctx.print(cliText(ctx, { en: "Credential revoked.", de: "Schlüssel widerrufen." }));
|
|
75
77
|
},
|
|
76
78
|
}),
|
|
77
79
|
];
|