With previous_response_id, a Responses API call sees the earlier turn, image included, so "the same sign" needs no upload. The rest of follow-up.mjs matches responses-tool.mjs:
const response = await openai.responses.create({
model: 'gpt-6-luna',
previous_response_id: process.argv[2], // response.id of 8.2.4's call
input: 'Same sign, but saffron (#e09a10) on deep purple (#5b3f99), and add the '
+ 'tagline "Books that feel like home" under the name',
tools: [{ type: 'image_generation', model: 'gpt-image-2.5-sunburst', quality: 'low',
size: '1024x1024' }],
});Output
edit | mainline: 2098 in | image tool: {"image_tokens":0,"text_tokens":321}The tool chose edit and kept the design, swapping in BookNest's saffron and purple with a legible tagline. Its usage shows no image input tokens because, as OpenAI 86 documents, cached image inputs are billed but not counted in the response: budget slightly above usage for multi-turn edits.

<!doctype html>
<style>
body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; }
canvas { display: block; max-width: 100%; }
input[type=text] { width: 300px; }
</style>
<p>Turn 2 colors: sign <input type="color" id="fg" value="#e09a10"> on <input type="color" id="bg" value="#5b3f99">
tagline <input type="text" id="tag" value="Books that feel like home"></p>
<canvas id="c" width="600" height="280"></canvas>
<script>
const ctx = document.getElementById('c').getContext('2d');
function sign(cx, cy, r, fg, bg, tagline) {
ctx.fillStyle = bg; ctx.beginPath(); ctx.arc(cx, cy, r, 0, 7); ctx.fill();
ctx.strokeStyle = fg; ctx.lineWidth = 4; ctx.stroke();
ctx.fillStyle = fg;
ctx.beginPath(); ctx.moveTo(cx - r * 0.55, cy + r * 0.12); ctx.quadraticCurveTo(cx, cy + r * 0.6, cx + r * 0.55, cy + r * 0.12);
ctx.quadraticCurveTo(cx, cy + r * 0.32, cx - r * 0.55, cy + r * 0.12); ctx.fill();
ctx.beginPath(); ctx.ellipse(cx, cy - r * 0.08, r * 0.2, r * 0.15, 0, 0, 7); ctx.fill();
ctx.beginPath(); ctx.arc(cx + r * 0.16, cy - r * 0.22, r * 0.1, 0, 7); ctx.fill();
ctx.textAlign = 'center'; ctx.font = `bold ${r * 0.22}px Georgia`; ctx.fillText('BookNest', cx, cy - r * 0.45);
if (tagline) { ctx.font = `italic ${r * 0.1}px Georgia`; ctx.fillText(tagline, cx, cy + r * 0.72, r * 1.5); }
ctx.textAlign = 'start';
}
function draw() {
const fg = document.getElementById('fg').value, bg = document.getElementById('bg').value, tag = document.getElementById('tag').value;
ctx.clearRect(0, 0, 600, 280);
ctx.fillStyle = '#263238'; ctx.font = 'bold 12px system-ui';
ctx.fillText('turn 1 (Flare)', 60, 18); ctx.fillText('turn 2 (Sunburst, edit)', 350, 18);
sign(120, 110, 80, '#1f5f8b', '#fff3e0', '');
sign(420, 110, 80, fg, bg, tag);
// The chain of response ids
ctx.font = '11px monospace'; ctx.fillStyle = '#263238';
ctx.fillText('resp_turn1', 80, 214);
ctx.fillText(`previous_response_id: "resp_turn1"`, 330, 214);
ctx.strokeStyle = '#8d6e63'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(170, 210); ctx.lineTo(320, 210); ctx.lineTo(312, 205); ctx.moveTo(320, 210); ctx.lineTo(312, 215); ctx.stroke();
ctx.font = '12px system-ui';
ctx.fillText('No upload needed: the second call sees the earlier turn, image included. Cached image inputs', 10, 244);
ctx.fillText('are billed but not counted in usage, so budget a little above what usage reports ($0.0078 here).', 10, 262);
}
document.querySelectorAll('input').forEach(i => i.addEventListener('input', draw));
draw();
</script>