Multi-Turn Editing

Multi-Turn Editing with Previous Response IDs

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:

follow-up.mjs: a second turn on 8.2.4's sign (the request)JavaScript
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.

Turn 1 (8.2.4, Flare), turn 2 (follow-up.mjs, Sunburst, $0.0078) and compose.mjs's scene ($0.0225), all low quality
Turn 1 (8.2.4, Flare), turn 2 (follow-up.mjs, Sunburst, $0.0078) and compose.mjs's scene ($0.0225), all low quality
Multi-turn editing: previous_response_id chains turns, so "the same sign" recolors and adds a taglineHTMLLive
<!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>