@k2b/cloud 0.31.0 → 0.33.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 +8 -6
- package/src/_internal/define-app.ts +20 -9
- package/src/_internal/page-responses.ts +5 -1
- package/src/ai/app-skills.ts +1 -7
- package/src/ai/audio-tool.ts +2 -1
- package/src/ai/browser.ts +109 -0
- package/src/ai/capabilities.ts +6 -2
- package/src/ai/chart-block.ts +431 -81
- package/src/ai/chat/action-sentences.ts +13 -7
- package/src/ai/chat/blocks.tsx +11 -7
- package/src/ai/chat/builtin-tools.tsx +9 -7
- package/src/ai/chat/chart-block.tsx +218 -44
- package/src/ai/chat/explorer-block.tsx +276 -0
- package/src/ai/chat/file-tools.tsx +40 -1
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/message-utils.ts +7 -0
- package/src/ai/chat/messages.ts +16 -0
- package/src/ai/chat/presentation.tsx +13 -3
- package/src/ai/chat/reference-text.tsx +34 -0
- package/src/ai/chat/tool-groups.ts +1 -0
- package/src/ai/chat/tool-names.ts +14 -0
- package/src/ai/chat/turn-view.tsx +17 -7
- package/src/ai/chat/view-block.browser-harness.tsx +35 -0
- package/src/ai/chat/view-block.tsx +417 -0
- package/src/ai/chat/visual-tools.tsx +32 -53
- package/src/ai/client-tool-results.ts +9 -0
- package/src/ai/code-source-contracts.ts +17 -2
- package/src/ai/code-source-tools.ts +1 -0
- package/src/ai/default-tool-contracts.ts +334 -91
- package/src/ai/default-tools.ts +89 -13
- package/src/ai/executor.ts +173 -31
- package/src/ai/file-error.ts +2 -0
- package/src/ai/file-tools.ts +27 -7
- package/src/ai/files-store.ts +60 -0
- package/src/ai/html-pdf-tool.ts +3 -2
- package/src/ai/http.ts +37 -12
- package/src/ai/index.ts +90 -0
- package/src/ai/local-tool-contracts.ts +244 -0
- package/src/ai/malformed-tool-call-repair.ts +144 -0
- package/src/ai/markdown-pdf-tool.ts +2 -1
- package/src/ai/memories.ts +48 -1
- package/src/ai/memory-learning.ts +17 -6
- package/src/ai/memory-tool.ts +1 -0
- package/src/ai/migrate.ts +15 -0
- package/src/ai/openai-api-messages.ts +53 -0
- package/src/ai/openai-api-slots.ts +40 -0
- package/src/ai/openai-api.ts +383 -0
- package/src/ai/protocol.ts +6 -2
- package/src/ai/provider.ts +17 -4
- package/src/ai/request-context.ts +33 -0
- package/src/ai/routes.ts +96 -57
- package/src/ai/runtime.ts +48 -12
- package/src/ai/settings.ts +5 -0
- package/src/ai/skill-catalog.ts +20 -5
- package/src/ai/skill-format.ts +17 -7
- package/src/ai/skill-tool.ts +15 -5
- package/src/ai/skills-routes.ts +20 -6
- package/src/ai/skills.ts +26 -7
- package/src/ai/store.ts +25 -0
- package/src/ai/system-prompt.ts +69 -10
- package/src/ai/timeline.ts +3 -1
- package/src/ai/tools.ts +3 -0
- package/src/ai/types.ts +9 -0
- package/src/ai/view-block.ts +941 -0
- package/src/ai/vision-tool.ts +2 -1
- package/src/api/admin-identity.ts +6 -1
- package/src/api/app-approval-actions.ts +36 -0
- package/src/api/app-approval.ts +10 -2
- package/src/api/approvals-loader.ts +15 -0
- package/src/api/approvals.ts +120 -0
- package/src/api/index.ts +3 -0
- package/src/api/me.ts +12 -3
- package/src/approvals/Approvals.browser-harness.tsx +16 -0
- package/src/approvals/Approvals.tsx +621 -0
- package/src/approvals/ApprovalsNavStatus.tsx +20 -0
- package/src/approvals/messages.ts +95 -0
- package/src/approvals/ui.ts +8 -0
- package/src/browser/app-approval.ts +32 -0
- package/src/browser/choose-files.tsx +26 -13
- package/src/browser/commands.ts +13 -4
- package/src/browser/file-provider-error.ts +11 -0
- package/src/browser/file-providers.ts +3 -12
- package/src/browser/files.tsx +1 -2
- package/src/browser/live-connection.ts +2 -3
- package/src/browser/navigation-search.ts +4 -1
- package/src/browser/provider-list.ts +2 -1
- package/src/browser/save-files.tsx +13 -2
- package/src/cli/account.ts +21 -2
- package/src/cli/commands.ts +4 -2
- package/src/contracts/app-approval.ts +120 -0
- package/src/contracts/approval-paths.ts +4 -0
- package/src/contracts/approvals.ts +81 -0
- package/src/contracts/command-path.ts +17 -0
- package/src/contracts/commands.ts +5 -15
- package/src/contracts/shared.ts +5 -0
- package/src/events/live-protocol.ts +2 -1
- package/src/events/live-server-message.ts +40 -0
- package/src/events/testing.ts +88 -0
- package/src/server/index.ts +2 -0
- package/src/server/middleware/auth.ts +11 -1
- package/src/server/request-timeout.ts +22 -0
- package/src/server/services/access.ts +4 -3
- package/src/services/app-approval.ts +103 -15
- package/src/services/approvals/cursor.ts +15 -0
- package/src/services/approvals/device.ts +112 -0
- package/src/services/approvals/index.ts +27 -11
- package/src/services/approvals/list.ts +120 -0
- package/src/services/approvals/migrate.ts +9 -1
- package/src/services/identity/models-only-auth.ts +25 -0
- package/src/services/notifications/email-frame.ts +16 -3
- package/src/services/outbox.ts +13 -6
- package/src/services/service-account-credentials.ts +22 -10
- package/src/services/settings/core-settings.ts +27 -0
- package/src/shared/ai-platform-prompt.ts +29 -3
- package/src/shared/email-html.ts +4 -0
- package/src/shared/index.ts +6 -1
- package/src/shared/markdown/extensions/code.ts +14 -0
- package/src/shared/markdown/index.ts +4 -4
- package/src/shared/theme.ts +41 -20
- package/src/ssr/AnnouncementBanners.tsx +30 -0
- package/src/ssr/AppBadge.tsx +4 -4
- package/src/ssr/AppLaunchpadPanel.tsx +6 -6
- package/src/ssr/CloudShell.island.tsx +91 -0
- package/src/ssr/GlobalSearchTrigger.tsx +51 -0
- package/src/ssr/Layout.tsx +56 -85
- package/src/ssr/LayoutHeader.tsx +31 -77
- package/src/ssr/LayoutHelp.tsx +2 -57
- package/src/ssr/LayoutHelpTrigger.tsx +51 -0
- package/src/ssr/{LayoutPreferences.island.tsx → LayoutPreferences.tsx} +2 -2
- package/src/ssr/LayoutRail.tsx +15 -69
- package/src/ssr/MinimalLayout.tsx +2 -3
- package/src/ssr/MinimalLayoutPreferences.island.tsx +12 -15
- package/src/ssr/MobileNavigation.tsx +13 -8
- package/src/ssr/MobileProfileActions.tsx +10 -7
- package/src/ssr/PageTrailRow.tsx +14 -0
- package/src/ssr/{ProfilePreferences.island.tsx → ProfilePreferences.tsx} +12 -7
- package/src/ssr/PwaLayout.tsx +0 -2
- package/src/ssr/PwaRuntime.island.tsx +1 -1
- package/src/ssr/{RailApps.island.tsx → RailApps.tsx} +2 -2
- package/src/ssr/ShellRuntime.island.tsx +170 -0
- package/src/ssr/TimezoneCookie.island.tsx +3 -19
- package/src/ssr/WorkspaceNavigationProvider.tsx +20 -1
- package/src/ssr/announcements-dialog.tsx +66 -0
- package/src/ssr/app-badges.ts +50 -6
- package/src/ssr/{AppLaunchpad.island.tsx → app-launchpad.tsx} +42 -62
- package/src/ssr/busy-action.ts +19 -0
- package/src/ssr/islands/index.ts +9 -6
- package/src/ssr/launchpad-surface.tsx +15 -0
- package/src/ssr/layout-help-registry.tsx +60 -0
- package/src/ssr/mobile-navigation.browser-harness.tsx +20 -0
- package/src/ssr/page-trail.browser-harness.tsx +52 -3
- package/src/ssr/preference-controller.ts +30 -21
- package/src/ssr/profile-actions.ts +26 -15
- package/src/ssr/profile-preferences-messages.ts +8 -4
- package/src/ssr/rail-context.ts +8 -12
- package/src/ssr/rail-navigation.ts +5 -3
- package/src/ssr/runtime.ts +6 -1
- package/src/ssr/shell-data.tsx +48 -0
- package/src/ssr/timezone-cookie.ts +22 -0
- package/src/ssr/workspace-navigation.ts +25 -2
- package/src/styles/approvals.css +390 -0
- package/src/styles/global.css +22 -7
- package/src/styles/utilities-navigation.css +13 -0
- package/src/workflows/language/compiler.ts +1 -1
- package/src/ssr/AppWorkspaceController.island.tsx +0 -21
- package/src/ssr/BrowserPushRegistration.island.tsx +0 -12
- package/src/ssr/GlobalAnnouncements.island.tsx +0 -117
- package/src/ssr/GlobalSearchTrigger.island.tsx +0 -75
- package/src/ssr/LayoutHelpTrigger.island.tsx +0 -61
- package/src/ssr/PageTrailRow.island.tsx +0 -20
- package/src/ssr/PageTrailRuntime.island.tsx +0 -26
- package/src/ssr/RegisteredHelpDocuments.island.tsx +0 -3
- /package/src/ssr/{LayoutBreadcrumbs.island.tsx → LayoutBreadcrumbs.tsx} +0 -0
package/src/ai/file-tools.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "@k2b/cloud/services/document-extraction";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import type { RequestActor } from "../server";
|
|
9
|
+
import { aiMissingChatFileError } from "./file-error";
|
|
9
10
|
import {
|
|
10
11
|
AI_PROJECT_FILE_MOUNT,
|
|
11
12
|
AI_SKILL_FILE_MOUNT,
|
|
@@ -14,8 +15,9 @@ import {
|
|
|
14
15
|
mountAiProjectFilePath,
|
|
15
16
|
mountAiSkillFilePath,
|
|
16
17
|
} from "./file-mount";
|
|
17
|
-
import { aiFileStore, guessAiMediaType, isAiWorkingFilePath, normalizeAiFilePath } from "./files-store";
|
|
18
|
+
import { aiFileStore, assertAiModelWritablePath, guessAiMediaType, isAiWorkingFilePath, normalizeAiFilePath } from "./files-store";
|
|
18
19
|
import { defineAiTool } from "./tools";
|
|
20
|
+
import { CLOUD_AI_VIEW_MAX_BYTES, CloudAiViewSummarySchema, cloudAiViewSummary, isAiViewFile, parseCloudAiView } from "./view-block";
|
|
19
21
|
|
|
20
22
|
const FILE_READ_MAX_BYTES = 64 * 1024;
|
|
21
23
|
const FILE_WRITE_MAX_BYTES = 100 * 1024;
|
|
@@ -234,7 +236,7 @@ export const createCloudAiReadFileTool = () => {
|
|
|
234
236
|
length: input.length,
|
|
235
237
|
})
|
|
236
238
|
: null);
|
|
237
|
-
if (!stored) throw
|
|
239
|
+
if (!stored) throw aiMissingChatFileError(path, ctx.localToolsEnabled);
|
|
238
240
|
const extractAsDocument = shouldExtractDocument(path, stored.mediaType);
|
|
239
241
|
if (isTextMediaType(stored.mediaType) && !extractAsDocument) {
|
|
240
242
|
if (input.offset > stored.size) throw new Error(`Offset ${input.offset} is past the end of ${path} (${stored.size} bytes).`);
|
|
@@ -271,7 +273,7 @@ export const createCloudAiReadFileTool = () => {
|
|
|
271
273
|
? await aiFileStore.readTurnFile({ turnId: ctx.turnId, path })
|
|
272
274
|
: await aiFileStore.read({ conversationId: conversationId(ctx.conversationId, "read_file"), path });
|
|
273
275
|
if (!fullFile) {
|
|
274
|
-
throw new Error(
|
|
276
|
+
throw snapshot ? new Error(`Attached file snapshot is unavailable: ${path}`) : aiMissingChatFileError(path, ctx.localToolsEnabled);
|
|
275
277
|
}
|
|
276
278
|
try {
|
|
277
279
|
extracted = await extractDocumentMarkdown({ bytes: fullFile.bytes, filename: path, signal: ctx.signal });
|
|
@@ -328,6 +330,7 @@ export const createCloudAiWriteFileTool = () =>
|
|
|
328
330
|
const id = conversationId(ctx.conversationId, "write_file");
|
|
329
331
|
const path = toolPath(input.path, "write");
|
|
330
332
|
assertConversationFilePath(path, "written");
|
|
333
|
+
assertAiModelWritablePath(path);
|
|
331
334
|
const existing = await aiFileStore.stat({ conversationId: id, path });
|
|
332
335
|
if (existing?.origin === "user") throw new Error(`Cannot overwrite user-uploaded file ${path}. Choose another path.`);
|
|
333
336
|
const bytes = new TextEncoder().encode(input.content);
|
|
@@ -356,13 +359,29 @@ export const CloudAiPresentInputSchema = z.object({
|
|
|
356
359
|
"One sentence in the user's language that says what the file is and what it is for, without repeating its name. Shown next to the file in the chat's results.",
|
|
357
360
|
),
|
|
358
361
|
});
|
|
359
|
-
export const CloudAiPresentOutputSchema = z.object({
|
|
362
|
+
export const CloudAiPresentOutputSchema = z.object({
|
|
363
|
+
path: z.string(),
|
|
364
|
+
size: z.number(),
|
|
365
|
+
mediaType: z.string(),
|
|
366
|
+
view: CloudAiViewSummarySchema.optional(),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
/** A view file as it is now; invalid user edits leave an ordinary file. */
|
|
370
|
+
const readView = async (conversationId: string, path: string) => {
|
|
371
|
+
const file = await aiFileStore.read({ conversationId, path });
|
|
372
|
+
if (!file || !isAiViewFile(file) || file.bytes.byteLength > CLOUD_AI_VIEW_MAX_BYTES) return undefined;
|
|
373
|
+
try {
|
|
374
|
+
return parseCloudAiView(JSON.parse(new TextDecoder().decode(file.bytes)));
|
|
375
|
+
} catch {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
360
379
|
|
|
361
380
|
export const createCloudAiPresentTool = () =>
|
|
362
381
|
defineAiTool({
|
|
363
382
|
name: "present",
|
|
364
383
|
description:
|
|
365
|
-
"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.",
|
|
384
|
+
"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. A view file from cloud.view.* (/views/…view.json) shows inline as its chart, group, table, stats or details view, so present every view the person should see.",
|
|
366
385
|
inputSchema: CloudAiPresentInputSchema,
|
|
367
386
|
outputSchema: CloudAiPresentOutputSchema,
|
|
368
387
|
approval: "never",
|
|
@@ -372,8 +391,9 @@ export const createCloudAiPresentTool = () =>
|
|
|
372
391
|
const path = toolPath(input.path, "read");
|
|
373
392
|
assertConversationFilePath(path, "presented");
|
|
374
393
|
const stat = await aiFileStore.stat({ conversationId: id, path });
|
|
375
|
-
if (!stat) throw
|
|
376
|
-
|
|
394
|
+
if (!stat) throw aiMissingChatFileError(path, ctx.localToolsEnabled);
|
|
395
|
+
const view = isAiViewFile(stat) && stat.size <= CLOUD_AI_VIEW_MAX_BYTES ? await readView(id, path) : undefined;
|
|
396
|
+
return { path: stat.path, size: stat.size, mediaType: stat.mediaType, ...(view ? { view: cloudAiViewSummary(view) } : {}) };
|
|
377
397
|
});
|
|
378
398
|
|
|
379
399
|
type MathToken = { kind: "number"; value: number } | { kind: "ident"; name: string } | { kind: "op"; op: string };
|
package/src/ai/files-store.ts
CHANGED
|
@@ -2,6 +2,14 @@ import { type SQL, sql } from "bun";
|
|
|
2
2
|
import { stripImageMetadata } from "../services/image-metadata";
|
|
3
3
|
import { AiFileVersionConflict, AiFileWriteError, aiFileContentVersion } from "./file-content-version";
|
|
4
4
|
import { isAiImage } from "./file-media-type";
|
|
5
|
+
import {
|
|
6
|
+
CLOUD_AI_VIEW_MAX_BYTES,
|
|
7
|
+
CLOUD_AI_VIEW_SUFFIX,
|
|
8
|
+
type CloudAiViewSummary,
|
|
9
|
+
checkCloudAiView,
|
|
10
|
+
cloudAiViewSummary,
|
|
11
|
+
isAiViewPath,
|
|
12
|
+
} from "./view-block";
|
|
5
13
|
|
|
6
14
|
export { guessAiMediaType } from "./file-media-type";
|
|
7
15
|
|
|
@@ -697,6 +705,7 @@ export async function createAiConversationArtifact(input: {
|
|
|
697
705
|
const { aiConversations } = await import("./store");
|
|
698
706
|
const conversation = await aiConversations.getConversation({ conversationId: input.conversationId, ownerUserId: input.ownerUserId });
|
|
699
707
|
if (!conversation || conversation.archivedAt) throw new Error("Conversation access denied");
|
|
708
|
+
assertAiModelWritablePath(input.path);
|
|
700
709
|
return aiFileStore.createToolArtifact(input);
|
|
701
710
|
}
|
|
702
711
|
|
|
@@ -710,6 +719,57 @@ export async function writeAiConversationFile(input: {
|
|
|
710
719
|
expectedVersion: string | null;
|
|
711
720
|
}): Promise<AiFileStat> {
|
|
712
721
|
if (normalizeAiFilePath(input.path) !== input.path) throw new Error("Invalid file path");
|
|
722
|
+
assertAiModelWritablePath(input.path);
|
|
713
723
|
const current = await aiFileStore.stat(input);
|
|
714
724
|
return aiFileStore.write({ ...input, origin: current?.origin ?? "assistant", allowUserOverwrite: current?.origin === "user" });
|
|
715
725
|
}
|
|
726
|
+
|
|
727
|
+
export function assertAiModelWritablePath(path: string): void {
|
|
728
|
+
if (isAiViewPath(normalizeAiFilePath(path) ?? path))
|
|
729
|
+
throw new Error("The /views folder holds views that code runs create with cloud.view.*; choose another path.");
|
|
730
|
+
}
|
|
731
|
+
export class AiViewInvalidError extends Error {
|
|
732
|
+
constructor(readonly issues: string[]) {
|
|
733
|
+
super(`View rejected: ${issues.join("; ")}`);
|
|
734
|
+
this.name = "AiViewInvalidError";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Run-produced views use the same ownership, budget and retry rules as other chat artifacts. */
|
|
739
|
+
export async function createAiConversationView(input: {
|
|
740
|
+
conversationId: string;
|
|
741
|
+
ownerUserId: string;
|
|
742
|
+
turnId: string;
|
|
743
|
+
runId: string;
|
|
744
|
+
index: number;
|
|
745
|
+
view: unknown;
|
|
746
|
+
}): Promise<{ path: string } & CloudAiViewSummary> {
|
|
747
|
+
const { aiConversations } = await import("./store");
|
|
748
|
+
const conversation = await aiConversations.getConversation({ conversationId: input.conversationId, ownerUserId: input.ownerUserId });
|
|
749
|
+
if (!conversation || conversation.archivedAt) throw new Error("Conversation access denied");
|
|
750
|
+
const checked = checkCloudAiView(input.view);
|
|
751
|
+
if ("issues" in checked) throw new AiViewInvalidError(checked.issues);
|
|
752
|
+
const bytes = new TextEncoder().encode(JSON.stringify(checked.view));
|
|
753
|
+
if (bytes.byteLength > CLOUD_AI_VIEW_MAX_BYTES)
|
|
754
|
+
throw new AiViewInvalidError([`view: At most ${CLOUD_AI_VIEW_MAX_BYTES} bytes are allowed.`]);
|
|
755
|
+
const slug =
|
|
756
|
+
checked.view.title
|
|
757
|
+
.normalize("NFKD")
|
|
758
|
+
.replace(/\p{M}/gu, "")
|
|
759
|
+
.toLowerCase()
|
|
760
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
761
|
+
.replace(/^-+|-+$/g, "")
|
|
762
|
+
.slice(0, 48)
|
|
763
|
+
.replace(/-+$/g, "") || "view";
|
|
764
|
+
const key = `${input.turnId}:${input.runId}:${input.index}`;
|
|
765
|
+
const digest = new Bun.CryptoHasher("sha256").update(key).digest("hex").slice(0, 8);
|
|
766
|
+
const path = `/views/${slug}-${digest}${CLOUD_AI_VIEW_SUFFIX}`;
|
|
767
|
+
await aiFileStore.createToolArtifact({
|
|
768
|
+
conversationId: input.conversationId,
|
|
769
|
+
path,
|
|
770
|
+
bytes,
|
|
771
|
+
mediaType: "application/json",
|
|
772
|
+
producerCallKey: `view:${key}`,
|
|
773
|
+
});
|
|
774
|
+
return { path, ...cloudAiViewSummary(checked.view) };
|
|
775
|
+
}
|
package/src/ai/html-pdf-tool.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type RenderHtmlToPdfResult,
|
|
9
9
|
renderHtmlToPdfWithConfig,
|
|
10
10
|
} from "../services/pdf";
|
|
11
|
+
import { aiMissingChatFileError } from "./file-error";
|
|
11
12
|
import { aiProjectFilePathFromMount, aiSkillFilePathFromMount } from "./file-mount";
|
|
12
13
|
import { type AiFileContent, type AiFileStat, aiFileStore, normalizeAiFilePath } from "./files-store";
|
|
13
14
|
import { withAiPdfConversionSlot } from "./pdf-conversions";
|
|
@@ -122,7 +123,7 @@ export const createCloudAiHtmlToPdfTool = (dependencies: HtmlPdfToolDependencies
|
|
|
122
123
|
for (const path of [sourcePath, cssPath, headerPath, footerPath, ...assetPaths]) {
|
|
123
124
|
if (path === undefined) continue;
|
|
124
125
|
const file = await stat({ conversationId, path });
|
|
125
|
-
if (!file) throw
|
|
126
|
+
if (!file) throw aiMissingChatFileError(path, ctx.localToolsEnabled);
|
|
126
127
|
if (path === sourcePath && file.origin !== "assistant") {
|
|
127
128
|
throw new Error(`Create an assistant-owned HTML file with write_file before converting ${sourcePath}.`);
|
|
128
129
|
}
|
|
@@ -134,7 +135,7 @@ export const createCloudAiHtmlToPdfTool = (dependencies: HtmlPdfToolDependencies
|
|
|
134
135
|
|
|
135
136
|
const load = async (path: string): Promise<AiFileContent> => {
|
|
136
137
|
const file = await read({ conversationId, path });
|
|
137
|
-
if (!file) throw
|
|
138
|
+
if (!file) throw aiMissingChatFileError(path, ctx.localToolsEnabled);
|
|
138
139
|
return file;
|
|
139
140
|
};
|
|
140
141
|
const text = async (path: string | undefined): Promise<string | undefined> => (path === undefined ? undefined : utf8(await load(path)));
|
package/src/ai/http.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type AuthContext, err, fail, respond } from "../server";
|
|
|
6
6
|
import { aiAttachmentMarker } from "./attachments";
|
|
7
7
|
import { CODE_RUNTIME_TOOL_NAMES } from "./browser-code-contracts";
|
|
8
8
|
import { AI_TURN_ATTACHMENT_MAX_ITEMS } from "./limits";
|
|
9
|
+
import { AI_LOCAL_TOOL_NAMES, AiLocalClientSchema, AiLocalContextSchema } from "./local-tool-contracts";
|
|
9
10
|
import { AiQuotaError } from "./quotas";
|
|
10
11
|
import { AiResourceMarkerSchema } from "./resource-markers";
|
|
11
12
|
import { AI_SHORT_ID_PATTERN } from "./short-id";
|
|
@@ -120,7 +121,10 @@ export const AiUserContentPartSchema = z.union([
|
|
|
120
121
|
|
|
121
122
|
export type AiTurnContentPart = z.infer<typeof AiUserContentPartSchema>;
|
|
122
123
|
|
|
123
|
-
export const AiClientToolIdSchema = z.enum(["local_bash", ...CODE_RUNTIME_TOOL_NAMES] satisfies [
|
|
124
|
+
export const AiClientToolIdSchema = z.enum(["local_bash", ...AI_LOCAL_TOOL_NAMES, ...CODE_RUNTIME_TOOL_NAMES] satisfies [
|
|
125
|
+
AiClientToolId,
|
|
126
|
+
...AiClientToolId[],
|
|
127
|
+
]);
|
|
124
128
|
|
|
125
129
|
// Names use aiCapabilityToolName's provider grammar and 64-character limit. The 64-item cap matches allowedTools, with headroom
|
|
126
130
|
// for retired and not-yet-known names; 64 names × 64 ASCII characters bounds advertised name text to 4 KiB.
|
|
@@ -157,12 +161,21 @@ export const AiTurnInputSchema = z
|
|
|
157
161
|
|
|
158
162
|
export type AiTurnInput = z.infer<typeof AiTurnInputSchema>;
|
|
159
163
|
|
|
160
|
-
export const AiSubmitConversationDraftInputSchema = z
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
export const AiSubmitConversationDraftInputSchema = z
|
|
165
|
+
.object({
|
|
166
|
+
queueId: z.uuid().optional(),
|
|
167
|
+
draftRevision: z.number().int().min(1),
|
|
168
|
+
modelProfileId: z.string().trim().min(1).optional(),
|
|
169
|
+
clientToolIds: AiClientToolIdsSchema.optional(),
|
|
170
|
+
/** Set by a local client such as cld; the web never sends it. */
|
|
171
|
+
client: AiLocalClientSchema.optional(),
|
|
172
|
+
/** AGENTS.md files and local Skills a local client found; only valid together with client. */
|
|
173
|
+
localContext: AiLocalContextSchema.optional(),
|
|
174
|
+
})
|
|
175
|
+
.refine((input) => !input.localContext || Boolean(input.client), {
|
|
176
|
+
message: "Local context requires a local client.",
|
|
177
|
+
path: ["localContext"],
|
|
178
|
+
});
|
|
166
179
|
|
|
167
180
|
export const AiSteerInputSchema = z.object({
|
|
168
181
|
message: z.string().trim().min(1).max(20000),
|
|
@@ -187,6 +200,13 @@ export const AiMessageRetryInputSchema = z
|
|
|
187
200
|
.max(AI_TURN_ATTACHMENT_MAX_ITEMS * 2 + 1)
|
|
188
201
|
.optional(),
|
|
189
202
|
modelProfileId: z.string().trim().min(1).optional(),
|
|
203
|
+
clientToolIds: AiClientToolIdsSchema.optional(),
|
|
204
|
+
client: AiLocalClientSchema.optional(),
|
|
205
|
+
localContext: AiLocalContextSchema.omit({ selectedSkills: true }).optional(),
|
|
206
|
+
})
|
|
207
|
+
.refine((input) => !input.localContext || Boolean(input.client), {
|
|
208
|
+
message: "Local context requires a local client.",
|
|
209
|
+
path: ["localContext"],
|
|
190
210
|
})
|
|
191
211
|
.refine(
|
|
192
212
|
(input) =>
|
|
@@ -272,13 +292,18 @@ export const toAiErrorResponse = (c: Context<AuthContext>, error: unknown) => {
|
|
|
272
292
|
return respond(c, fail(err.internal(message)));
|
|
273
293
|
};
|
|
274
294
|
|
|
275
|
-
export const toAiActionFailureResponse = (
|
|
295
|
+
export const toAiActionFailureResponse = (
|
|
296
|
+
c: Context<AuthContext>,
|
|
297
|
+
result: { ok: false; status: 400 | 403 | 404 | 409; message: string },
|
|
298
|
+
) => {
|
|
276
299
|
const failure =
|
|
277
300
|
result.status === 400
|
|
278
301
|
? err.badInput(result.message)
|
|
279
|
-
: result.status ===
|
|
280
|
-
?
|
|
281
|
-
|
|
282
|
-
|
|
302
|
+
: result.status === 403
|
|
303
|
+
? err.forbidden(result.message)
|
|
304
|
+
: result.status === 404
|
|
305
|
+
? // err.notFound appends " not found" to its subject — the runtime message is already complete.
|
|
306
|
+
{ code: "NOT_FOUND" as const, message: result.message, status: 404 as const }
|
|
307
|
+
: conflict(result.message);
|
|
283
308
|
return respond(c, fail(failure));
|
|
284
309
|
};
|
package/src/ai/index.ts
CHANGED
|
@@ -56,7 +56,9 @@ export { listAiCredentialProfileIds } from "./credentials";
|
|
|
56
56
|
export {
|
|
57
57
|
CLOUD_AI_CHART_MAX_SERIES,
|
|
58
58
|
CLOUD_AI_CHART_MAX_VALUES,
|
|
59
|
+
CLOUD_AI_GROUP_MAX_CHARTS,
|
|
59
60
|
CLOUD_AI_TEXT_EDITOR_MAX_CHARS,
|
|
61
|
+
CLOUD_AI_VIEW_MAX_MARKS,
|
|
60
62
|
type CloudAiCardInput,
|
|
61
63
|
CloudAiCardInputSchema,
|
|
62
64
|
type CloudAiCardOutput,
|
|
@@ -65,10 +67,13 @@ export {
|
|
|
65
67
|
CloudAiChartInputSchema,
|
|
66
68
|
type CloudAiChartOutput,
|
|
67
69
|
CloudAiChartOutputSchema,
|
|
70
|
+
type CloudAiDateFormat,
|
|
71
|
+
type CloudAiFormat,
|
|
68
72
|
type CloudAiLocalBashInput,
|
|
69
73
|
CloudAiLocalBashInputSchema,
|
|
70
74
|
type CloudAiLocalBashOutput,
|
|
71
75
|
CloudAiLocalBashOutputSchema,
|
|
76
|
+
type CloudAiNumberFormat,
|
|
72
77
|
type CloudAiSurveyInput,
|
|
73
78
|
CloudAiSurveyInputSchema,
|
|
74
79
|
type CloudAiSurveyOutput,
|
|
@@ -77,6 +82,10 @@ export {
|
|
|
77
82
|
CloudAiTextEditorInputSchema,
|
|
78
83
|
type CloudAiTextEditorOutput,
|
|
79
84
|
CloudAiTextEditorOutputSchema,
|
|
85
|
+
cloudAiAppChartSchema,
|
|
86
|
+
cloudAiChartSchema,
|
|
87
|
+
cloudAiFormatSchema,
|
|
88
|
+
cloudAiNumberFormatSchema,
|
|
80
89
|
parseCloudAiChartInput,
|
|
81
90
|
} from "./default-tool-contracts";
|
|
82
91
|
export {
|
|
@@ -99,7 +108,9 @@ export {
|
|
|
99
108
|
type AiConversationFileOverview,
|
|
100
109
|
type AiConversationWorkingGroup,
|
|
101
110
|
type AiFileStat,
|
|
111
|
+
AiViewInvalidError,
|
|
102
112
|
createAiConversationArtifact,
|
|
113
|
+
createAiConversationView,
|
|
103
114
|
guessAiMediaType,
|
|
104
115
|
isAiWorkingFilePath,
|
|
105
116
|
listAiConversationFiles,
|
|
@@ -154,6 +165,50 @@ export {
|
|
|
154
165
|
AiInvalidationDomainSchema,
|
|
155
166
|
AiInvalidationSchema,
|
|
156
167
|
} from "./live-events";
|
|
168
|
+
export {
|
|
169
|
+
AI_LOCAL_BATCH_MAX_ITEMS,
|
|
170
|
+
AI_LOCAL_INSTRUCTIONS_MAX_BYTES,
|
|
171
|
+
AI_LOCAL_LIST_MAX_LIMIT,
|
|
172
|
+
AI_LOCAL_READ_MAX_BYTES,
|
|
173
|
+
AI_LOCAL_SELECTED_SKILLS_MAX_ITEMS,
|
|
174
|
+
AI_LOCAL_SKILL_FILES_MAX_ITEMS,
|
|
175
|
+
AI_LOCAL_SKILLS_MAX_ITEMS,
|
|
176
|
+
AI_LOCAL_TOOL_NAMES,
|
|
177
|
+
AI_LOCAL_WRITE_MAX_BYTES,
|
|
178
|
+
type AiLocalClient,
|
|
179
|
+
AiLocalClientSchema,
|
|
180
|
+
type AiLocalContext,
|
|
181
|
+
AiLocalContextSchema,
|
|
182
|
+
type AiLocalToolName,
|
|
183
|
+
type CloudAiLoadLocalSkillInput,
|
|
184
|
+
CloudAiLoadLocalSkillInputSchema,
|
|
185
|
+
type CloudAiLoadLocalSkillOutput,
|
|
186
|
+
CloudAiLoadLocalSkillOutputSchema,
|
|
187
|
+
type CloudAiLocalDownloadInput,
|
|
188
|
+
CloudAiLocalDownloadInputSchema,
|
|
189
|
+
type CloudAiLocalDownloadOutput,
|
|
190
|
+
CloudAiLocalDownloadOutputSchema,
|
|
191
|
+
type CloudAiLocalListInput,
|
|
192
|
+
CloudAiLocalListInputSchema,
|
|
193
|
+
type CloudAiLocalListOutput,
|
|
194
|
+
CloudAiLocalListOutputSchema,
|
|
195
|
+
type CloudAiLocalMoveInput,
|
|
196
|
+
CloudAiLocalMoveInputSchema,
|
|
197
|
+
type CloudAiLocalMoveOutput,
|
|
198
|
+
CloudAiLocalMoveOutputSchema,
|
|
199
|
+
type CloudAiLocalReadInput,
|
|
200
|
+
CloudAiLocalReadInputSchema,
|
|
201
|
+
type CloudAiLocalReadOutput,
|
|
202
|
+
CloudAiLocalReadOutputSchema,
|
|
203
|
+
type CloudAiLocalUploadInput,
|
|
204
|
+
CloudAiLocalUploadInputSchema,
|
|
205
|
+
type CloudAiLocalUploadOutput,
|
|
206
|
+
CloudAiLocalUploadOutputSchema,
|
|
207
|
+
type CloudAiLocalWriteInput,
|
|
208
|
+
CloudAiLocalWriteInputSchema,
|
|
209
|
+
type CloudAiLocalWriteOutput,
|
|
210
|
+
CloudAiLocalWriteOutputSchema,
|
|
211
|
+
} from "./local-tool-contracts";
|
|
157
212
|
export { AI_ENRICH_CRON_SETTING_KEY, AI_MEMORY_LEARNING_CRON_SETTING_KEY, aiMaintenanceJobs } from "./maintenance";
|
|
158
213
|
export {
|
|
159
214
|
AI_MEMORY_CONTENT_MAX_CHARS,
|
|
@@ -257,6 +312,8 @@ export {
|
|
|
257
312
|
type AiSkillDocument,
|
|
258
313
|
type AiSkillExtraFrontmatter,
|
|
259
314
|
type AiSkillReferenceInput,
|
|
315
|
+
isAiSkillManualOnly,
|
|
316
|
+
mountAiSkillInstructionLinks,
|
|
260
317
|
parseAiSkillMarkdown,
|
|
261
318
|
serializeAiSkillMarkdown,
|
|
262
319
|
validateAiSkillDescription,
|
|
@@ -371,6 +428,39 @@ export type {
|
|
|
371
428
|
} from "./types";
|
|
372
429
|
export { AI_MESSAGE_FEEDBACK_REASONS, isAiImageMediaType } from "./types";
|
|
373
430
|
export { isAiSettingsError } from "./validate";
|
|
431
|
+
export {
|
|
432
|
+
CLOUD_AI_STATS_MAX_ITEMS,
|
|
433
|
+
CLOUD_AI_TABLE_MAX_COLUMNS,
|
|
434
|
+
CLOUD_AI_TABLE_MAX_ROWS,
|
|
435
|
+
CLOUD_AI_VIEW_DIRECTORY,
|
|
436
|
+
CLOUD_AI_VIEW_MAX_BYTES,
|
|
437
|
+
CLOUD_AI_VIEW_SUFFIX,
|
|
438
|
+
type CloudAiChartView,
|
|
439
|
+
type CloudAiDetailsView,
|
|
440
|
+
type CloudAiExplorerRequest,
|
|
441
|
+
type CloudAiExplorerView,
|
|
442
|
+
type CloudAiGroupView,
|
|
443
|
+
type CloudAiStatsView,
|
|
444
|
+
type CloudAiTableView,
|
|
445
|
+
type CloudAiView,
|
|
446
|
+
type CloudAiViewReference,
|
|
447
|
+
type CloudAiViewSummary,
|
|
448
|
+
CloudAiViewSummarySchema,
|
|
449
|
+
type CloudAiViewTable,
|
|
450
|
+
checkCloudAiView,
|
|
451
|
+
cloudAiDetailsRows,
|
|
452
|
+
cloudAiExplorerControls,
|
|
453
|
+
cloudAiExplorerState,
|
|
454
|
+
cloudAiStatsTiles,
|
|
455
|
+
cloudAiTableTsv,
|
|
456
|
+
cloudAiViewSummary,
|
|
457
|
+
cloudAiViewTable,
|
|
458
|
+
isAiViewFile,
|
|
459
|
+
isAiViewPath,
|
|
460
|
+
parseCloudAiChartToolView,
|
|
461
|
+
parseCloudAiView,
|
|
462
|
+
sortCloudAiViewRows,
|
|
463
|
+
} from "./view-block";
|
|
374
464
|
export {
|
|
375
465
|
type CloudAiViewImageInput,
|
|
376
466
|
CloudAiViewImageInputSchema,
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AI_SKILL_DESCRIPTION_MAX_CHARS, AI_SKILL_INSTRUCTIONS_MAX_CHARS, AI_SKILL_NAME_PATTERN } from "./skill-format";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Contracts of the client tools a local client such as `cld` runs on the user's computer, and of the context it
|
|
6
|
+
* sends with a turn. Cloud declares and validates them; the client executes them and enforces its own path rules.
|
|
7
|
+
*/
|
|
8
|
+
export const AI_LOCAL_TOOL_NAMES = [
|
|
9
|
+
"local_list",
|
|
10
|
+
"local_read",
|
|
11
|
+
"local_upload",
|
|
12
|
+
"local_download",
|
|
13
|
+
"local_write",
|
|
14
|
+
"local_move",
|
|
15
|
+
"load_local_skill",
|
|
16
|
+
] as const;
|
|
17
|
+
export type AiLocalToolName = (typeof AI_LOCAL_TOOL_NAMES)[number];
|
|
18
|
+
|
|
19
|
+
/** Longest absolute path a client reports, Linux PATH_MAX. */
|
|
20
|
+
export const AI_LOCAL_PATH_MAX_CHARS = 4096;
|
|
21
|
+
/** Entries per local_list page by default and at most, like code_files. */
|
|
22
|
+
export const AI_LOCAL_LIST_DEFAULT_LIMIT = 100;
|
|
23
|
+
export const AI_LOCAL_LIST_MAX_LIMIT = 1000;
|
|
24
|
+
/** Bytes per local_read call by default and at most, like read_file. */
|
|
25
|
+
export const AI_LOCAL_READ_DEFAULT_BYTES = 16 * 1024;
|
|
26
|
+
export const AI_LOCAL_READ_MAX_BYTES = 64 * 1024;
|
|
27
|
+
/** Text one local_write call writes, like write_file. */
|
|
28
|
+
export const AI_LOCAL_WRITE_MAX_BYTES = 100 * 1024;
|
|
29
|
+
/** Paths per upload or move batch, like the code-mode input paths and the 64-item request limits. */
|
|
30
|
+
export const AI_LOCAL_BATCH_MAX_ITEMS = 64;
|
|
31
|
+
/** AGENTS.md text one turn carries, the Codex default. */
|
|
32
|
+
export const AI_LOCAL_INSTRUCTIONS_MAX_BYTES = 32 * 1024;
|
|
33
|
+
/** Local Skills one turn lists, like the 64-item request limits. */
|
|
34
|
+
export const AI_LOCAL_SKILLS_MAX_ITEMS = 64;
|
|
35
|
+
/** Local Skills one message selects explicitly. */
|
|
36
|
+
export const AI_LOCAL_SELECTED_SKILLS_MAX_ITEMS = 4;
|
|
37
|
+
/** Files a loaded local Skill lists, like Cloud Skill references. */
|
|
38
|
+
export const AI_LOCAL_SKILL_FILES_MAX_ITEMS = 50;
|
|
39
|
+
|
|
40
|
+
const LocalPath = z.string().min(1).max(AI_LOCAL_PATH_MAX_CHARS);
|
|
41
|
+
const ChatPath = z.string().trim().min(1).max(500);
|
|
42
|
+
const SkillName = z.string().regex(AI_SKILL_NAME_PATTERN).max(64);
|
|
43
|
+
/** One line of client-reported text: control characters and line breaks never reach the prompt. */
|
|
44
|
+
const FactLine = (max: number) =>
|
|
45
|
+
z
|
|
46
|
+
.string()
|
|
47
|
+
.max(max)
|
|
48
|
+
.transform((value) => value.replace(/[\p{Cc}\p{Zl}\p{Zp}]+/gu, " ").trim())
|
|
49
|
+
.pipe(z.string().min(1));
|
|
50
|
+
|
|
51
|
+
/** A request the client did not run: the person said no, a rule refused it, or it failed. */
|
|
52
|
+
const LocalToolProblem = z.object({
|
|
53
|
+
status: z.enum(["denied", "failed"]),
|
|
54
|
+
error: z.string().max(2000),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const CloudAiLocalListInputSchema = z.object({
|
|
58
|
+
path: LocalPath.default(".").describe("Folder to list, relative to the workspace."),
|
|
59
|
+
pattern: z
|
|
60
|
+
.string()
|
|
61
|
+
.trim()
|
|
62
|
+
.min(1)
|
|
63
|
+
.max(500)
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Glob such as **/*.pdf or 2026-09/*.{pdf,jpg}; matching ignores case. ** lists subfolders."),
|
|
66
|
+
recursive: z.boolean().default(false),
|
|
67
|
+
limit: z.number().int().min(1).max(AI_LOCAL_LIST_MAX_LIMIT).default(AI_LOCAL_LIST_DEFAULT_LIMIT),
|
|
68
|
+
after: LocalPath.optional().describe("nextAfter of the previous page."),
|
|
69
|
+
});
|
|
70
|
+
export const CloudAiLocalListOutputSchema = z.discriminatedUnion("status", [
|
|
71
|
+
z.object({
|
|
72
|
+
status: z.literal("completed"),
|
|
73
|
+
path: LocalPath,
|
|
74
|
+
entries: z
|
|
75
|
+
.array(
|
|
76
|
+
z.object({
|
|
77
|
+
path: LocalPath,
|
|
78
|
+
type: z.enum(["file", "directory", "other"]),
|
|
79
|
+
size: z.number().int().min(0),
|
|
80
|
+
modifiedAt: z.string().max(40),
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
.max(AI_LOCAL_LIST_MAX_LIMIT),
|
|
84
|
+
nextAfter: LocalPath.nullable(),
|
|
85
|
+
unreadable: z.array(LocalPath).max(AI_LOCAL_LIST_MAX_LIMIT).optional(),
|
|
86
|
+
}),
|
|
87
|
+
LocalToolProblem,
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
export const CloudAiLocalReadInputSchema = z.object({
|
|
91
|
+
path: LocalPath,
|
|
92
|
+
offset: z.number().int().min(1).default(1).describe("First line to read."),
|
|
93
|
+
maxBytes: z.number().int().min(1).max(AI_LOCAL_READ_MAX_BYTES).default(AI_LOCAL_READ_DEFAULT_BYTES),
|
|
94
|
+
});
|
|
95
|
+
// Two completed shapes share their status, so this is a plain union rather than one keyed by status.
|
|
96
|
+
export const CloudAiLocalReadOutputSchema = z.union([
|
|
97
|
+
z.object({
|
|
98
|
+
status: z.literal("completed"),
|
|
99
|
+
kind: z.literal("text"),
|
|
100
|
+
path: LocalPath,
|
|
101
|
+
/** Lines prefixed with their number and a tab; short lines can make the numbers outweigh the text. */
|
|
102
|
+
content: z.string().max(AI_LOCAL_READ_MAX_BYTES * 10),
|
|
103
|
+
firstLine: z.number().int().min(1),
|
|
104
|
+
lastLine: z.number().int().min(0),
|
|
105
|
+
totalBytes: z.number().int().min(0),
|
|
106
|
+
nextOffset: z.number().int().min(1).nullable(),
|
|
107
|
+
}),
|
|
108
|
+
z.object({
|
|
109
|
+
status: z.literal("completed"),
|
|
110
|
+
kind: z.literal("uploaded"),
|
|
111
|
+
path: LocalPath,
|
|
112
|
+
chatPath: ChatPath,
|
|
113
|
+
mediaType: z.string().max(120),
|
|
114
|
+
size: z.number().int().min(0),
|
|
115
|
+
}),
|
|
116
|
+
LocalToolProblem,
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
export const CloudAiLocalUploadInputSchema = z.object({
|
|
120
|
+
paths: z.array(LocalPath).min(1).max(AI_LOCAL_BATCH_MAX_ITEMS),
|
|
121
|
+
directory: ChatPath.optional().describe("Chat folder for all files; by default each file keeps its folder from the workspace."),
|
|
122
|
+
});
|
|
123
|
+
export const CloudAiLocalUploadOutputSchema = z.discriminatedUnion("status", [
|
|
124
|
+
z.object({
|
|
125
|
+
status: z.literal("completed"),
|
|
126
|
+
files: z
|
|
127
|
+
.array(z.object({ path: LocalPath, chatPath: ChatPath, mediaType: z.string().max(120), size: z.number().int().min(0) }))
|
|
128
|
+
.max(AI_LOCAL_BATCH_MAX_ITEMS),
|
|
129
|
+
failed: z.array(z.object({ path: LocalPath, error: z.string().max(2000) })).max(AI_LOCAL_BATCH_MAX_ITEMS),
|
|
130
|
+
}),
|
|
131
|
+
LocalToolProblem,
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
export const CloudAiLocalDownloadInputSchema = z.object({
|
|
135
|
+
path: ChatPath.describe("Chat file to save."),
|
|
136
|
+
to: LocalPath.describe("Target file, relative to the workspace."),
|
|
137
|
+
replace: z.boolean().default(false),
|
|
138
|
+
});
|
|
139
|
+
export const CloudAiLocalDownloadOutputSchema = z.discriminatedUnion("status", [
|
|
140
|
+
z.object({ status: z.literal("completed"), path: LocalPath, size: z.number().int().min(0), replaced: z.boolean() }),
|
|
141
|
+
LocalToolProblem,
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
export const CloudAiLocalWriteInputSchema = z.object({
|
|
145
|
+
path: LocalPath,
|
|
146
|
+
content: z.string().refine((value) => new TextEncoder().encode(value).byteLength <= AI_LOCAL_WRITE_MAX_BYTES, "Content exceeds 100 KiB."),
|
|
147
|
+
mode: z.enum(["create", "append", "replace"]).default("create"),
|
|
148
|
+
});
|
|
149
|
+
export const CloudAiLocalWriteOutputSchema = z.discriminatedUnion("status", [
|
|
150
|
+
z.object({
|
|
151
|
+
status: z.literal("completed"),
|
|
152
|
+
path: LocalPath,
|
|
153
|
+
mode: z.enum(["create", "append", "replace"]),
|
|
154
|
+
size: z.number().int().min(0),
|
|
155
|
+
}),
|
|
156
|
+
LocalToolProblem,
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
const LocalMove = z.object({ from: LocalPath, to: LocalPath });
|
|
160
|
+
export const CloudAiLocalMoveInputSchema = z.object({
|
|
161
|
+
moves: z.array(LocalMove).min(1).max(AI_LOCAL_BATCH_MAX_ITEMS),
|
|
162
|
+
});
|
|
163
|
+
/** A move batch reports what already moved even when it stops, so the model can name or undo it. */
|
|
164
|
+
export const CloudAiLocalMoveOutputSchema = z.object({
|
|
165
|
+
status: z.enum(["completed", "denied", "failed"]),
|
|
166
|
+
moved: z.array(LocalMove).max(AI_LOCAL_BATCH_MAX_ITEMS),
|
|
167
|
+
conflicts: z.array(z.object({ to: LocalPath, reason: z.string().max(500) })).max(AI_LOCAL_BATCH_MAX_ITEMS),
|
|
168
|
+
error: z.string().max(2000).optional(),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const CloudAiLoadLocalSkillInputSchema = z.object({ name: SkillName });
|
|
172
|
+
export const CloudAiLoadLocalSkillOutputSchema = z.discriminatedUnion("status", [
|
|
173
|
+
z.object({
|
|
174
|
+
status: z.literal("completed"),
|
|
175
|
+
name: SkillName,
|
|
176
|
+
directory: LocalPath,
|
|
177
|
+
instructions: z.string().max(AI_SKILL_INSTRUCTIONS_MAX_CHARS),
|
|
178
|
+
files: z.array(LocalPath).max(AI_LOCAL_SKILL_FILES_MAX_ITEMS),
|
|
179
|
+
}),
|
|
180
|
+
LocalToolProblem,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
/** Facts about the local client a turn comes from; Cloud renders them into the "# Local client" prompt section. */
|
|
184
|
+
export const AiLocalClientSchema = z.object({
|
|
185
|
+
kind: z.enum(["cli", "acp"]),
|
|
186
|
+
/** The editor's ACP clientInfo.name. */
|
|
187
|
+
name: FactLine(120).optional(),
|
|
188
|
+
cwd: FactLine(AI_LOCAL_PATH_MAX_CHARS),
|
|
189
|
+
os: FactLine(120),
|
|
190
|
+
shell: FactLine(120),
|
|
191
|
+
localTools: z.enum(["ask", "off"]),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/** AGENTS.md files and local Skills the client found in its workspace for one turn. */
|
|
195
|
+
export const AiLocalContextSchema = z.object({
|
|
196
|
+
instructions: z
|
|
197
|
+
.array(z.object({ path: LocalPath, content: z.string() }))
|
|
198
|
+
.max(64)
|
|
199
|
+
.refine(
|
|
200
|
+
(files) =>
|
|
201
|
+
files.reduce((bytes, file) => bytes + new TextEncoder().encode(file.content).byteLength, 0) <= AI_LOCAL_INSTRUCTIONS_MAX_BYTES,
|
|
202
|
+
"AGENTS.md content exceeds 32 KiB.",
|
|
203
|
+
)
|
|
204
|
+
.default([]),
|
|
205
|
+
skills: z
|
|
206
|
+
.array(
|
|
207
|
+
z.object({
|
|
208
|
+
name: SkillName,
|
|
209
|
+
description: z.string().trim().min(1).max(AI_SKILL_DESCRIPTION_MAX_CHARS),
|
|
210
|
+
scope: z.enum(["project", "global"]),
|
|
211
|
+
manualOnly: z.boolean().optional(),
|
|
212
|
+
}),
|
|
213
|
+
)
|
|
214
|
+
.max(AI_LOCAL_SKILLS_MAX_ITEMS)
|
|
215
|
+
.refine((skills) => new Set(skills.map((skill) => skill.name)).size === skills.length, "Local Skill names must be unique.")
|
|
216
|
+
.default([]),
|
|
217
|
+
selectedSkills: z
|
|
218
|
+
.array(
|
|
219
|
+
z.object({
|
|
220
|
+
name: SkillName,
|
|
221
|
+
directory: LocalPath,
|
|
222
|
+
instructions: z.string().trim().min(1).max(AI_SKILL_INSTRUCTIONS_MAX_CHARS),
|
|
223
|
+
}),
|
|
224
|
+
)
|
|
225
|
+
.max(AI_LOCAL_SELECTED_SKILLS_MAX_ITEMS)
|
|
226
|
+
.default([]),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export type CloudAiLocalListInput = z.infer<typeof CloudAiLocalListInputSchema>;
|
|
230
|
+
export type CloudAiLocalListOutput = z.infer<typeof CloudAiLocalListOutputSchema>;
|
|
231
|
+
export type CloudAiLocalReadInput = z.infer<typeof CloudAiLocalReadInputSchema>;
|
|
232
|
+
export type CloudAiLocalReadOutput = z.infer<typeof CloudAiLocalReadOutputSchema>;
|
|
233
|
+
export type CloudAiLocalUploadInput = z.infer<typeof CloudAiLocalUploadInputSchema>;
|
|
234
|
+
export type CloudAiLocalUploadOutput = z.infer<typeof CloudAiLocalUploadOutputSchema>;
|
|
235
|
+
export type CloudAiLocalDownloadInput = z.infer<typeof CloudAiLocalDownloadInputSchema>;
|
|
236
|
+
export type CloudAiLocalDownloadOutput = z.infer<typeof CloudAiLocalDownloadOutputSchema>;
|
|
237
|
+
export type CloudAiLocalWriteInput = z.infer<typeof CloudAiLocalWriteInputSchema>;
|
|
238
|
+
export type CloudAiLocalWriteOutput = z.infer<typeof CloudAiLocalWriteOutputSchema>;
|
|
239
|
+
export type CloudAiLocalMoveInput = z.infer<typeof CloudAiLocalMoveInputSchema>;
|
|
240
|
+
export type CloudAiLocalMoveOutput = z.infer<typeof CloudAiLocalMoveOutputSchema>;
|
|
241
|
+
export type CloudAiLoadLocalSkillInput = z.infer<typeof CloudAiLoadLocalSkillInputSchema>;
|
|
242
|
+
export type CloudAiLoadLocalSkillOutput = z.infer<typeof CloudAiLoadLocalSkillOutputSchema>;
|
|
243
|
+
export type AiLocalClient = z.infer<typeof AiLocalClientSchema>;
|
|
244
|
+
export type AiLocalContext = z.infer<typeof AiLocalContextSchema>;
|