@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
|
@@ -1,117 +1,44 @@
|
|
|
1
1
|
import { sql } from "bun";
|
|
2
|
-
import { toPgTextArray } from "../services/postgres";
|
|
3
2
|
import { decryptValue, encryptValue } from "../services/settings/crypto";
|
|
4
|
-
import {
|
|
5
|
-
AI_REQUEST_HEADERS_PROVIDER_ERROR,
|
|
6
|
-
AiRequestHeadersSchema,
|
|
7
|
-
patchAiRequestHeaders,
|
|
8
|
-
providerSupportsRequestHeaders,
|
|
9
|
-
} from "../shared/ai-request-options";
|
|
10
|
-
import type { AiModelProfile } from "./types";
|
|
3
|
+
import { AiRequestHeadersSchema, patchAiRequestHeaders } from "../shared/ai-request-options";
|
|
11
4
|
|
|
12
5
|
type SqlClient = typeof sql;
|
|
13
|
-
export type AiRequestHeaderPatch = { profileId: string; patch: unknown };
|
|
14
6
|
|
|
15
|
-
const decryptHeaders = async (
|
|
7
|
+
const decryptHeaders = async (providerId: string, secret: string): Promise<Record<string, string>> => {
|
|
16
8
|
try {
|
|
17
9
|
const parsed = AiRequestHeadersSchema.safeParse(await decryptValue(secret));
|
|
18
10
|
if (parsed.success && Object.values(parsed.data).every((value) => typeof value === "string"))
|
|
19
11
|
return patchAiRequestHeaders({}, parsed.data);
|
|
20
12
|
} catch {}
|
|
21
|
-
console.warn(`[ai] ignoring unreadable request headers for
|
|
13
|
+
console.warn(`[ai] ignoring unreadable request headers for provider ${JSON.stringify(providerId)}`);
|
|
22
14
|
return {};
|
|
23
15
|
};
|
|
24
16
|
|
|
25
17
|
/** Secrets stay server-side, in a separate encrypted row like provider API keys. */
|
|
26
|
-
export const getAiRequestHeaders = async (
|
|
27
|
-
const [row] = await db<{ secret: string }[]>`SELECT secret FROM ai.
|
|
28
|
-
return row ? decryptHeaders(
|
|
18
|
+
export const getAiRequestHeaders = async (providerId: string, db: SqlClient = sql): Promise<Record<string, string>> => {
|
|
19
|
+
const [row] = await db<{ secret: string }[]>`SELECT secret FROM ai.provider_request_headers WHERE provider_id = ${providerId}`;
|
|
20
|
+
return row ? decryptHeaders(providerId, row.secret) : {};
|
|
29
21
|
};
|
|
30
22
|
|
|
31
|
-
export const setAiRequestHeaders = async (
|
|
23
|
+
export const setAiRequestHeaders = async (providerId: string, headers: Record<string, string>, db: SqlClient = sql): Promise<void> => {
|
|
32
24
|
const normalized = patchAiRequestHeaders({}, headers);
|
|
33
25
|
if (!Object.keys(normalized).length) {
|
|
34
|
-
await db`DELETE FROM ai.
|
|
26
|
+
await db`DELETE FROM ai.provider_request_headers WHERE provider_id = ${providerId}`;
|
|
35
27
|
return;
|
|
36
28
|
}
|
|
37
29
|
const encrypted = await encryptValue(normalized);
|
|
38
|
-
await db`INSERT INTO ai.
|
|
39
|
-
ON CONFLICT (
|
|
30
|
+
await db`INSERT INTO ai.provider_request_headers (provider_id, secret) VALUES (${providerId}, ${encrypted})
|
|
31
|
+
ON CONFLICT (provider_id) DO UPDATE SET secret = EXCLUDED.secret, updated_at = now()`;
|
|
40
32
|
};
|
|
41
33
|
|
|
42
|
-
/** Admin reads contain names only. Never serialize header values into
|
|
34
|
+
/** Admin reads contain names only. Never serialize header values into providers. */
|
|
43
35
|
export const listAiRequestHeaderNames = async (db: SqlClient = sql): Promise<Record<string, string[]>> => {
|
|
44
|
-
const rows = await db<{
|
|
36
|
+
const rows = await db<{ provider_id: string; secret: string }[]>`SELECT provider_id, secret FROM ai.provider_request_headers`;
|
|
45
37
|
const names: Record<string, string[]> = {};
|
|
46
38
|
for (const row of rows)
|
|
47
|
-
Object.defineProperty(names, row.
|
|
48
|
-
value: Object.keys(await decryptHeaders(row.
|
|
39
|
+
Object.defineProperty(names, row.provider_id, {
|
|
40
|
+
value: Object.keys(await decryptHeaders(row.provider_id, row.secret)).sort(),
|
|
49
41
|
enumerable: true,
|
|
50
42
|
});
|
|
51
43
|
return names;
|
|
52
44
|
};
|
|
53
|
-
|
|
54
|
-
export const pruneAiRequestHeaders = async (keepProfileIds: readonly string[], db: SqlClient = sql): Promise<void> => {
|
|
55
|
-
if (!keepProfileIds.length) await db`DELETE FROM ai.model_request_headers`;
|
|
56
|
-
else await db`DELETE FROM ai.model_request_headers WHERE profile_id <> ALL(${toPgTextArray([...keepProfileIds])}::text[])`;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/** Follow credentials: changing provider discards stored secrets; omission preserves them on the same provider. */
|
|
60
|
-
export const planAiProfileRequestHeaders = (input: {
|
|
61
|
-
currentProfiles: readonly AiModelProfile[];
|
|
62
|
-
nextProfiles: readonly AiModelProfile[];
|
|
63
|
-
existingNames: Record<string, string[]>;
|
|
64
|
-
submitted: readonly AiRequestHeaderPatch[];
|
|
65
|
-
}): { keepHeaderProfileIds: string[]; patches: { profileId: string; patch: Record<string, string | null> }[]; error?: string } => {
|
|
66
|
-
const current = new Map(input.currentProfiles.map((profile) => [profile.id, profile]));
|
|
67
|
-
const keepHeaderProfileIds = input.nextProfiles
|
|
68
|
-
.filter(
|
|
69
|
-
(profile) =>
|
|
70
|
-
providerSupportsRequestHeaders(profile.provider) &&
|
|
71
|
-
current.get(profile.id)?.provider === profile.provider &&
|
|
72
|
-
!profile.capabilities.includes("transcription"),
|
|
73
|
-
)
|
|
74
|
-
.map((profile) => profile.id);
|
|
75
|
-
const patches: { profileId: string; patch: Record<string, string | null> }[] = [];
|
|
76
|
-
for (const submitted of input.submitted) {
|
|
77
|
-
const profile = input.nextProfiles.find((profile) => profile.id === submitted.profileId);
|
|
78
|
-
const parsed = AiRequestHeadersSchema.safeParse(submitted.patch);
|
|
79
|
-
if (!parsed.success)
|
|
80
|
-
return { keepHeaderProfileIds: [], patches: [], error: `requestHeaders: ${zodHeaderMessage(parsed.error.issues)}` };
|
|
81
|
-
if (!profile || !providerSupportsRequestHeaders(profile.provider))
|
|
82
|
-
return { keepHeaderProfileIds: [], patches: [], error: AI_REQUEST_HEADERS_PROVIDER_ERROR };
|
|
83
|
-
if (profile.capabilities.includes("transcription"))
|
|
84
|
-
return {
|
|
85
|
-
keepHeaderProfileIds: [],
|
|
86
|
-
patches: [],
|
|
87
|
-
error: "requestHeaders: Request settings are not supported on transcription profiles.",
|
|
88
|
-
};
|
|
89
|
-
const oldNames =
|
|
90
|
-
keepHeaderProfileIds.includes(profile.id) && Object.hasOwn(input.existingNames, profile.id) ? input.existingNames[profile.id]! : [];
|
|
91
|
-
try {
|
|
92
|
-
patchAiRequestHeaders(Object.fromEntries(oldNames.map((name) => [name, ""])), parsed.data);
|
|
93
|
-
} catch {
|
|
94
|
-
return {
|
|
95
|
-
keepHeaderProfileIds: [],
|
|
96
|
-
patches: [],
|
|
97
|
-
error: "requestHeaders: At most 32 extra headers are allowed after applying the patch.",
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
patches.push({ profileId: profile.id, patch: parsed.data });
|
|
101
|
-
}
|
|
102
|
-
return { keepHeaderProfileIds, patches };
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const zodHeaderMessage = (issues: readonly { path: readonly PropertyKey[]; message: string }[]) =>
|
|
106
|
-
issues.map((issue) => `${issue.path.map(String).join(".") || "headers"}: ${issue.message}`).join("; ");
|
|
107
|
-
|
|
108
|
-
/** Apply after pruning so a provider change cannot inherit old secrets. Call inside the settings transaction. */
|
|
109
|
-
export const storeAiRequestHeaderPlan = async (
|
|
110
|
-
plan: ReturnType<typeof planAiProfileRequestHeaders>,
|
|
111
|
-
db: SqlClient = sql,
|
|
112
|
-
): Promise<void> => {
|
|
113
|
-
if (plan.error) throw new Error(plan.error);
|
|
114
|
-
await pruneAiRequestHeaders(plan.keepHeaderProfileIds, db);
|
|
115
|
-
for (const { profileId, patch } of plan.patches)
|
|
116
|
-
await setAiRequestHeaders(profileId, patchAiRequestHeaders(await getAiRequestHeaders(profileId, db), patch), db);
|
|
117
|
-
};
|
package/src/ai/resource-refs.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { AiConversationResourceObservation } from "./types";
|
|
|
13
13
|
|
|
14
14
|
const LinkedResourceReferenceSchema = CloudResourceReferenceSchema.extend({
|
|
15
15
|
href: CloudResourceHrefSchema.optional(),
|
|
16
|
-
});
|
|
16
|
+
}).strip();
|
|
17
17
|
|
|
18
18
|
export const addCloudResourceHrefs = <T>(
|
|
19
19
|
value: T,
|
|
@@ -67,12 +67,14 @@ export const collectConversationResourceObservations = (...values: unknown[]): A
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const view = CloudResourceViewSchema.safeParse(value);
|
|
70
|
+
const view = CloudResourceViewSchema.strip().safeParse(value);
|
|
71
71
|
if (view.success) {
|
|
72
72
|
const resource: CloudResourceView = view.data;
|
|
73
73
|
observations.set(observationKey(resource.ref), {
|
|
74
74
|
ref: resource.ref,
|
|
75
75
|
title: resource.title,
|
|
76
|
+
context: resource.context,
|
|
77
|
+
status: resource.status,
|
|
76
78
|
preview: resource.preview,
|
|
77
79
|
icon: resource.icon,
|
|
78
80
|
href: resource.links.find((link) => link.rel === "open")?.href,
|
|
@@ -87,6 +89,8 @@ export const collectConversationResourceObservations = (...values: unknown[]): A
|
|
|
87
89
|
...previous,
|
|
88
90
|
ref,
|
|
89
91
|
...(resource.title !== undefined ? { title: resource.title } : {}),
|
|
92
|
+
...(resource.context !== undefined ? { context: resource.context } : {}),
|
|
93
|
+
...(resource.status !== undefined ? { status: resource.status } : {}),
|
|
90
94
|
...(resource.preview !== undefined ? { preview: resource.preview } : {}),
|
|
91
95
|
...(resource.icon !== undefined ? { icon: resource.icon } : {}),
|
|
92
96
|
...(resource.href !== undefined ? { href: resource.href } : {}),
|
package/src/ai/routes.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { coreSettings } from "../services/settings/api";
|
|
|
37
37
|
import { resolveAppIdentityPresentation } from "../shared/app-presentation";
|
|
38
38
|
import { readThemeFromCookieHeader } from "../shared/theme";
|
|
39
39
|
import { type AiStoredActionReview, isLegacyActionReview, isUnknownActionOutcome, storedActionReview } from "./action-review";
|
|
40
|
-
import { assistantAiSettingsState,
|
|
40
|
+
import { assistantAiSettingsState, peopleAiModels, resolveAssistantAudioModel, selectAssistantAiModel } from "./assistant-models";
|
|
41
41
|
import { AI_AUDIO_MAX_BYTES } from "./audio-format";
|
|
42
42
|
import { buildAiCapabilityCatalog } from "./capabilities";
|
|
43
43
|
import { checkAiCapability, resolveAiCapabilityActor } from "./capability-execution";
|
|
@@ -254,10 +254,10 @@ const publicMemory = async (userId: string, memory: Awaited<ReturnType<typeof ai
|
|
|
254
254
|
(await publicMemories(userId, [memory]))[0]!;
|
|
255
255
|
|
|
256
256
|
/** Fire-and-forget: remember the model this user actually ran a turn with (preselected for new chats). */
|
|
257
|
-
const rememberLastUsedModel = (actor: RequestActor,
|
|
257
|
+
const rememberLastUsedModel = (actor: RequestActor, model: import("../shared/ai-models").AiModelRequest | undefined): void => {
|
|
258
258
|
const userId = aiPrefsUserId(actor);
|
|
259
|
-
if (!userId || !
|
|
260
|
-
void aiUserPrefs.update(userId, {
|
|
259
|
+
if (!userId || !model) return;
|
|
260
|
+
void aiUserPrefs.update(userId, { selection: model }).catch(() => undefined);
|
|
261
261
|
};
|
|
262
262
|
|
|
263
263
|
const conversationDetail = async (conversation: AiConversation, subject: AccessSubject) => {
|
|
@@ -348,7 +348,7 @@ export const aiRoutes = (() => {
|
|
|
348
348
|
return c.json(await getAiChatQuotas(c.get("accessSubject")));
|
|
349
349
|
})
|
|
350
350
|
.get("/models", async (c) => {
|
|
351
|
-
return respond(c, ok(await
|
|
351
|
+
return respond(c, ok(await peopleAiModels(c.get("accessSubject"))));
|
|
352
352
|
})
|
|
353
353
|
.get("/prefs", async (c) => {
|
|
354
354
|
const ctx = await resolveContext(c);
|
|
@@ -438,12 +438,11 @@ export const aiRoutes = (() => {
|
|
|
438
438
|
const state = await readAiSettingsState();
|
|
439
439
|
let previewProfile: ReturnType<typeof selectAiModelProfile> | null = null;
|
|
440
440
|
if (state.ok && state.enabled) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (preferredId) previewProfile = selectAiModelProfile(state, ctx.modelPolicy, preferredId);
|
|
441
|
+
try {
|
|
442
|
+
previewProfile = selectAiModelProfile(state, ctx.modelPolicy, prefs.selection ?? undefined);
|
|
443
|
+
} catch {
|
|
444
|
+
/* The prompt preview remains available without a usable model. */
|
|
445
|
+
}
|
|
447
446
|
}
|
|
448
447
|
const toolsSupported = Boolean(previewProfile?.capabilities.includes("tools"));
|
|
449
448
|
const availableSkills = toolsSupported
|
|
@@ -1022,7 +1021,7 @@ export const aiRoutes = (() => {
|
|
|
1022
1021
|
locale: getLocale(c),
|
|
1023
1022
|
theme: readThemeFromCookieHeader(c.req.header("cookie")),
|
|
1024
1023
|
timeZone: getTimeZone(c),
|
|
1025
|
-
|
|
1024
|
+
model: body.model ?? project?.defaultModel ?? undefined,
|
|
1026
1025
|
modelPolicy: ctx.modelPolicy,
|
|
1027
1026
|
project: project ?? undefined,
|
|
1028
1027
|
clientToolIds: body.clientToolIds,
|
|
@@ -1043,7 +1042,7 @@ export const aiRoutes = (() => {
|
|
|
1043
1042
|
return respond(c, ok({ queued: true }));
|
|
1044
1043
|
}
|
|
1045
1044
|
const result = await submitAiChatTurn(submission);
|
|
1046
|
-
rememberLastUsedModel(ctx.actor,
|
|
1045
|
+
rememberLastUsedModel(ctx.actor, body.model);
|
|
1047
1046
|
return respond(
|
|
1048
1047
|
c,
|
|
1049
1048
|
ok({
|
|
@@ -1115,9 +1114,9 @@ export const aiRoutes = (() => {
|
|
|
1115
1114
|
if (originalProject && !currentProject) return respond(c, fail(err.notFound("Project")));
|
|
1116
1115
|
|
|
1117
1116
|
try {
|
|
1118
|
-
const
|
|
1117
|
+
const model = await selectAssistantAiModel(
|
|
1119
1118
|
c.get("accessSubject"),
|
|
1120
|
-
body.
|
|
1119
|
+
body.model ?? originalProject?.defaultModel ?? undefined,
|
|
1121
1120
|
ctx.modelPolicy,
|
|
1122
1121
|
);
|
|
1123
1122
|
if ((await prepareConversationForMessageRetry(conversation.id)) === "busy") {
|
|
@@ -1134,7 +1133,7 @@ export const aiRoutes = (() => {
|
|
|
1134
1133
|
locale: getLocale(c),
|
|
1135
1134
|
theme: readThemeFromCookieHeader(c.req.header("cookie")),
|
|
1136
1135
|
timeZone: getTimeZone(c),
|
|
1137
|
-
|
|
1136
|
+
model,
|
|
1138
1137
|
modelPolicy: ctx.modelPolicy,
|
|
1139
1138
|
systemPrompt,
|
|
1140
1139
|
project: originalProject,
|
|
@@ -1158,7 +1157,7 @@ export const aiRoutes = (() => {
|
|
|
1158
1157
|
}
|
|
1159
1158
|
: undefined,
|
|
1160
1159
|
});
|
|
1161
|
-
rememberLastUsedModel(ctx.actor,
|
|
1160
|
+
rememberLastUsedModel(ctx.actor, body.model);
|
|
1162
1161
|
return respond(
|
|
1163
1162
|
c,
|
|
1164
1163
|
ok({
|
|
@@ -1230,7 +1229,7 @@ export const aiRoutes = (() => {
|
|
|
1230
1229
|
const result = await submitAiCompaction({
|
|
1231
1230
|
conversationId: conversation.id,
|
|
1232
1231
|
actor: ctx.actor,
|
|
1233
|
-
|
|
1232
|
+
model: body.model,
|
|
1234
1233
|
modelPolicy: ctx.modelPolicy,
|
|
1235
1234
|
});
|
|
1236
1235
|
return respond(c, ok({ turn: publicTurn(result.turn, conversation.shortId) }), 201);
|
package/src/ai/runtime.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type ApprovalDecisionChannel, approvals } from "../services/approvals";
|
|
|
8
8
|
import { logger } from "../services/logging";
|
|
9
9
|
import { superviseRuntimeTask } from "../services/runtime-lifecycle";
|
|
10
10
|
import { coreSettings } from "../services/settings/api";
|
|
11
|
+
import type { AiModelRequest } from "../shared/ai-models";
|
|
11
12
|
import {
|
|
12
13
|
AI_WEBSITE_APPROVAL_TOOL,
|
|
13
14
|
type AiToolApprovalChangeContext,
|
|
@@ -15,16 +16,17 @@ import {
|
|
|
15
16
|
aiTurnAllowsRememberedApprovals,
|
|
16
17
|
rememberAiToolApproval,
|
|
17
18
|
} from "./approvals";
|
|
18
|
-
import { aiChatAccessSubject,
|
|
19
|
+
import { aiChatAccessSubject, selectAssistantAiModel } from "./assistant-models";
|
|
19
20
|
import { startAiDictationRuntime } from "./dictation-runtime";
|
|
20
21
|
import { AiTurnExecutor, type ExecutorConfig } from "./executor";
|
|
21
22
|
import { canonicalizeAiConversationAttachments, snapshotAiConversationFiles } from "./file-context";
|
|
22
23
|
import type { AiLocalClient, AiLocalContext } from "./local-tool-contracts";
|
|
23
24
|
import { drainQueuedMessages } from "./message-queue";
|
|
25
|
+
import { aiUserPrefs } from "./prefs";
|
|
24
26
|
import { AI_TURN_LEASE_MS } from "./protocol";
|
|
25
27
|
import { aiQuotas } from "./quotas";
|
|
26
28
|
import { parseAiResourceMarker } from "./resource-markers";
|
|
27
|
-
import { isAiVisionModelConfigured } from "./settings";
|
|
29
|
+
import { isAiVisionModelConfigured, readAiSettingsState } from "./settings";
|
|
28
30
|
import { aiConversations } from "./store";
|
|
29
31
|
import { publishAiWireEvent } from "./stream";
|
|
30
32
|
import { aiToolAudit } from "./tool-audit";
|
|
@@ -110,7 +112,7 @@ export type SubmitAiChatTurnInput = {
|
|
|
110
112
|
theme?: "light" | "dark";
|
|
111
113
|
timeZone?: string;
|
|
112
114
|
modelPolicy?: AiModelPolicy;
|
|
113
|
-
|
|
115
|
+
model?: AiModelRequest;
|
|
114
116
|
/** Optional instructions that apply only to this turn. */
|
|
115
117
|
systemPrompt?: string;
|
|
116
118
|
project?: AiChatTurnRunConfig["project"];
|
|
@@ -143,21 +145,43 @@ export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
|
|
|
143
145
|
);
|
|
144
146
|
const canonicalInput = files?.attached.length ? inputMessage.content : input.input;
|
|
145
147
|
const hasImageAttachments = Boolean(files?.attached.some((file) => isAiImageMediaType(file.mediaType)));
|
|
148
|
+
const state = await readAiSettingsState();
|
|
146
149
|
const canInspectAttachedImages =
|
|
147
150
|
input.canInspectAttachedImages ??
|
|
148
|
-
(input.toolSource?.kind === "default" && (await isAiVisionModelConfigured(input.modelPolicy?.allowedDataBoundaries)));
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
input:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
(input.toolSource?.kind === "default" && (await isAiVisionModelConfigured(input.modelPolicy?.allowedDataBoundaries, state)));
|
|
152
|
+
let requested = input.model;
|
|
153
|
+
let previousLabel: string | undefined;
|
|
154
|
+
if (input.assistantChat) {
|
|
155
|
+
const latest = await aiConversations.getLatestTurn({ conversationId: input.conversationId, kind: "chat" });
|
|
156
|
+
const previous = latest ? await aiConversations.getTurnRunConfig({ conversationId: input.conversationId, turnId: latest.id }) : null;
|
|
157
|
+
const inherited = previous?.kind === "chat" ? previous.model : undefined;
|
|
158
|
+
requested ??= inherited;
|
|
159
|
+
if (inherited && "id" in inherited && requested && "id" in requested && requested.id === inherited.id) {
|
|
160
|
+
if (!state.profiles.some((profile) => profile.id === inherited.id)) {
|
|
161
|
+
previousLabel = previous?.kind === "chat" ? (previous.modelLabel ?? inherited.id) : inherited.id;
|
|
162
|
+
const subject = aiChatAccessSubject(input.actor);
|
|
163
|
+
const prefs = subject?.type === "user" ? await aiUserPrefs.get(subject.userId) : null;
|
|
164
|
+
requested = prefs?.selection && "class" in prefs.selection ? prefs.selection : { class: "balanced" };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const model = input.assistantChat
|
|
169
|
+
? await selectAssistantAiModel(aiChatAccessSubject(input.actor), requested, input.modelPolicy, state)
|
|
170
|
+
: requested;
|
|
171
|
+
const { resolved } = await validateAiTurnRequest(
|
|
172
|
+
{
|
|
173
|
+
input: canonicalInput,
|
|
174
|
+
hasImageAttachments,
|
|
175
|
+
canInspectAttachedImages,
|
|
176
|
+
modelPolicy: input.modelPolicy,
|
|
177
|
+
model,
|
|
178
|
+
},
|
|
179
|
+
state,
|
|
180
|
+
);
|
|
159
181
|
const quotaSubject = aiChatAccessSubject(input.actor);
|
|
160
|
-
if (input.assistantChat && quotaSubject) await aiQuotas.assertAllowed(quotaSubject, resolved.profile.id);
|
|
182
|
+
if (input.assistantChat && quotaSubject) await aiQuotas.assertAllowed(quotaSubject, resolved.profile.id, state);
|
|
183
|
+
const modelLabel =
|
|
184
|
+
resolved.profile.label + (resolved.profile.id !== resolved.profile.modelId ? ` · ${resolved.profile.variantLabel}` : "");
|
|
161
185
|
const runConfig: AiChatTurnRunConfig = {
|
|
162
186
|
kind: "chat",
|
|
163
187
|
selectedSkillIds: [
|
|
@@ -177,7 +201,10 @@ export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
|
|
|
177
201
|
...(input.theme ? { theme: input.theme } : {}),
|
|
178
202
|
...(input.timeZone ? { timeZone: input.timeZone } : {}),
|
|
179
203
|
modelPolicy: input.modelPolicy,
|
|
180
|
-
|
|
204
|
+
model,
|
|
205
|
+
modelResolved: resolved.model,
|
|
206
|
+
modelLabel,
|
|
207
|
+
...(previousLabel ? { modelReplacement: { kind: "model_replaced", previousLabel, nextLabel: modelLabel } } : {}),
|
|
181
208
|
systemPrompt: input.systemPrompt,
|
|
182
209
|
project: input.project,
|
|
183
210
|
files,
|
|
@@ -191,7 +218,7 @@ export const prepareAiChatTurn = async (input: SubmitAiChatTurnInput) => {
|
|
|
191
218
|
|
|
192
219
|
return {
|
|
193
220
|
conversationId: input.conversationId,
|
|
194
|
-
|
|
221
|
+
modelId: resolved.profile.id,
|
|
195
222
|
runConfig,
|
|
196
223
|
userMessage,
|
|
197
224
|
truncateFromSeq: input.truncateFromSeq,
|
|
@@ -244,7 +271,7 @@ export const deliverAiInterChatMessage = async (input: {
|
|
|
244
271
|
};
|
|
245
272
|
const delivered = await aiConversations.deliverInterChatMessage({
|
|
246
273
|
messageId: input.message.id,
|
|
247
|
-
|
|
274
|
+
modelId: resolved.profile.id,
|
|
248
275
|
runConfig,
|
|
249
276
|
userMessage: { role: "user", content: [{ type: "text", text }] },
|
|
250
277
|
sourceHref: input.sourceHref,
|
|
@@ -257,20 +284,21 @@ export type SubmitAiCompactionInput = {
|
|
|
257
284
|
conversationId: string;
|
|
258
285
|
actor?: RequestActor;
|
|
259
286
|
modelPolicy?: AiModelPolicy;
|
|
260
|
-
|
|
287
|
+
model?: AiModelRequest;
|
|
261
288
|
};
|
|
262
289
|
|
|
263
290
|
export const submitAiCompaction = async (input: SubmitAiCompactionInput): Promise<{ turn: AiTurn }> => {
|
|
264
|
-
const { resolved } = await validateAiTurnRequest({ input: "", modelPolicy: input.modelPolicy,
|
|
291
|
+
const { resolved } = await validateAiTurnRequest({ input: "", modelPolicy: input.modelPolicy, model: input.model });
|
|
265
292
|
const runConfig: AiCompactionTurnRunConfig = {
|
|
266
293
|
kind: "compact",
|
|
267
294
|
actor: input.actor,
|
|
268
295
|
modelPolicy: input.modelPolicy,
|
|
269
|
-
|
|
296
|
+
model: { id: resolved.profile.id },
|
|
297
|
+
modelResolved: resolved.model,
|
|
270
298
|
};
|
|
271
299
|
const turn = await aiConversations.createCompactionTurn({
|
|
272
300
|
conversationId: input.conversationId,
|
|
273
|
-
|
|
301
|
+
modelId: resolved.profile.id,
|
|
274
302
|
runConfig,
|
|
275
303
|
});
|
|
276
304
|
await enqueueAiTurn({ conversationId: input.conversationId, turnId: turn.id });
|