Skip to content

Issue with regenerate() — transport drops trigger, server doesn't delete stale messages #1012

@alexander-zuev

Description

@alexander-zuev

Bug

regenerate() from useAgentChat fails with Anthropic models. The AI SDK correctly truncates messages client-side (removes last assistant message), but two issues in @cloudflare/ai-chat prevent it from working:

  1. ws-chat-transport.ts drops the trigger field — AI SDK passes trigger: 'regenerate-message' to the transport, but the transport never includes it in the WebSocket body
  2. persistMessages only UPSERTs, never DELETEs — when the client sends a truncated array, the old assistant message row stays in SQLite

Result: this.messages on the server still ends with the stale assistant message → Anthropic rejects with 400:

AI_APICallError: This model does not support assistant message prefill.
The conversation must end with a user message.

statusCode: 400
requestBodyValues.messages: [{ role: 'user', ... }, { role: 'assistant', ... }]
                                                      ^^^^^^^^^^^^^^^^^^^^^^^^
                                                      stale — should have been deleted

Steps to reproduce

Repro repo: https://github.com/alexander-zuev/ai-chat-regenerate-bug-repro

pnpm install
npx wrangler dev
  1. Send a message, wait for assistant response
  2. Click "Regenerate Last"
  3. Fails with the error above

The repro includes a side-by-side comparison: stock SDK (broken) vs a patched persistMessages that deletes stale rows (works).

Root cause

Transport (ws-chat-transport.ts):

// trigger is on options but never included in the body
const bodyPayload = JSON.stringify({
  messages: options.messages,
  ...extraBody,  // trigger is NOT here
});

Server (persistMessages in index.ts):

// UPSERT only — old rows never deleted
this.sql`
  insert into cf_ai_chat_agent_messages (id, message)
  values (${safe.id}, ${json})
  on conflict(id) do update set message = excluded.message
`;
// Then reloads ALL rows from SQLite, including stale ones
const persisted = this._loadMessagesFromDb();
this.messages = autoTransformMessages(persisted);

Suggested fix

  1. ws-chat-transport.ts — include trigger in the body:
const bodyPayload = JSON.stringify({
  messages: options.messages,
  trigger: options.trigger,
  ...extraBody,
});
  1. Server handler — when trigger === 'regenerate-message', delete DB rows not in the incoming array:
const incomingIds = new Set(mergedMessages.map(m => m.id));
const allDbIds = this.sql`SELECT id FROM cf_ai_chat_agent_messages`.toArray();
for (const row of allDbIds) {
  if (!incomingIds.has(row.id)) {
    this.sql`DELETE FROM cf_ai_chat_agent_messages WHERE id = ${row.id}`;
  }
}

Versions

  • @cloudflare/ai-chat: 0.1.5
  • agents: 0.6.0
  • ai: 6.0.104

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions