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