@agent-native/core 0.58.1 → 0.58.2
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/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +26 -12
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +10 -2
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +29 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/attachment-adapters.d.ts.map +1 -1
- package/dist/client/chat/attachment-adapters.js +29 -4
- package/dist/client/chat/attachment-adapters.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +5 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/attachment-accept.d.ts +2 -0
- package/dist/client/composer/attachment-accept.d.ts.map +1 -1
- package/dist/client/composer/attachment-accept.js +30 -1
- package/dist/client/composer/attachment-accept.js.map +1 -1
- package/dist/data-widgets/index.d.ts +5 -5
- package/dist/file-upload/pre-upload-attachments.d.ts +2 -0
- package/dist/file-upload/pre-upload-attachments.d.ts.map +1 -1
- package/dist/file-upload/pre-upload-attachments.js +66 -9
- package/dist/file-upload/pre-upload-attachments.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +16 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +67 -5
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/prompts/framework-core-compact.d.ts.map +1 -1
- package/dist/server/prompts/framework-core-compact.js +2 -1
- package/dist/server/prompts/framework-core-compact.js.map +1 -1
- package/dist/server/prompts/framework-core.d.ts.map +1 -1
- package/dist/server/prompts/framework-core.js +1 -0
- package/dist/server/prompts/framework-core.js.map +1 -1
- package/docs/content/native-chat-ui.md +12 -0
- package/package.json +1 -1
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { getActiveFileUploadProvider, uploadFile } from "./registry.js";
|
|
2
|
-
const IMAGE_DATA_URL_RE = /^data:(image\/[^;]+);base64,(.+)$/;
|
|
3
2
|
const FILE_DATA_URL_RE = /^data:([^;]+);base64,(.+)$/;
|
|
3
|
+
const SVG_REFERENCE_SECURITY_NOTE = "SVG content may contain active markup; use this URL as a file reference unless the target app sanitizes it.";
|
|
4
|
+
function normalizeContentType(value) {
|
|
5
|
+
return value?.split(";")[0]?.trim().toLowerCase() || undefined;
|
|
6
|
+
}
|
|
7
|
+
function hasSvgFilename(name) {
|
|
8
|
+
return /\.svg$/i.test(name ?? "");
|
|
9
|
+
}
|
|
10
|
+
function isSvgAttachment(args) {
|
|
11
|
+
return (normalizeContentType(args.contentType) === "image/svg+xml" ||
|
|
12
|
+
hasSvgFilename(args.name));
|
|
13
|
+
}
|
|
14
|
+
function isSvgPayload(args) {
|
|
15
|
+
const contentType = normalizeContentType(args.contentType);
|
|
16
|
+
return (contentType === "image/svg+xml" ||
|
|
17
|
+
((contentType === undefined ||
|
|
18
|
+
contentType === "application/octet-stream") &&
|
|
19
|
+
hasSvgFilename(args.name)));
|
|
20
|
+
}
|
|
21
|
+
function markReferenceOnlySvgAttachment(att, contentType) {
|
|
22
|
+
att.type = "file";
|
|
23
|
+
att.contentType = normalizeContentType(contentType) ?? "image/svg+xml";
|
|
24
|
+
att.referenceOnly = true;
|
|
25
|
+
att.securityNote = SVG_REFERENCE_SECURITY_NOTE;
|
|
26
|
+
}
|
|
4
27
|
function escapeXmlAttr(value) {
|
|
5
28
|
return value
|
|
6
29
|
.replace(/&/g, "&")
|
|
@@ -63,13 +86,23 @@ export async function preUploadAttachments(opts) {
|
|
|
63
86
|
continue;
|
|
64
87
|
if (att.url) {
|
|
65
88
|
// Already pre-uploaded earlier in the pipeline — reuse it.
|
|
89
|
+
const isReferenceOnlySvg = isSvgAttachment(att);
|
|
90
|
+
if (isReferenceOnlySvg) {
|
|
91
|
+
markReferenceOnlySvgAttachment(att, att.contentType);
|
|
92
|
+
}
|
|
66
93
|
const entry = {
|
|
67
94
|
name: att.name,
|
|
68
95
|
url: att.url,
|
|
69
96
|
provider: att.uploadProvider || "unknown",
|
|
70
97
|
contentType: att.contentType,
|
|
98
|
+
...(isReferenceOnlySvg
|
|
99
|
+
? {
|
|
100
|
+
referenceOnly: true,
|
|
101
|
+
securityNote: SVG_REFERENCE_SECURITY_NOTE,
|
|
102
|
+
}
|
|
103
|
+
: {}),
|
|
71
104
|
};
|
|
72
|
-
if (isImage) {
|
|
105
|
+
if (isImage && !isReferenceOnlySvg) {
|
|
73
106
|
uploaded.push(entry);
|
|
74
107
|
}
|
|
75
108
|
else {
|
|
@@ -77,11 +110,15 @@ export async function preUploadAttachments(opts) {
|
|
|
77
110
|
}
|
|
78
111
|
continue;
|
|
79
112
|
}
|
|
80
|
-
const
|
|
81
|
-
const match = att.data.match(re);
|
|
113
|
+
const match = att.data.match(FILE_DATA_URL_RE);
|
|
82
114
|
if (!match)
|
|
83
115
|
continue;
|
|
84
|
-
const
|
|
116
|
+
const dataUrlMimeType = normalizeContentType(match[1]);
|
|
117
|
+
const mimeType = dataUrlMimeType || normalizeContentType(att.contentType) || match[1];
|
|
118
|
+
const uploadAsImage = isImage && !isSvgPayload({ name: att.name, contentType: mimeType });
|
|
119
|
+
const uploadAsFile = !uploadAsImage && (isImage || (includeFiles && isFile));
|
|
120
|
+
if (!uploadAsImage && !uploadAsFile)
|
|
121
|
+
continue;
|
|
85
122
|
let bytes;
|
|
86
123
|
try {
|
|
87
124
|
bytes = new Uint8Array(Buffer.from(match[2], "base64"));
|
|
@@ -97,20 +134,33 @@ export async function preUploadAttachments(opts) {
|
|
|
97
134
|
ownerEmail: opts.ownerEmail || undefined,
|
|
98
135
|
});
|
|
99
136
|
if (!result) {
|
|
100
|
-
if (
|
|
137
|
+
if (uploadAsImage)
|
|
101
138
|
providerMissing = true;
|
|
102
139
|
continue;
|
|
103
140
|
}
|
|
104
141
|
att.url = result.url;
|
|
105
142
|
att.uploadProvider = result.provider;
|
|
143
|
+
const isReferenceOnlySvg = isSvgPayload({
|
|
144
|
+
name: att.name,
|
|
145
|
+
contentType: mimeType,
|
|
146
|
+
});
|
|
147
|
+
if (isReferenceOnlySvg) {
|
|
148
|
+
markReferenceOnlySvgAttachment(att, mimeType);
|
|
149
|
+
}
|
|
106
150
|
const entry = {
|
|
107
151
|
name: att.name,
|
|
108
152
|
url: result.url,
|
|
109
153
|
provider: result.provider,
|
|
110
|
-
contentType: att.contentType,
|
|
154
|
+
contentType: isReferenceOnlySvg ? att.contentType : mimeType,
|
|
111
155
|
sizeBytes: bytes.byteLength,
|
|
156
|
+
...(isReferenceOnlySvg
|
|
157
|
+
? {
|
|
158
|
+
referenceOnly: true,
|
|
159
|
+
securityNote: SVG_REFERENCE_SECURITY_NOTE,
|
|
160
|
+
}
|
|
161
|
+
: {}),
|
|
112
162
|
};
|
|
113
|
-
if (
|
|
163
|
+
if (uploadAsImage) {
|
|
114
164
|
uploaded.push(entry);
|
|
115
165
|
}
|
|
116
166
|
else {
|
|
@@ -141,11 +191,18 @@ export async function preUploadAttachments(opts) {
|
|
|
141
191
|
`url="${escapeXmlAttr(f.url)}"`,
|
|
142
192
|
f.contentType ? `contentType="${escapeXmlAttr(f.contentType)}"` : null,
|
|
143
193
|
`provider="${escapeXmlAttr(f.provider)}"`,
|
|
194
|
+
f.referenceOnly ? `referenceOnly="true"` : null,
|
|
195
|
+
f.securityNote
|
|
196
|
+
? `securityNote="${escapeXmlAttr(f.securityNote)}"`
|
|
197
|
+
: null,
|
|
144
198
|
].filter(Boolean);
|
|
145
199
|
lines.push(`<chat-file-attachment ${attrs.join(" ")} />`);
|
|
146
200
|
}
|
|
201
|
+
const hasReferenceOnlySvg = uploadedFiles.some((file) => file.referenceOnly && isSvgAttachment(file));
|
|
147
202
|
injectedText = [
|
|
148
|
-
|
|
203
|
+
hasReferenceOnlySvg
|
|
204
|
+
? '<chat-attachments note="The user attached these files. Image attachment URLs may be used for embedding. File attachment URLs are references; SVG files are unsanitized vector source and must not be inlined as HTML or embedded in outbound content unless the target app sanitizes or stores them safely.">'
|
|
205
|
+
: '<chat-attachments note="The user attached these files. Image attachment URLs may be used for embedding in HTML, slide content, or outbound messages. File attachment URLs are references for reading or attaching in target apps.">',
|
|
149
206
|
...lines,
|
|
150
207
|
"</chat-attachments>",
|
|
151
208
|
].join("\n");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pre-upload-attachments.js","sourceRoot":"","sources":["../../src/file-upload/pre-upload-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentChatAttachment } from \"../agent/types.js\";\nimport { getActiveFileUploadProvider, uploadFile } from \"./registry.js\";\n\nexport interface PreUploadedImageAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n}\n\n/**\n * A file/non-image attachment that was successfully uploaded to a hosted URL.\n * Consumers can use the URL in place of the base64 data to avoid persisting\n * large blobs in the thread repo and SQL.\n */\nexport interface PreUploadedFileAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n sizeBytes?: number;\n}\n\nexport interface PreUploadAttachmentsResult {\n /** Same array reference. Each image attachment that was uploaded also gets a\n * `url` property attached (non-breaking; consumers that don't read it are\n * unaffected). */\n attachments: AgentChatAttachment[];\n /** Set when at least one image was uploaded. List of hosted URLs the agent\n * can embed in HTML, slide content, documents, etc. */\n uploaded: PreUploadedImageAttachment[];\n /** Uploaded non-image files (PDF, generic binary). Parallel to `uploaded`\n * but for the file/document attachment type. */\n uploadedFiles: PreUploadedFileAttachment[];\n /** True if at least one image attachment failed to upload because no\n * file-upload provider is configured. Templates use this to render a\n * \"Connect Builder.io\" suggestion. */\n providerMissing: boolean;\n /** A pre-formatted block to inject into the user message text so the agent\n * has each hosted URL inline. Null when nothing was uploaded or no provider\n * is configured. */\n injectedText: string | null;\n}\n\nconst IMAGE_DATA_URL_RE = /^data:(image\\/[^;]+);base64,(.+)$/;\nconst FILE_DATA_URL_RE = /^data:([^;]+);base64,(.+)$/;\n\nfunction escapeXmlAttr(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\n/**\n * Returns true when a file-upload provider is currently configured.\n * Used to decide whether to attempt upload-first or fall back to base64.\n */\nexport function isFileUploadProviderConfigured(): boolean {\n return getActiveFileUploadProvider() !== null;\n}\n\n/**\n * Pre-upload chat image attachments through the active file-upload provider\n * (Builder.io by default) so the agent can embed hosted URLs in HTML, slide\n * content, and outbound messages. Keeps the original base64 data URL on the\n * attachment so multimodal vision still works — only adds a hosted `url`.\n *\n * Safe to call when no provider is configured: it returns the attachments\n * untouched with `providerMissing: true` so callers can surface a connect-\n * Builder.io hint to the agent.\n */\nexport async function preUploadImageAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n}): Promise<PreUploadAttachmentsResult> {\n return preUploadAttachments({ ...opts, includeFiles: false });\n}\n\n/**\n * Pre-upload ALL chat attachments (images AND files/PDFs) through the active\n * file-upload provider. When a provider is configured, each attachment gets a\n * `url` property injected so downstream code can store/send URLs instead of\n * base64. The base64 data is kept in-memory for the current turn so vision and\n * file-reading still work; callers that persist the attachment can drop the\n * data when a URL exists.\n *\n * Falls back gracefully when no provider is configured: returns untouched\n * attachments with `providerMissing: true` for image-type failures.\n */\nexport async function preUploadAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n /** When false, only images are uploaded (legacy behaviour). Default: true */\n includeFiles?: boolean;\n}): Promise<PreUploadAttachmentsResult> {\n const list = Array.isArray(opts.attachments) ? opts.attachments : [];\n const includeFiles = opts.includeFiles !== false;\n const uploaded: PreUploadedImageAttachment[] = [];\n const uploadedFiles: PreUploadedFileAttachment[] = [];\n let providerMissing = false;\n\n if (list.length === 0) {\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing: false,\n injectedText: null,\n };\n }\n\n for (const att of list) {\n const isImage = att.type === \"image\";\n const isFile = att.type === \"file\" || att.type === \"document\";\n if (!isImage && !(includeFiles && isFile)) continue;\n if (typeof att.data !== \"string\") continue;\n\n if ((att as any).url) {\n // Already pre-uploaded earlier in the pipeline — reuse it.\n const entry = {\n name: att.name,\n url: (att as any).url as string,\n provider: ((att as any).uploadProvider as string) || \"unknown\",\n contentType: att.contentType,\n };\n if (isImage) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n continue;\n }\n\n const re = isImage ? IMAGE_DATA_URL_RE : FILE_DATA_URL_RE;\n const match = att.data.match(re);\n if (!match) continue;\n const mimeType = att.contentType || match[1];\n let bytes: Uint8Array;\n try {\n bytes = new Uint8Array(Buffer.from(match[2], \"base64\"));\n } catch {\n continue;\n }\n\n try {\n const result = await uploadFile({\n data: bytes,\n filename: att.name,\n mimeType,\n ownerEmail: opts.ownerEmail || undefined,\n });\n if (!result) {\n if (isImage) providerMissing = true;\n continue;\n }\n (att as any).url = result.url;\n (att as any).uploadProvider = result.provider;\n const entry = {\n name: att.name,\n url: result.url,\n provider: result.provider,\n contentType: att.contentType,\n sizeBytes: bytes.byteLength,\n };\n if (isImage) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n } catch (err) {\n // Real upload failure (network, API). Keep the base64 so the model\n // can still see the image/file, but don't crash the turn.\n console.warn(\n \"[agent-native] pre-upload of chat attachment failed:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n let injectedText: string | null = null;\n if (uploaded.length > 0 || uploadedFiles.length > 0) {\n const lines: string[] = [];\n for (const u of uploaded) {\n const attrs = [\n u.name ? `name=\"${escapeXmlAttr(u.name)}\"` : null,\n `url=\"${escapeXmlAttr(u.url)}\"`,\n u.contentType ? `contentType=\"${escapeXmlAttr(u.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(u.provider)}\"`,\n ].filter(Boolean);\n lines.push(`<chat-image-attachment ${attrs.join(\" \")} />`);\n }\n for (const f of uploadedFiles) {\n const attrs = [\n f.name ? `name=\"${escapeXmlAttr(f.name)}\"` : null,\n `url=\"${escapeXmlAttr(f.url)}\"`,\n f.contentType ? `contentType=\"${escapeXmlAttr(f.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(f.provider)}\"`,\n ].filter(Boolean);\n lines.push(`<chat-file-attachment ${attrs.join(\" \")} />`);\n }\n injectedText = [\n '<chat-attachments note=\"The user attached these files. They have been uploaded — use the url attribute when embedding in HTML, slide content, or any outbound message.\">',\n ...lines,\n \"</chat-attachments>\",\n ].join(\"\\n\");\n } else if (providerMissing) {\n injectedText = [\n \"<chat-image-attachment-upload-error>\",\n \"The user attached one or more images, but no file-upload provider is configured for this app.\",\n \"Tell the user to connect or reconnect Builder.io from Settings → File uploads. If `connect-builder` is available, use it to render the inline connection card. Workspaces with a custom storage provider can also use one registered via registerFileUploadProvider().\",\n \"Until that's done, you can still SEE the image, but you do NOT have a URL to embed it in HTML or share with other apps.\",\n \"</chat-image-attachment-upload-error>\",\n ].join(\"\\n\");\n }\n\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing,\n injectedText,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pre-upload-attachments.js","sourceRoot":"","sources":["../../src/file-upload/pre-upload-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentChatAttachment } from \"../agent/types.js\";\nimport { getActiveFileUploadProvider, uploadFile } from \"./registry.js\";\n\nexport interface PreUploadedImageAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n}\n\n/**\n * A file/non-image attachment that was successfully uploaded to a hosted URL.\n * Consumers can use the URL in place of the base64 data to avoid persisting\n * large blobs in the thread repo and SQL.\n */\nexport interface PreUploadedFileAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n sizeBytes?: number;\n referenceOnly?: boolean;\n securityNote?: string;\n}\n\nexport interface PreUploadAttachmentsResult {\n /** Same array reference. Each image attachment that was uploaded also gets a\n * `url` property attached (non-breaking; consumers that don't read it are\n * unaffected). */\n attachments: AgentChatAttachment[];\n /** Set when at least one image was uploaded. List of hosted URLs the agent\n * can embed in HTML, slide content, documents, etc. */\n uploaded: PreUploadedImageAttachment[];\n /** Uploaded non-image files (PDF, generic binary). Parallel to `uploaded`\n * but for the file/document attachment type. */\n uploadedFiles: PreUploadedFileAttachment[];\n /** True if at least one image attachment failed to upload because no\n * file-upload provider is configured. Templates use this to render a\n * \"Connect Builder.io\" suggestion. */\n providerMissing: boolean;\n /** A pre-formatted block to inject into the user message text so the agent\n * has each hosted URL inline. Null when nothing was uploaded or no provider\n * is configured. */\n injectedText: string | null;\n}\n\nconst FILE_DATA_URL_RE = /^data:([^;]+);base64,(.+)$/;\nconst SVG_REFERENCE_SECURITY_NOTE =\n \"SVG content may contain active markup; use this URL as a file reference unless the target app sanitizes it.\";\n\nfunction normalizeContentType(value: string | undefined): string | undefined {\n return value?.split(\";\")[0]?.trim().toLowerCase() || undefined;\n}\n\nfunction hasSvgFilename(name: string | undefined): boolean {\n return /\\.svg$/i.test(name ?? \"\");\n}\n\nfunction isSvgAttachment(args: {\n name?: string;\n contentType?: string;\n}): boolean {\n return (\n normalizeContentType(args.contentType) === \"image/svg+xml\" ||\n hasSvgFilename(args.name)\n );\n}\n\nfunction isSvgPayload(args: { name?: string; contentType?: string }): boolean {\n const contentType = normalizeContentType(args.contentType);\n return (\n contentType === \"image/svg+xml\" ||\n ((contentType === undefined ||\n contentType === \"application/octet-stream\") &&\n hasSvgFilename(args.name))\n );\n}\n\nfunction markReferenceOnlySvgAttachment(\n att: AgentChatAttachment,\n contentType: string | undefined,\n) {\n att.type = \"file\";\n att.contentType = normalizeContentType(contentType) ?? \"image/svg+xml\";\n (att as any).referenceOnly = true;\n (att as any).securityNote = SVG_REFERENCE_SECURITY_NOTE;\n}\n\nfunction escapeXmlAttr(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\n/**\n * Returns true when a file-upload provider is currently configured.\n * Used to decide whether to attempt upload-first or fall back to base64.\n */\nexport function isFileUploadProviderConfigured(): boolean {\n return getActiveFileUploadProvider() !== null;\n}\n\n/**\n * Pre-upload chat image attachments through the active file-upload provider\n * (Builder.io by default) so the agent can embed hosted URLs in HTML, slide\n * content, and outbound messages. Keeps the original base64 data URL on the\n * attachment so multimodal vision still works — only adds a hosted `url`.\n *\n * Safe to call when no provider is configured: it returns the attachments\n * untouched with `providerMissing: true` so callers can surface a connect-\n * Builder.io hint to the agent.\n */\nexport async function preUploadImageAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n}): Promise<PreUploadAttachmentsResult> {\n return preUploadAttachments({ ...opts, includeFiles: false });\n}\n\n/**\n * Pre-upload ALL chat attachments (images AND files/PDFs) through the active\n * file-upload provider. When a provider is configured, each attachment gets a\n * `url` property injected so downstream code can store/send URLs instead of\n * base64. The base64 data is kept in-memory for the current turn so vision and\n * file-reading still work; callers that persist the attachment can drop the\n * data when a URL exists.\n *\n * Falls back gracefully when no provider is configured: returns untouched\n * attachments with `providerMissing: true` for image-type failures.\n */\nexport async function preUploadAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n /** When false, only images are uploaded (legacy behaviour). Default: true */\n includeFiles?: boolean;\n}): Promise<PreUploadAttachmentsResult> {\n const list = Array.isArray(opts.attachments) ? opts.attachments : [];\n const includeFiles = opts.includeFiles !== false;\n const uploaded: PreUploadedImageAttachment[] = [];\n const uploadedFiles: PreUploadedFileAttachment[] = [];\n let providerMissing = false;\n\n if (list.length === 0) {\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing: false,\n injectedText: null,\n };\n }\n\n for (const att of list) {\n const isImage = att.type === \"image\";\n const isFile = att.type === \"file\" || att.type === \"document\";\n if (!isImage && !(includeFiles && isFile)) continue;\n if (typeof att.data !== \"string\") continue;\n\n if ((att as any).url) {\n // Already pre-uploaded earlier in the pipeline — reuse it.\n const isReferenceOnlySvg = isSvgAttachment(att);\n if (isReferenceOnlySvg) {\n markReferenceOnlySvgAttachment(att, att.contentType);\n }\n const entry = {\n name: att.name,\n url: (att as any).url as string,\n provider: ((att as any).uploadProvider as string) || \"unknown\",\n contentType: att.contentType,\n ...(isReferenceOnlySvg\n ? {\n referenceOnly: true,\n securityNote: SVG_REFERENCE_SECURITY_NOTE,\n }\n : {}),\n };\n if (isImage && !isReferenceOnlySvg) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n continue;\n }\n\n const match = att.data.match(FILE_DATA_URL_RE);\n if (!match) continue;\n const dataUrlMimeType = normalizeContentType(match[1]);\n const mimeType =\n dataUrlMimeType || normalizeContentType(att.contentType) || match[1];\n const uploadAsImage =\n isImage && !isSvgPayload({ name: att.name, contentType: mimeType });\n const uploadAsFile =\n !uploadAsImage && (isImage || (includeFiles && isFile));\n if (!uploadAsImage && !uploadAsFile) continue;\n\n let bytes: Uint8Array;\n try {\n bytes = new Uint8Array(Buffer.from(match[2], \"base64\"));\n } catch {\n continue;\n }\n\n try {\n const result = await uploadFile({\n data: bytes,\n filename: att.name,\n mimeType,\n ownerEmail: opts.ownerEmail || undefined,\n });\n if (!result) {\n if (uploadAsImage) providerMissing = true;\n continue;\n }\n (att as any).url = result.url;\n (att as any).uploadProvider = result.provider;\n const isReferenceOnlySvg = isSvgPayload({\n name: att.name,\n contentType: mimeType,\n });\n if (isReferenceOnlySvg) {\n markReferenceOnlySvgAttachment(att, mimeType);\n }\n const entry = {\n name: att.name,\n url: result.url,\n provider: result.provider,\n contentType: isReferenceOnlySvg ? att.contentType : mimeType,\n sizeBytes: bytes.byteLength,\n ...(isReferenceOnlySvg\n ? {\n referenceOnly: true,\n securityNote: SVG_REFERENCE_SECURITY_NOTE,\n }\n : {}),\n };\n if (uploadAsImage) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n } catch (err) {\n // Real upload failure (network, API). Keep the base64 so the model\n // can still see the image/file, but don't crash the turn.\n console.warn(\n \"[agent-native] pre-upload of chat attachment failed:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n let injectedText: string | null = null;\n if (uploaded.length > 0 || uploadedFiles.length > 0) {\n const lines: string[] = [];\n for (const u of uploaded) {\n const attrs = [\n u.name ? `name=\"${escapeXmlAttr(u.name)}\"` : null,\n `url=\"${escapeXmlAttr(u.url)}\"`,\n u.contentType ? `contentType=\"${escapeXmlAttr(u.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(u.provider)}\"`,\n ].filter(Boolean);\n lines.push(`<chat-image-attachment ${attrs.join(\" \")} />`);\n }\n for (const f of uploadedFiles) {\n const attrs = [\n f.name ? `name=\"${escapeXmlAttr(f.name)}\"` : null,\n `url=\"${escapeXmlAttr(f.url)}\"`,\n f.contentType ? `contentType=\"${escapeXmlAttr(f.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(f.provider)}\"`,\n f.referenceOnly ? `referenceOnly=\"true\"` : null,\n f.securityNote\n ? `securityNote=\"${escapeXmlAttr(f.securityNote)}\"`\n : null,\n ].filter(Boolean);\n lines.push(`<chat-file-attachment ${attrs.join(\" \")} />`);\n }\n const hasReferenceOnlySvg = uploadedFiles.some(\n (file) => file.referenceOnly && isSvgAttachment(file),\n );\n injectedText = [\n hasReferenceOnlySvg\n ? '<chat-attachments note=\"The user attached these files. Image attachment URLs may be used for embedding. File attachment URLs are references; SVG files are unsanitized vector source and must not be inlined as HTML or embedded in outbound content unless the target app sanitizes or stores them safely.\">'\n : '<chat-attachments note=\"The user attached these files. Image attachment URLs may be used for embedding in HTML, slide content, or outbound messages. File attachment URLs are references for reading or attaching in target apps.\">',\n ...lines,\n \"</chat-attachments>\",\n ].join(\"\\n\");\n } else if (providerMissing) {\n injectedText = [\n \"<chat-image-attachment-upload-error>\",\n \"The user attached one or more images, but no file-upload provider is configured for this app.\",\n \"Tell the user to connect or reconnect Builder.io from Settings → File uploads. If `connect-builder` is available, use it to render the inline connection card. Workspaces with a custom storage provider can also use one registered via registerFileUploadProvider().\",\n \"Until that's done, you can still SEE the image, but you do NOT have a URL to embed it in HTML or share with other apps.\",\n \"</chat-image-attachment-upload-error>\",\n ].join(\"\\n\");\n }\n\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing,\n injectedText,\n };\n}\n"]}
|
|
@@ -15,6 +15,7 @@ export declare function assembleA2AFinalResponse(events: readonly AgentChatEvent
|
|
|
15
15
|
responseText: string;
|
|
16
16
|
finalText: string;
|
|
17
17
|
};
|
|
18
|
+
declare function createDataWidgetActionEntries(): Record<string, ActionEntry>;
|
|
18
19
|
type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
|
|
19
20
|
export interface AgentChatPluginOptions {
|
|
20
21
|
/** Template-specific actions (email ops, booking ops, etc.) */
|
|
@@ -292,6 +293,8 @@ export declare const _agentChatPromptSectionsForTests: {
|
|
|
292
293
|
frameworkCore: string;
|
|
293
294
|
frameworkCoreCompact: string;
|
|
294
295
|
frameworkContextSections: Record<string, string>;
|
|
296
|
+
generateActionsPrompt: typeof generateActionsPrompt;
|
|
297
|
+
createDataWidgetActionEntries: typeof createDataWidgetActionEntries;
|
|
295
298
|
};
|
|
296
299
|
/**
|
|
297
300
|
* Pre-load the agent's context: AGENTS.md (workspace/template/runtime
|
|
@@ -317,6 +320,19 @@ export declare const _agentChatPromptSectionsForTests: {
|
|
|
317
320
|
* the bundle in dev so changes land instantly.
|
|
318
321
|
*/
|
|
319
322
|
export declare function loadResourcesForPrompt(owner: string, compact?: boolean, selfAppId?: string): Promise<string>;
|
|
323
|
+
/**
|
|
324
|
+
* Generates a system prompt section describing registered template actions.
|
|
325
|
+
* This helps the agent prefer template-specific actions over raw db-query/db-exec.
|
|
326
|
+
*
|
|
327
|
+
* Two output modes:
|
|
328
|
+
*
|
|
329
|
+
* - `"tool"` — used in production, where template actions are registered
|
|
330
|
+
* as native Anthropic tools. Output reads `name(arg*: type; ...) — desc`.
|
|
331
|
+
* - `"cli"` — used in dev, where template actions are NOT registered as
|
|
332
|
+
* native tools and must be invoked via `bash(command="pnpm action ...")`.
|
|
333
|
+
* Output reads `pnpm action name --arg <type> [--opt <type>] — desc`.
|
|
334
|
+
*/
|
|
335
|
+
declare function generateActionsPrompt(registry: Record<string, ActionEntry>, mode?: "cli" | "tool"): string;
|
|
320
336
|
export declare function shouldBlockInProductCodeEditingSurface(input: {
|
|
321
337
|
surface?: string | null;
|
|
322
338
|
userAgent?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-chat-plugin.d.ts","sourceRoot":"","sources":["../../src/server/agent-chat-plugin.ts"],"names":[],"mappings":"AAcA,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAsBtC,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAElB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,gBAAgB,EAcjB,MAAM,wBAAwB,CAAC;AA8EhC,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAC;AAIrC,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-chat-plugin.d.ts","sourceRoot":"","sources":["../../src/server/agent-chat-plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,8 @@ import { updateTaskStatusMessage } from "../a2a/task-store.js";
|
|
|
33
33
|
import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
|
|
34
34
|
import { buildRuntimeContextPrompt } from "../agent/runtime-context.js";
|
|
35
35
|
import { buildFrameworkCore, buildFrameworkCoreCompact, FIRST_SESSION_PERSONALIZATION, getModelFamilyOverlay, } from "./prompts/index.js";
|
|
36
|
+
import { ACTION_CHAT_UI_DATA_WIDGET_RENDERER } from "../action-ui.js";
|
|
37
|
+
import { dataWidgetResultSchema } from "../data-widgets/index.js";
|
|
36
38
|
// Lazy fs — loaded via dynamic import() on first use.
|
|
37
39
|
// This avoids require() which bundlers convert to createRequire(import.meta.url)
|
|
38
40
|
// that crashes on CF Workers where import.meta.url is undefined.
|
|
@@ -643,6 +645,58 @@ function createUrlTools() {
|
|
|
643
645
|
},
|
|
644
646
|
};
|
|
645
647
|
}
|
|
648
|
+
function createDataWidgetActionEntries() {
|
|
649
|
+
return {
|
|
650
|
+
"render-data-widget": {
|
|
651
|
+
readOnly: true,
|
|
652
|
+
parallelSafe: true,
|
|
653
|
+
chatUI: {
|
|
654
|
+
renderer: ACTION_CHAT_UI_DATA_WIDGET_RENDERER,
|
|
655
|
+
title: "Data widget",
|
|
656
|
+
description: "Render a validated native data table or chart in chat.",
|
|
657
|
+
},
|
|
658
|
+
tool: {
|
|
659
|
+
description: "Render a native Agent-Native chat data widget from compact, real data you already retrieved or the user provided. Use this for in-chat tables, charts, graphs, trends, and compact reports when no domain-specific action already returns a native widget. Never fabricate rows or metrics just to make a chart.",
|
|
660
|
+
parameters: {
|
|
661
|
+
type: "object",
|
|
662
|
+
properties: {
|
|
663
|
+
widget: {
|
|
664
|
+
type: "string",
|
|
665
|
+
enum: ["data-table", "data-chart", "data-insights"],
|
|
666
|
+
description: "Widget kind. Use data-chart for a chart, data-table for a table, or data-insights for a combined summary/chart/table card.",
|
|
667
|
+
},
|
|
668
|
+
widgetId: {
|
|
669
|
+
type: "string",
|
|
670
|
+
description: "Optional stable widget identifier.",
|
|
671
|
+
},
|
|
672
|
+
title: {
|
|
673
|
+
type: "string",
|
|
674
|
+
description: "Optional widget title.",
|
|
675
|
+
},
|
|
676
|
+
summary: {
|
|
677
|
+
type: "object",
|
|
678
|
+
description: "Optional scalar summary values for data-insights cards.",
|
|
679
|
+
},
|
|
680
|
+
display: {
|
|
681
|
+
type: "object",
|
|
682
|
+
description: "Optional display metadata: title, description, primaryAction.",
|
|
683
|
+
},
|
|
684
|
+
table: {
|
|
685
|
+
type: "object",
|
|
686
|
+
description: "For data-table/data-insights: { title?, columns: [{ key, label, align? }], rows, totalRows?, sampledRows?, truncated? }.",
|
|
687
|
+
},
|
|
688
|
+
chartSeries: {
|
|
689
|
+
type: "object",
|
|
690
|
+
description: "For data-chart/data-insights: { type: 'bar'|'line'|'area', title?, xKey, series: [{ key, label, color? }], data, sampled? }.",
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
required: ["widget"],
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
run: async (args) => dataWidgetResultSchema.parse(args),
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
}
|
|
646
700
|
/**
|
|
647
701
|
* Creates db-* tools (db-query, db-exec, db-patch, db-schema) as native tools.
|
|
648
702
|
* These let the agent read and write the app's own SQL database. Scoping to
|
|
@@ -1951,6 +2005,8 @@ export const _agentChatPromptSectionsForTests = (() => {
|
|
|
1951
2005
|
frameworkCore,
|
|
1952
2006
|
frameworkCoreCompact,
|
|
1953
2007
|
frameworkContextSections: FRAMEWORK_CONTEXT_SECTIONS,
|
|
2008
|
+
generateActionsPrompt,
|
|
2009
|
+
createDataWidgetActionEntries,
|
|
1954
2010
|
};
|
|
1955
2011
|
})();
|
|
1956
2012
|
/**
|
|
@@ -2137,10 +2193,13 @@ function generateActionsPrompt(registry, mode = "tool") {
|
|
|
2137
2193
|
if (!registry || Object.keys(registry).length === 0)
|
|
2138
2194
|
return "";
|
|
2139
2195
|
const actionEntries = Object.entries(registry);
|
|
2196
|
+
const nativeWidgetNote = (entry) => entry.chatUI && typeof entry.chatUI.renderer === "string"
|
|
2197
|
+
? ` Native chat widget: \`${entry.chatUI.renderer}\`.`
|
|
2198
|
+
: "";
|
|
2140
2199
|
if (mode === "tool") {
|
|
2141
2200
|
const summaryLines = actionEntries.map(([name, entry]) => {
|
|
2142
2201
|
const desc = compactPromptLine(entry.tool.description, MAX_ACTION_SUMMARY_DESCRIPTION_CHARS);
|
|
2143
|
-
return `- \`${name}\` — ${desc}`;
|
|
2202
|
+
return `- \`${name}\` — ${desc}${nativeWidgetNote(entry)}`;
|
|
2144
2203
|
});
|
|
2145
2204
|
return `\n\n## Available Actions
|
|
2146
2205
|
|
|
@@ -2154,7 +2213,7 @@ ${summaryLines.join("\n")}`;
|
|
|
2154
2213
|
const requiredFields = new Set(entry.tool.parameters?.required ?? []);
|
|
2155
2214
|
// CLI mode: emit `pnpm action <name> --required <type> [--optional <type>]`
|
|
2156
2215
|
if (!params || Object.keys(params).length === 0) {
|
|
2157
|
-
return `- \`pnpm action ${name}\` — ${desc}`;
|
|
2216
|
+
return `- \`pnpm action ${name}\` — ${desc}${nativeWidgetNote(entry)}`;
|
|
2158
2217
|
}
|
|
2159
2218
|
const entries = Object.entries(params);
|
|
2160
2219
|
// Required first (alphabetical), then optional (alphabetical)
|
|
@@ -2181,7 +2240,7 @@ ${summaryLines.join("\n")}`;
|
|
|
2181
2240
|
}
|
|
2182
2241
|
const cmd = ["pnpm action " + name, ...required, ...optional].join(" ");
|
|
2183
2242
|
const requiredNote = requiredNames.length > 0 ? ` Required: ${requiredNames.join(", ")}.` : "";
|
|
2184
|
-
return `- \`${cmd}\` — ${desc}.${requiredNote}`;
|
|
2243
|
+
return `- \`${cmd}\` — ${desc}.${requiredNote}${nativeWidgetNote(entry)}`;
|
|
2185
2244
|
});
|
|
2186
2245
|
return `\n\n## Available Actions
|
|
2187
2246
|
|
|
@@ -2749,10 +2808,13 @@ export function createAgentChatPlugin(options) {
|
|
|
2749
2808
|
workspaceFilesTool = createWorkspaceFilesTool();
|
|
2750
2809
|
}
|
|
2751
2810
|
catch { }
|
|
2752
|
-
let toolActions =
|
|
2811
|
+
let toolActions = createDataWidgetActionEntries();
|
|
2753
2812
|
try {
|
|
2754
2813
|
const { createExtensionActionEntries } = await import("../extensions/actions.js");
|
|
2755
|
-
toolActions =
|
|
2814
|
+
toolActions = {
|
|
2815
|
+
...toolActions,
|
|
2816
|
+
...createExtensionActionEntries(),
|
|
2817
|
+
};
|
|
2756
2818
|
}
|
|
2757
2819
|
catch { }
|
|
2758
2820
|
let browserSessionTools = {};
|