@@ -1508,15 +1508,49 @@ export function resolveModel(
15081508 }
15091509}
15101510
1511+ // The SDK's SandboxInstance.streamPrompt/.prompt accept `string | PromptInputPart[]`
1512+ // but the published package does not re-export the PromptInputPart type by name from
1513+ // any of its entry points, so it's derived structurally off the method signature
1514+ // itself — this stays in lockstep with the SDK's actual accepted shape.
1515+ export type PromptInputPart = Extract <
1516+ Parameters < SandboxInstance [ 'streamPrompt' ] > [ 0 ] ,
1517+ readonly unknown [ ]
1518+ > [ number ]
1519+
1520+ function historyTranscript (
1521+ history : Array < { role : 'user' | 'assistant' ; content : string } > ,
1522+ ) : string {
1523+ return history
1524+ . map ( ( entry ) => `${ entry . role === 'assistant' ? 'Assistant' : 'User' } : ${ entry . content } ` )
1525+ . join ( '\n\n' )
1526+ }
1527+
15111528export function flattenHistory (
15121529 message : string ,
15131530 history ?: Array < { role : 'user' | 'assistant' ; content : string } > ,
15141531) : string {
15151532 if ( ! history ?. length ) return message
1516- const transcript = history
1517- . map ( ( entry ) => `${ entry . role === 'assistant' ? 'Assistant' : 'User' } : ${ entry . content } ` )
1518- . join ( '\n\n' )
1519- return `${ transcript } \n\nUser: ${ message } `
1533+ return `${ historyTranscript ( history ) } \n\nUser: ${ message } `
1534+ }
1535+
1536+ /**
1537+ * History-aware equivalent of flattenHistory for multimodal prompt parts: the
1538+ * transcript is folded into the first text part (image/file parts carry no
1539+ * text to prepend to) rather than replacing the message wholesale.
1540+ */
1541+ export function mergeHistoryIntoParts (
1542+ parts : PromptInputPart [ ] ,
1543+ history ?: Array < { role : 'user' | 'assistant' ; content : string } > ,
1544+ ) : PromptInputPart [ ] {
1545+ if ( ! history ?. length ) return parts
1546+ const textIndex = parts . findIndex ( ( part ) => part . type === 'text' )
1547+ if ( textIndex === - 1 ) {
1548+ throw new Error ( 'mergeHistoryIntoParts requires at least one text part to carry the history' )
1549+ }
1550+ const textPart = parts [ textIndex ] as Extract < PromptInputPart , { type : 'text' } >
1551+ const merged = [ ...parts ]
1552+ merged [ textIndex ] = { ...textPart , text : `${ historyTranscript ( history ) } \n\nUser: ${ textPart . text } ` }
1553+ return merged
15201554}
15211555
15221556export function mergeExtraMcp (
@@ -1581,7 +1615,7 @@ type StreamPromptOptions = Parameters<SandboxInstance['streamPrompt']>[1]
15811615export async function * streamSandboxPrompt (
15821616 shell : SandboxRuntimeConfig ,
15831617 box : SandboxInstance ,
1584- message : string ,
1618+ message : string | PromptInputPart [ ] ,
15851619 options ?: StreamSandboxPromptOptions ,
15861620) : AsyncGenerator < unknown > {
15871621 const harness = options ?. harness ?? 'opencode'
@@ -1595,7 +1629,10 @@ export async function* streamSandboxPrompt(
15951629 // if the UI snap was bypassed. Provider-less ids pass (session's own config).
15961630 if ( model ?. model ) assertHarnessModelCompatible ( harness , model . model )
15971631
1598- const prompt = flattenHistory ( message , options ?. history )
1632+ const prompt =
1633+ typeof message === 'string'
1634+ ? flattenHistory ( message , options ?. history )
1635+ : mergeHistoryIntoParts ( message , options ?. history )
15991636
16001637 const appToolMcp = options ?. appToolMcp ?? { }
16011638 const extraMcp = mergeExtraMcp ( appToolMcp , options ?. baseProfileMcp ?? { } , options ?. extraMcp )
@@ -1645,7 +1682,7 @@ export async function* streamSandboxPrompt(
16451682export async function runSandboxPrompt (
16461683 shell : SandboxRuntimeConfig ,
16471684 box : SandboxInstance ,
1648- message : string ,
1685+ message : string | PromptInputPart [ ] ,
16491686 options ?: StreamSandboxPromptOptions ,
16501687) : Promise < string > {
16511688 let fullText = ''
@@ -1816,7 +1853,7 @@ export async function mintSandboxScopedToken(
18161853export async function driveSandboxTurn (
18171854 shell : SandboxRuntimeConfig ,
18181855 box : SandboxInstance ,
1819- message : string ,
1856+ message : string | PromptInputPart [ ] ,
18201857 options : StreamSandboxPromptOptions & { sessionId : string } ,
18211858) : Promise < Outcome < PromptResult > > {
18221859 const harness = options . harness ?? 'opencode'
@@ -1825,7 +1862,10 @@ export async function driveSandboxTurn(
18251862 modelApiKey : options . modelApiKey ,
18261863 } )
18271864 if ( model ?. model ) assertHarnessModelCompatible ( harness , model . model )
1828- const prompt = flattenHistory ( message , options . history )
1865+ const prompt =
1866+ typeof message === 'string'
1867+ ? flattenHistory ( message , options . history )
1868+ : mergeHistoryIntoParts ( message , options . history )
18291869 const appToolMcp = options . appToolMcp ?? { }
18301870 const extraMcp = mergeExtraMcp ( appToolMcp , options . baseProfileMcp ?? { } , options . extraMcp )
18311871 const profile = attachReasoningEffort (
0 commit comments