























@@ -43,6 +43,11 @@ type ArtifactQuery = {
4343agentId?: string;
4444};
454546+type ArtifactCollectionOptions = {
47+includeDownloadData?: boolean;
48+downloadArtifactId?: string;
49+};
50+4651type ResolvedArtifactSession = {
4752sessionKey: string;
4853agentId?: string;
@@ -137,19 +142,50 @@ function mimeFromDataUrl(value: string): string | undefined {
137142}
138143139144function base64FromDataUrl(value: string): string | undefined {
140-const match = /^data:[^,]*;base64,(.*)$/is.exec(value.trim());
141-return match?.[1]?.replace(/\s+/g, "");
145+const trimmed = value.trim();
146+const commaIndex = trimmed.indexOf(",");
147+if (commaIndex < 0 || trimmed.slice(0, 5).toLowerCase() !== "data:") {
148+return undefined;
149+}
150+const metadata = trimmed.slice(0, commaIndex).toLowerCase();
151+if (!metadata.includes(";base64")) {
152+return undefined;
153+}
154+return trimmed.slice(commaIndex + 1).replace(/\s+/g, "");
155+}
156+157+function isBase64Whitespace(value: string): boolean {
158+return value === " " || value === "\n" || value === "\r" || value === "\t";
142159}
143160144161function estimateBase64Size(value: string | undefined): number | undefined {
145162if (!value) {
146163return undefined;
147164}
148-try {
149-return Buffer.from(value, "base64").byteLength;
150-} catch {
165+let encodedLength = 0;
166+let padding = 0;
167+for (let index = 0; index < value.length; index += 1) {
168+const char = value[index];
169+if (!char || isBase64Whitespace(char)) {
170+continue;
171+}
172+encodedLength += 1;
173+}
174+for (let index = value.length - 1; index >= 0 && padding < 2; index -= 1) {
175+const char = value[index];
176+if (!char || isBase64Whitespace(char)) {
177+continue;
178+}
179+if (char === "=") {
180+padding += 1;
181+continue;
182+}
183+break;
184+}
185+if (encodedLength === 0) {
151186return undefined;
152187}
188+return Math.max(0, Math.floor((encodedLength * 3) / 4) - padding);
153189}
154190155191function mediaUrlValue(value: unknown): string | undefined {
@@ -213,7 +249,10 @@ function resolveMessageTaskId(message: Record<string, unknown>): string | undefi
213249);
214250}
215251216-function resolveBlockDownload(block: Record<string, unknown>): {
252+function resolveBlockDownload(
253+block: Record<string, unknown>,
254+opts: { includeData: boolean },
255+): {
217256mode: ArtifactDownloadMode;
218257data?: string;
219258url?: string;
@@ -251,7 +290,7 @@ function resolveBlockDownload(block: Record<string, unknown>): {
251290 ? Math.floor(explicitSize)
252291 : estimateBase64Size(base64);
253292if (base64) {
254-return { mode: "bytes", data: base64, mimeType, sizeBytes };
293+return { mode: "bytes", ...(opts.includeData ? { data: base64 } : {}), mimeType, sizeBytes };
255294}
256295if (remoteUrl) {
257296return { mode: "url", url: remoteUrl, mimeType, sizeBytes };
@@ -282,6 +321,8 @@ export function collectArtifactsFromMessages(params: {
282321sessionKey: string;
283322runId?: string;
284323taskId?: string;
324+includeDownloadData?: boolean;
325+downloadArtifactId?: string;
285326}): ArtifactRecord[] {
286327const artifacts: ArtifactRecord[] = [];
287328let messageFallbackSeq = 0;
@@ -299,6 +340,8 @@ function collectArtifactsFromMessage(params: {
299340sessionKey: string;
300341runId?: string;
301342taskId?: string;
343+includeDownloadData?: boolean;
344+downloadArtifactId?: string;
302345}): void {
303346const msg = asOptionalRecord(params.message);
304347if (!msg) {
@@ -326,15 +369,19 @@ function collectArtifactsFromMessage(params: {
326369asNonEmptyString(block.filename) ??
327370asNonEmptyString(block.alt) ??
328371`${type} ${params.artifacts.length + 1}`;
329-const download = resolveBlockDownload(block);
372+const id = artifactId({
373+sessionKey: params.sessionKey,
374+ messageSeq,
375+ contentIndex,
376+ title,
377+ type,
378+});
379+const includeData = params.downloadArtifactId
380+ ? params.downloadArtifactId === id
381+ : params.includeDownloadData !== false;
382+const download = resolveBlockDownload(block, { includeData });
330383const summary: ArtifactRecord = {
331-id: artifactId({
332-sessionKey: params.sessionKey,
333- messageSeq,
334- contentIndex,
335- title,
336- type,
337-}),
384+ id,
338385 type,
339386 title,
340387 ...(download.mimeType ? { mimeType: download.mimeType } : {}),
@@ -397,6 +444,7 @@ function resolveQuerySession(
397444async function loadArtifacts(
398445query: ArtifactQuery,
399446cfg?: OpenClawConfig,
447+opts: ArtifactCollectionOptions = {},
400448): Promise<{ artifacts: ArtifactRecord[]; sessionKey?: string }> {
401449const resolved = resolveQuerySession(query, cfg);
402450if (!resolved) {
@@ -425,11 +473,14 @@ async function loadArtifacts(
425473 sessionKey,
426474runId: query.runId,
427475taskId: query.taskId,
476+includeDownloadData: opts.includeDownloadData,
477+downloadArtifactId: opts.downloadArtifactId,
428478});
429479},
430480{
431481mode: "full",
432482reason: "artifact query transcript scan",
483+cache: "skip",
433484},
434485);
435486return {
@@ -456,11 +507,12 @@ function requireQueryable(params: ArtifactQuery, respond: RespondFn): boolean {
456507async function findArtifact(
457508params: ArtifactsGetParams,
458509cfg?: OpenClawConfig,
510+opts: ArtifactCollectionOptions = {},
459511): Promise<{
460512artifact?: ArtifactRecord;
461513sessionKey?: string;
462514}> {
463-const loaded = await loadArtifacts(params, cfg);
515+const loaded = await loadArtifacts(params, cfg, opts);
464516return {
465517sessionKey: loaded.sessionKey,
466518artifact: loaded.artifacts.find((artifact) => artifact.id === params.artifactId),
@@ -480,7 +532,9 @@ export const artifactsHandlers: GatewayRequestHandlers = {
480532if (!requireQueryable(params, respond)) {
481533return;
482534}
483-const { artifacts, sessionKey } = await loadArtifacts(params, context.getRuntimeConfig?.());
535+const { artifacts, sessionKey } = await loadArtifacts(params, context.getRuntimeConfig?.(), {
536+includeDownloadData: false,
537+});
484538if (!sessionKey && (params.runId || params.taskId)) {
485539respond(
486540false,
@@ -498,7 +552,9 @@ export const artifactsHandlers: GatewayRequestHandlers = {
498552if (!requireQueryable(params, respond)) {
499553return;
500554}
501-const { artifact } = await findArtifact(params, context.getRuntimeConfig?.());
555+const { artifact } = await findArtifact(params, context.getRuntimeConfig?.(), {
556+includeDownloadData: false,
557+});
502558if (!artifact) {
503559respond(
504560false,
@@ -520,7 +576,9 @@ export const artifactsHandlers: GatewayRequestHandlers = {
520576if (!requireQueryable(params, respond)) {
521577return;
522578}
523-const { artifact } = await findArtifact(params, context.getRuntimeConfig?.());
579+const { artifact } = await findArtifact(params, context.getRuntimeConfig?.(), {
580+downloadArtifactId: params.artifactId,
581+});
524582if (!artifact) {
525583respond(
526584false,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。