@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
package/src/ai/http.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { z } from "zod";
|
|
|
4
4
|
import { CapabilityAppIdSchema } from "../contracts/capabilities";
|
|
5
5
|
import { type AuthContext, err, fail, respond } from "../server";
|
|
6
6
|
import { aiAttachmentMarker } from "./attachments";
|
|
7
|
+
import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
|
|
7
8
|
import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
|
|
9
|
+
import { AiQuotaError } from "./quotas";
|
|
8
10
|
import { AiResourceMarkerSchema } from "./resource-markers";
|
|
9
11
|
import { AI_SHORT_ID_PATTERN } from "./short-id";
|
|
10
12
|
import { AI_SKILL_NAME_MAX_CHARS, AI_SKILL_NAME_PATTERN } from "./skill-format";
|
|
@@ -19,21 +21,25 @@ import { isAiSettingsError } from "./validate";
|
|
|
19
21
|
|
|
20
22
|
export const AiDraftContentPartSchema = z.discriminatedUnion("type", [
|
|
21
23
|
z.object({ type: z.literal("text"), text: z.string().max(20_000) }),
|
|
22
|
-
AiResourceMarkerSchema.extend({ type: z.literal("resource") }),
|
|
24
|
+
AiResourceMarkerSchema.extend({ type: z.literal("resource"), inline: z.boolean().optional() }),
|
|
23
25
|
z.object({
|
|
24
26
|
type: z.literal("file"),
|
|
25
27
|
path: z.string().trim().min(1).max(500),
|
|
26
28
|
mediaType: z.string().trim().min(1).max(120),
|
|
27
29
|
size: z.number().int().min(0),
|
|
28
30
|
version: z.number().int().min(1),
|
|
31
|
+
inline: z.boolean().optional(),
|
|
29
32
|
}),
|
|
33
|
+
z.object({ type: z.literal("project-file"), path: z.string().min(1).max(500).startsWith("/project/"), inline: z.boolean().optional() }),
|
|
30
34
|
]);
|
|
31
35
|
|
|
32
36
|
export const AiConversationDraftInputSchema = z
|
|
33
37
|
.object({
|
|
34
|
-
content: z.array(AiDraftContentPartSchema).max(
|
|
38
|
+
content: z.array(AiDraftContentPartSchema).max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1),
|
|
35
39
|
})
|
|
36
40
|
.superRefine((value, context) => {
|
|
41
|
+
if (value.content.reduce((length, part) => length + (part.type === "text" ? part.text.length : 0), 0) > 20_000)
|
|
42
|
+
context.addIssue({ code: "custom", path: ["content"], message: "Draft text exceeds 20000 characters" });
|
|
37
43
|
if (value.content.filter((part) => part.type !== "text").length > AI_TURN_ATTACHMENT_MAX_ITEMS) {
|
|
38
44
|
context.addIssue({ code: "custom", path: ["content"], message: `A draft can attach at most ${AI_TURN_ATTACHMENT_MAX_ITEMS} items` });
|
|
39
45
|
}
|
|
@@ -41,7 +47,9 @@ export const AiConversationDraftInputSchema = z
|
|
|
41
47
|
|
|
42
48
|
export const AiInitialConversationDraftInputSchema = z
|
|
43
49
|
.object({
|
|
44
|
-
content: z
|
|
50
|
+
content: z
|
|
51
|
+
.array(z.union([AiDraftContentPartSchema.options[0], AiDraftContentPartSchema.options[1]]))
|
|
52
|
+
.max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1),
|
|
45
53
|
})
|
|
46
54
|
.superRefine((value, context) => {
|
|
47
55
|
if (value.content.filter((part) => part.type === "resource").length > AI_TURN_ATTACHMENT_MAX_ITEMS) {
|
|
@@ -112,7 +120,7 @@ export const AiUserContentPartSchema = z.union([
|
|
|
112
120
|
|
|
113
121
|
export type AiTurnContentPart = z.infer<typeof AiUserContentPartSchema>;
|
|
114
122
|
|
|
115
|
-
export const AiClientToolIdSchema = z.enum(["local_bash"] satisfies [AiClientToolId]);
|
|
123
|
+
export const AiClientToolIdSchema = z.enum(["local_bash", ...CODE_RUNTIME_TOOL_NAMES] satisfies [AiClientToolId, ...AiClientToolId[]]);
|
|
116
124
|
|
|
117
125
|
export const AiTurnInputSchema = z
|
|
118
126
|
.object({
|
|
@@ -120,10 +128,14 @@ export const AiTurnInputSchema = z
|
|
|
120
128
|
content: z
|
|
121
129
|
.array(AiUserContentPartSchema)
|
|
122
130
|
.min(1)
|
|
123
|
-
.max(AI_TURN_ATTACHMENT_MAX_ITEMS + 1)
|
|
131
|
+
.max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
|
|
124
132
|
.optional(),
|
|
125
133
|
modelProfileId: z.string().trim().min(1).optional(),
|
|
126
|
-
clientToolIds: z
|
|
134
|
+
clientToolIds: z
|
|
135
|
+
.array(AiClientToolIdSchema)
|
|
136
|
+
.max(1 + CODE_RUNTIME_TOOL_NAMES.length)
|
|
137
|
+
.refine((ids) => new Set(ids).size === ids.length, "Client tool IDs must be unique")
|
|
138
|
+
.optional(),
|
|
127
139
|
})
|
|
128
140
|
.refine((input) => Boolean(input.message?.trim() || input.content?.length), {
|
|
129
141
|
message: "Message or content is required.",
|
|
@@ -141,9 +153,14 @@ export const AiTurnInputSchema = z
|
|
|
141
153
|
export type AiTurnInput = z.infer<typeof AiTurnInputSchema>;
|
|
142
154
|
|
|
143
155
|
export const AiSubmitConversationDraftInputSchema = z.object({
|
|
156
|
+
queueId: z.uuid().optional(),
|
|
144
157
|
draftRevision: z.number().int().min(1),
|
|
145
158
|
modelProfileId: z.string().trim().min(1).optional(),
|
|
146
|
-
clientToolIds: z
|
|
159
|
+
clientToolIds: z
|
|
160
|
+
.array(AiClientToolIdSchema)
|
|
161
|
+
.max(1 + CODE_RUNTIME_TOOL_NAMES.length)
|
|
162
|
+
.refine((ids) => new Set(ids).size === ids.length, "Client tool IDs must be unique")
|
|
163
|
+
.optional(),
|
|
147
164
|
});
|
|
148
165
|
|
|
149
166
|
export const AiSteerInputSchema = z.object({
|
|
@@ -166,7 +183,7 @@ export const AiMessageRetryInputSchema = z
|
|
|
166
183
|
content: z
|
|
167
184
|
.array(AiUserContentPartSchema)
|
|
168
185
|
.min(1)
|
|
169
|
-
.max(AI_TURN_ATTACHMENT_MAX_ITEMS + 1)
|
|
186
|
+
.max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
|
|
170
187
|
.optional(),
|
|
171
188
|
modelProfileId: z.string().trim().min(1).optional(),
|
|
172
189
|
})
|
|
@@ -245,6 +262,7 @@ const aiSettingsServiceError = (error: AiSettingsError) => {
|
|
|
245
262
|
};
|
|
246
263
|
|
|
247
264
|
export const toAiErrorResponse = (c: Context<AuthContext>, error: unknown) => {
|
|
265
|
+
if (error instanceof AiQuotaError) return c.json({ ok: false, error: error.message, message: error.message, code: error.code }, 429);
|
|
248
266
|
if (isAiSettingsError(error)) {
|
|
249
267
|
return respond(c, fail(aiSettingsServiceError(error.aiError)));
|
|
250
268
|
}
|
package/src/ai/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { assistantAiSettingsState, listAssistantAiModels } from "./assistant-models";
|
|
2
1
|
export { type AiApprovalPreferenceRoutes, type AiApprovalPreferenceView, createAiApprovalPreferenceRoutes } from "./approval-routes";
|
|
3
2
|
export {
|
|
4
3
|
type AiToolApprovalContext,
|
|
@@ -12,12 +11,17 @@ export {
|
|
|
12
11
|
rememberAiToolApproval,
|
|
13
12
|
revokeAiToolApprovalPreference,
|
|
14
13
|
} from "./approvals";
|
|
14
|
+
export { assistantAiSettingsState, listAssistantAiModels, selectAssistantAiModelId } from "./assistant-models";
|
|
15
15
|
export { type AiAttachmentRef, aiAttachmentMarker, formatAiFileSize, parseAiAttachmentMarkers } from "./attachments";
|
|
16
|
+
export { CloudAiTranscribeAudioInputSchema, CloudAiTranscribeAudioOutputSchema, createCloudAiTranscribeAudioTool } from "./audio-tool";
|
|
17
|
+
export { CODE_RUNTIME_TOOL_NAMES, CodeResourceId, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
|
|
16
18
|
export { aiCapabilityId, aiCapabilityToolName } from "./capabilities";
|
|
19
|
+
export { getAiChatQuotas } from "./chat-quotas";
|
|
17
20
|
export {
|
|
18
21
|
type AiChatTaskOccurrenceView,
|
|
19
22
|
type AiChatTaskView,
|
|
20
23
|
AiConversationIdSchema,
|
|
24
|
+
ChatTaskGrantsSchema,
|
|
21
25
|
ChatTaskIdSchema,
|
|
22
26
|
ChatTaskOccurrenceIdSchema,
|
|
23
27
|
ChatTaskScheduleInputSchema,
|
|
@@ -37,6 +41,10 @@ export {
|
|
|
37
41
|
aiChatTasks,
|
|
38
42
|
} from "./chat-tasks";
|
|
39
43
|
export { parseAiSse } from "./client/transport";
|
|
44
|
+
// Framework-owned managed execution host transport.
|
|
45
|
+
export { CODE_CAPABILITY_TOKEN_HEADER, createCodeCapabilityTransport } from "./code-capability-transport";
|
|
46
|
+
export { authorizeCodeExecution } from "./code-execution";
|
|
47
|
+
export { CODE_SOURCE_TOOLS, type CodeSourceToolName } from "./code-source-contracts";
|
|
40
48
|
export { listAiCredentialProfileIds } from "./credentials";
|
|
41
49
|
export {
|
|
42
50
|
CLOUD_AI_TEXT_EDITOR_MAX_CHARS,
|
|
@@ -66,13 +74,18 @@ export {
|
|
|
66
74
|
shouldApplyEnrichedDescription,
|
|
67
75
|
shouldApplyEnrichedTitle,
|
|
68
76
|
} from "./enrich";
|
|
77
|
+
export { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
|
|
78
|
+
export { AiFileLocation, AiFileReference } from "./file-reference-contracts";
|
|
69
79
|
export {
|
|
70
80
|
AI_FILES_MAX_CONVERSATION_BYTES_DEFAULT,
|
|
71
81
|
AI_FILES_MAX_FILE_BYTES_DEFAULT,
|
|
72
82
|
type AiFileStat,
|
|
83
|
+
createAiConversationArtifact,
|
|
73
84
|
guessAiMediaType,
|
|
74
85
|
listAiConversationFiles,
|
|
75
86
|
normalizeAiFilePath,
|
|
87
|
+
readAiConversationFile,
|
|
88
|
+
writeAiConversationFile,
|
|
76
89
|
} from "./files-store";
|
|
77
90
|
export {
|
|
78
91
|
AI_FIRECRAWL_API_KEY_SETTING_KEY,
|
|
@@ -202,6 +215,7 @@ export {
|
|
|
202
215
|
isNewerWireEvent,
|
|
203
216
|
} from "./protocol";
|
|
204
217
|
export { createAiProvider } from "./provider";
|
|
218
|
+
export { AiQuotaError } from "./quotas";
|
|
205
219
|
export { isConversationResourceCursor } from "./resource-refs";
|
|
206
220
|
export type { AiRoutes } from "./routes";
|
|
207
221
|
export {
|
|
@@ -214,7 +228,7 @@ export {
|
|
|
214
228
|
selectAiModelProfile,
|
|
215
229
|
toPublicAiSettingsState,
|
|
216
230
|
} from "./settings";
|
|
217
|
-
export { AI_SHORT_ID_PATTERN, createAiShortId } from "./short-id";
|
|
231
|
+
export { AI_SHORT_ID_PATTERN, createAiShortId, withAiShortIdForDb } from "./short-id";
|
|
218
232
|
export {
|
|
219
233
|
AI_SKILL_DESCRIPTION_MAX_CHARS,
|
|
220
234
|
AI_SKILL_EXTRA_FRONTMATTER_MAX_CHARS,
|
|
@@ -237,7 +251,6 @@ export {
|
|
|
237
251
|
validateAiSkillName,
|
|
238
252
|
validateAiSkillReferences,
|
|
239
253
|
} from "./skill-format";
|
|
240
|
-
export { seedCloudAiSkills } from "./skill-seeds";
|
|
241
254
|
export {
|
|
242
255
|
type AiLoadedSkillSnapshot,
|
|
243
256
|
type AiSkill,
|
|
@@ -251,6 +264,7 @@ export {
|
|
|
251
264
|
AiSkillRevisionConflictError,
|
|
252
265
|
type AiSkillSummary,
|
|
253
266
|
aiSkills,
|
|
267
|
+
seedCloudAiSkills,
|
|
254
268
|
} from "./skills";
|
|
255
269
|
export { type AiSkillsRoutes, aiSkillsRoutes } from "./skills-routes";
|
|
256
270
|
export { aiConversations } from "./store";
|
|
@@ -273,8 +287,17 @@ export {
|
|
|
273
287
|
runAiStructured,
|
|
274
288
|
} from "./structured";
|
|
275
289
|
export { aiGlobalInstructionsContext, composeAiSystemPrompt, renderAiGlobalInstructions } from "./system-prompt";
|
|
290
|
+
export { type AiTaskRequest, type AiTaskRequestInput, AiTaskRequestSchema } from "./task-contracts";
|
|
291
|
+
export { executeAiTask } from "./task-execution";
|
|
276
292
|
export { type AiToolApprovalState, type AiToolCallLocation, aiToolAudit } from "./tool-audit";
|
|
277
293
|
export { defineAiTool, isFrontendToolMode, type PreparedAiTools, prepareAiTools } from "./tools";
|
|
294
|
+
export {
|
|
295
|
+
type AiResolvedAudioModel,
|
|
296
|
+
createAiTranscriptionProvider,
|
|
297
|
+
type RunAiTranscriptionInput,
|
|
298
|
+
resolveAiAudioModel,
|
|
299
|
+
runAiTranscription,
|
|
300
|
+
} from "./transcription";
|
|
278
301
|
export type {
|
|
279
302
|
AiAccessResult,
|
|
280
303
|
AiCapabilityToolPresentation,
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { type SQL, sql } from "bun";
|
|
2
|
+
import type { AccessSubject } from "../server/services/access";
|
|
3
|
+
import { aiBudgetUnits, aiCostDecimal, aiCostUnits, aiDecimalUnits, hasBillableAiPricing } from "../shared/ai-costs";
|
|
4
|
+
import { AiQuotaError, aiQuotas, quotaWindow } from "./quotas";
|
|
5
|
+
import type { AiModelProfile } from "./types";
|
|
6
|
+
|
|
7
|
+
export class AiBackgroundCostError extends Error {
|
|
8
|
+
readonly code = "ai_background_cost_stop";
|
|
9
|
+
constructor() {
|
|
10
|
+
super("Background AI is paused by the cost safety limit. An administrator must review Usage rules.");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Admission can fail without latching the instance-wide emergency stop. */
|
|
15
|
+
export class AiBackgroundAdmissionError extends Error {
|
|
16
|
+
readonly code: string;
|
|
17
|
+
constructor(readonly retryable: boolean) {
|
|
18
|
+
super(
|
|
19
|
+
retryable
|
|
20
|
+
? "Background AI budget is reserved by running calls. Try again after they finish."
|
|
21
|
+
: "This call cannot fit in the remaining background AI budget.",
|
|
22
|
+
);
|
|
23
|
+
this.code = retryable ? "ai_background_budget_reserved" : "ai_background_budget_insufficient";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AiCallContext = {
|
|
28
|
+
kind: "chat" | "background";
|
|
29
|
+
task: string;
|
|
30
|
+
subject?: AccessSubject | null;
|
|
31
|
+
userId?: string;
|
|
32
|
+
conversationId?: string;
|
|
33
|
+
turnId?: string;
|
|
34
|
+
workflowRunId?: string;
|
|
35
|
+
stepKey?: string;
|
|
36
|
+
traceId?: string;
|
|
37
|
+
appId?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export async function backgroundCostState(db: SQL = sql) {
|
|
41
|
+
const [row] = await db<
|
|
42
|
+
{ used: number; reserved: number; usedExact: string; reservedExact: string; unknown: number; stoppedAt: Date | null; unit: string }[]
|
|
43
|
+
>`
|
|
44
|
+
SELECT COALESCE(sum(cost),0)::text AS "usedExact",
|
|
45
|
+
COALESCE(sum(reserved) FILTER(WHERE finished_at IS NULL AND lease_expires_at>now()),0)::text AS "reservedExact",
|
|
46
|
+
COALESCE(sum(cost),0)::float8 AS used,
|
|
47
|
+
COALESCE(sum(reserved) FILTER(WHERE finished_at IS NULL AND lease_expires_at>now()),0)::float8 AS reserved,
|
|
48
|
+
count(*) FILTER(WHERE (COALESCE((pricing->>'inputPerMillion')::numeric,0)>0 OR COALESCE((pricing->>'outputPerMillion')::numeric,0)>0) AND cost IS NULL AND (finished_at IS NOT NULL OR lease_expires_at<=now()))::int AS unknown,
|
|
49
|
+
(SELECT background_stopped_at FROM ai.cost_config WHERE singleton) AS "stoppedAt",
|
|
50
|
+
(SELECT unit FROM ai.cost_config WHERE singleton) AS unit
|
|
51
|
+
FROM ai.inference_calls WHERE kind='background' AND started_at>=now()-interval '24 hours'`;
|
|
52
|
+
return row!;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Called under the configuration lock; persist transitions before notification delivery. */
|
|
56
|
+
async function checkBackgroundBudget(db: SQL) {
|
|
57
|
+
const config = await aiQuotas.config(db);
|
|
58
|
+
const state = await backgroundCostState(db);
|
|
59
|
+
if (!config.background?.enabled) return state;
|
|
60
|
+
const [flags] = await db<{ background_warned: boolean }[]>`SELECT background_warned FROM ai.cost_config WHERE singleton`;
|
|
61
|
+
const warning = config.background.warnAt !== null && aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.warnAt);
|
|
62
|
+
const stop = aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.stopAt) || state.unknown > 0;
|
|
63
|
+
for (const kind of [...(warning && !flags!.background_warned ? ["warning"] : []), ...(stop && !state.stoppedAt ? ["stop"] : [])])
|
|
64
|
+
await db`INSERT INTO ai.cost_alerts(id,kind,cost,unit) VALUES(${crypto.randomUUID()}::uuid,${kind},${state.usedExact}::numeric,${config.unit ?? "EUR"})`;
|
|
65
|
+
await db`UPDATE ai.cost_config SET background_warned=${warning},
|
|
66
|
+
background_stopped_at=CASE WHEN ${stop} THEN COALESCE(background_stopped_at,clock_timestamp()) ELSE background_stopped_at END WHERE singleton`;
|
|
67
|
+
return backgroundCostState(db);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Admission and reservation share the settings lock across all worker processes. */
|
|
71
|
+
export async function beginAiCall(
|
|
72
|
+
profile: AiModelProfile,
|
|
73
|
+
context: AiCallContext,
|
|
74
|
+
inputTokens: number,
|
|
75
|
+
requestedOutput?: number,
|
|
76
|
+
outputBound?: number,
|
|
77
|
+
) {
|
|
78
|
+
const result = await sql.begin(async (db) => {
|
|
79
|
+
await db`SELECT singleton FROM ai.cost_config WHERE singleton FOR UPDATE`;
|
|
80
|
+
const config = await aiQuotas.config(db);
|
|
81
|
+
let remaining: bigint | undefined;
|
|
82
|
+
let backgroundHeadroom: bigint | undefined;
|
|
83
|
+
if (hasBillableAiPricing(profile.pricing) && context.kind === "background" && config.background?.enabled) {
|
|
84
|
+
const state = await checkBackgroundBudget(db);
|
|
85
|
+
if (state.stoppedAt || aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.stopAt) || state.unknown) {
|
|
86
|
+
await db`UPDATE ai.cost_config SET background_stopped_at=COALESCE(background_stopped_at,clock_timestamp()) WHERE singleton`;
|
|
87
|
+
return { error: new AiBackgroundCostError() };
|
|
88
|
+
}
|
|
89
|
+
backgroundHeadroom = aiBudgetUnits(config.background.stopAt) - aiDecimalUnits(state.usedExact);
|
|
90
|
+
remaining = backgroundHeadroom - aiDecimalUnits(state.reservedExact);
|
|
91
|
+
}
|
|
92
|
+
if (hasBillableAiPricing(profile.pricing) && context.kind === "chat" && context.subject && config.enabled) {
|
|
93
|
+
const now = new Date();
|
|
94
|
+
const snapshot = await aiQuotas.snapshot(context.subject, profile.id, now, db);
|
|
95
|
+
for (const balance of snapshot.balances) {
|
|
96
|
+
if (balance.bypassed || balance.limit === null) continue;
|
|
97
|
+
if (balance.unknown)
|
|
98
|
+
return {
|
|
99
|
+
error: new AiQuotaError(
|
|
100
|
+
"quota_usage_unknown",
|
|
101
|
+
"Chat costs could not be measured. Reset the allowance or wait for the next window.",
|
|
102
|
+
),
|
|
103
|
+
};
|
|
104
|
+
const rule = config.rules.find((rule) => rule.scope === balance.scope)!;
|
|
105
|
+
const window = quotaWindow(rule.anchor, rule.hours, now);
|
|
106
|
+
const user = context.subject.type === "user" ? context.subject.userId : null;
|
|
107
|
+
const service = context.subject.type === "service_account" ? context.subject.serviceAccountId : null;
|
|
108
|
+
const [pending] = await db<{ committed: string }[]>`
|
|
109
|
+
SELECT (COALESCE(sum(cost),0)+COALESCE(sum(reserved) FILTER(WHERE finished_at IS NULL AND lease_expires_at>now()),0))::text AS committed FROM ai.inference_calls c
|
|
110
|
+
WHERE kind='chat'
|
|
111
|
+
AND user_id IS NOT DISTINCT FROM ${user}::uuid AND service_account_id IS NOT DISTINCT FROM ${service}::uuid
|
|
112
|
+
AND (${balance.scope}='*' OR model_profile_id=${balance.scope}) AND started_at>=${window.from} AND started_at<${window.until}
|
|
113
|
+
AND started_at>COALESCE((SELECT max(created_at) FROM ai.cost_resets r
|
|
114
|
+
WHERE r.user_id IS NOT DISTINCT FROM ${user}::uuid AND r.service_account_id IS NOT DISTINCT FROM ${service}::uuid AND r.scope=${balance.scope}),'-infinity'::timestamptz)`;
|
|
115
|
+
const available = aiBudgetUnits(balance.limit) - aiDecimalUnits(pending?.committed ?? "0");
|
|
116
|
+
remaining = remaining === undefined || available < remaining ? available : remaining;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
let maxOutputTokens = requestedOutput;
|
|
120
|
+
let reserved = "0";
|
|
121
|
+
if (profile.pricing && remaining !== undefined) {
|
|
122
|
+
const inputCost = aiCostUnits(profile.pricing, inputTokens, 0);
|
|
123
|
+
const outputPrice = aiCostUnits(profile.pricing, 0, 1);
|
|
124
|
+
const affordable = outputPrice > 0n ? Number((remaining - inputCost) / outputPrice) : Infinity;
|
|
125
|
+
if (inputCost > remaining || affordable < 1)
|
|
126
|
+
return {
|
|
127
|
+
error:
|
|
128
|
+
context.kind === "background"
|
|
129
|
+
? new AiBackgroundAdmissionError(backgroundHeadroom !== undefined && inputCost + outputPrice <= backgroundHeadroom)
|
|
130
|
+
: new AiQuotaError("quota_exhausted", "Chat cost limit reached."),
|
|
131
|
+
};
|
|
132
|
+
const reserveOutput = Math.min(requestedOutput ?? outputBound ?? affordable, affordable);
|
|
133
|
+
// Preserve adapter defaults unless an explicit maximum or budget requires a cap.
|
|
134
|
+
if (requestedOutput !== undefined || reserveOutput < (outputBound ?? Infinity)) maxOutputTokens = reserveOutput;
|
|
135
|
+
reserved = aiCostDecimal(aiCostUnits(profile.pricing, inputTokens, Number.isFinite(reserveOutput) ? reserveOutput : 0));
|
|
136
|
+
}
|
|
137
|
+
const workflow = context.workflowRunId
|
|
138
|
+
? (
|
|
139
|
+
await db<{ id: string; name: string; app: string; revision: number }[]>`
|
|
140
|
+
SELECT w.id,w.name,r.app_id AS app,v.revision FROM workflows.run r
|
|
141
|
+
JOIN workflows.workflow w ON w.id=r.workflow_id JOIN workflows.version v ON v.id=r.workflow_version_id
|
|
142
|
+
WHERE r.id=${context.workflowRunId}::uuid`
|
|
143
|
+
)[0]
|
|
144
|
+
: undefined;
|
|
145
|
+
const id = crypto.randomUUID();
|
|
146
|
+
const userId = context.subject?.type === "user" ? context.subject.userId : context.userId;
|
|
147
|
+
const serviceId = context.subject?.type === "service_account" ? context.subject.serviceAccountId : null;
|
|
148
|
+
await db`INSERT INTO ai.inference_calls(id,user_id,service_account_id,model_profile_id,provider_model,kind,task,
|
|
149
|
+
turn_id,turn_attempt,conversation_id,workflow_run_id,step_key,trace_id,app_id,workflow_id,workflow_name,workflow_version,pricing,reserved,lease_expires_at)
|
|
150
|
+
VALUES(${id}::uuid,CASE WHEN ${serviceId}::uuid IS NULL THEN COALESCE(${userId ?? null}::uuid,(SELECT created_by_user_id FROM ai.conversations WHERE id=${context.conversationId ?? null}::uuid)) ELSE NULL END,
|
|
151
|
+
${serviceId}::uuid,${profile.id},${profile.model},${context.kind},${context.task},${context.turnId ?? null}::uuid,
|
|
152
|
+
(SELECT attempt FROM ai.turns WHERE id=${context.turnId ?? null}::uuid),${context.conversationId ?? null}::uuid,
|
|
153
|
+
${context.workflowRunId ?? null}::uuid,${context.stepKey ?? null},${context.traceId ?? null},COALESCE(${workflow?.app ?? context.appId ?? null},(SELECT launched_by_app_id FROM ai.conversations WHERE id=${context.conversationId ?? null}::uuid),'core'),${workflow?.id ?? null}::uuid,${workflow?.name ?? null},${workflow?.revision ?? null},
|
|
154
|
+
(${profile.pricing ? JSON.stringify(profile.pricing) : null}::text)::jsonb,${reserved}::numeric,now()+interval '2 minutes')`;
|
|
155
|
+
return { id, maxOutputTokens };
|
|
156
|
+
});
|
|
157
|
+
if (result.error) throw result.error;
|
|
158
|
+
return { id: result.id!, maxOutputTokens: result.maxOutputTokens };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function finishAiCall(
|
|
162
|
+
id: string,
|
|
163
|
+
usage: { input: number; output: number; estimated?: boolean } | undefined,
|
|
164
|
+
status: "ok" | "failed",
|
|
165
|
+
) {
|
|
166
|
+
await sql.begin(async (db) => {
|
|
167
|
+
await db`SELECT singleton FROM ai.cost_config WHERE singleton FOR UPDATE`;
|
|
168
|
+
const [call] = await db<
|
|
169
|
+
{ pricing: AiModelProfile["pricing"]; kind: string }[]
|
|
170
|
+
>`SELECT pricing,kind FROM ai.inference_calls WHERE id=${id}::uuid AND finished_at IS NULL FOR UPDATE`;
|
|
171
|
+
if (!call) return;
|
|
172
|
+
const cost =
|
|
173
|
+
call.pricing?.inputPerMillion === 0 && call.pricing.outputPerMillion === 0
|
|
174
|
+
? "0"
|
|
175
|
+
: call.pricing && usage
|
|
176
|
+
? aiCostDecimal(aiCostUnits(call.pricing, usage.input, usage.output))
|
|
177
|
+
: null;
|
|
178
|
+
await db`UPDATE ai.inference_calls SET input=${usage?.input ?? null},output=${usage?.output ?? null},estimated=${usage?.estimated ?? false},
|
|
179
|
+
cost=${cost}::numeric,reserved=0,finished_at=clock_timestamp(),status=${status},error_code=${status === "failed" ? "ai_provider_call_failed" : null} WHERE id=${id}::uuid`;
|
|
180
|
+
if (call.kind === "background") await checkBackgroundBudget(db);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function releaseBackgroundCostStop(actorId: string) {
|
|
185
|
+
return sql.begin(async (db) => {
|
|
186
|
+
await db`SELECT singleton FROM ai.cost_config WHERE singleton FOR UPDATE`;
|
|
187
|
+
const config = await aiQuotas.config(db);
|
|
188
|
+
const state = await backgroundCostState(db);
|
|
189
|
+
if (config.background?.enabled && (aiDecimalUnits(state.usedExact) >= aiBudgetUnits(config.background.stopAt) || state.unknown))
|
|
190
|
+
throw new AiBackgroundCostError();
|
|
191
|
+
if (!state.stoppedAt) return state;
|
|
192
|
+
await db`UPDATE ai.cost_config SET background_stopped_at=NULL,background_warned=false,revision=revision+1 WHERE singleton`;
|
|
193
|
+
const next = await aiQuotas.config(db);
|
|
194
|
+
await db`INSERT INTO ai.cost_changes(revision,actor_id,config) VALUES(${next.revision},${actorId}::uuid,(${JSON.stringify({ ...next, action: "release-background-stop" })}::text)::jsonb)`;
|
|
195
|
+
return backgroundCostState(db);
|
|
196
|
+
});
|
|
197
|
+
}
|
package/src/ai/live-events.ts
CHANGED
|
@@ -7,6 +7,7 @@ export const AI_INVALIDATION_DOMAINS = [
|
|
|
7
7
|
"conversation-sources",
|
|
8
8
|
"conversation-files",
|
|
9
9
|
"conversation-tasks",
|
|
10
|
+
"conversation-dictations",
|
|
10
11
|
"project-list",
|
|
11
12
|
"project-detail",
|
|
12
13
|
"project-context",
|
|
@@ -129,7 +130,10 @@ const AiConversationSchema = z
|
|
|
129
130
|
keywords: z.array(z.string()),
|
|
130
131
|
pinnedAt: z.string().nullable(),
|
|
131
132
|
archivedAt: z.string().nullable(),
|
|
132
|
-
|
|
133
|
+
done: z.boolean().nullable(),
|
|
134
|
+
isDone: z.boolean(),
|
|
135
|
+
lastUsedAt: z.string(),
|
|
136
|
+
runStatus: z.enum(["idle", "queued", "running", "needs_attention", "waiting_for_browser", "failed"]),
|
|
133
137
|
runError: z.string().nullable(),
|
|
134
138
|
unreadCompletion: z.boolean(),
|
|
135
139
|
projectId: z.string().nullable(),
|
package/src/ai/live.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { latestAiInvalidationCursor } from "./live-outbox";
|
|
2
|
-
export {
|
|
2
|
+
export { type AiLiveRoutes, type AiLiveRoutesConfig, aiLiveRoutes } from "./live-routes";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import {
|
|
2
3
|
MARKDOWN_PDF_MAX_CUSTOM_CSS_BYTES,
|
|
3
4
|
MARKDOWN_PDF_MAX_MARKDOWN_BYTES,
|
|
@@ -5,9 +6,8 @@ import {
|
|
|
5
6
|
type RenderMarkdownToPdfInput,
|
|
6
7
|
renderMarkdownToPdf,
|
|
7
8
|
} from "../services/pdf";
|
|
8
|
-
import { z } from "zod";
|
|
9
9
|
import { aiProjectFilePathFromMount } from "./file-mount";
|
|
10
|
-
import { aiFileStore, normalizeAiFilePath
|
|
10
|
+
import { type AiFileContent, aiFileStore, normalizeAiFilePath } from "./files-store";
|
|
11
11
|
import { defineAiTool } from "./tools";
|
|
12
12
|
|
|
13
13
|
export const CloudAiMarkdownToPdfInputSchema = z
|
package/src/ai/memories.ts
CHANGED
|
@@ -459,9 +459,7 @@ export const aiMemories = {
|
|
|
459
459
|
action: "retired" as const,
|
|
460
460
|
kind: memory.kind,
|
|
461
461
|
content: memory.content,
|
|
462
|
-
...(memory.resource_type && memory.resource_id
|
|
463
|
-
? { resourceRef: { type: memory.resource_type, id: memory.resource_id } }
|
|
464
|
-
: {}),
|
|
462
|
+
...(memory.resource_type && memory.resource_id ? { resourceRef: { type: memory.resource_type, id: memory.resource_id } } : {}),
|
|
465
463
|
}));
|
|
466
464
|
}
|
|
467
465
|
|
|
@@ -509,19 +507,20 @@ export const aiMemories = {
|
|
|
509
507
|
);
|
|
510
508
|
const created = rows[0];
|
|
511
509
|
return created
|
|
512
|
-
? [
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
510
|
+
? [
|
|
511
|
+
{
|
|
512
|
+
action: "added",
|
|
513
|
+
kind: created.kind,
|
|
514
|
+
content: created.content,
|
|
515
|
+
...(resourceRef ? { resourceRef } : {}),
|
|
516
|
+
},
|
|
517
|
+
]
|
|
518
518
|
: [];
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
if (duplicate && !targets.some((memory) => memory.id === duplicate.id)) {
|
|
522
522
|
const duplicateResourceMatches =
|
|
523
|
-
proposal.kind !== "workflow" ||
|
|
524
|
-
(duplicate.resource_type === resourceRef?.type && duplicate.resource_id === resourceRef?.id);
|
|
523
|
+
proposal.kind !== "workflow" || (duplicate.resource_type === resourceRef?.type && duplicate.resource_id === resourceRef?.id);
|
|
525
524
|
if (duplicate.kind !== proposal.kind || !duplicateResourceMatches) return [];
|
|
526
525
|
await tx`
|
|
527
526
|
UPDATE ai.memories old
|
|
@@ -532,15 +531,17 @@ export const aiMemories = {
|
|
|
532
531
|
AND old.source = 'background' AND old.priority = 'normal'
|
|
533
532
|
AND old.deleted_at IS NULL AND old.superseded_by_id IS NULL
|
|
534
533
|
`;
|
|
535
|
-
return [
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
534
|
+
return [
|
|
535
|
+
{
|
|
536
|
+
action: "merged",
|
|
537
|
+
kind: duplicate.kind,
|
|
538
|
+
content: duplicate.content,
|
|
539
|
+
previousContent: targets.map((memory) => memory.content).join(" · "),
|
|
540
|
+
...(duplicate.resource_type && duplicate.resource_id
|
|
541
|
+
? { resourceRef: { type: duplicate.resource_type, id: duplicate.resource_id } }
|
|
542
|
+
: {}),
|
|
543
|
+
},
|
|
544
|
+
];
|
|
544
545
|
}
|
|
545
546
|
|
|
546
547
|
const destination = targets[0]!;
|
|
@@ -565,13 +566,15 @@ export const aiMemories = {
|
|
|
565
566
|
AND deleted_at IS NULL AND superseded_by_id IS NULL
|
|
566
567
|
`;
|
|
567
568
|
}
|
|
568
|
-
return [
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
569
|
+
return [
|
|
570
|
+
{
|
|
571
|
+
action: proposal.action === "merge" ? "merged" : "updated",
|
|
572
|
+
kind: proposal.kind,
|
|
573
|
+
content,
|
|
574
|
+
previousContent: targets.map((memory) => memory.content).join(" · "),
|
|
575
|
+
...(resourceRef ? { resourceRef } : {}),
|
|
576
|
+
},
|
|
577
|
+
];
|
|
575
578
|
});
|
|
576
579
|
},
|
|
577
580
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sql } from "bun";
|
|
2
2
|
import type { CloudResourceRef } from "../contracts/capabilities";
|
|
3
|
-
import { withAiShortId } from "./short-id";
|
|
4
3
|
import type { AiMemoryKind } from "./memories";
|
|
4
|
+
import { withAiShortId } from "./short-id";
|
|
5
5
|
|
|
6
6
|
export type AiMemoryLearningRunStatus = "running" | "ok" | "skipped" | "failed";
|
|
7
7
|
export type AiMemoryLearningRunKind = "turn" | "workflow";
|