send images on the serve path too, before anyone switches to it
runOpenCodeTurnOnServe ignored params.images entirely, which is defect B4 rebuilt on the new path: the image renders in your own bubble and the model never receives it, with nothing reporting a loss. Fixed before the path is switched on for anyone rather than after. data: URIs, not file://, and that is measured — the wrong choice is accepted with a 200 and then dies inside the turn with "Anthropic Messages media must contain valid base64". The data URI round-trips and the model describes the image. Strictly better than the subprocess path here: no temp file to spill and nothing to clean up, because the bytes travel in the request. Both prompt paths carry them — an ordinary send and a mid-turn injection. Verified end to end through the chat socket with the serve engine on: a red png came back "**Red**", with deltas streaming. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -188,7 +188,10 @@ export async function runOpenCodeTurnOnServe(
|
||||
try {
|
||||
await serveJson(config, `/api/session/${live.openCodeSessionId}/prompt`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ prompt: { text: params.prompt }, delivery: 'steer' }),
|
||||
body: JSON.stringify({
|
||||
prompt: { text: params.prompt, files: promptFiles(params.images) },
|
||||
delivery: 'steer',
|
||||
}),
|
||||
cwd,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -255,7 +258,10 @@ export async function runOpenCodeTurnOnServe(
|
||||
// `delivery` is stated explicitly because it DEFAULTS to `"steer"`, which injects into a running
|
||||
// turn. For an ordinary send that is the wrong default — two quick messages would merge into one
|
||||
// turn instead of running in order. `steer` is Phase C's job, wired to the button that means it.
|
||||
body: JSON.stringify({ prompt: { text: params.prompt }, delivery: 'queue' }),
|
||||
body: JSON.stringify({
|
||||
prompt: { text: params.prompt, files: promptFiles(params.images) },
|
||||
delivery: 'queue',
|
||||
}),
|
||||
cwd,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -273,6 +279,28 @@ function retire(turn: ServeTurn, event: ChatEvent | null): void {
|
||||
|
||||
const errText = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
||||
|
||||
/**
|
||||
* Images as `prompt.files`, which this surface takes by URI.
|
||||
*
|
||||
* **`data:` URIs, not `file://`.** Measured, because the choice is not obvious and the wrong one fails
|
||||
* at the provider rather than at the API: a `file://` attachment is accepted with a 200 and then dies
|
||||
* inside the turn with `Anthropic Messages media must contain valid base64`. A `data:` URI round-trips
|
||||
* and the model describes the image.
|
||||
*
|
||||
* This is strictly better than the subprocess path, which has to spill each image to a temp file for
|
||||
* `--file` and delete it afterwards. Here the bytes go in the request and there is nothing to clean up.
|
||||
*
|
||||
* Silently dropping these is exactly defect B4 — the user sees their image in their own bubble and the
|
||||
* model never receives it — so this exists before the serve path is switched on for anyone, not after.
|
||||
*/
|
||||
function promptFiles(images: OpenCodeRunParams['images']): { uri: string; name: string }[] | undefined {
|
||||
if (!images?.length) return undefined;
|
||||
return images.map((image, index) => ({
|
||||
uri: `data:${image.mediaType || 'image/png'};base64,${image.data}`,
|
||||
name: `attachment-${index + 1}`,
|
||||
}));
|
||||
}
|
||||
|
||||
/** The serve analog of `listRunningOpenCodeTurns`. Same shape, so the Live panel needs no changes. */
|
||||
export function listRunningServeTurns(): { sessionKey: string }[] {
|
||||
return [...bySessionKey.keys()].map((sessionKey) => ({ sessionKey }));
|
||||
|
||||
Reference in New Issue
Block a user