@k2b/cloud 0.6.0 → 0.8.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/README.md +2 -2
- package/package.json +22 -11
- package/scripts/README.md +10 -0
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +20 -35
- package/scripts/preload.ts +16 -12
- package/scripts/sync-recovery-smoke.ts +22 -6
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +131 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +80 -37
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +18 -151
- package/src/access/PrincipalPicker.tsx +110 -0
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +201 -0
- package/src/ai/browser.ts +4 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +229 -90
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +83 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +25 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +31 -47
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +21 -2
- package/src/ai/chat/tool-groups.ts +58 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +14 -10
- package/src/ai/chat-tasks.ts +297 -43
- package/src/ai/client/controller.ts +165 -39
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +29 -11
- package/src/ai/client/transport.ts +6 -0
- package/src/ai/code-capability-routes.ts +105 -0
- package/src/ai/code-capability-transport.ts +39 -0
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +118 -0
- package/src/ai/code-runtime-tools.ts +179 -0
- package/src/ai/code-source-contracts.ts +270 -0
- package/src/ai/code-source-tools.ts +188 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +99 -3
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +49 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +189 -33
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +27 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +8 -1
- package/src/ai/files-store.ts +212 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +42 -10
- package/src/ai/http.ts +26 -8
- package/src/ai/index.ts +26 -3
- package/src/ai/inference-calls.ts +197 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/live.ts +1 -1
- package/src/ai/markdown-pdf-tool.ts +2 -2
- package/src/ai/memories.ts +30 -27
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +64 -32
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +178 -0
- package/src/ai/migrate.ts +189 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +75 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +18 -43
- package/src/ai/projects.ts +33 -14
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +192 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +241 -11
- package/src/ai/run-timeout.ts +12 -0
- package/src/ai/runtime-tools.ts +7 -5
- package/src/ai/runtime.ts +74 -17
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +58 -0
- package/src/ai/skill-seeds.ts +114 -20
- package/src/ai/skill-tool.ts +21 -5
- package/src/ai/skills-routes.ts +49 -4
- package/src/ai/skills.ts +358 -163
- package/src/ai/store.ts +416 -201
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +12 -1
- package/src/ai/system-prompt.ts +14 -2
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +32 -0
- package/src/ai/todo-tool.ts +19 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +3 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +60 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +80 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth.ts +1 -1
- package/src/api/capabilities.ts +324 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +8 -2
- package/src/api/mcp.ts +45 -34
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +67 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +6 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +116 -35
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +77 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +10 -1
- package/src/cli/admin/instance.ts +2 -2
- package/src/cli/admin/linux.ts +4 -2
- package/src/cli/admin/sync.ts +1 -1
- package/src/cli/capabilities.ts +42 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -51
- package/src/config/index.ts +3 -1
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +75 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +5 -2
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +5 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +18 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -6
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +3 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/app-approval.ts +11 -10
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +8 -5
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/index.ts +15 -0
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +21 -4
- package/src/services/mandates/policy.ts +165 -2
- package/src/services/notifications/batches.ts +125 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +131 -72
- package/src/services/pdf/index.ts +11 -8
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +29 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +26 -35
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +58 -84
- package/src/services/webauthn.ts +1 -1
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +3 -0
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +30 -16
- package/src/ssr/AppLaunchpad.island.tsx +33 -136
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +22 -23
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +11 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +19 -90
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +4 -2
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/notifications/live.ts +0 -50
- package/src/services/session/test-fixture.ts +0 -18
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { openAICompatibleTranscription, type TranscriptionProvider } from "@k2b/nessi/ai";
|
|
2
|
+
import { coreSettings } from "../services";
|
|
3
|
+
import { logger, type TraceContext, trace } from "../services/logging";
|
|
4
|
+
import { AI_AUDIO_MAX_BYTES, inspectAiAudio } from "./audio-format";
|
|
5
|
+
import { getAiCredential } from "./credentials";
|
|
6
|
+
import { beginAiCall, finishAiCall } from "./inference-calls";
|
|
7
|
+
import { readAiSettingsState, selectAiModelProfile } from "./settings";
|
|
8
|
+
import { type AiUsageAttribution, safelyRecordStructuredRun } from "./structured-runs";
|
|
9
|
+
import type { AiDataBoundary, AiModelProfile } from "./types";
|
|
10
|
+
|
|
11
|
+
export const AI_AUDIO_MODEL_SETTING_KEY = "ai.audio_model_id";
|
|
12
|
+
/** Matches the existing chat turn execution budget; callers may cancel sooner. */
|
|
13
|
+
export const AI_TRANSCRIPTION_TIMEOUT_MS = 10 * 60_000;
|
|
14
|
+
|
|
15
|
+
export type AiResolvedAudioModel = { profile: AiModelProfile; provider: TranscriptionProvider };
|
|
16
|
+
|
|
17
|
+
export const createAiTranscriptionProvider = (profile: AiModelProfile, apiKey?: string): TranscriptionProvider => {
|
|
18
|
+
if (
|
|
19
|
+
profile.capabilities.length !== 1 ||
|
|
20
|
+
!profile.capabilities.includes("transcription") ||
|
|
21
|
+
(profile.provider !== "openai" && profile.provider !== "openai-compatible")
|
|
22
|
+
) {
|
|
23
|
+
throw new Error("Choose an audio transcription profile with an OpenAI-compatible endpoint.");
|
|
24
|
+
}
|
|
25
|
+
const baseURL = profile.baseURL ?? (profile.provider === "openai" ? "https://api.openai.com/v1" : undefined);
|
|
26
|
+
if (!baseURL) throw new Error("Audio transcription requires a base URL.");
|
|
27
|
+
return openAICompatibleTranscription(profile.model, { name: profile.provider, baseURL, apiKey });
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const resolveAiAudioModel = async (
|
|
31
|
+
input: { requestedModelId?: string; allowedDataBoundaries?: AiDataBoundary[] } = {},
|
|
32
|
+
): Promise<AiResolvedAudioModel> => {
|
|
33
|
+
const state = await readAiSettingsState();
|
|
34
|
+
if (!state.ok) throw Object.assign(new Error(state.error.message), { aiError: state.error });
|
|
35
|
+
if (!state.enabled) throw new Error("AI is disabled.");
|
|
36
|
+
const modelId = input.requestedModelId ?? String((await coreSettings.get<string>(AI_AUDIO_MODEL_SETTING_KEY)) ?? "").trim();
|
|
37
|
+
if (!modelId) throw new Error("No audio transcription model is configured.");
|
|
38
|
+
const profile = selectAiModelProfile(state, {
|
|
39
|
+
kind: "locked",
|
|
40
|
+
modelId,
|
|
41
|
+
requiredCapabilities: ["transcription"],
|
|
42
|
+
allowedDataBoundaries: input.allowedDataBoundaries,
|
|
43
|
+
});
|
|
44
|
+
const credential = await getAiCredential(profile.id);
|
|
45
|
+
if (profile.provider === "openai" && !credential?.trim()) throw new Error("The audio model is missing provider credentials.");
|
|
46
|
+
return { profile, provider: createAiTranscriptionProvider(profile, credential?.trim() || undefined) };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type RunAiTranscriptionInput = {
|
|
50
|
+
task: string;
|
|
51
|
+
file: Blob;
|
|
52
|
+
filename: string;
|
|
53
|
+
language?: string;
|
|
54
|
+
prompt?: string;
|
|
55
|
+
requestedModelId?: string;
|
|
56
|
+
allowedDataBoundaries?: AiDataBoundary[];
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
appId?: string;
|
|
59
|
+
traceParent?: TraceContext;
|
|
60
|
+
/** Attribution is metadata; the caller must authorize the source before calling. */
|
|
61
|
+
attribution?: AiUsageAttribution;
|
|
62
|
+
resolveModel?: () => Promise<AiResolvedAudioModel>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** One provider call. Durable execution and input authorization belong to the caller. */
|
|
66
|
+
export const runAiTranscription = async (input: RunAiTranscriptionInput): Promise<{ text: string; modelProfileId: string }> => {
|
|
67
|
+
return trace.withSpan(
|
|
68
|
+
{
|
|
69
|
+
name: `ai.transcription.${input.task}`,
|
|
70
|
+
source: `ai:transcription:${input.task}`,
|
|
71
|
+
category: "ai",
|
|
72
|
+
appId: input.appId,
|
|
73
|
+
parent: input.traceParent,
|
|
74
|
+
},
|
|
75
|
+
async (span) => {
|
|
76
|
+
const startedAt = Date.now();
|
|
77
|
+
let failure: AiTranscriptionError | undefined;
|
|
78
|
+
let stage: TranscriptionStage = "input";
|
|
79
|
+
let resolved: AiResolvedAudioModel | undefined;
|
|
80
|
+
let succeeded = false;
|
|
81
|
+
let callId: string | undefined;
|
|
82
|
+
const timeout = AbortSignal.timeout(AI_TRANSCRIPTION_TIMEOUT_MS);
|
|
83
|
+
const signal = input.signal ? AbortSignal.any([input.signal, timeout]) : timeout;
|
|
84
|
+
try {
|
|
85
|
+
signal.throwIfAborted();
|
|
86
|
+
if (input.file.size > AI_AUDIO_MAX_BYTES) throw new Error("Audio transcription accepts files up to 25 MB.");
|
|
87
|
+
const format = inspectAiAudio(new Uint8Array(await input.file.arrayBuffer()));
|
|
88
|
+
stage = "configuration";
|
|
89
|
+
resolved = await (input.resolveModel?.() ?? resolveAiAudioModel(input));
|
|
90
|
+
const filename =
|
|
91
|
+
input.filename
|
|
92
|
+
.split(/[\\/]/)
|
|
93
|
+
.at(-1)
|
|
94
|
+
?.replace(/\.[^.]*$/, "") || "audio";
|
|
95
|
+
stage = "provider";
|
|
96
|
+
callId = (
|
|
97
|
+
await beginAiCall(
|
|
98
|
+
{ ...resolved.profile, pricing: undefined },
|
|
99
|
+
{ kind: "background", task: input.task, appId: input.appId, traceId: span.traceId, ...input.attribution },
|
|
100
|
+
0,
|
|
101
|
+
)
|
|
102
|
+
).id;
|
|
103
|
+
const result = await resolved.provider.transcribe({
|
|
104
|
+
file: new Blob([input.file], { type: format.mediaType }),
|
|
105
|
+
filename: `${filename}.${format.extension}`,
|
|
106
|
+
language: input.language,
|
|
107
|
+
prompt: input.prompt,
|
|
108
|
+
signal,
|
|
109
|
+
});
|
|
110
|
+
signal.throwIfAborted();
|
|
111
|
+
succeeded = true;
|
|
112
|
+
return { text: result.text, modelProfileId: resolved.profile.id };
|
|
113
|
+
} catch (error) {
|
|
114
|
+
failure = describeTranscriptionFailure(error, stage, signal.aborted);
|
|
115
|
+
logger("ai:transcription")[input.signal?.aborted ? "info" : "error"](failure.message, {
|
|
116
|
+
traceId: span.traceId,
|
|
117
|
+
task: input.task,
|
|
118
|
+
modelProfileId: resolved?.profile.id ?? input.requestedModelId,
|
|
119
|
+
providerModel: resolved?.profile.model,
|
|
120
|
+
stage,
|
|
121
|
+
errorCode: failure.code,
|
|
122
|
+
httpStatus: failure.httpStatus,
|
|
123
|
+
});
|
|
124
|
+
throw failure;
|
|
125
|
+
} finally {
|
|
126
|
+
if (callId) {
|
|
127
|
+
try {
|
|
128
|
+
await finishAiCall(callId, undefined, succeeded ? "ok" : "failed");
|
|
129
|
+
} catch {
|
|
130
|
+
logger("ai:transcription").error("AI cost booking failed", { callId });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await safelyRecordStructuredRun({
|
|
134
|
+
traceId: span.traceId,
|
|
135
|
+
task: input.task,
|
|
136
|
+
appId: input.appId,
|
|
137
|
+
attribution: input.attribution,
|
|
138
|
+
modelProfileId: resolved?.profile.id,
|
|
139
|
+
providerModel: resolved?.profile.model,
|
|
140
|
+
mode: "transcription",
|
|
141
|
+
attempts: resolved ? 1 : 0,
|
|
142
|
+
status: succeeded ? "ok" : "failed",
|
|
143
|
+
durationMs: Date.now() - startedAt,
|
|
144
|
+
// Provider error bodies may echo source data. Keep accounting metadata-only.
|
|
145
|
+
errorCode: failure?.code,
|
|
146
|
+
error: failure?.message,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export type TranscriptionStage = "input" | "configuration" | "provider";
|
|
154
|
+
/** Only classified metadata escapes the adapter: response bodies can contain private source data. */
|
|
155
|
+
export class AiTranscriptionError extends Error {
|
|
156
|
+
constructor(
|
|
157
|
+
readonly code: string,
|
|
158
|
+
message: string,
|
|
159
|
+
readonly retryable = false,
|
|
160
|
+
readonly httpStatus?: number,
|
|
161
|
+
) {
|
|
162
|
+
super(message);
|
|
163
|
+
this.name = "AiTranscriptionError";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export const describeTranscriptionFailure = (error: unknown, stage: TranscriptionStage, aborted = false): AiTranscriptionError => {
|
|
167
|
+
if (error instanceof AiTranscriptionError) return error;
|
|
168
|
+
if (aborted) return new AiTranscriptionError("transcription_aborted", "Audio transcription was canceled or timed out.");
|
|
169
|
+
const message = error instanceof Error ? error.message : "";
|
|
170
|
+
const match = /^(?:openai|openai-compatible) (\d{3}): /.exec(message);
|
|
171
|
+
if (match) {
|
|
172
|
+
const status = Number(match[1]);
|
|
173
|
+
const hint =
|
|
174
|
+
status === 401 || status === 403
|
|
175
|
+
? "Check provider credentials and model access."
|
|
176
|
+
: status === 404
|
|
177
|
+
? "Check the provider base URL and model name."
|
|
178
|
+
: status === 429
|
|
179
|
+
? "The provider rate limit was reached."
|
|
180
|
+
: "The provider rejected the audio transcription request.";
|
|
181
|
+
return new AiTranscriptionError(
|
|
182
|
+
`transcription_http_${status}`,
|
|
183
|
+
`Audio provider HTTP ${status}. ${hint}`,
|
|
184
|
+
[408, 409, 425, 429].includes(status) || status >= 500,
|
|
185
|
+
status,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (/^(?:openai|openai-compatible) connection failed: /.test(message))
|
|
189
|
+
return new AiTranscriptionError(
|
|
190
|
+
"transcription_connection_failed",
|
|
191
|
+
"Could not connect to the audio provider. Check its endpoint and network availability.",
|
|
192
|
+
true,
|
|
193
|
+
);
|
|
194
|
+
return new AiTranscriptionError(
|
|
195
|
+
`transcription_${stage}_failed`,
|
|
196
|
+
stage === "configuration"
|
|
197
|
+
? "Audio model configuration could not be resolved. Check the audio profile, base URL, credentials and access policy."
|
|
198
|
+
: stage === "input"
|
|
199
|
+
? "Audio input is invalid or exceeds the supported size."
|
|
200
|
+
: "Audio provider returned an invalid response or an unclassified error.",
|
|
201
|
+
);
|
|
202
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { LoopAggregate, OutboundEvent } from "@k2b/nessi";
|
|
2
|
+
import { sql } from "bun";
|
|
3
|
+
|
|
4
|
+
export type TimingInterval = { start: number; end: number };
|
|
5
|
+
const union = (intervals: TimingInterval[]): TimingInterval[] => {
|
|
6
|
+
const result: TimingInterval[] = [];
|
|
7
|
+
for (const interval of intervals.filter((x) => x.end > x.start).sort((a, b) => a.start - b.start)) {
|
|
8
|
+
const previous = result.at(-1);
|
|
9
|
+
if (previous && interval.start <= previous.end) previous.end = Math.max(previous.end, interval.end);
|
|
10
|
+
else result.push({ ...interval });
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
const duration = (intervals: TimingInterval[]) => union(intervals).reduce((sum, x) => sum + x.end - x.start, 0);
|
|
15
|
+
const excluding = (intervals: TimingInterval[], removed: TimingInterval[]) => {
|
|
16
|
+
let result = union(intervals);
|
|
17
|
+
for (const cut of union(removed))
|
|
18
|
+
result = result.flatMap((x) =>
|
|
19
|
+
cut.end <= x.start || cut.start >= x.end
|
|
20
|
+
? [x]
|
|
21
|
+
: [
|
|
22
|
+
{ start: x.start, end: Math.min(x.end, cut.start) },
|
|
23
|
+
{ start: Math.max(x.start, cut.end), end: x.end },
|
|
24
|
+
].filter((x) => x.end > x.start),
|
|
25
|
+
);
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Exclusive phase durations; parallel tools and nested approvals never double-count wall time. */
|
|
30
|
+
export function summarizeTurnTiming(input: {
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
generation: TimingInterval[];
|
|
34
|
+
tools: TimingInterval[];
|
|
35
|
+
waits: TimingInterval[];
|
|
36
|
+
outputTokens?: number;
|
|
37
|
+
}): NonNullable<LoopAggregate["timing"]> {
|
|
38
|
+
const clip = (rows: TimingInterval[]) => rows.map((x) => ({ start: Math.max(input.start, x.start), end: Math.min(input.end, x.end) }));
|
|
39
|
+
const generation = clip(input.generation),
|
|
40
|
+
waits = excluding(clip(input.waits), generation);
|
|
41
|
+
const generationMs = duration(generation),
|
|
42
|
+
actionWaitMs = duration(waits);
|
|
43
|
+
const toolExecutionMs = duration(excluding(clip(input.tools), [...generation, ...waits]));
|
|
44
|
+
const wallMs = Math.max(0, input.end - input.start);
|
|
45
|
+
return {
|
|
46
|
+
wallMs,
|
|
47
|
+
totalElapsedMs: wallMs,
|
|
48
|
+
generationMs,
|
|
49
|
+
actionWaitMs,
|
|
50
|
+
toolExecutionMs,
|
|
51
|
+
...(generationMs > 0 && input.outputTokens !== undefined ? { outputTokensPerSecond: input.outputTokens / (generationMs / 1000) } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One recorder per executor attempt; rows survive client actions and process resumption. */
|
|
56
|
+
export function createTurnTimingRecorder(turnId: string, db = sql) {
|
|
57
|
+
let requestId: string | null = null;
|
|
58
|
+
const finishGeneration = async () => {
|
|
59
|
+
if (!requestId) return;
|
|
60
|
+
const id = requestId;
|
|
61
|
+
requestId = null;
|
|
62
|
+
await db`UPDATE ai.turn_generation_timings SET ended_at=now() WHERE turn_id=${turnId}::uuid AND request_id=${id} AND ended_at IS NULL`;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
finishGeneration,
|
|
66
|
+
async event(event: OutboundEvent) {
|
|
67
|
+
if (event.type === "turn_start" && !event.resumed) {
|
|
68
|
+
await finishGeneration();
|
|
69
|
+
requestId = event.turnId;
|
|
70
|
+
await db`INSERT INTO ai.turn_generation_timings(turn_id,request_id) VALUES(${turnId}::uuid,${requestId}) ON CONFLICT DO NOTHING`;
|
|
71
|
+
} else if (
|
|
72
|
+
event.type === "usage" ||
|
|
73
|
+
event.type === "tool_execution_start" ||
|
|
74
|
+
event.type === "turn_end" ||
|
|
75
|
+
event.type === "loop_end"
|
|
76
|
+
) {
|
|
77
|
+
await finishGeneration();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function withDurableTurnTiming(turnId: string, aggregate: LoopAggregate, db = sql): Promise<LoopAggregate> {
|
|
84
|
+
const [turn] = await db<
|
|
85
|
+
{ start: Date; end: Date }[]
|
|
86
|
+
>`SELECT created_at AS start,COALESCE(completed_at,now()) AS end FROM ai.turns WHERE id=${turnId}::uuid`;
|
|
87
|
+
const generation = await db<
|
|
88
|
+
{ start: Date; end: Date | null }[]
|
|
89
|
+
>`SELECT started_at AS start,ended_at AS end FROM ai.turn_generation_timings WHERE turn_id=${turnId}::uuid`;
|
|
90
|
+
// An older/incomplete trace cannot justify a speed computed from the whole loop's tokens.
|
|
91
|
+
if (!turn || generation.length < aggregate.assistantMessageCount || generation.some((x) => !x.end)) {
|
|
92
|
+
const { timing: _, ...rest } = aggregate;
|
|
93
|
+
return rest;
|
|
94
|
+
}
|
|
95
|
+
const tools = await db<
|
|
96
|
+
{ start: Date; end: Date }[]
|
|
97
|
+
>`SELECT started_at AS start,COALESCE(completed_at,now()) AS end FROM ai.tool_calls WHERE turn_id=${turnId}::uuid AND started_at IS NOT NULL`;
|
|
98
|
+
const waits = await db<{ start: Date; end: Date }[]>`SELECT a.created_at AS start,
|
|
99
|
+
CASE WHEN a.kind='client_tool' THEN LEAST(COALESCE(t.started_at,a.resolved_at,now()),COALESCE(a.resolved_at,now())) ELSE COALESCE(a.resolved_at,now()) END AS end
|
|
100
|
+
FROM ai.pending_actions a LEFT JOIN ai.tool_calls t ON t.turn_id=a.turn_id AND t.call_id=a.call_id WHERE a.turn_id=${turnId}::uuid`;
|
|
101
|
+
const intervals = (rows: { start: Date; end: Date | null }[]) =>
|
|
102
|
+
rows.flatMap((x) => (x.end ? [{ start: new Date(x.start).getTime(), end: new Date(x.end).getTime() }] : []));
|
|
103
|
+
return {
|
|
104
|
+
...aggregate,
|
|
105
|
+
timing: summarizeTurnTiming({
|
|
106
|
+
start: new Date(turn.start).getTime(),
|
|
107
|
+
end: new Date(turn.end).getTime(),
|
|
108
|
+
generation: intervals(generation),
|
|
109
|
+
tools: intervals(tools),
|
|
110
|
+
waits: intervals(waits),
|
|
111
|
+
outputTokens: aggregate.usage?.output,
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
114
|
+
}
|
package/src/ai/types.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { CapabilityActionReview, CloudResourceRef } from "../contracts/capa
|
|
|
16
16
|
import type { RequestActor } from "../server";
|
|
17
17
|
import type { AiTurnBlock } from "./protocol";
|
|
18
18
|
|
|
19
|
-
export const AI_MODEL_CAPABILITIES = ["streaming", "tools", "vision"] as const;
|
|
19
|
+
export const AI_MODEL_CAPABILITIES = ["streaming", "tools", "vision", "transcription"] as const;
|
|
20
20
|
export type AiModelCapability = (typeof AI_MODEL_CAPABILITIES)[number];
|
|
21
21
|
|
|
22
22
|
export const AI_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"] as const;
|
|
@@ -49,8 +49,7 @@ export type AiModelProfile = {
|
|
|
49
49
|
maxLoadedTools?: number;
|
|
50
50
|
/** Tool-using model rounds per chat turn. Missing or <= 0 is unlimited. */
|
|
51
51
|
maxToolRounds?: number;
|
|
52
|
-
|
|
53
|
-
creditsPerOutputToken?: number;
|
|
52
|
+
pricing?: import("../shared/ai-costs").AiModelPricing;
|
|
54
53
|
};
|
|
55
54
|
|
|
56
55
|
export type AiPublicModelProfile = Pick<
|
|
@@ -118,8 +117,9 @@ export type AiResolvedModel = {
|
|
|
118
117
|
|
|
119
118
|
export type AiDraftContentPart =
|
|
120
119
|
| { type: "text"; text: string }
|
|
121
|
-
| { type: "resource"; ref: CloudResourceRef; title?: string; icon?: string; href?: string }
|
|
122
|
-
| { type: "file"; path: string; mediaType: string; size: number; version: number }
|
|
120
|
+
| { type: "resource"; ref: CloudResourceRef; title?: string; icon?: string; href?: string; inline?: boolean }
|
|
121
|
+
| { type: "file"; path: string; mediaType: string; size: number; version: number; inline?: boolean }
|
|
122
|
+
| { type: "project-file"; path: string; inline?: boolean };
|
|
123
123
|
|
|
124
124
|
export type AiConversationDraft = {
|
|
125
125
|
content: AiDraftContentPart[];
|
|
@@ -129,10 +129,12 @@ export type AiConversationDraft = {
|
|
|
129
129
|
|
|
130
130
|
export type AiConversationFieldSource = "default" | "auto" | "user";
|
|
131
131
|
export type AiConversationTitleSource = AiConversationFieldSource;
|
|
132
|
-
export type AiConversationRunStatus = "idle" | "queued" | "running" | "needs_attention" | "failed";
|
|
133
|
-
export type AiConversationStatusFilter = Exclude<AiConversationRunStatus, "idle" | "queued"> | "unread";
|
|
132
|
+
export type AiConversationRunStatus = "idle" | "queued" | "running" | "needs_attention" | "waiting_for_browser" | "failed";
|
|
133
|
+
export type AiConversationStatusFilter = Exclude<AiConversationRunStatus, "idle" | "queued" | "waiting_for_browser"> | "unread";
|
|
134
134
|
|
|
135
135
|
export type AiConversation = {
|
|
136
|
+
activity?: { completed: number; total: number; step: string | null; tool: string | null };
|
|
137
|
+
todoPlan?: { seq: number; todos: import("./todo-contracts").AiTodoPlan["todos"] };
|
|
136
138
|
id: string;
|
|
137
139
|
shortId: string;
|
|
138
140
|
title: string;
|
|
@@ -146,6 +148,11 @@ export type AiConversation = {
|
|
|
146
148
|
keywords: string[];
|
|
147
149
|
pinnedAt: string | null;
|
|
148
150
|
archivedAt: string | null;
|
|
151
|
+
/** null: automatic after seven days without use; false: explicitly active; true: finished. */
|
|
152
|
+
done: boolean | null;
|
|
153
|
+
isDone: boolean;
|
|
154
|
+
hasActiveSchedule?: boolean;
|
|
155
|
+
lastUsedAt: string;
|
|
149
156
|
runStatus: AiConversationRunStatus;
|
|
150
157
|
/** Error from the latest turn when `runStatus` is `failed`. */
|
|
151
158
|
runError: string | null;
|
|
@@ -247,7 +254,7 @@ export type AiConversationPage = {
|
|
|
247
254
|
|
|
248
255
|
export type AiConversationProjectUpdateResult =
|
|
249
256
|
| { ok: true; conversation: AiConversation }
|
|
250
|
-
| { ok: false; reason: "not_found" | "active_turn" };
|
|
257
|
+
| { ok: false; reason: "not_found" | "active_turn" | "already_assigned" };
|
|
251
258
|
|
|
252
259
|
export type AiStoredMessage = {
|
|
253
260
|
id: string;
|
|
@@ -270,7 +277,10 @@ export type AiStoredMessage = {
|
|
|
270
277
|
compactedAt: string | null;
|
|
271
278
|
/** UI metadata (e.g. how many messages a compaction summary replaced). */
|
|
272
279
|
meta: {
|
|
280
|
+
/** The atomically consumed composer revision, used to confirm a local send. */
|
|
281
|
+
submittedDraftRevision?: number;
|
|
273
282
|
compactedCount?: number;
|
|
283
|
+
todoPlan?: import("./todo-contracts").AiTodoPlan;
|
|
274
284
|
steerId?: string;
|
|
275
285
|
agentMessage?: {
|
|
276
286
|
id: string;
|
|
@@ -336,6 +346,9 @@ export type AiTurnFinalizedEvent = {
|
|
|
336
346
|
};
|
|
337
347
|
|
|
338
348
|
export type AiTurn = {
|
|
349
|
+
deadline?: string | null;
|
|
350
|
+
runBudgetMs?: number | null;
|
|
351
|
+
cancelRequestedAt?: string | null;
|
|
339
352
|
id: string;
|
|
340
353
|
shortId: string;
|
|
341
354
|
conversationId: string;
|
|
@@ -419,7 +432,17 @@ export type AiPendingTurnActionRecord = {
|
|
|
419
432
|
|
|
420
433
|
export type AiTurnToolSource = { kind: "none" } | { kind: "default"; appTools?: boolean };
|
|
421
434
|
|
|
422
|
-
export type AiClientToolId =
|
|
435
|
+
export type AiClientToolId =
|
|
436
|
+
| "local_bash"
|
|
437
|
+
| "code_run"
|
|
438
|
+
| "code_action"
|
|
439
|
+
| "code_inspect"
|
|
440
|
+
| "code_interact"
|
|
441
|
+
| "code_stop"
|
|
442
|
+
| "code_open"
|
|
443
|
+
| "code_present"
|
|
444
|
+
| "code_export"
|
|
445
|
+
| "code_secret";
|
|
423
446
|
|
|
424
447
|
/** Immutable project instructions and context manifest captured for one turn. */
|
|
425
448
|
export type AiProjectPromptSnapshot = {
|
|
@@ -517,6 +540,7 @@ export type AiConversationSourceObservation = {
|
|
|
517
540
|
};
|
|
518
541
|
|
|
519
542
|
export type AiConversationFileSnapshotEntry = {
|
|
543
|
+
dictationRecordedAt?: string;
|
|
520
544
|
path: string;
|
|
521
545
|
size: number;
|
|
522
546
|
mediaType: string;
|
|
@@ -531,7 +555,15 @@ export type AiConversationFileSnapshot = {
|
|
|
531
555
|
total: number;
|
|
532
556
|
};
|
|
533
557
|
|
|
558
|
+
export type AiBackgroundRunContext = {
|
|
559
|
+
taskId: string;
|
|
560
|
+
occurrenceId: string;
|
|
561
|
+
context: Array<{ seq: number; kind: "message" | "summary"; message: Message }>;
|
|
562
|
+
};
|
|
563
|
+
|
|
534
564
|
export type AiChatTurnRunConfig = {
|
|
565
|
+
background?: AiBackgroundRunContext;
|
|
566
|
+
selectedSkillIds?: string[];
|
|
535
567
|
/** Server-owned marker: model grants apply only to interactive Assistant chat. */
|
|
536
568
|
assistantChat?: true;
|
|
537
569
|
kind?: "chat";
|
|
@@ -614,16 +646,18 @@ export type AiConversationService = {
|
|
|
614
646
|
search?: string;
|
|
615
647
|
refs?: CloudResourceRef[];
|
|
616
648
|
archived?: boolean;
|
|
649
|
+
done?: boolean;
|
|
617
650
|
status?: AiConversationStatusFilter;
|
|
618
651
|
projectId?: string;
|
|
619
652
|
unassigned?: boolean;
|
|
620
653
|
limit?: number;
|
|
621
654
|
}): Promise<AiConversation[]>;
|
|
622
|
-
listSidebarConversations(input: { ownerUserId: string
|
|
655
|
+
listSidebarConversations(input: { ownerUserId: string }): Promise<AiConversation[]>;
|
|
623
656
|
listConversationsPage(input: {
|
|
624
657
|
ownerUserId: string;
|
|
625
658
|
search?: string;
|
|
626
659
|
archived?: boolean;
|
|
660
|
+
done?: boolean;
|
|
627
661
|
status?: AiConversationStatusFilter;
|
|
628
662
|
projectId?: string;
|
|
629
663
|
unassigned?: boolean;
|
|
@@ -712,8 +746,14 @@ export type AiConversationService = {
|
|
|
712
746
|
conversationId: string;
|
|
713
747
|
ownerUserId?: string;
|
|
714
748
|
projectId: string | null;
|
|
749
|
+
onlyUnassigned?: boolean;
|
|
715
750
|
}): Promise<AiConversationProjectUpdateResult>;
|
|
716
751
|
setConversationPinned(input: { conversationId: string; ownerUserId?: string; pinned: boolean }): Promise<AiConversation | null>;
|
|
752
|
+
setConversationDone(input: {
|
|
753
|
+
conversationId: string;
|
|
754
|
+
ownerUserId: string;
|
|
755
|
+
done: boolean | null;
|
|
756
|
+
}): Promise<{ ok: true; conversation: AiConversation } | { ok: false; reason: "not_found" | "active_turn" }>;
|
|
717
757
|
archiveConversation(input: { conversationId: string; ownerUserId?: string }): Promise<boolean>;
|
|
718
758
|
restoreConversation(input: { conversationId: string; ownerUserId?: string }): Promise<AiConversation | null>;
|
|
719
759
|
markConversationViewed(input: { conversationId: string; ownerUserId?: string }): Promise<boolean>;
|
|
@@ -788,6 +828,7 @@ export type AiConversationService = {
|
|
|
788
828
|
}): Promise<void>;
|
|
789
829
|
compactMessages(input: {
|
|
790
830
|
conversationId: string;
|
|
831
|
+
turnId?: string;
|
|
791
832
|
checkpointSeq: number;
|
|
792
833
|
summary: Message;
|
|
793
834
|
modelProfileId?: string | null;
|
|
@@ -803,6 +844,7 @@ export type AiConversationService = {
|
|
|
803
844
|
createCompactionTurn(input: { conversationId: string; modelProfileId: string; runConfig: AiCompactionTurnRunConfig }): Promise<AiTurn>;
|
|
804
845
|
/** Persist the user message and create its turn in one transaction. */
|
|
805
846
|
submitChatTurn(input: {
|
|
847
|
+
queuedMessageId?: string;
|
|
806
848
|
conversationId: string;
|
|
807
849
|
modelProfileId: string;
|
|
808
850
|
runConfig: AiChatTurnRunConfig;
|
|
@@ -819,6 +861,7 @@ export type AiConversationService = {
|
|
|
819
861
|
retrySourceTurnId?: string;
|
|
820
862
|
}): Promise<{ turn: AiTurn; message: AiStoredMessage }>;
|
|
821
863
|
getTurnRunConfig(input: { conversationId: string; turnId: string }): Promise<AiTurnRunConfig | null>;
|
|
864
|
+
getLatestTurn(input: { conversationId: string }): Promise<AiTurn | null>;
|
|
822
865
|
getTurn(input: { conversationId: string; turnId: string }): Promise<AiTurn | null>;
|
|
823
866
|
getTurnByShortId(input: { conversationId: string; shortId: string }): Promise<AiTurn | null>;
|
|
824
867
|
getActiveTurn(input: { conversationId: string }): Promise<{ turn: AiTurn; liveBlocks: AiTurnBlock[]; liveSeq: number } | null>;
|
|
@@ -888,6 +931,9 @@ export type AiConversationService = {
|
|
|
888
931
|
/** Atomically make pending steers visible to the model and return them in order. */
|
|
889
932
|
takePendingTurnSteers(input: { conversationId: string; turnId: string; leaseOwner: string }): Promise<AiTurnSteer[]>;
|
|
890
933
|
createSessionStore(input: {
|
|
934
|
+
background?: AiBackgroundRunContext;
|
|
935
|
+
/** Called after a durable append, for incremental usage/history projection. */
|
|
936
|
+
onMessage?: (message: AiStoredMessage) => Promise<void>;
|
|
891
937
|
conversationId: string;
|
|
892
938
|
modelProfileId?: string | null;
|
|
893
939
|
turnId?: string | null;
|
|
@@ -956,6 +1002,8 @@ export type AiToolRuntime<TInput extends z.ZodType = z.ZodType, TOutput extends
|
|
|
956
1002
|
selectedModel?: AiResolvedModel;
|
|
957
1003
|
locale?: string;
|
|
958
1004
|
timeZone?: string;
|
|
1005
|
+
/** Localized short status; never include payloads or secrets. */
|
|
1006
|
+
reportProgress?: (message: string) => Promise<void>;
|
|
959
1007
|
},
|
|
960
1008
|
): Promise<z.infer<TOutput>>;
|
|
961
1009
|
}
|
package/src/ai/ui.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { AiTurnBlockView } from "./chat/blocks";
|
|
2
|
+
|
|
1
3
|
import { latestLoopUsage, latestUsage, latestUsageSnapshot, textFromMessage } from "./chat/message-utils";
|
|
2
4
|
|
|
3
5
|
export {
|
|
@@ -9,6 +11,7 @@ export {
|
|
|
9
11
|
aiChatAttachments,
|
|
10
12
|
aiChatModelOptions,
|
|
11
13
|
aiComposerAttachmentRecords,
|
|
14
|
+
aiComposerDraft,
|
|
12
15
|
aiComposerFileAccept,
|
|
13
16
|
aiComposerSendInput,
|
|
14
17
|
createAiPastedTextFile,
|