@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
package/src/ai/transcription.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { openAICompatibleTranscription, type TranscriptionProvider } from "@k2b/nessi/ai";
|
|
2
|
-
import { coreSettings } from "../services";
|
|
3
2
|
import { logger, type TraceContext, trace } from "../services/logging";
|
|
4
3
|
import { AI_AUDIO_MAX_BYTES, inspectAiAudio } from "./audio-format";
|
|
5
4
|
import { getAiCredential } from "./credentials";
|
|
6
5
|
import { beginAiCall, finishAiCall } from "./inference-calls";
|
|
7
|
-
import {
|
|
6
|
+
import { classifyProviderFailure, recordAiProviderStatus } from "./provider-connections";
|
|
7
|
+
import { readAiSettingsState, resolveAiModelRef, selectAiModelProfile } from "./settings";
|
|
8
8
|
import { type AiUsageAttribution, safelyRecordStructuredRun } from "./structured-runs";
|
|
9
9
|
import type { AiDataBoundary, AiModelProfile } from "./types";
|
|
10
10
|
|
|
11
|
-
export const AI_AUDIO_MODEL_SETTING_KEY = "ai.audio_model_id";
|
|
12
11
|
/** Matches the existing chat turn execution budget; callers may cancel sooner. */
|
|
13
12
|
export const AI_TRANSCRIPTION_TIMEOUT_MS = 10 * 60_000;
|
|
14
13
|
|
|
@@ -37,36 +36,42 @@ export const createAiTranscriptionProvider = (profile: AiModelProfile, apiKey?:
|
|
|
37
36
|
model: provider.model,
|
|
38
37
|
transcribe: async (request) => {
|
|
39
38
|
try {
|
|
40
|
-
|
|
39
|
+
const result = await provider.transcribe(request);
|
|
40
|
+
await recordAiProviderStatus(profile.providerId, { status: "ok", message: "The provider connection works." });
|
|
41
|
+
return result;
|
|
41
42
|
} catch (error) {
|
|
42
|
-
|
|
43
|
+
const failure = describeTranscriptionFailure(error, "provider", request.signal?.aborted);
|
|
44
|
+
if (!request.signal?.aborted)
|
|
45
|
+
await recordAiProviderStatus(
|
|
46
|
+
profile.providerId,
|
|
47
|
+
failure.httpStatus
|
|
48
|
+
? classifyProviderFailure({ status: failure.httpStatus })
|
|
49
|
+
: failure.code === "transcription_connection_failed"
|
|
50
|
+
? classifyProviderFailure(new TypeError())
|
|
51
|
+
: classifyProviderFailure(error),
|
|
52
|
+
);
|
|
53
|
+
throw failure;
|
|
43
54
|
}
|
|
44
55
|
},
|
|
45
56
|
};
|
|
46
57
|
};
|
|
47
58
|
|
|
48
59
|
export const resolveAiAudioModel = async (
|
|
49
|
-
input: {
|
|
60
|
+
input: { model?: import("../shared/ai-models").AiModelRequest; allowedDataBoundaries?: AiDataBoundary[] } = {},
|
|
50
61
|
): Promise<AiResolvedAudioModel> => {
|
|
51
62
|
const state = await readAiSettingsState();
|
|
52
|
-
if (!state.ok)
|
|
53
|
-
throw Object.assign(
|
|
54
|
-
new AiTranscriptionError(
|
|
55
|
-
"transcription_configuration_failed",
|
|
56
|
-
`Audio transcription is not available: ${state.error.message} An administrator can fix this in the AI settings.`,
|
|
57
|
-
),
|
|
58
|
-
{ aiError: state.error },
|
|
59
|
-
);
|
|
60
63
|
if (!state.enabled)
|
|
61
64
|
throw new AiTranscriptionError(
|
|
62
65
|
"transcription_configuration_failed",
|
|
63
66
|
"Audio transcription is not available: AI is disabled. An administrator can enable AI in the AI settings.",
|
|
64
67
|
);
|
|
65
|
-
const modelId = input.
|
|
68
|
+
const modelId = input.model
|
|
69
|
+
? resolveAiModelRef(state, input.model).id
|
|
70
|
+
: state.assignments.find((assignment) => assignment.slot === "audio")?.variantId;
|
|
66
71
|
if (!modelId)
|
|
67
72
|
throw new AiTranscriptionError(
|
|
68
73
|
"transcription_configuration_failed",
|
|
69
|
-
"Audio transcription is not set up: no audio model is
|
|
74
|
+
"Audio transcription is not set up: no audio model is assigned. An administrator can configure an audio model in the AI settings.",
|
|
70
75
|
);
|
|
71
76
|
let profile: AiModelProfile;
|
|
72
77
|
try {
|
|
@@ -93,7 +98,7 @@ export const resolveAiAudioModel = async (
|
|
|
93
98
|
error instanceof Error && "aiError" in error ? { aiError: error.aiError } : {},
|
|
94
99
|
);
|
|
95
100
|
}
|
|
96
|
-
const credential = await getAiCredential(profile.
|
|
101
|
+
const credential = await getAiCredential(profile.providerId);
|
|
97
102
|
if (profile.provider === "openai" && !credential?.trim())
|
|
98
103
|
throw new AiTranscriptionError(
|
|
99
104
|
"transcription_configuration_failed",
|
|
@@ -108,7 +113,7 @@ export type RunAiTranscriptionInput = {
|
|
|
108
113
|
filename: string;
|
|
109
114
|
language?: string;
|
|
110
115
|
prompt?: string;
|
|
111
|
-
|
|
116
|
+
model?: import("../shared/ai-models").AiModelRequest;
|
|
112
117
|
allowedDataBoundaries?: AiDataBoundary[];
|
|
113
118
|
signal?: AbortSignal;
|
|
114
119
|
appId?: string;
|
|
@@ -119,7 +124,7 @@ export type RunAiTranscriptionInput = {
|
|
|
119
124
|
};
|
|
120
125
|
|
|
121
126
|
/** One provider call. Durable execution and input authorization belong to the caller. */
|
|
122
|
-
export const runAiTranscription = async (input: RunAiTranscriptionInput): Promise<{ text: string;
|
|
127
|
+
export const runAiTranscription = async (input: RunAiTranscriptionInput): Promise<{ text: string; modelId: string }> => {
|
|
123
128
|
return trace.withSpan(
|
|
124
129
|
{
|
|
125
130
|
name: `ai.transcription.${input.task}`,
|
|
@@ -165,13 +170,13 @@ export const runAiTranscription = async (input: RunAiTranscriptionInput): Promis
|
|
|
165
170
|
});
|
|
166
171
|
signal.throwIfAborted();
|
|
167
172
|
succeeded = true;
|
|
168
|
-
return { text: result.text,
|
|
173
|
+
return { text: result.text, modelId: resolved.profile.id };
|
|
169
174
|
} catch (error) {
|
|
170
175
|
failure = describeTranscriptionFailure(error, stage, signal.aborted);
|
|
171
176
|
logger("ai:transcription")[input.signal?.aborted ? "info" : "error"](failure.message, {
|
|
172
177
|
traceId: span.traceId,
|
|
173
178
|
task: input.task,
|
|
174
|
-
|
|
179
|
+
modelId: resolved?.profile.id ?? input.model,
|
|
175
180
|
providerModel: resolved?.profile.model,
|
|
176
181
|
stage,
|
|
177
182
|
errorCode: failure.code,
|
|
@@ -191,7 +196,7 @@ export const runAiTranscription = async (input: RunAiTranscriptionInput): Promis
|
|
|
191
196
|
task: input.task,
|
|
192
197
|
appId: input.appId,
|
|
193
198
|
attribution: input.attribution,
|
|
194
|
-
|
|
199
|
+
modelId: resolved?.profile.id,
|
|
195
200
|
providerModel: resolved?.profile.model,
|
|
196
201
|
mode: "transcription",
|
|
197
202
|
attempts: resolved ? 1 : 0,
|
package/src/ai/types.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
CloudResourceRef,
|
|
21
21
|
} from "../contracts/capabilities";
|
|
22
22
|
import type { RequestActor } from "../server";
|
|
23
|
+
import type { AiModelRequest, AiResolvedModelRef } from "../shared/ai-models";
|
|
23
24
|
import type { AiActionCheck, AiStoredActionReview } from "./action-review";
|
|
24
25
|
import type { AiLocalClient, AiLocalContext, AiLocalToolName } from "./local-tool-contracts";
|
|
25
26
|
import type { AiTurnBlock } from "./protocol";
|
|
@@ -27,6 +28,8 @@ import type { AiTurnBlock } from "./protocol";
|
|
|
27
28
|
/** Current reader-confirmed metadata for a recently sent resource. */
|
|
28
29
|
export type AiRecentResource = {
|
|
29
30
|
ref: CloudResourceRef;
|
|
31
|
+
context?: string;
|
|
32
|
+
status?: string;
|
|
30
33
|
title: string;
|
|
31
34
|
icon?: string;
|
|
32
35
|
href?: string;
|
|
@@ -44,7 +47,7 @@ export type AiRecentSentResourceRef = {
|
|
|
44
47
|
conversationId: string;
|
|
45
48
|
};
|
|
46
49
|
|
|
47
|
-
export const AI_MODEL_CAPABILITIES = ["
|
|
50
|
+
export const AI_MODEL_CAPABILITIES = ["tools", "vision", "transcription"] as const;
|
|
48
51
|
export type AiModelCapability = (typeof AI_MODEL_CAPABILITIES)[number];
|
|
49
52
|
|
|
50
53
|
export const AI_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"] as const;
|
|
@@ -62,11 +65,15 @@ export type AiDataPolicy = AiDataBoundary;
|
|
|
62
65
|
export type AiModelProfile = {
|
|
63
66
|
id: string;
|
|
64
67
|
label: string;
|
|
68
|
+
modelId: string;
|
|
69
|
+
providerId: string;
|
|
70
|
+
variantLabel: string;
|
|
71
|
+
description: string;
|
|
72
|
+
icon: import("../shared/ai-models").AiModelIcon;
|
|
73
|
+
showInPicker: boolean;
|
|
65
74
|
provider: AiProviderId;
|
|
66
75
|
model: string;
|
|
67
76
|
enabled: boolean;
|
|
68
|
-
/** Small logo (data URL, hard-compressed) shown in the admin card and the composer model picker. */
|
|
69
|
-
image?: string;
|
|
70
77
|
capabilities: AiModelCapability[];
|
|
71
78
|
dataBoundary: AiDataBoundary;
|
|
72
79
|
baseURL?: string;
|
|
@@ -82,67 +89,34 @@ export type AiModelProfile = {
|
|
|
82
89
|
pricing?: import("../shared/ai-costs").AiModelPricing;
|
|
83
90
|
};
|
|
84
91
|
|
|
85
|
-
export type AiPublicModelProfile = Pick<
|
|
86
|
-
AiModelProfile,
|
|
87
|
-
"id" | "label" | "provider" | "model" | "image" | "capabilities" | "dataBoundary" | "contextWindow"
|
|
88
|
-
>;
|
|
89
|
-
|
|
90
92
|
export type AiUserContentPart = ContentPart;
|
|
91
93
|
|
|
92
|
-
export type AiSettingsErrorCode =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
fields?: Record<string, string>;
|
|
94
|
+
export type AiSettingsErrorCode = "ai_disabled" | "ai_no_model" | "model_policy_mismatch";
|
|
95
|
+
export type AiSettingsError = { code: AiSettingsErrorCode; message: string; fields?: Record<string, string> };
|
|
96
|
+
export type AiSettingsState = {
|
|
97
|
+
ok: true;
|
|
98
|
+
enabled: boolean;
|
|
99
|
+
assignments: import("../shared/ai-models").AiModelAssignment[];
|
|
100
|
+
globalInstructions: string;
|
|
101
|
+
localClientInstructions: string;
|
|
102
|
+
compactionInstructions: string;
|
|
103
|
+
maxToolResultChars: number;
|
|
104
|
+
firecrawlConfigured: boolean;
|
|
105
|
+
profiles: AiModelProfile[];
|
|
105
106
|
};
|
|
106
107
|
|
|
107
|
-
export type AiSettingsState =
|
|
108
|
-
| {
|
|
109
|
-
ok: true;
|
|
110
|
-
enabled: boolean;
|
|
111
|
-
defaultModelId: string;
|
|
112
|
-
visionModelId?: string;
|
|
113
|
-
globalInstructions: string;
|
|
114
|
-
localClientInstructions: string;
|
|
115
|
-
compactionInstructions: string;
|
|
116
|
-
maxToolResultChars: number;
|
|
117
|
-
firecrawlConfigured: boolean;
|
|
118
|
-
profiles: AiModelProfile[];
|
|
119
|
-
}
|
|
120
|
-
| {
|
|
121
|
-
ok: false;
|
|
122
|
-
enabled: boolean;
|
|
123
|
-
defaultModelId: string;
|
|
124
|
-
visionModelId?: string;
|
|
125
|
-
globalInstructions: string;
|
|
126
|
-
localClientInstructions: string;
|
|
127
|
-
compactionInstructions: string;
|
|
128
|
-
maxToolResultChars: number;
|
|
129
|
-
firecrawlConfigured: boolean;
|
|
130
|
-
profiles: AiModelProfile[];
|
|
131
|
-
error: AiSettingsError;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
108
|
export type AiModelPolicy =
|
|
135
109
|
| { kind: "platform-default"; allowedDataBoundaries?: AiDataBoundary[]; requiredCapabilities?: AiModelCapability[] }
|
|
136
110
|
| { kind: "locked"; modelId: string; allowedDataBoundaries?: AiDataBoundary[]; requiredCapabilities?: AiModelCapability[] }
|
|
137
111
|
| {
|
|
138
112
|
kind: "selectable";
|
|
139
|
-
defaultModelId?: string;
|
|
140
113
|
allowedModelIds?: string[];
|
|
141
114
|
allowedDataBoundaries?: AiDataBoundary[];
|
|
142
115
|
requiredCapabilities?: AiModelCapability[];
|
|
143
116
|
};
|
|
144
117
|
|
|
145
118
|
export type AiResolvedModel = {
|
|
119
|
+
model: import("../shared/ai-models").AiResolvedModelRef;
|
|
146
120
|
profile: AiModelProfile;
|
|
147
121
|
provider: Provider;
|
|
148
122
|
};
|
|
@@ -230,7 +204,7 @@ export type AiEnrichmentRun = {
|
|
|
230
204
|
conversationId: string;
|
|
231
205
|
status: AiEnrichmentRunStatus;
|
|
232
206
|
trigger: AiEnrichmentTrigger;
|
|
233
|
-
|
|
207
|
+
modelId: string | null;
|
|
234
208
|
/** nessi structured mode of the successful call: native | fallback | repair. */
|
|
235
209
|
mode: string | null;
|
|
236
210
|
durationMs: number | null;
|
|
@@ -298,7 +272,7 @@ export type AiStoredMessage = {
|
|
|
298
272
|
kind: "message" | "summary";
|
|
299
273
|
message: Message;
|
|
300
274
|
loopId: string | null;
|
|
301
|
-
|
|
275
|
+
modelId: string | null;
|
|
302
276
|
providerModel: string | null;
|
|
303
277
|
usage: Usage | null;
|
|
304
278
|
stopReason: string | null;
|
|
@@ -313,6 +287,9 @@ export type AiStoredMessage = {
|
|
|
313
287
|
meta: {
|
|
314
288
|
/** The atomically consumed composer revision, used to confirm a local send. */
|
|
315
289
|
submittedDraftRevision?: number;
|
|
290
|
+
modelRequest?: AiModelRequest;
|
|
291
|
+
modelResolved?: AiResolvedModelRef;
|
|
292
|
+
modelReplacement?: { kind: "model_replaced"; previousLabel: string; nextLabel: string };
|
|
316
293
|
compactedCount?: number;
|
|
317
294
|
todoPlan?: import("./todo-contracts").AiTodoPlan;
|
|
318
295
|
steerId?: string;
|
|
@@ -353,6 +330,7 @@ export type AiConversationTimelineEntry = {
|
|
|
353
330
|
id: string;
|
|
354
331
|
seq: number;
|
|
355
332
|
loopId: string | null;
|
|
333
|
+
modelReplacement?: { kind: "model_replaced"; previousLabel: string; nextLabel: string };
|
|
356
334
|
userPreview: string;
|
|
357
335
|
assistantPreview: string;
|
|
358
336
|
isSteer: boolean;
|
|
@@ -418,7 +396,7 @@ export type AiTurn = {
|
|
|
418
396
|
conversationId: string;
|
|
419
397
|
status: AiTurnStatus;
|
|
420
398
|
attempt: number;
|
|
421
|
-
|
|
399
|
+
modelId: string | null;
|
|
422
400
|
createdAt: string;
|
|
423
401
|
completedAt: string | null;
|
|
424
402
|
error: string | null;
|
|
@@ -534,7 +512,7 @@ export type AiProjectPromptSnapshot = {
|
|
|
534
512
|
instructions: string;
|
|
535
513
|
context: string;
|
|
536
514
|
references: CloudResourceRef[];
|
|
537
|
-
|
|
515
|
+
defaultModel: import("../shared/ai-models").AiModelRequest | null;
|
|
538
516
|
};
|
|
539
517
|
|
|
540
518
|
export type AiProjectFileToolStat = {
|
|
@@ -569,6 +547,8 @@ export type AiSkillFileToolSource = {
|
|
|
569
547
|
|
|
570
548
|
export type AiConversationResourceRef = {
|
|
571
549
|
ref: CloudResourceRef;
|
|
550
|
+
context?: string | null;
|
|
551
|
+
status?: string | null;
|
|
572
552
|
title: string | null;
|
|
573
553
|
preview: string | null;
|
|
574
554
|
icon: string | null;
|
|
@@ -581,6 +561,8 @@ export type AiConversationResourceRef = {
|
|
|
581
561
|
|
|
582
562
|
export type AiConversationResourceObservation = {
|
|
583
563
|
ref: CloudResourceRef;
|
|
564
|
+
context?: string;
|
|
565
|
+
status?: string;
|
|
584
566
|
title?: string;
|
|
585
567
|
preview?: string;
|
|
586
568
|
icon?: string;
|
|
@@ -601,6 +583,8 @@ export type AiConversationResourceOccurrence = AiConversationResourceRef & {
|
|
|
601
583
|
export type AiConversationSourceKind = "result" | "web" | "file" | "resource" | "activity";
|
|
602
584
|
|
|
603
585
|
export type AiConversationSource = {
|
|
586
|
+
context?: string | null;
|
|
587
|
+
status?: string | null;
|
|
604
588
|
kind: AiConversationSourceKind;
|
|
605
589
|
key: string;
|
|
606
590
|
title: string;
|
|
@@ -683,7 +667,10 @@ export type AiChatTurnRunConfig = {
|
|
|
683
667
|
/** Request locale persisted with the turn so async execution keeps the caller preference. */
|
|
684
668
|
locale?: string;
|
|
685
669
|
modelPolicy?: AiModelPolicy;
|
|
686
|
-
|
|
670
|
+
model?: AiModelRequest;
|
|
671
|
+
modelResolved?: AiResolvedModelRef;
|
|
672
|
+
modelLabel?: string;
|
|
673
|
+
modelReplacement?: { kind: "model_replaced"; previousLabel: string; nextLabel: string };
|
|
687
674
|
/** Optional instructions that apply only to this turn. */
|
|
688
675
|
systemPrompt?: string;
|
|
689
676
|
project?: AiProjectPromptSnapshot;
|
|
@@ -708,7 +695,8 @@ export type AiCompactionTurnRunConfig = {
|
|
|
708
695
|
kind: "compact";
|
|
709
696
|
actor?: RequestActor;
|
|
710
697
|
modelPolicy?: AiModelPolicy;
|
|
711
|
-
|
|
698
|
+
model?: AiModelRequest;
|
|
699
|
+
modelResolved?: AiResolvedModelRef;
|
|
712
700
|
};
|
|
713
701
|
|
|
714
702
|
export type AiTurnRunConfig = AiChatTurnRunConfig | AiCompactionTurnRunConfig;
|
|
@@ -851,7 +839,7 @@ export type AiConversationService = {
|
|
|
851
839
|
failInterChatMessage(input: { messageId: string; error: string }): Promise<boolean>;
|
|
852
840
|
deliverInterChatMessage(input: {
|
|
853
841
|
messageId: string;
|
|
854
|
-
|
|
842
|
+
modelId: string;
|
|
855
843
|
runConfig: AiChatTurnRunConfig;
|
|
856
844
|
userMessage: Message;
|
|
857
845
|
sourceHref?: string;
|
|
@@ -909,7 +897,7 @@ export type AiConversationService = {
|
|
|
909
897
|
conversationId: string;
|
|
910
898
|
status: AiEnrichmentRunStatus;
|
|
911
899
|
trigger: AiEnrichmentTrigger;
|
|
912
|
-
|
|
900
|
+
modelId?: string;
|
|
913
901
|
mode?: string;
|
|
914
902
|
durationMs?: number;
|
|
915
903
|
titleUpdated?: boolean;
|
|
@@ -954,7 +942,7 @@ export type AiConversationService = {
|
|
|
954
942
|
turnId?: string;
|
|
955
943
|
checkpointSeq: number;
|
|
956
944
|
summary: Message;
|
|
957
|
-
|
|
945
|
+
modelId?: string | null;
|
|
958
946
|
}): Promise<void>;
|
|
959
947
|
listTurnMessages(input: { conversationId: string; loopId: string; includeCompacted?: boolean }): Promise<AiStoredMessage[]>;
|
|
960
948
|
setMessageFeedback(input: {
|
|
@@ -964,12 +952,12 @@ export type AiConversationService = {
|
|
|
964
952
|
}): Promise<AiMessageFeedback | null>;
|
|
965
953
|
clearMessageFeedback(input: { conversationId: string; messageShortId: string }): Promise<boolean>;
|
|
966
954
|
/** Create a queued compaction turn. Chat turns must use submitChatTurn. */
|
|
967
|
-
createCompactionTurn(input: { conversationId: string;
|
|
955
|
+
createCompactionTurn(input: { conversationId: string; modelId: string; runConfig: AiCompactionTurnRunConfig }): Promise<AiTurn>;
|
|
968
956
|
/** Persist the user message and create its turn in one transaction. */
|
|
969
957
|
submitChatTurn(input: {
|
|
970
958
|
queuedMessageId?: string;
|
|
971
959
|
conversationId: string;
|
|
972
|
-
|
|
960
|
+
modelId: string;
|
|
973
961
|
runConfig: AiChatTurnRunConfig;
|
|
974
962
|
userMessage: Message;
|
|
975
963
|
/** Atomically consume this saved draft revision when present. */
|
|
@@ -984,7 +972,7 @@ export type AiConversationService = {
|
|
|
984
972
|
retrySourceTurnId?: string;
|
|
985
973
|
}): Promise<{ turn: AiTurn; message: AiStoredMessage }>;
|
|
986
974
|
getTurnRunConfig(input: { conversationId: string; turnId: string }): Promise<AiTurnRunConfig | null>;
|
|
987
|
-
getLatestTurn(input: { conversationId: string }): Promise<AiTurn | null>;
|
|
975
|
+
getLatestTurn(input: { conversationId: string; kind?: AiTurnRunConfig["kind"] }): Promise<AiTurn | null>;
|
|
988
976
|
getTurn(input: { conversationId: string; turnId: string }): Promise<AiTurn | null>;
|
|
989
977
|
getTurnByShortId(input: { conversationId: string; shortId: string }): Promise<AiTurn | null>;
|
|
990
978
|
getActiveTurn(input: { conversationId: string }): Promise<{
|
|
@@ -1076,7 +1064,7 @@ export type AiConversationService = {
|
|
|
1076
1064
|
/** Called after a durable append, for incremental usage/history projection. */
|
|
1077
1065
|
onMessage?: (message: AiStoredMessage) => Promise<void>;
|
|
1078
1066
|
conversationId: string;
|
|
1079
|
-
|
|
1067
|
+
modelId?: string | null;
|
|
1080
1068
|
turnId?: string | null;
|
|
1081
1069
|
leaseOwner?: string | null;
|
|
1082
1070
|
/** Ephemeral provider input for this turn; user-message persistence remains reference-only. */
|
package/src/ai/ui.tsx
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export { AiTurnBlockView } from "./chat/blocks";
|
|
2
|
+
export { AiModelCard, type AiModelCardStop, aiModelClassLabel, aiModelClassSubtitle } from "./model-card";
|
|
3
|
+
export {
|
|
4
|
+
AiModelChooser,
|
|
5
|
+
type AiModelChooserState,
|
|
6
|
+
AiModelField,
|
|
7
|
+
AiModelNote,
|
|
8
|
+
aiModelBlocksSending,
|
|
9
|
+
aiModelRequestFor,
|
|
10
|
+
aiModelUnavailableText,
|
|
11
|
+
resolveAiModelChoice,
|
|
12
|
+
} from "./model-chooser";
|
|
13
|
+
export { aiModelChooserMessages } from "./model-chooser-messages";
|
|
14
|
+
export { AiModelIcon, aiModelDisplayIcon } from "./model-icon";
|
|
2
15
|
|
|
3
16
|
import { latestLoopUsage, latestUsage, latestUsageSnapshot, textFromMessage } from "./chat/message-utils";
|
|
4
17
|
|
package/src/ai/usage.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type AiUsageRun = {
|
|
|
12
12
|
createdAt: string;
|
|
13
13
|
userId: string | null;
|
|
14
14
|
userLabel: string | null;
|
|
15
|
-
|
|
15
|
+
modelId: string | null;
|
|
16
16
|
providerModel: string | null;
|
|
17
17
|
appId: string | null;
|
|
18
18
|
conversationId: string | null;
|
|
@@ -42,7 +42,7 @@ export type AiUsageFeedback = {
|
|
|
42
42
|
conversationTitle: string;
|
|
43
43
|
userId: string | null;
|
|
44
44
|
userLabel: string | null;
|
|
45
|
-
|
|
45
|
+
modelId: string | null;
|
|
46
46
|
providerModel: string | null;
|
|
47
47
|
rating: "up" | "down";
|
|
48
48
|
reasons: string[];
|
|
@@ -137,7 +137,7 @@ const events = (since: Date, until: Date) => sql`
|
|
|
137
137
|
`;
|
|
138
138
|
const globalFilter = (q: AiUsageQuery) => sql`
|
|
139
139
|
(${q.userId ?? null}::text IS NULL OR (${q.userId === "unassigned"} AND e.user_id IS NULL) OR e.user_id::text=${q.userId ?? null})
|
|
140
|
-
AND (${q.
|
|
140
|
+
AND (${q.modelId ?? null}::text IS NULL OR e.model_profile_id=${q.modelId ?? null})
|
|
141
141
|
AND (${q.providerModel ?? null}::text IS NULL OR e.provider_model=${q.providerModel ?? null})
|
|
142
142
|
AND (${q.workflowId ?? null}::uuid IS NULL OR e.workflow_id=${q.workflowId ?? null}::uuid)
|
|
143
143
|
AND (${q.workflowRunId ?? null}::uuid IS NULL OR e.workflow_run_id=${q.workflowRunId ?? null}::uuid)
|
|
@@ -187,7 +187,7 @@ const pageOf = async <T>(projection: ReturnType<typeof events>, q: AiUsageQuery)
|
|
|
187
187
|
return { items, total, page, perPage: q.perPage };
|
|
188
188
|
};
|
|
189
189
|
const runColumns = () => sql`e.id, e.kind, e.task, e.status, e.created_at::text AS "createdAt", e.user_id::text AS "userId",
|
|
190
|
-
COALESCE(NULLIF(u.display_name,''),u.uid,sa.name) AS "userLabel",e.model_profile_id AS "
|
|
190
|
+
COALESCE(NULLIF(u.display_name,''),u.uid,sa.name) AS "userLabel",e.model_profile_id AS "modelId",e.provider_model AS "providerModel",
|
|
191
191
|
e.app_id AS "appId",e.conversation_id::text AS "conversationId",e.turn_id::text AS "turnId",e.workflow_run_id::text AS "workflowRunId",
|
|
192
192
|
e.workflow_id::text AS "workflowId",e.workflow_name AS "workflowName",e.estimated,e.trace_id AS "traceId",e.tokens,e.cost::float8 AS cost,e.duration_ms AS "durationMs",e.error_code AS "errorCode",e.error,e.attempts,
|
|
193
193
|
e.request_started_at::text AS "requestStartedAt",e.generation_ms AS "generationMs",e.headers_ms AS "headersMs",e.first_byte_ms AS "firstByteMs",
|
|
@@ -235,7 +235,7 @@ export const aiUsage = {
|
|
|
235
235
|
// The response period is authoritative for both summary and feedback. Editing a rating never moves its response into a different period.
|
|
236
236
|
const feedbackRows = sql`SELECT m.id::text AS id,c.id::text AS "conversationId",c.title AS "conversationTitle",
|
|
237
237
|
e.user_id::text AS "userId",COALESCE(NULLIF(u.display_name,''),u.uid,sa.name) AS "userLabel",
|
|
238
|
-
e.model_profile_id AS "
|
|
238
|
+
e.model_profile_id AS "modelId",e.provider_model AS "providerModel",
|
|
239
239
|
CASE WHEN m.feedback_rating=1 THEN 'up' ELSE 'down' END AS rating,
|
|
240
240
|
m.feedback_reasons AS reasons,m.feedback_comment AS comment,m.created_at::text AS "createdAt",m.feedback_updated_at::text AS "updatedAt"
|
|
241
241
|
FROM (${filtered}) e JOIN ai.messages m ON m.loop_id=e.turn_id::text AND m.conversation_id=e.conversation_id
|
|
@@ -266,7 +266,7 @@ export const aiUsage = {
|
|
|
266
266
|
FROM ai.conversations c WHERE c.created_at >= ${since} AND c.created_at <= ${until} AND c.launched_by_app_id IS NOT NULL
|
|
267
267
|
AND (${q.userId ?? null}::text IS NULL OR (${q.userId === "unassigned"} AND c.created_by_user_id IS NULL) OR c.created_by_user_id::text=${q.userId ?? null})
|
|
268
268
|
AND (${q.appId ?? null}::text IS NULL OR c.launched_by_app_id=${q.appId ?? null})
|
|
269
|
-
AND ((${q.
|
|
269
|
+
AND ((${q.modelId ?? null}::text IS NULL AND ${q.providerModel ?? null}::text IS NULL) OR EXISTS (
|
|
270
270
|
SELECT 1 FROM (${filtered}) e WHERE e.kind='chat' AND e.conversation_id=c.id
|
|
271
271
|
)) GROUP BY c.launched_by_app_id ORDER BY chats DESC,c.launched_by_app_id`,
|
|
272
272
|
q,
|
|
@@ -305,12 +305,12 @@ export const aiUsage = {
|
|
|
305
305
|
LEFT JOIN auth.users u ON u.id=e.user_id LEFT JOIN auth.service_accounts sa ON sa.id=e.user_id WHERE e.kind=${kind} AND e.id=${id}`;
|
|
306
306
|
return row ?? null;
|
|
307
307
|
},
|
|
308
|
-
facets: async (field: "userId" | "
|
|
308
|
+
facets: async (field: "userId" | "modelId" | "providerModel" | "appId" | "task", search: string, input: Partial<AiUsageQuery>) => {
|
|
309
309
|
const { query, since, until } = period(input);
|
|
310
310
|
const key =
|
|
311
311
|
field === "userId"
|
|
312
312
|
? sql`e.user_id::text`
|
|
313
|
-
: field === "
|
|
313
|
+
: field === "modelId"
|
|
314
314
|
? sql`e.model_profile_id`
|
|
315
315
|
: field === "providerModel"
|
|
316
316
|
? sql`e.provider_model`
|
package/src/ai/validate.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Input } from "@k2b/nessi";
|
|
2
|
+
import type { AiModelRequest } from "../shared/ai-models";
|
|
2
3
|
import { readAiSettingsState, resolveAiModelFromState } from "./settings";
|
|
3
4
|
import type { AiModelPolicy, AiSettingsError, AiSettingsState } from "./types";
|
|
4
5
|
|
|
5
6
|
export const isAiSettingsError = (error: unknown): error is Error & { aiError: AiSettingsError } =>
|
|
6
|
-
error instanceof Error &&
|
|
7
|
+
error instanceof Error && "aiError" in error && typeof error.aiError === "object";
|
|
7
8
|
|
|
8
9
|
const inputIncludesFiles = (input: Input): boolean =>
|
|
9
10
|
Array.isArray(input) && input.some((part) => typeof part === "object" && part.type === "file");
|
|
@@ -13,27 +14,27 @@ export type ValidateAiTurnInput = {
|
|
|
13
14
|
hasImageAttachments?: boolean;
|
|
14
15
|
canInspectAttachedImages?: boolean;
|
|
15
16
|
modelPolicy?: AiModelPolicy;
|
|
16
|
-
|
|
17
|
+
model?: AiModelRequest;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
export const validateAiTurnRequest = async (
|
|
20
21
|
input: ValidateAiTurnInput,
|
|
22
|
+
providedState?: AiSettingsState,
|
|
21
23
|
): Promise<{ settings: Extract<AiSettingsState, { ok: true }>; resolved: Awaited<ReturnType<typeof resolveAiModelFromState>> }> => {
|
|
22
|
-
const settings = await readAiSettingsState();
|
|
23
|
-
if (!settings.ok) throw Object.assign(new Error(settings.error.message), { aiError: settings.error });
|
|
24
|
+
const settings = providedState ?? (await readAiSettingsState());
|
|
24
25
|
if (!settings.enabled) {
|
|
25
26
|
throw Object.assign(new Error("AI is disabled."), {
|
|
26
27
|
aiError: { code: "ai_disabled", message: "AI is disabled." } satisfies AiSettingsError,
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const resolved = await resolveAiModelFromState(settings, input.modelPolicy ?? { kind: "platform-default" }, input.
|
|
31
|
+
const resolved = await resolveAiModelFromState(settings, input.modelPolicy ?? { kind: "platform-default" }, input.model);
|
|
31
32
|
if (inputIncludesFiles(input.input) && !resolved.profile.capabilities.includes("vision")) {
|
|
32
33
|
throw Object.assign(new Error(`AI model "${resolved.profile.id}" does not support image input.`), {
|
|
33
34
|
aiError: {
|
|
34
35
|
code: "model_policy_mismatch",
|
|
35
36
|
message: `AI model "${resolved.profile.label}" does not support image input.`,
|
|
36
|
-
fields: {
|
|
37
|
+
fields: { modelId: "Choose a model with vision support." },
|
|
37
38
|
} satisfies AiSettingsError,
|
|
38
39
|
});
|
|
39
40
|
}
|
|
@@ -44,7 +45,7 @@ export const validateAiTurnRequest = async (
|
|
|
44
45
|
aiError: {
|
|
45
46
|
code: "model_policy_mismatch",
|
|
46
47
|
message: `AI model "${resolved.profile.label}" needs Vision support or Tools with a configured Vision tool model.`,
|
|
47
|
-
fields: {
|
|
48
|
+
fields: { modelId: "Choose a Vision model, or configure the view_image fallback." },
|
|
48
49
|
} satisfies AiSettingsError,
|
|
49
50
|
});
|
|
50
51
|
}
|
package/src/ai/view-block.ts
CHANGED
|
@@ -41,7 +41,7 @@ export const CLOUD_AI_DETAILS_VIEWPORT_ITEMS = 12;
|
|
|
41
41
|
// the viewport at any width and its length counts no further.
|
|
42
42
|
export const CLOUD_AI_DETAILS_MAX_CHARS = 4096;
|
|
43
43
|
|
|
44
|
-
const referenceSchema = () => CloudResourceReferenceSchema.pick({ type: true, id: true, title: true });
|
|
44
|
+
const referenceSchema = () => CloudResourceReferenceSchema.pick({ type: true, id: true, title: true, context: true, status: true });
|
|
45
45
|
const ReferenceSchema = /* @__PURE__ */ referenceSchema();
|
|
46
46
|
export type CloudAiViewReference = z.output<typeof ReferenceSchema>;
|
|
47
47
|
const cellTypeSchema = () => z.enum(["text", "number", "date", "datetime", "ref"]);
|
|
@@ -371,7 +371,7 @@ const ExplorerViewSchema = ExplorerDataSchema.superRefine((value, ctx) => {
|
|
|
371
371
|
};
|
|
372
372
|
}
|
|
373
373
|
const domains = [
|
|
374
|
-
...("xAxis" in input && input.xAxis?.domain ? [input.xAxis.domain] : []),
|
|
374
|
+
...("xAxis" in input && input.kind !== "bar" && input.xAxis?.domain ? [input.xAxis.domain] : []),
|
|
375
375
|
...("yAxis" in input && input.yAxis?.domain ? [input.yAxis.domain] : []),
|
|
376
376
|
];
|
|
377
377
|
const stepping = domains.some(
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
PaginationQuerySchema,
|
|
11
11
|
PaginationResponseSchema,
|
|
12
12
|
parsePagination,
|
|
13
|
+
ServiceAccountKindSchema,
|
|
14
|
+
ServiceAccountStatusSchema,
|
|
13
15
|
UserProfileSchema,
|
|
14
16
|
UserProviderSchema,
|
|
15
17
|
} from "../contracts";
|
|
@@ -34,6 +36,8 @@ const QuerySchema = z
|
|
|
34
36
|
profile: UserProfileSchema.optional(),
|
|
35
37
|
user_ids: z.string().optional(),
|
|
36
38
|
group_ids: z.string().optional(),
|
|
39
|
+
service_account_kinds: z.string().optional(),
|
|
40
|
+
service_account_status: ServiceAccountStatusSchema.optional(),
|
|
37
41
|
exclude_user_ids: z.string().optional(),
|
|
38
42
|
exclude_group_ids: z.string().optional(),
|
|
39
43
|
exclude_service_account_ids: z.string().optional(),
|
|
@@ -73,6 +77,14 @@ const parseKinds = (value?: string) => {
|
|
|
73
77
|
return { ok: false as const, message: "Invalid kinds query parameter." };
|
|
74
78
|
};
|
|
75
79
|
|
|
80
|
+
const parseServiceAccountKinds = (value?: string) => {
|
|
81
|
+
const kinds = parseCsv(value);
|
|
82
|
+
if (kinds.length === 0) return { ok: true as const, value: undefined };
|
|
83
|
+
const parsed = z.array(ServiceAccountKindSchema).safeParse([...new Set(kinds)]);
|
|
84
|
+
if (parsed.success) return { ok: true as const, value: parsed.data };
|
|
85
|
+
return { ok: false as const, message: "Invalid service_account_kinds query parameter." };
|
|
86
|
+
};
|
|
87
|
+
|
|
76
88
|
const parseUuidList = (value: string | undefined, label: string) => {
|
|
77
89
|
const ids = parseCsv(value);
|
|
78
90
|
if (ids.length === 0) return { ok: true as const, value: undefined };
|
|
@@ -126,6 +138,8 @@ export const createAccountsEntitiesRoutes = (dependencies: AccountsEntitiesRoute
|
|
|
126
138
|
const params = parsePagination(query);
|
|
127
139
|
const kinds = parseKinds(query.kinds);
|
|
128
140
|
if (!kinds.ok) return respond(c, fail(err.badInput(kinds.message)));
|
|
141
|
+
const serviceAccountKinds = parseServiceAccountKinds(query.service_account_kinds);
|
|
142
|
+
if (!serviceAccountKinds.ok) return respond(c, fail(err.badInput(serviceAccountKinds.message)));
|
|
129
143
|
const excludeUserIds = parseUuidList(query.exclude_user_ids, "exclude_user_ids");
|
|
130
144
|
if (!excludeUserIds.ok) return respond(c, fail(err.badInput(excludeUserIds.message)));
|
|
131
145
|
const excludeGroupIds = parseUuidList(query.exclude_group_ids, "exclude_group_ids");
|
|
@@ -146,6 +160,8 @@ export const createAccountsEntitiesRoutes = (dependencies: AccountsEntitiesRoute
|
|
|
146
160
|
profile: query.profile,
|
|
147
161
|
userIds: userIds.value,
|
|
148
162
|
groupIds: groupIds.value,
|
|
163
|
+
serviceAccountKinds: serviceAccountKinds.value,
|
|
164
|
+
serviceAccountStatus: query.service_account_status,
|
|
149
165
|
excludeUserIds: excludeUserIds.value,
|
|
150
166
|
excludeGroupIds: excludeGroupIds.value,
|
|
151
167
|
excludeServiceAccountIds: excludeServiceAccountIds.value,
|