@k2b/cloud 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -4
- package/src/_internal/app-presentation.ts +22 -1
- package/src/_internal/capabilities.ts +2 -2
- package/src/_internal/define-app.ts +17 -5
- package/src/_internal/registry.ts +50 -6
- package/src/_internal/widget-declarations.ts +53 -0
- package/src/ai/app-skills.ts +1 -7
- package/src/ai/approval-routes.ts +40 -4
- package/src/ai/approvals.ts +68 -10
- 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 +64 -39
- 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/live-turn.browser-harness.tsx +4 -1
- package/src/ai/chat/message-actions.tsx +9 -1
- package/src/ai/chat/message-utils.ts +7 -0
- package/src/ai/chat/messages.ts +34 -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 +67 -12
- package/src/ai/chat/tool-names.ts +18 -2
- package/src/ai/chat/turn-layout.ts +8 -0
- package/src/ai/chat/turn-view.tsx +111 -32
- 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/chat/web-tools.tsx +95 -1
- package/src/ai/client/controller.ts +6 -3
- package/src/ai/client-tool-results.ts +9 -0
- package/src/ai/code-runtime-tools.ts +50 -17
- package/src/ai/code-source-contracts.ts +25 -21
- package/src/ai/code-source-tools.ts +3 -1
- package/src/ai/default-tool-contracts.ts +334 -91
- package/src/ai/default-tools.ts +87 -11
- package/src/ai/executor.ts +343 -45
- package/src/ai/fetch-file-tool.ts +41 -4
- 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/firecrawl-tools.ts +87 -4
- package/src/ai/html-pdf-tool.ts +3 -2
- package/src/ai/http.ts +37 -12
- package/src/ai/index.ts +98 -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/migrate.ts +120 -2
- 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 +28 -3
- package/src/ai/provider.ts +16 -4
- package/src/ai/request-context.ts +33 -0
- package/src/ai/routes.ts +99 -49
- package/src/ai/runtime.ts +90 -15
- 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 +91 -1
- package/src/ai/system-prompt.ts +69 -10
- package/src/ai/timeline.ts +3 -1
- package/src/ai/tool-audit.ts +11 -1
- package/src/ai/tools.ts +94 -3
- package/src/ai/types.ts +48 -0
- package/src/ai/view-block.ts +941 -0
- package/src/ai/vision-tool.ts +2 -1
- package/src/ai/web-provenance.ts +103 -0
- 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/capabilities.ts +3 -2
- package/src/api/index.ts +3 -0
- package/src/api/me.ts +12 -3
- package/src/api/widgets.ts +36 -5
- 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/CloudResourceSearch.tsx +1 -1
- package/src/browser/app-approval.ts +32 -0
- package/src/browser/choose-files.tsx +26 -13
- package/src/browser/command-shortcuts.ts +21 -6
- 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/provider-list.ts +2 -1
- package/src/browser/save-files.tsx +13 -2
- package/src/browser/spotlight-position.ts +4 -1
- package/src/browser/widgets.ts +7 -5
- package/src/capabilities/executions.ts +3 -0
- package/src/cli/account.ts +21 -2
- package/src/cli/commands.ts +24 -2
- package/src/cli/index.ts +16 -0
- package/src/cli/plugin.ts +30 -0
- package/src/contracts/app-approval.ts +120 -0
- package/src/contracts/app.ts +34 -2
- 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/registry.ts +9 -1
- package/src/contracts/shared.ts +5 -0
- package/src/contracts/widgets.ts +47 -2
- package/src/desktop/solid.tsx +6 -7
- 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 +3 -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/server/widget-request.ts +27 -0
- 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 +241 -0
- package/src/services/approvals/list.ts +120 -0
- package/src/services/approvals/migrate.ts +41 -0
- package/src/services/identity/models-only-auth.ts +25 -0
- package/src/services/notifications/email-frame.ts +16 -3
- 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 +26 -1
- package/src/shared/app-presentation.ts +11 -0
- package/src/shared/email-html.ts +46 -7
- 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/ssr/AdminLayout.tsx +4 -7
- package/src/ssr/AppBadge.tsx +4 -4
- package/src/ssr/AppLaunchpad.island.tsx +1 -1
- package/src/ssr/AppLaunchpadPanel.tsx +4 -4
- package/src/ssr/Layout.tsx +38 -14
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +17 -20
- package/src/ssr/LayoutHelp.tsx +1 -1
- package/src/ssr/LayoutRail.tsx +1 -2
- package/src/ssr/PageTrail.tsx +23 -0
- package/src/ssr/PageTrailRow.island.tsx +20 -0
- package/src/ssr/PageTrailRuntime.island.tsx +26 -0
- package/src/ssr/ProfilePreferences.island.tsx +7 -3
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/app-badges.ts +50 -6
- package/src/ssr/islands/index.ts +1 -0
- package/src/ssr/layout-runtime.ts +60 -0
- package/src/ssr/mobile-menu-history.ts +1 -1
- package/src/ssr/page-trail-runtime.ts +313 -0
- package/src/ssr/page-trail.browser-harness.tsx +265 -0
- package/src/ssr/page-trail.ts +59 -0
- package/src/ssr/platform-messages.ts +4 -0
- package/src/ssr/rail-navigation.ts +5 -3
- package/src/ssr/runtime.ts +6 -1
- package/src/styles/approvals.css +390 -0
- package/src/styles/effects.css +21 -4
- package/src/styles/global.css +39 -3
- package/src/styles/utilities-layout.css +24 -1
- package/src/styles/utilities-navigation.css +6 -18
|
@@ -3,6 +3,7 @@ import { type RequestOptions as HttpsRequestOptions, request as httpsRequest } f
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { type PublicNetworkAddress, resolvePublicNetworkAddresses } from "../services/network-security";
|
|
5
5
|
import { AI_FILES_MAX_FILE_BYTES_DEFAULT, aiFileStore, guessAiMediaType, normalizeAiFilePath } from "./files-store";
|
|
6
|
+
import { authorizeWebRead } from "./firecrawl-tools";
|
|
6
7
|
import { defineAiTool } from "./tools";
|
|
7
8
|
|
|
8
9
|
const FETCH_FILE_MAX_REDIRECTS = 5;
|
|
@@ -225,9 +226,12 @@ const responseLocation = (headers: IncomingHttpHeaders): string | null => {
|
|
|
225
226
|
return typeof value === "string" && value.trim() ? value : null;
|
|
226
227
|
};
|
|
227
228
|
|
|
229
|
+
/** How a download that a website approval let through may proceed: only on that origin, each request shown first. */
|
|
230
|
+
type WebsiteApprovedDownload = { origin: string; onRequest: (url: URL, hop: number) => Promise<void> };
|
|
231
|
+
|
|
228
232
|
export const downloadPublicFile = async (
|
|
229
233
|
rawUrl: string,
|
|
230
|
-
options: FetchFileDependencies & { maxBytes?: number; signal?: AbortSignal } = {},
|
|
234
|
+
options: FetchFileDependencies & { maxBytes?: number; signal?: AbortSignal; approvedWebsite?: WebsiteApprovedDownload } = {},
|
|
231
235
|
): Promise<{ bytes: Uint8Array; headers: IncomingHttpHeaders; url: URL }> => {
|
|
232
236
|
const resolve = options.resolve ?? resolvePublicNetworkAddresses;
|
|
233
237
|
const perform =
|
|
@@ -238,6 +242,14 @@ export const downloadPublicFile = async (
|
|
|
238
242
|
let url = publicHttpsUrl(rawUrl);
|
|
239
243
|
|
|
240
244
|
for (let redirects = 0; redirects <= FETCH_FILE_MAX_REDIRECTS; redirects++) {
|
|
245
|
+
if (options.approvedWebsite) {
|
|
246
|
+
// A website approval covers its own origin only; an open redirect must not carry the address to another one.
|
|
247
|
+
if (url.origin !== options.approvedWebsite.origin)
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Redirect to another website — The file moved to ${url.toString()}, which this chat has not allowed. Download that address directly so the person can decide.`,
|
|
250
|
+
);
|
|
251
|
+
await options.approvedWebsite.onRequest(url, redirects);
|
|
252
|
+
}
|
|
241
253
|
const addresses = await resolveFileAddresses(resolve, url.hostname);
|
|
242
254
|
const response = await perform(url, addresses, { maxBytes, signal: options.signal });
|
|
243
255
|
if (response.statusCode >= 300 && response.statusCode < 400) {
|
|
@@ -280,11 +292,15 @@ const responseMediaType = (headers: IncomingHttpHeaders, path: string): string =
|
|
|
280
292
|
|
|
281
293
|
export const runCloudAiFetchFile = async (
|
|
282
294
|
input: z.infer<typeof CloudAiFetchFileInputSchema>,
|
|
283
|
-
context: { conversationId?: string; signal?: AbortSignal },
|
|
295
|
+
context: { conversationId?: string; signal?: AbortSignal; approvedWebsite?: WebsiteApprovedDownload },
|
|
284
296
|
dependencies: FetchFileDependencies = {},
|
|
285
297
|
): Promise<z.infer<typeof CloudAiFetchFileOutputSchema>> => {
|
|
286
298
|
if (!context.conversationId) throw new Error("The fetch_file tool needs a conversation context.");
|
|
287
|
-
const downloaded = await downloadPublicFile(input.url, {
|
|
299
|
+
const downloaded = await downloadPublicFile(input.url, {
|
|
300
|
+
...dependencies,
|
|
301
|
+
signal: context.signal,
|
|
302
|
+
approvedWebsite: context.approvedWebsite,
|
|
303
|
+
});
|
|
288
304
|
const path = normalizeAiFilePath(`/imports/${safeFilename(input.filename, downloaded.url)}`);
|
|
289
305
|
if (!path) throw new Error("Could not derive a valid conversation file path.");
|
|
290
306
|
const stat = await aiFileStore.createAssistantFile({
|
|
@@ -307,7 +323,28 @@ export const createCloudAiFetchFileTool = (dependencies: FetchFileDependencies =
|
|
|
307
323
|
timeoutMs: 90_000,
|
|
308
324
|
promptHint:
|
|
309
325
|
"fetch an exact public HTTPS source file into the chat when the user provides a file link; inspect it with read_file or view_image and present it when useful.",
|
|
310
|
-
}).server((input, context) =>
|
|
326
|
+
}).server(async (input, context) => {
|
|
327
|
+
const url = publicHttpsUrl(input.url);
|
|
328
|
+
const allowedForChat = await authorizeWebRead("file", url.toString(), context);
|
|
329
|
+
return runCloudAiFetchFile(
|
|
330
|
+
input,
|
|
331
|
+
{
|
|
332
|
+
...context,
|
|
333
|
+
...(allowedForChat
|
|
334
|
+
? {
|
|
335
|
+
approvedWebsite: {
|
|
336
|
+
origin: url.origin,
|
|
337
|
+
// The first request's receipt is already shown; each redirect on the same website adds one.
|
|
338
|
+
onRequest: async (next: URL, hop: number) => {
|
|
339
|
+
if (hop > 0) await context.reportWebsiteReceipts?.([{ index: hop, method: "GET", url: next.toString() }]);
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
: {}),
|
|
344
|
+
},
|
|
345
|
+
dependencies,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
311
348
|
|
|
312
349
|
export type CloudAiFetchFileInput = z.infer<typeof CloudAiFetchFileInputSchema>;
|
|
313
350
|
export type CloudAiFetchFileOutput = z.infer<typeof CloudAiFetchFileOutputSchema>;
|
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
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { isIP } from "node:net";
|
|
2
|
-
import { truncateMiddle } from "@k2b/nessi";
|
|
2
|
+
import { type ToolContext, truncateMiddle } from "@k2b/nessi";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import type { RequestActor } from "../server";
|
|
4
5
|
import { coreSettings } from "../services";
|
|
6
|
+
import { AI_WEBSITE_APPROVAL_TOOL, aiTurnAllowsWebsiteApprovals, aiWebsiteApprovalScope, findRememberedAiToolApproval } from "./approvals";
|
|
7
|
+
import { aiConversations } from "./store";
|
|
5
8
|
import { defineAiTool } from "./tools";
|
|
9
|
+
import type { AiApprovalTarget, AiWebsiteReceipt } from "./types";
|
|
10
|
+
import { hasWebAddressProvenance, recordWebAddresses, webAddressesIn } from "./web-provenance";
|
|
6
11
|
|
|
7
12
|
export const AI_FIRECRAWL_API_KEY_SETTING_KEY = "ai.firecrawl_api_key";
|
|
8
13
|
|
|
@@ -274,13 +279,70 @@ export const createCloudAiWebSearchTool = (config: FirecrawlToolConfig = {}) =>
|
|
|
274
279
|
...result,
|
|
275
280
|
snippet: truncateSnippet(result.snippet),
|
|
276
281
|
})),
|
|
277
|
-
}).server(async (input, ctx) =>
|
|
282
|
+
}).server(async (input, ctx) => {
|
|
283
|
+
const results = await runCloudAiWebSearch(input, { ...config, signal: ctx.signal });
|
|
284
|
+
if (ctx.conversationId)
|
|
285
|
+
await recordWebAddresses(
|
|
286
|
+
ctx.conversationId,
|
|
287
|
+
results.flatMap((result) => [...webAddressesIn(result.url)]),
|
|
288
|
+
);
|
|
289
|
+
return results;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const webReadApprovalMessage = (kind: "page" | "file", url: string, locale: string | undefined) =>
|
|
293
|
+
locale?.startsWith("de")
|
|
294
|
+
? [
|
|
295
|
+
`${kind === "page" ? "Webseite lesen" : "Datei laden"}: ${url}`,
|
|
296
|
+
"Diese Adresse steht nicht in deiner Nachricht, in einem Suchergebnis oder als Link einer gelesenen Seite.",
|
|
297
|
+
"Die vollständige Adresse geht an die Website.",
|
|
298
|
+
].join("\n")
|
|
299
|
+
: [
|
|
300
|
+
`${kind === "page" ? "Read web page" : "Download file"}: ${url}`,
|
|
301
|
+
"This address is not in your message, in a search result, or linked from a page read earlier.",
|
|
302
|
+
"The full address goes to the website.",
|
|
303
|
+
].join("\n");
|
|
304
|
+
|
|
305
|
+
export type WebReadContext = Pick<ToolContext, "requestApproval"> & {
|
|
306
|
+
actor: RequestActor;
|
|
307
|
+
conversationId?: string;
|
|
308
|
+
turnId?: string;
|
|
309
|
+
locale?: string;
|
|
310
|
+
requestApprovalFor?: (message: string, target: AiApprovalTarget) => Promise<boolean>;
|
|
311
|
+
reportWebsiteReceipts?: (receipts: AiWebsiteReceipt[]) => Promise<void>;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Lets a public web read through when the chat supplied its address. A read sends its full address to the website,
|
|
316
|
+
* and an address the chat did not supply may carry private data the model put into it, so any other address asks
|
|
317
|
+
* like an HTTP request from code: with the full URL, and with the website approval of a signed-in chat. Returns
|
|
318
|
+
* whether such an approval let it through without asking; the chat then shows the receipt before the read goes out.
|
|
319
|
+
* Throws when the person declines.
|
|
320
|
+
*/
|
|
321
|
+
export const authorizeWebRead = async (kind: "page" | "file", url: string, ctx: WebReadContext): Promise<boolean> => {
|
|
322
|
+
if (ctx.conversationId && (await hasWebAddressProvenance(ctx.conversationId, url))) return false;
|
|
323
|
+
const target = { toolName: AI_WEBSITE_APPROVAL_TOOL, approvalScope: aiWebsiteApprovalScope(new URL(url).origin), always: false };
|
|
324
|
+
const runConfig = ctx.conversationId
|
|
325
|
+
? await aiConversations.getTurnRunConfig({ conversationId: ctx.conversationId, turnId: ctx.turnId ?? "" }).catch(() => null)
|
|
326
|
+
: null;
|
|
327
|
+
const actorUserId = ctx.actor.kind === "user" ? ctx.actor.user.id : (ctx.actor.delegatedUser?.id ?? "");
|
|
328
|
+
if (
|
|
329
|
+
aiTurnAllowsWebsiteApprovals(runConfig) &&
|
|
330
|
+
(await findRememberedAiToolApproval({ actorUserId }, { ...target, conversationId: ctx.conversationId, chatOnly: true })) !== null
|
|
331
|
+
) {
|
|
332
|
+
await ctx.reportWebsiteReceipts?.([{ index: 0, method: "GET", url }]);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
const message = webReadApprovalMessage(kind, url, ctx.locale);
|
|
336
|
+
const approved = ctx.requestApprovalFor ? await ctx.requestApprovalFor(message, target) : await ctx.requestApproval(message);
|
|
337
|
+
if (!approved) throw new Error("The person did not allow reading this address. Use an address from their message or a search result.");
|
|
338
|
+
return false;
|
|
339
|
+
};
|
|
278
340
|
|
|
279
341
|
export const createCloudAiWebExtractTool = (config: FirecrawlToolConfig = {}) =>
|
|
280
342
|
defineAiTool({
|
|
281
343
|
name: "web_extract",
|
|
282
344
|
description:
|
|
283
|
-
"Read one web page by URL and return clean Markdown. Inspect the relevant pages needed to support the answer; for research or comparison, prefer primary sources and read more than one useful source when warranted.",
|
|
345
|
+
"Read one web page by URL and return clean Markdown. Inspect the relevant pages needed to support the answer; for research or comparison, prefer primary sources and read more than one useful source when warranted. Use addresses exactly as the user wrote them, as web_search returned them, or as a page you already read links them; any other address asks the user first.",
|
|
284
346
|
inputSchema: CloudAiWebExtractInputSchema,
|
|
285
347
|
outputSchema: CloudAiWebExtractOutputSchema,
|
|
286
348
|
approval: "never",
|
|
@@ -297,7 +359,28 @@ export const createCloudAiWebExtractTool = (config: FirecrawlToolConfig = {}) =>
|
|
|
297
359
|
truncated: output.truncated || content !== output.content,
|
|
298
360
|
};
|
|
299
361
|
},
|
|
300
|
-
}).server(async (input, ctx) =>
|
|
362
|
+
}).server(async (input, ctx) => {
|
|
363
|
+
const url = assertPublicHttpUrl(input.url);
|
|
364
|
+
const allowedForChat = await authorizeWebRead("page", url, ctx);
|
|
365
|
+
const output = await runCloudAiWebExtract({ url }, { ...config, signal: ctx.signal });
|
|
366
|
+
const final = URL.canParse(output.url) ? new URL(output.url) : null;
|
|
367
|
+
if (allowedForChat && final && final.href !== url) {
|
|
368
|
+
// Firecrawl follows redirects on its side. The chat shows where the read ended, and a website approval covers
|
|
369
|
+
// only content from its own origin.
|
|
370
|
+
await ctx.reportWebsiteReceipts?.([{ index: 1, method: "GET", url: final.href }]);
|
|
371
|
+
if (final.origin !== new URL(url).origin)
|
|
372
|
+
throw new Error(
|
|
373
|
+
`The page redirected to ${final.href}, which this chat has not allowed. Read that address directly so the person can decide.`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
// The page and every address it links to may be read again without asking.
|
|
377
|
+
if (ctx.conversationId)
|
|
378
|
+
await recordWebAddresses(
|
|
379
|
+
ctx.conversationId,
|
|
380
|
+
new Set([...webAddressesIn(url), ...webAddressesIn(output.url), ...webAddressesIn(output.content)]),
|
|
381
|
+
);
|
|
382
|
+
return output;
|
|
383
|
+
});
|
|
301
384
|
|
|
302
385
|
export type CloudAiWebSearchInput = z.infer<typeof CloudAiWebSearchInputSchema>;
|
|
303
386
|
export type CloudAiWebSearchOutput = z.infer<typeof CloudAiWebSearchOutputSchema>;
|
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
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
export { type AiAppSkillIssue, aiAppSkills, appSkillsRegistryState, reconcileAppSkills } from "./app-skill-store";
|
|
2
2
|
export { type AiApprovalPreferenceRoutes, type AiApprovalPreferenceView, createAiApprovalPreferenceRoutes } from "./approval-routes";
|
|
3
3
|
export {
|
|
4
|
+
AI_WEBSITE_APPROVAL_TOOL,
|
|
4
5
|
type AiToolApprovalContext,
|
|
5
6
|
type AiToolApprovalPreference,
|
|
7
|
+
type AiToolApprovalRemember,
|
|
6
8
|
aiToolAllowsAlways,
|
|
7
9
|
aiToolApprovalScope,
|
|
8
10
|
aiToolNeedsApproval,
|
|
11
|
+
aiTurnAllowsWebsiteApprovals,
|
|
12
|
+
aiWebsiteApprovalScope,
|
|
13
|
+
findRememberedAiToolApproval,
|
|
9
14
|
forgetAiToolApproval,
|
|
10
15
|
hasRememberedAiToolApproval,
|
|
11
16
|
listAiToolApprovalPreferences,
|
|
17
|
+
parseAiWebsiteApprovalScope,
|
|
12
18
|
rememberAiToolApproval,
|
|
13
19
|
revokeAiToolApprovalPreference,
|
|
14
20
|
} from "./approvals";
|
|
@@ -50,7 +56,9 @@ export { listAiCredentialProfileIds } from "./credentials";
|
|
|
50
56
|
export {
|
|
51
57
|
CLOUD_AI_CHART_MAX_SERIES,
|
|
52
58
|
CLOUD_AI_CHART_MAX_VALUES,
|
|
59
|
+
CLOUD_AI_GROUP_MAX_CHARTS,
|
|
53
60
|
CLOUD_AI_TEXT_EDITOR_MAX_CHARS,
|
|
61
|
+
CLOUD_AI_VIEW_MAX_MARKS,
|
|
54
62
|
type CloudAiCardInput,
|
|
55
63
|
CloudAiCardInputSchema,
|
|
56
64
|
type CloudAiCardOutput,
|
|
@@ -59,10 +67,13 @@ export {
|
|
|
59
67
|
CloudAiChartInputSchema,
|
|
60
68
|
type CloudAiChartOutput,
|
|
61
69
|
CloudAiChartOutputSchema,
|
|
70
|
+
type CloudAiDateFormat,
|
|
71
|
+
type CloudAiFormat,
|
|
62
72
|
type CloudAiLocalBashInput,
|
|
63
73
|
CloudAiLocalBashInputSchema,
|
|
64
74
|
type CloudAiLocalBashOutput,
|
|
65
75
|
CloudAiLocalBashOutputSchema,
|
|
76
|
+
type CloudAiNumberFormat,
|
|
66
77
|
type CloudAiSurveyInput,
|
|
67
78
|
CloudAiSurveyInputSchema,
|
|
68
79
|
type CloudAiSurveyOutput,
|
|
@@ -71,6 +82,10 @@ export {
|
|
|
71
82
|
CloudAiTextEditorInputSchema,
|
|
72
83
|
type CloudAiTextEditorOutput,
|
|
73
84
|
CloudAiTextEditorOutputSchema,
|
|
85
|
+
cloudAiAppChartSchema,
|
|
86
|
+
cloudAiChartSchema,
|
|
87
|
+
cloudAiFormatSchema,
|
|
88
|
+
cloudAiNumberFormatSchema,
|
|
74
89
|
parseCloudAiChartInput,
|
|
75
90
|
} from "./default-tool-contracts";
|
|
76
91
|
export {
|
|
@@ -93,7 +108,9 @@ export {
|
|
|
93
108
|
type AiConversationFileOverview,
|
|
94
109
|
type AiConversationWorkingGroup,
|
|
95
110
|
type AiFileStat,
|
|
111
|
+
AiViewInvalidError,
|
|
96
112
|
createAiConversationArtifact,
|
|
113
|
+
createAiConversationView,
|
|
97
114
|
guessAiMediaType,
|
|
98
115
|
isAiWorkingFilePath,
|
|
99
116
|
listAiConversationFiles,
|
|
@@ -148,6 +165,50 @@ export {
|
|
|
148
165
|
AiInvalidationDomainSchema,
|
|
149
166
|
AiInvalidationSchema,
|
|
150
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";
|
|
151
212
|
export { AI_ENRICH_CRON_SETTING_KEY, AI_MEMORY_LEARNING_CRON_SETTING_KEY, aiMaintenanceJobs } from "./maintenance";
|
|
152
213
|
export {
|
|
153
214
|
AI_MEMORY_CONTENT_MAX_CHARS,
|
|
@@ -251,6 +312,8 @@ export {
|
|
|
251
312
|
type AiSkillDocument,
|
|
252
313
|
type AiSkillExtraFrontmatter,
|
|
253
314
|
type AiSkillReferenceInput,
|
|
315
|
+
isAiSkillManualOnly,
|
|
316
|
+
mountAiSkillInstructionLinks,
|
|
254
317
|
parseAiSkillMarkdown,
|
|
255
318
|
serializeAiSkillMarkdown,
|
|
256
319
|
validateAiSkillDescription,
|
|
@@ -309,6 +372,7 @@ export {
|
|
|
309
372
|
} from "./transcription";
|
|
310
373
|
export type {
|
|
311
374
|
AiAccessResult,
|
|
375
|
+
AiApprovalTarget,
|
|
312
376
|
AiCapabilityToolPresentation,
|
|
313
377
|
AiClientToolId,
|
|
314
378
|
AiConversation,
|
|
@@ -360,9 +424,43 @@ export type {
|
|
|
360
424
|
AiTurnStatus,
|
|
361
425
|
AiTurnToolSource,
|
|
362
426
|
AiUserContentPart,
|
|
427
|
+
AiWebsiteReceipt,
|
|
363
428
|
} from "./types";
|
|
364
429
|
export { AI_MESSAGE_FEEDBACK_REASONS, isAiImageMediaType } from "./types";
|
|
365
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";
|
|
366
464
|
export {
|
|
367
465
|
type CloudAiViewImageInput,
|
|
368
466
|
CloudAiViewImageInputSchema,
|