























@@ -1,4 +1,5 @@
11import crypto from "node:crypto";
2+import type { ImageContent } from "@earendil-works/pi-ai";
23import {
34hasOutboundReplyContent,
45resolveSendableOutboundReplyParts,
@@ -94,6 +95,7 @@ import {
9495resolveQueuedReplyRuntimeConfig,
9596resolveModelFallbackOptions,
9697} from "./agent-runner-utils.js";
98+import { resolveAgentTurnAttachments } from "./agent-turn-attachments.js";
9799import { type BlockReplyPipeline } from "./block-reply-pipeline.js";
98100import { resolveOriginMessageProvider } from "./origin-routing.js";
99101import {
@@ -166,6 +168,80 @@ const FALLBACK_SELECTION_STATE_KEYS = [
166168"authProfileOverrideCompactionCount",
167169] as const satisfies ReadonlyArray<keyof FallbackSelectionState>;
168170171+function countCurrentPiImageAttachmentCandidates(ctx: TemplateContext): number {
172+const pathsFromArray = Array.isArray(ctx.MediaPaths) ? ctx.MediaPaths : undefined;
173+const paths =
174+pathsFromArray && pathsFromArray.length > 0
175+ ? pathsFromArray
176+ : normalizeOptionalString(ctx.MediaPath)
177+ ? [ctx.MediaPath]
178+ : [];
179+if (paths.length === 0) {
180+return 0;
181+}
182+const types =
183+Array.isArray(ctx.MediaTypes) && ctx.MediaTypes.length === paths.length
184+ ? ctx.MediaTypes
185+ : undefined;
186+let count = 0;
187+for (const [index, pathValue] of paths.entries()) {
188+const mediaPath = normalizeOptionalString(pathValue);
189+const mediaType = normalizeOptionalString(types?.[index] ?? ctx.MediaType);
190+if (mediaPath && mediaType?.startsWith("image/")) {
191+count++;
192+}
193+}
194+return count;
195+}
196+197+async function resolveNativePiTurnImages(params: {
198+ctx: TemplateContext;
199+cfg: OpenClawConfig;
200+images?: ImageContent[];
201+imageOrder?: GetReplyOptions["imageOrder"];
202+}): Promise<{
203+images?: ImageContent[];
204+imageOrder?: GetReplyOptions["imageOrder"];
205+}> {
206+if (Array.isArray(params.images) && params.images.length > 0) {
207+return { images: params.images, imageOrder: params.imageOrder };
208+}
209+210+const currentImageCandidateCount = countCurrentPiImageAttachmentCandidates(params.ctx);
211+if (currentImageCandidateCount === 0) {
212+return { images: params.images, imageOrder: params.imageOrder };
213+}
214+215+try {
216+const resolved = await resolveAgentTurnAttachments({
217+ctx: params.ctx,
218+cfg: params.cfg,
219+includeRecentHistoryImages: false,
220+});
221+const images = resolved.attachments.map(
222+(attachment): ImageContent => ({
223+type: "image",
224+data: attachment.data,
225+mimeType: attachment.mediaType,
226+}),
227+);
228+if (images.length < currentImageCandidateCount) {
229+logVerbose(
230+`agent-runner: native PI media resolution produced ${images.length}/${currentImageCandidateCount} current image attachment(s); falling back to prompt image refs`,
231+);
232+return { images: params.images, imageOrder: params.imageOrder };
233+}
234+return images.length > 0
235+ ? { images, imageOrder: images.map(() => "inline" as const) }
236+ : { images: params.images, imageOrder: params.imageOrder };
237+} catch (error) {
238+logVerbose(
239+`agent-runner: media attachment image resolution failed, proceeding without native images: ${formatErrorMessage(error)}`,
240+);
241+return { images: params.images, imageOrder: params.imageOrder };
242+}
243+}
244+169245function setFallbackSelectionStateField(
170246entry: SessionEntry,
171247key: keyof FallbackSelectionState,
@@ -1176,6 +1252,12 @@ export async function runAgentTurnWithFallback(params: {
11761252requesterSenderUsername: params.followupRun.run.senderUsername,
11771253requesterSenderE164: params.followupRun.run.senderE164,
11781254});
1255+const nativePiTurnImages = await resolveNativePiTurnImages({
1256+ctx: params.sessionCtx,
1257+cfg: runtimeConfig,
1258+images: params.opts?.images,
1259+imageOrder: params.opts?.imageOrder,
1260+});
11791261let didNotifyAgentRunStart = false;
11801262const notifyAgentRunStart = () => {
11811263if (didNotifyAgentRunStart) {
@@ -1807,8 +1889,8 @@ export async function runAgentTurnWithFallback(params: {
18071889forceHeartbeatTool: params.opts?.forceHeartbeatTool,
18081890bootstrapContextMode: params.opts?.bootstrapContextMode,
18091891bootstrapContextRunKind: params.opts?.isHeartbeat ? "heartbeat" : "default",
1810-images: params.opts?.images,
1811-imageOrder: params.opts?.imageOrder,
1892+images: nativePiTurnImages.images,
1893+imageOrder: nativePiTurnImages.imageOrder,
18121894abortSignal: params.replyOperation?.abortSignal ?? params.opts?.abortSignal,
18131895replyOperation: params.replyOperation,
18141896blockReplyBreak: params.resolvedBlockStreamingBreak,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。