@@ -5,19 +5,20 @@ import {
55 normalizeLowercaseStringOrEmpty ,
66 normalizeOptionalString ,
77} from "@openclaw/normalization-core/string-coerce" ;
8+ import type { ActiveMediaModel } from "../../packages/media-understanding-common/src/active-model.js" ;
9+ import {
10+ extractMediaUserText ,
11+ formatAudioTranscripts ,
12+ formatMediaUnderstandingBody ,
13+ } from "../../packages/media-understanding-common/src/format.js" ;
814import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js" ;
915import type { MsgContext } from "../auto-reply/templating.js" ;
1016import type { OpenClawConfig } from "../config/types.js" ;
1117import { logVerbose , shouldLogVerbose } from "../globals.js" ;
18+ import type { ImageContent } from "../llm/types.js" ;
1219import { renderFileContextBlock } from "../media/file-context.js" ;
1320import { extractFileContentFromSource , normalizeMimeType } from "../media/input-files.js" ;
1421import { wrapExternalContent } from "../security/external-content.js" ;
15- import type { ActiveMediaModel } from "../../packages/media-understanding-common/src/active-model.js" ;
16- import {
17- extractMediaUserText ,
18- formatAudioTranscripts ,
19- formatMediaUnderstandingBody ,
20- } from "../../packages/media-understanding-common/src/format.js" ;
2122import { resolveAttachmentKind } from "./attachments.js" ;
2223import { runWithConcurrency } from "./concurrency.js" ;
2324import { DEFAULT_ECHO_TRANSCRIPT_FORMAT , sendTranscriptEcho } from "./echo-transcript.js" ;
@@ -49,6 +50,11 @@ type ApplyMediaUnderstandingResult = {
4950 appliedFile : boolean ;
5051} ;
5152
53+ type ExtractedFileBlocks = {
54+ blocks : string [ ] ;
55+ images : ImageContent [ ] ;
56+ } ;
57+
5258const CAPABILITY_ORDER : MediaUnderstandingCapability [ ] = [ "image" , "audio" , "video" ] ;
5359const EMPTY_VOICE_NOTE_PLACEHOLDER =
5460 "[Voice note could not be transcribed because the audio attachment was too small]" ;
@@ -383,12 +389,13 @@ async function extractFileBlocks(params: {
383389 cfg : OpenClawConfig ;
384390 limits : FileExtractionLimits ;
385391 skipAttachmentIndexes ?: Set < number > ;
386- } ) : Promise < string [ ] > {
392+ } ) : Promise < ExtractedFileBlocks > {
387393 const { attachments, cache, cfg, limits, skipAttachmentIndexes } = params ;
388394 if ( ! attachments || attachments . length === 0 ) {
389- return [ ] ;
395+ return { blocks : [ ] , images : [ ] } ;
390396 }
391397 const blocks : string [ ] = [ ] ;
398+ const images : ImageContent [ ] = [ ] ;
392399 for ( const attachment of attachments ) {
393400 if ( ! attachment ) {
394401 continue ;
@@ -494,21 +501,13 @@ async function extractFileBlocks(params: {
494501 continue ;
495502 }
496503 const text = extracted ?. text ?. trim ( ) ?? "" ;
504+ if ( extracted ?. images && extracted . images . length > 0 ) {
505+ images . push ( ...extracted . images ) ;
506+ }
497507 let blockText = text ? wrapUntrustedAttachmentContent ( text ) : "" ;
498508 if ( ! blockText ) {
499509 if ( extracted ?. images && extracted . images . length > 0 ) {
500- // Forward extracted PDF page images as inline base64 data URIs so
501- // vision-capable models can read scanned documents. Mirrors the
502- // /v1/responses path behavior (openresponses-http.ts:629-630) where
503- // the same extractor output is forwarded as separate image content.
504- //
505- // Base64 data URIs are used because the chat path has no image-carrying
506- // field on ApplyMediaUnderstandingResult; embedding them in the text block
507- // ensures they reach the model without structural type changes.
508- const imageContext = extracted . images
509- . map ( ( img , i ) => `` )
510- . join ( "\n\n" ) ;
511- blockText = `[PDF content rendered to images]\n\n${ imageContext } ` ;
510+ blockText = "[PDF content rendered to images]" ;
512511 } else {
513512 blockText = "[No extractable text]" ;
514513 }
@@ -522,7 +521,7 @@ async function extractFileBlocks(params: {
522521 } ) ,
523522 ) ;
524523 }
525- return blocks ;
524+ return { blocks, images } ;
526525}
527526
528527export async function applyMediaUnderstanding ( params : {
@@ -681,13 +680,17 @@ export async function applyMediaUnderstanding(params: {
681680 )
682681 . map ( ( output ) => output . attachmentIndex ) ,
683682 ) ;
684- const fileBlocks = await extractFileBlocks ( {
683+ const extractedFiles = await extractFileBlocks ( {
685684 attachments,
686685 cache,
687686 cfg,
688687 limits : resolveFileExtractionLimits ( cfg ) ,
689688 skipAttachmentIndexes : audioAttachmentIndexes . size > 0 ? audioAttachmentIndexes : undefined ,
690689 } ) ;
690+ const fileBlocks = extractedFiles . blocks ;
691+ if ( extractedFiles . images . length > 0 ) {
692+ ctx . CurrentTurnImages = [ ...( ctx . CurrentTurnImages ?? [ ] ) , ...extractedFiles . images ] ;
693+ }
691694 if ( fileBlocks . length > 0 ) {
692695 ctx . Body = appendFileBlocks ( ctx . Body , fileBlocks ) ;
693696 }
0 commit comments