@k2b/cloud 0.7.0 → 0.9.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 +5 -4
- package/scripts/README.md +10 -0
- package/scripts/build.ts +14 -1
- package/scripts/preload.ts +7 -0
- package/scripts/sync-recovery-smoke.ts +21 -6
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +35 -12
- package/src/_internal/define-app.ts +28 -11
- package/src/_internal/page-responses.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/runtime-context.ts +1 -1
- package/src/access/PermissionEditor.tsx +15 -9
- package/src/access/PrincipalPicker.tsx +9 -3
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -2
- package/src/ai/admin.ts +2 -2
- package/src/ai/assistant-models.ts +1 -1
- package/src/ai/audio-tool.ts +1 -1
- package/src/ai/browser-code-contracts.ts +174 -52
- package/src/ai/browser.ts +4 -2
- package/src/ai/capabilities.ts +3 -3
- package/src/ai/chat/blocks.tsx +127 -50
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-table.tsx +1 -1
- package/src/ai/chat/composer-adapter.ts +48 -13
- package/src/ai/chat/message-utils.ts +18 -5
- package/src/ai/chat/presentation.tsx +24 -11
- package/src/ai/chat/tool-disclosure.tsx +6 -2
- package/src/ai/chat/tool-groups.ts +8 -2
- package/src/ai/chat-quotas.ts +2 -2
- package/src/ai/chat-task-contracts.ts +9 -13
- package/src/ai/chat-tasks.ts +39 -18
- package/src/ai/client/controller.ts +87 -38
- package/src/ai/client/projection.ts +13 -6
- package/src/ai/client/transport.ts +4 -2
- package/src/ai/code-capability-routes.ts +22 -27
- package/src/ai/code-capability-transport.ts +2 -1
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +9 -5
- package/src/ai/code-runtime-tools.ts +16 -3
- package/src/ai/code-source-contracts.ts +100 -32
- package/src/ai/code-source-tools.ts +49 -6
- package/src/ai/compaction.ts +3 -3
- package/src/ai/default-tools.ts +87 -14
- package/src/ai/dictation-runtime.ts +1 -1
- package/src/ai/draft-content.ts +27 -12
- package/src/ai/enrich.ts +1 -1
- package/src/ai/executor.ts +8 -12
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +6 -1
- package/src/ai/file-tools.ts +8 -2
- package/src/ai/files-store.ts +55 -17
- package/src/ai/grids-skill.ts +2 -0
- package/src/ai/http.ts +18 -7
- package/src/ai/index.ts +24 -25
- package/src/ai/inference-calls.ts +3 -4
- 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 +5 -5
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +23 -12
- package/src/ai/migrate.ts +6 -4
- package/src/ai/model-pricing.ts +4 -4
- package/src/ai/pdf-render-worker.ts +3 -2
- package/src/ai/projects-routes.ts +15 -40
- package/src/ai/projects.ts +18 -13
- package/src/ai/quota-provider.ts +6 -5
- package/src/ai/quota-report.ts +5 -5
- package/src/ai/quotas.ts +3 -3
- package/src/ai/routes.ts +60 -34
- package/src/ai/run-timeout.ts +3 -1
- package/src/ai/runtime-tools.ts +6 -7
- package/src/ai/runtime.ts +38 -20
- package/src/ai/settings.ts +1 -1
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-search.ts +21 -10
- package/src/ai/skill-seeds.ts +16 -7
- package/src/ai/skill-tool.ts +6 -1
- package/src/ai/skills-routes.ts +32 -15
- package/src/ai/skills.ts +54 -28
- package/src/ai/store.ts +80 -47
- package/src/ai/structured.ts +6 -2
- package/src/ai/system-prompt.ts +7 -5
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/todo-contracts.ts +24 -12
- package/src/ai/todo-tool.ts +16 -13
- package/src/ai/tools.ts +1 -1
- package/src/ai/transcription.ts +4 -4
- package/src/ai/turn-timing.ts +1 -1
- package/src/ai/ui.tsx +2 -1
- package/src/ai/usage.ts +1 -1
- package/src/ai/vision-tool.ts +38 -17
- package/src/api/admin-ai-quotas.ts +14 -8
- package/src/api/admin-ai-skills.ts +3 -3
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +2 -2
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth/schemas.ts +7 -1
- package/src/api/auth.ts +4 -3
- package/src/api/capabilities.ts +45 -13
- package/src/api/index.ts +5 -5
- package/src/api/mcp.ts +5 -12
- package/src/browser/CloudResourceSearch.tsx +1 -1
- package/src/browser/command-bridge.ts +1 -1
- package/src/browser/command-shortcuts.ts +1 -1
- package/src/browser/commands.ts +8 -7
- package/src/browser/testing.ts +2 -1
- package/src/capabilities/client.ts +1 -1
- package/src/capabilities/command-link.ts +1 -1
- package/src/capabilities/server.ts +2 -1
- package/src/cli/admin/ai-quotas.ts +5 -5
- package/src/cli/admin/app-credentials.ts +7 -9
- package/src/cli/admin/index.ts +3 -3
- 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 -29
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -54
- package/src/config/index.ts +3 -1
- package/src/contracts/capabilities.ts +8 -1
- package/src/contracts/index.ts +5 -7
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +12 -3
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -2
- package/src/server/app-assets.ts +27 -0
- package/src/server/index.ts +3 -2
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +3 -3
- package/src/server/services/access.ts +3 -1
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/announcements/index.ts +4 -4
- package/src/services/app-approval.ts +11 -10
- package/src/services/auth-flows/magic-link.ts +13 -1
- package/src/services/identity/key-config.ts +6 -4
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/logging/index.ts +1 -1
- package/src/services/logging/trace.ts +1 -1
- package/src/services/mandates/index.ts +5 -2
- package/src/services/mandates/policy.ts +106 -6
- package/src/services/notifications/batches.ts +1 -0
- package/src/services/pdf/gotenberg.ts +50 -14
- package/src/services/pdf/index.ts +11 -11
- package/src/services/public-http.ts +1 -1
- package/src/services/rail-shortcuts.ts +1 -1
- package/src/services/rail-snapshot.ts +1 -1
- package/src/services/request-cache-redis.ts +2 -1
- package/src/services/session/user.ts +1 -1
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +6 -8
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +3 -3
- package/src/services/webauthn.ts +1 -1
- package/src/shared/index.ts +3 -6
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminSidebar.tsx +2 -2
- package/src/ssr/AppLaunchpad.island.tsx +12 -2
- package/src/ssr/AppLaunchpadPanel.tsx +4 -4
- package/src/ssr/GlobalSearchDialog.tsx +6 -6
- package/src/ssr/GlobalSearchTrigger.island.tsx +2 -2
- package/src/ssr/Layout.tsx +4 -3
- package/src/ssr/LayoutHeader.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +4 -6
- package/src/ssr/LayoutHelpTrigger.island.tsx +2 -2
- package/src/ssr/LayoutRail.tsx +3 -2
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/ProfilePreferences.island.tsx +1 -1
- package/src/ssr/RailApps.island.tsx +7 -7
- package/src/ssr/admin-navigation.ts +6 -2
- package/src/ssr/index.ts +1 -0
- package/src/ssr/islands/index.ts +2 -2
- package/src/ssr/mobile-menu-history.ts +1 -1
- package/src/ssr/rail-navigation.ts +3 -2
- package/src/ssr/request-path.ts +12 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +16 -91
- package/src/workflows/ai/store.ts +1 -1
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/store/index.ts +2 -2
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/session/test-fixture.ts +0 -18
package/src/ai/file-tools.ts
CHANGED
|
@@ -108,7 +108,12 @@ export const CloudAiListFilesOutputSchema = z.object({
|
|
|
108
108
|
mediaType: z.string(),
|
|
109
109
|
origin: z.enum(["user", "assistant", "project", "skill"]),
|
|
110
110
|
updatedAt: z.string(),
|
|
111
|
-
dictationRecordedAt: z
|
|
111
|
+
dictationRecordedAt: z
|
|
112
|
+
.string()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe(
|
|
115
|
+
"Present for prompt dictation recordings, not manually uploaded audio. Original audio remains available for re-evaluation.",
|
|
116
|
+
),
|
|
112
117
|
}),
|
|
113
118
|
),
|
|
114
119
|
truncated: z.boolean(),
|
|
@@ -342,7 +347,8 @@ export const CloudAiPresentOutputSchema = z.object({ path: z.string(), size: z.n
|
|
|
342
347
|
export const createCloudAiPresentTool = () =>
|
|
343
348
|
defineAiTool({
|
|
344
349
|
name: "present",
|
|
345
|
-
description:
|
|
350
|
+
description:
|
|
351
|
+
"Present a conversation file to the user as an openable and downloadable chat attachment. The returned path is a conversation-local file identifier, not a website URL. Refer to the delivered attachment by title; do not turn its path into a Markdown link.",
|
|
346
352
|
inputSchema: CloudAiPresentInputSchema,
|
|
347
353
|
outputSchema: CloudAiPresentOutputSchema,
|
|
348
354
|
approval: "never",
|
package/src/ai/files-store.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { type SQL, sql } from "bun";
|
|
2
|
+
import { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
|
|
3
3
|
|
|
4
4
|
export { guessAiMediaType } from "./file-media-type";
|
|
5
5
|
|
|
@@ -142,7 +142,8 @@ export const aiFileStore = {
|
|
|
142
142
|
producerCallKey: string;
|
|
143
143
|
mediaType: string;
|
|
144
144
|
}): Promise<AiFileStat> {
|
|
145
|
-
if (input.bytes.byteLength > AI_FILES_MAX_FILE_BYTES_DEFAULT)
|
|
145
|
+
if (input.bytes.byteLength > AI_FILES_MAX_FILE_BYTES_DEFAULT)
|
|
146
|
+
throw new AiFileWriteError("STORAGE_FULL", "File exceeds the destination file limit; nothing was written.");
|
|
146
147
|
if (!normalizeAiFilePath(input.path)) {
|
|
147
148
|
throw new Error("Invalid tool artifact path or file size.");
|
|
148
149
|
}
|
|
@@ -199,7 +200,8 @@ export const aiFileStore = {
|
|
|
199
200
|
},
|
|
200
201
|
|
|
201
202
|
async list(input: { conversationId: string; prefix?: string; after?: string; limit?: number }): Promise<AiFileStat[]> {
|
|
202
|
-
if (input.limit !== undefined && (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 1000))
|
|
203
|
+
if (input.limit !== undefined && (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 1000))
|
|
204
|
+
throw new Error("Invalid file page limit");
|
|
203
205
|
const prefix = input.prefix ?? "/";
|
|
204
206
|
const pattern = `${prefix.endsWith("/") ? prefix : `${prefix}/`}%`;
|
|
205
207
|
const rows = await sql<FileRow[]>`
|
|
@@ -306,16 +308,29 @@ export const aiFileStore = {
|
|
|
306
308
|
}
|
|
307
309
|
|
|
308
310
|
return sql.begin(async (tx) => {
|
|
309
|
-
const [conversation] = await tx<
|
|
310
|
-
|
|
311
|
+
const [conversation] = await tx<
|
|
312
|
+
{ id: string; created_by_user_id: string; archived_at: Date | null }[]
|
|
313
|
+
>`SELECT id,created_by_user_id,archived_at FROM ai.conversations WHERE id = ${input.conversationId} FOR UPDATE`;
|
|
314
|
+
if (
|
|
315
|
+
input.ownerUserId !== undefined &&
|
|
316
|
+
(!conversation || conversation.created_by_user_id !== input.ownerUserId || conversation.archived_at)
|
|
317
|
+
)
|
|
318
|
+
throw new Error("Conversation access denied");
|
|
311
319
|
if (input.expectedVersion !== undefined) {
|
|
312
|
-
const [existing] = await tx<
|
|
313
|
-
|
|
320
|
+
const [existing] = await tx<
|
|
321
|
+
FileContentRow[]
|
|
322
|
+
>`SELECT path,size,media_type,origin,updated_at,version,bytes FROM ai.files WHERE conversation_id=${input.conversationId} AND path=${input.path}`;
|
|
323
|
+
const version = existing
|
|
324
|
+
? aiFileContentVersion({ ...toContent(existing), id: `${input.conversationId}:${input.path}:${existing.version}` })
|
|
325
|
+
: null;
|
|
314
326
|
if (version !== input.expectedVersion) throw new AiFileVersionConflict();
|
|
315
327
|
}
|
|
316
328
|
const otherBytes = await aiConversationStoredBytes(tx, input.conversationId, input.path);
|
|
317
329
|
if (otherBytes + input.bytes.byteLength > maxConversation) {
|
|
318
|
-
throw new AiFileWriteError(
|
|
330
|
+
throw new AiFileWriteError(
|
|
331
|
+
"STORAGE_FULL",
|
|
332
|
+
`Conversation storage limit of ${Math.floor(maxConversation / (1024 * 1024))} MB exceeded.`,
|
|
333
|
+
);
|
|
319
334
|
}
|
|
320
335
|
if (input.origin === "user") {
|
|
321
336
|
if (input.allowUserOverwrite) {
|
|
@@ -347,7 +362,9 @@ export const aiFileStore = {
|
|
|
347
362
|
`;
|
|
348
363
|
if (!written[0]) throw new Error(`Cannot overwrite user-uploaded file ${input.path}.`);
|
|
349
364
|
}
|
|
350
|
-
const [written] = await tx<
|
|
365
|
+
const [written] = await tx<
|
|
366
|
+
FileRow[]
|
|
367
|
+
>`SELECT path,size,media_type,origin,dictation_recorded_at,updated_at,version FROM ai.files WHERE conversation_id=${input.conversationId} AND path=${input.path}`;
|
|
351
368
|
return toStat(written!);
|
|
352
369
|
});
|
|
353
370
|
},
|
|
@@ -374,7 +391,10 @@ export const aiFileStore = {
|
|
|
374
391
|
}
|
|
375
392
|
const total = await aiConversationStoredBytes(tx, input.conversationId);
|
|
376
393
|
if (total + input.bytes.byteLength > maxConversation) {
|
|
377
|
-
throw new AiFileWriteError(
|
|
394
|
+
throw new AiFileWriteError(
|
|
395
|
+
"STORAGE_FULL",
|
|
396
|
+
`Conversation storage limit of ${Math.floor(maxConversation / (1024 * 1024))} MB exceeded.`,
|
|
397
|
+
);
|
|
378
398
|
}
|
|
379
399
|
const appended = await tx<{ id: string }[]>`
|
|
380
400
|
INSERT INTO ai.files (conversation_id, path, bytes, media_type, size, origin, updated_at)
|
|
@@ -461,11 +481,19 @@ export const aiFileStore = {
|
|
|
461
481
|
};
|
|
462
482
|
|
|
463
483
|
/** Authorized services may expose this read after resolving the conversation owner. */
|
|
464
|
-
export const listAiConversationFiles = (
|
|
465
|
-
|
|
484
|
+
export const listAiConversationFiles = (
|
|
485
|
+
conversationId: string,
|
|
486
|
+
prefix?: string,
|
|
487
|
+
page?: { after?: string; limit: number },
|
|
488
|
+
): Promise<AiFileStat[]> => aiFileStore.list({ conversationId, prefix, ...page });
|
|
466
489
|
|
|
467
490
|
/** Authorized byte-preserving read for app-owned artifact importers. */
|
|
468
|
-
export async function readAiConversationFile(input: {
|
|
491
|
+
export async function readAiConversationFile(input: {
|
|
492
|
+
conversationId: string;
|
|
493
|
+
ownerUserId: string;
|
|
494
|
+
path: string;
|
|
495
|
+
version?: number;
|
|
496
|
+
}): Promise<AiFileContent | null> {
|
|
469
497
|
const { aiConversations } = await import("./store");
|
|
470
498
|
const conversation = await aiConversations.getConversation({ conversationId: input.conversationId, ownerUserId: input.ownerUserId });
|
|
471
499
|
if (!conversation || conversation.archivedAt) return null;
|
|
@@ -475,17 +503,27 @@ export async function readAiConversationFile(input: { conversationId: string; ow
|
|
|
475
503
|
|
|
476
504
|
/** Save one agent-produced file without overwriting an existing or user-edited file. */
|
|
477
505
|
export async function createAiConversationArtifact(input: {
|
|
478
|
-
conversationId: string;
|
|
506
|
+
conversationId: string;
|
|
507
|
+
ownerUserId: string;
|
|
508
|
+
path: string;
|
|
509
|
+
bytes: Uint8Array;
|
|
510
|
+
mediaType: string;
|
|
511
|
+
producerCallKey: string;
|
|
479
512
|
}): Promise<AiFileStat> {
|
|
480
513
|
const { aiConversations } = await import("./store");
|
|
481
|
-
const conversation = await aiConversations.getConversation({conversationId:input.conversationId,ownerUserId:input.ownerUserId});
|
|
514
|
+
const conversation = await aiConversations.getConversation({ conversationId: input.conversationId, ownerUserId: input.ownerUserId });
|
|
482
515
|
if (!conversation || conversation.archivedAt) throw new Error("Conversation access denied");
|
|
483
516
|
return aiFileStore.createToolArtifact(input);
|
|
484
517
|
}
|
|
485
518
|
|
|
486
519
|
/** Explicit, revision-checked file replacement for authorized transfer services. */
|
|
487
520
|
export async function writeAiConversationFile(input: {
|
|
488
|
-
conversationId: string;
|
|
521
|
+
conversationId: string;
|
|
522
|
+
ownerUserId: string;
|
|
523
|
+
path: string;
|
|
524
|
+
bytes: Uint8Array;
|
|
525
|
+
mediaType: string;
|
|
526
|
+
expectedVersion: string | null;
|
|
489
527
|
}): Promise<AiFileStat> {
|
|
490
528
|
if (normalizeAiFilePath(input.path) !== input.path) throw new Error("Invalid file path");
|
|
491
529
|
const current = await aiFileStore.stat(input);
|
package/src/ai/grids-skill.ts
CHANGED
|
@@ -153,4 +153,6 @@ A left join preserves unmatched readable source records. A relation with many ta
|
|
|
153
153
|
|
|
154
154
|
## Read stored document files
|
|
155
155
|
|
|
156
|
+
For download links, use the downloadUrl returned by document.list/read verbatim. It requires the user's current Cloud session and is not a public share. Studio/code tools needing file bytes use document.content.read plus capabilities.streams.read; do not fetch invented paths.
|
|
157
|
+
|
|
156
158
|
Use \`grids.document.read\` for metadata and available artifact keys. When the task needs actual PDF, XML or CSV bytes, use \`grids.document.content.read\` with the document ID and optional artifactKey; omission selects the primary artifact. In code mode, obtain the stream via capabilities.run in the current run and pass it to capabilities.streams.read to receive a File. Read XML/CSV as text or use an available PDF processor. A binary download alone does not extract PDF text or prove that you inspected its contents. Prefer GQL for structured data analysis. This is a read, not document issuance, a public share or sending a file. Respect the 50 MiB code-mode payload budget and 250 MiB total transfer budget; larger stored artifacts require a supported HTTP/CLI transfer. Streams expire, require current permissions and cannot be reused across turns or in mandate-backed background tasks. Never invent stream references or export capabilities.`;
|
package/src/ai/http.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { AiQuotaError } from "./quotas";
|
|
2
|
-
import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
|
|
3
1
|
import type { Input, Message } from "@k2b/nessi";
|
|
4
2
|
import type { Context } from "hono";
|
|
5
3
|
import { z } from "zod";
|
|
6
4
|
import { CapabilityAppIdSchema } from "../contracts/capabilities";
|
|
7
5
|
import { type AuthContext, err, fail, respond } from "../server";
|
|
8
6
|
import { aiAttachmentMarker } from "./attachments";
|
|
7
|
+
import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
|
|
9
8
|
import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
|
|
9
|
+
import { AiQuotaError } from "./quotas";
|
|
10
10
|
import { AiResourceMarkerSchema } from "./resource-markers";
|
|
11
11
|
import { AI_SHORT_ID_PATTERN } from "./short-id";
|
|
12
12
|
import { AI_SKILL_NAME_MAX_CHARS, AI_SKILL_NAME_PATTERN } from "./skill-format";
|
|
@@ -38,7 +38,8 @@ export const AiConversationDraftInputSchema = z
|
|
|
38
38
|
content: z.array(AiDraftContentPartSchema).max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1),
|
|
39
39
|
})
|
|
40
40
|
.superRefine((value, context) => {
|
|
41
|
-
if (value.content.reduce((length, part) => length + (part.type === "text" ? part.text.length : 0), 0) > 20_000)
|
|
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" });
|
|
42
43
|
if (value.content.filter((part) => part.type !== "text").length > AI_TURN_ATTACHMENT_MAX_ITEMS) {
|
|
43
44
|
context.addIssue({ code: "custom", path: ["content"], message: `A draft can attach at most ${AI_TURN_ATTACHMENT_MAX_ITEMS} items` });
|
|
44
45
|
}
|
|
@@ -46,7 +47,9 @@ export const AiConversationDraftInputSchema = z
|
|
|
46
47
|
|
|
47
48
|
export const AiInitialConversationDraftInputSchema = z
|
|
48
49
|
.object({
|
|
49
|
-
content: z
|
|
50
|
+
content: z
|
|
51
|
+
.array(z.union([AiDraftContentPartSchema.options[0], AiDraftContentPartSchema.options[1]]))
|
|
52
|
+
.max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1),
|
|
50
53
|
})
|
|
51
54
|
.superRefine((value, context) => {
|
|
52
55
|
if (value.content.filter((part) => part.type === "resource").length > AI_TURN_ATTACHMENT_MAX_ITEMS) {
|
|
@@ -128,7 +131,11 @@ export const AiTurnInputSchema = z
|
|
|
128
131
|
.max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
|
|
129
132
|
.optional(),
|
|
130
133
|
modelProfileId: z.string().trim().min(1).optional(),
|
|
131
|
-
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(),
|
|
132
139
|
})
|
|
133
140
|
.refine((input) => Boolean(input.message?.trim() || input.content?.length), {
|
|
134
141
|
message: "Message or content is required.",
|
|
@@ -149,7 +156,11 @@ export const AiSubmitConversationDraftInputSchema = z.object({
|
|
|
149
156
|
queueId: z.uuid().optional(),
|
|
150
157
|
draftRevision: z.number().int().min(1),
|
|
151
158
|
modelProfileId: z.string().trim().min(1).optional(),
|
|
152
|
-
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(),
|
|
153
164
|
});
|
|
154
165
|
|
|
155
166
|
export const AiSteerInputSchema = z.object({
|
|
@@ -251,7 +262,7 @@ const aiSettingsServiceError = (error: AiSettingsError) => {
|
|
|
251
262
|
};
|
|
252
263
|
|
|
253
264
|
export const toAiErrorResponse = (c: Context<AuthContext>, error: unknown) => {
|
|
254
|
-
if (error instanceof AiQuotaError) return c.json({ok:false,error:error.message,message:error.message,code:error.code},429);
|
|
265
|
+
if (error instanceof AiQuotaError) return c.json({ ok: false, error: error.message, message: error.message, code: error.code }, 429);
|
|
255
266
|
if (isAiSettingsError(error)) {
|
|
256
267
|
return respond(c, fail(aiSettingsServiceError(error.aiError)));
|
|
257
268
|
}
|
package/src/ai/index.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export { CODE_SOURCE_TOOLS, type CodeSourceToolName } from "./code-source-contracts";
|
|
2
|
-
export { CodeResourceId, CODE_RUNTIME_TOOL_NAMES, CodeRuntimeInput, parseCodeToolInput } from "./browser-code-contracts";
|
|
3
|
-
export { assistantAiSettingsState, listAssistantAiModels } from "./assistant-models";
|
|
4
1
|
export { type AiApprovalPreferenceRoutes, type AiApprovalPreferenceView, createAiApprovalPreferenceRoutes } from "./approval-routes";
|
|
5
2
|
export {
|
|
6
3
|
type AiToolApprovalContext,
|
|
@@ -14,14 +11,18 @@ export {
|
|
|
14
11
|
rememberAiToolApproval,
|
|
15
12
|
revokeAiToolApprovalPreference,
|
|
16
13
|
} from "./approvals";
|
|
14
|
+
export { assistantAiSettingsState, listAssistantAiModels, selectAssistantAiModelId } from "./assistant-models";
|
|
17
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";
|
|
18
18
|
export { aiCapabilityId, aiCapabilityToolName } from "./capabilities";
|
|
19
|
+
export { getAiChatQuotas } from "./chat-quotas";
|
|
19
20
|
export {
|
|
20
21
|
type AiChatTaskOccurrenceView,
|
|
21
22
|
type AiChatTaskView,
|
|
22
23
|
AiConversationIdSchema,
|
|
23
|
-
ChatTaskIdSchema,
|
|
24
24
|
ChatTaskGrantsSchema,
|
|
25
|
+
ChatTaskIdSchema,
|
|
25
26
|
ChatTaskOccurrenceIdSchema,
|
|
26
27
|
ChatTaskScheduleInputSchema,
|
|
27
28
|
chatTaskCreateFingerprint,
|
|
@@ -40,6 +41,10 @@ export {
|
|
|
40
41
|
aiChatTasks,
|
|
41
42
|
} from "./chat-tasks";
|
|
42
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";
|
|
43
48
|
export { listAiCredentialProfileIds } from "./credentials";
|
|
44
49
|
export {
|
|
45
50
|
CLOUD_AI_TEXT_EDITOR_MAX_CHARS,
|
|
@@ -69,16 +74,18 @@ export {
|
|
|
69
74
|
shouldApplyEnrichedDescription,
|
|
70
75
|
shouldApplyEnrichedTitle,
|
|
71
76
|
} from "./enrich";
|
|
77
|
+
export { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
|
|
78
|
+
export { AiFileLocation, AiFileReference } from "./file-reference-contracts";
|
|
72
79
|
export {
|
|
73
80
|
AI_FILES_MAX_CONVERSATION_BYTES_DEFAULT,
|
|
74
81
|
AI_FILES_MAX_FILE_BYTES_DEFAULT,
|
|
75
82
|
type AiFileStat,
|
|
83
|
+
createAiConversationArtifact,
|
|
76
84
|
guessAiMediaType,
|
|
77
85
|
listAiConversationFiles,
|
|
86
|
+
normalizeAiFilePath,
|
|
78
87
|
readAiConversationFile,
|
|
79
|
-
createAiConversationArtifact,
|
|
80
88
|
writeAiConversationFile,
|
|
81
|
-
normalizeAiFilePath,
|
|
82
89
|
} from "./files-store";
|
|
83
90
|
export {
|
|
84
91
|
AI_FIRECRAWL_API_KEY_SETTING_KEY,
|
|
@@ -208,6 +215,7 @@ export {
|
|
|
208
215
|
isNewerWireEvent,
|
|
209
216
|
} from "./protocol";
|
|
210
217
|
export { createAiProvider } from "./provider";
|
|
218
|
+
export { AiQuotaError } from "./quotas";
|
|
211
219
|
export { isConversationResourceCursor } from "./resource-refs";
|
|
212
220
|
export type { AiRoutes } from "./routes";
|
|
213
221
|
export {
|
|
@@ -243,7 +251,6 @@ export {
|
|
|
243
251
|
validateAiSkillName,
|
|
244
252
|
validateAiSkillReferences,
|
|
245
253
|
} from "./skill-format";
|
|
246
|
-
export { seedCloudAiSkills } from "./skills";
|
|
247
254
|
export {
|
|
248
255
|
type AiLoadedSkillSnapshot,
|
|
249
256
|
type AiSkill,
|
|
@@ -257,6 +264,7 @@ export {
|
|
|
257
264
|
AiSkillRevisionConflictError,
|
|
258
265
|
type AiSkillSummary,
|
|
259
266
|
aiSkills,
|
|
267
|
+
seedCloudAiSkills,
|
|
260
268
|
} from "./skills";
|
|
261
269
|
export { type AiSkillsRoutes, aiSkillsRoutes } from "./skills-routes";
|
|
262
270
|
export { aiConversations } from "./store";
|
|
@@ -279,8 +287,17 @@ export {
|
|
|
279
287
|
runAiStructured,
|
|
280
288
|
} from "./structured";
|
|
281
289
|
export { aiGlobalInstructionsContext, composeAiSystemPrompt, renderAiGlobalInstructions } from "./system-prompt";
|
|
290
|
+
export { type AiTaskRequest, type AiTaskRequestInput, AiTaskRequestSchema } from "./task-contracts";
|
|
291
|
+
export { executeAiTask } from "./task-execution";
|
|
282
292
|
export { type AiToolApprovalState, type AiToolCallLocation, aiToolAudit } from "./tool-audit";
|
|
283
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";
|
|
284
301
|
export type {
|
|
285
302
|
AiAccessResult,
|
|
286
303
|
AiCapabilityToolPresentation,
|
|
@@ -341,21 +358,3 @@ export {
|
|
|
341
358
|
type CloudAiViewImageOutput,
|
|
342
359
|
CloudAiViewImageOutputSchema,
|
|
343
360
|
} from "./vision-tool";
|
|
344
|
-
|
|
345
|
-
export {
|
|
346
|
-
createAiTranscriptionProvider,
|
|
347
|
-
resolveAiAudioModel,
|
|
348
|
-
runAiTranscription,
|
|
349
|
-
type AiResolvedAudioModel,
|
|
350
|
-
type RunAiTranscriptionInput,
|
|
351
|
-
} from "./transcription";
|
|
352
|
-
|
|
353
|
-
export { createCloudAiTranscribeAudioTool, CloudAiTranscribeAudioInputSchema, CloudAiTranscribeAudioOutputSchema } from "./audio-tool";
|
|
354
|
-
|
|
355
|
-
export { getAiChatQuotas } from "./chat-quotas";
|
|
356
|
-
|
|
357
|
-
export { AiFileLocation, AiFileReference } from "./file-reference-contracts";
|
|
358
|
-
export { aiFileContentVersion, AiFileWriteError, AiFileVersionConflict } from "./file-content-version";
|
|
359
|
-
|
|
360
|
-
// Framework-owned managed execution host transport.
|
|
361
|
-
export { createCodeCapabilityTransport, CODE_CAPABILITY_TOKEN_HEADER } from "./code-capability-transport";
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { sql, type SQL } from "bun";
|
|
1
|
+
import { type SQL, sql } from "bun";
|
|
3
2
|
import type { AccessSubject } from "../server/services/access";
|
|
4
|
-
import { aiCostDecimal, aiCostUnits, aiDecimalUnits,
|
|
3
|
+
import { aiBudgetUnits, aiCostDecimal, aiCostUnits, aiDecimalUnits, hasBillableAiPricing } from "../shared/ai-costs";
|
|
4
|
+
import { AiQuotaError, aiQuotas, quotaWindow } from "./quotas";
|
|
5
5
|
import type { AiModelProfile } from "./types";
|
|
6
|
-
import { aiQuotas, AiQuotaError, quotaWindow } from "./quotas";
|
|
7
6
|
|
|
8
7
|
export class AiBackgroundCostError extends Error {
|
|
9
8
|
readonly code = "ai_background_cost_stop";
|
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";
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { AiBackgroundAdmissionError, AiBackgroundCostError } from "./inference-calls";
|
|
2
|
-
import { sql } from "bun";
|
|
3
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { sql } from "bun";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { CloudResourceRefSchema } from "../contracts/capabilities";
|
|
6
5
|
import { coreSettings } from "../services";
|
|
7
6
|
import { logger } from "../services/logging";
|
|
8
7
|
import { normalizeLocale } from "../shared/locale";
|
|
9
8
|
import { parseAiAttachmentMarkers } from "./attachments";
|
|
9
|
+
import { AiBackgroundAdmissionError, AiBackgroundCostError } from "./inference-calls";
|
|
10
|
+
import { type AiBackgroundMemoryProposal, aiMemories } from "./memories";
|
|
10
11
|
import { type AiMemoryLearningChange, aiMemoryLearningRuns } from "./memory-learning-runs";
|
|
11
12
|
import {
|
|
13
|
+
type AiMemoryWorkflowEvidence,
|
|
14
|
+
type AiMemoryWorkflowPattern,
|
|
12
15
|
listAiPendingWorkflowPatterns,
|
|
13
16
|
listAiTurnWorkflowEvidence,
|
|
14
17
|
markAiWorkflowPatternReviewed,
|
|
15
|
-
type AiMemoryWorkflowEvidence,
|
|
16
|
-
type AiMemoryWorkflowPattern,
|
|
17
18
|
} from "./memory-workflow-evidence";
|
|
18
|
-
import { type AiBackgroundMemoryProposal, aiMemories } from "./memories";
|
|
19
19
|
import { aiConversations } from "./store";
|
|
20
20
|
import type { RunAiStructuredInput, RunAiStructuredResult } from "./structured";
|
|
21
21
|
import { resolveAiBackgroundModel, runAiStructured } from "./structured";
|
package/src/ai/memory-tool.ts
CHANGED
|
@@ -108,11 +108,7 @@ export const createCloudAiMemoryTool = (evidence = "") =>
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
const memory = await aiMemories.getByShortId(userId, input.id);
|
|
111
|
-
if (
|
|
112
|
-
memory &&
|
|
113
|
-
!memoryMutationSupportedByEvidence(evidence, input.id) &&
|
|
114
|
-
!memoryMutationSupportedByEvidence(evidence, memory.content)
|
|
115
|
-
) {
|
|
111
|
+
if (memory && !memoryMutationSupportedByEvidence(evidence, input.id) && !memoryMutationSupportedByEvidence(evidence, memory.content)) {
|
|
116
112
|
return { ok: false, message: "A memory can only be forgotten when the user identifies it in this turn." };
|
|
117
113
|
}
|
|
118
114
|
if (!memory || !(await aiMemories.deleteByShortId(userId, input.id))) return { ok: false, message: "Memory not found." };
|
|
@@ -37,10 +37,15 @@ export const recordAiMemoryWorkflowEvidence = async (input: {
|
|
|
37
37
|
input.resources.flatMap((resource) => {
|
|
38
38
|
const parsed = CloudResourceRefSchema.safeParse(resource.ref);
|
|
39
39
|
if (!parsed.success) return [];
|
|
40
|
-
return [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
return [
|
|
41
|
+
[
|
|
42
|
+
`${parsed.data.type}\0${parsed.data.id}`,
|
|
43
|
+
{
|
|
44
|
+
ref: parsed.data,
|
|
45
|
+
...(resource.title ? { title: resource.title.trim().slice(0, MAX_RESOURCE_TITLE_CHARS) } : {}),
|
|
46
|
+
},
|
|
47
|
+
] as const,
|
|
48
|
+
];
|
|
44
49
|
}),
|
|
45
50
|
).values(),
|
|
46
51
|
].slice(0, MAX_RECEIPT_RESOURCES);
|
package/src/ai/message-queue.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { logger } from "../services/logging";
|
|
3
3
|
import { aiChatAccessSubject, isAssistantChatTurn } from "./assistant-models";
|
|
4
|
-
import {
|
|
4
|
+
import { editAiDraftText } from "./draft-content";
|
|
5
5
|
import { canonicalizeAiConversationAttachments } from "./file-context";
|
|
6
|
+
import { AI_FILES_MAX_CONVERSATION_BYTES_DEFAULT } from "./files-store";
|
|
7
|
+
import { aiInputToUserMessage, aiTurnInputToContent } from "./http";
|
|
8
|
+
import { AiQuotaError, aiQuotas } from "./quotas";
|
|
6
9
|
import { aiResourceMarker } from "./resource-markers";
|
|
7
|
-
import { sql } from "bun";
|
|
8
|
-
import type { AiConversationService, AiDraftContentPart } from "./types";
|
|
9
10
|
import { aiConversations } from "./store";
|
|
10
|
-
import {
|
|
11
|
-
import { AI_FILES_MAX_CONVERSATION_BYTES_DEFAULT } from "./files-store";
|
|
11
|
+
import type { AiConversationService, AiDraftContentPart } from "./types";
|
|
12
12
|
|
|
13
13
|
const log = logger("ai:message-queue");
|
|
14
14
|
export const AI_MESSAGE_QUEUE_LIMIT = 32;
|
|
@@ -112,15 +112,26 @@ export const editQueuedMessage = async (conversationId: string, id: string, text
|
|
|
112
112
|
const parts = content.map((part) =>
|
|
113
113
|
part.type === "text"
|
|
114
114
|
? part
|
|
115
|
-
: part.type === "project-file"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
: part.type === "project-file"
|
|
116
|
+
? { type: "text" as const, text: `Project file: ${part.path}` }
|
|
117
|
+
: part.type === "file"
|
|
118
|
+
? { type: "attachment" as const, path: part.path, mediaType: part.mediaType, size: part.size }
|
|
119
|
+
: { type: "text" as const, text: aiResourceMarker({ ref: part.ref, title: part.title, icon: part.icon, href: part.href }) },
|
|
119
120
|
);
|
|
120
121
|
const { message } = aiInputToUserMessage(aiTurnInputToContent({ content: parts }));
|
|
121
122
|
const userMessage = canonicalizeAiConversationAttachments(message, row.submission.runConfig.files);
|
|
122
123
|
if (userMessage.role !== "user") throw new Error("Queued message must be a user message.");
|
|
123
|
-
const submission = {
|
|
124
|
+
const submission = {
|
|
125
|
+
...row.submission,
|
|
126
|
+
userMessage,
|
|
127
|
+
runConfig: {
|
|
128
|
+
...row.submission.runConfig,
|
|
129
|
+
input: userMessage.content,
|
|
130
|
+
selectedSkillIds: [
|
|
131
|
+
...new Set(content.flatMap((part) => (part.type === "resource" && part.ref.type === "core.ai.skill" ? [part.ref.id] : []))),
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
};
|
|
124
135
|
await tx`UPDATE ai.queued_messages SET submission = (${JSON.stringify(submission)}::text)::jsonb,
|
|
125
136
|
content = (${JSON.stringify(content)}::text)::jsonb WHERE id = ${id}::uuid`;
|
|
126
137
|
return true;
|
package/src/ai/migrate.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { migrateAiQuotas } from "./quotas-migrate";
|
|
2
|
-
import { migrateAiMessageQueue } from "./message-queue";
|
|
3
1
|
import { sql } from "bun";
|
|
2
|
+
import { migrateAiMessageQueue } from "./message-queue";
|
|
4
3
|
import { migrateAiModelAccess } from "./model-access-migrate";
|
|
4
|
+
import { migrateAiQuotas } from "./quotas-migrate";
|
|
5
5
|
import { withAiShortId } from "./short-id";
|
|
6
6
|
|
|
7
7
|
const backfillAiShortIds = async (
|
|
@@ -1441,8 +1441,10 @@ export const migrateCloudAi = async (): Promise<void> => {
|
|
|
1441
1441
|
SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_textsearch') AS installed
|
|
1442
1442
|
`;
|
|
1443
1443
|
if (extension?.installed) {
|
|
1444
|
-
await sql
|
|
1445
|
-
|
|
1444
|
+
await sql
|
|
1445
|
+
.unsafe(`CREATE INDEX IF NOT EXISTS skills_search_bm25_idx
|
|
1446
|
+
ON ai.skills USING bm25 ((search_text)) WITH (text_config='simple')`)
|
|
1447
|
+
.simple();
|
|
1446
1448
|
}
|
|
1447
1449
|
} catch (error) {
|
|
1448
1450
|
console.warn("Optional Skill BM25 index unavailable; native Skill search remains active", error);
|