Skip to content

Conversation

ammar-agent
Copy link
Collaborator

Problem

When users interrupt a thinking stream and resume with Anthropic's extended thinking enabled, the API returns an error:

Expected `thinking` but found `text`

This happens because:

  1. User interrupts during thinking phase → assistant message has only reasoning, no text
  2. On resume, filterEmptyAssistantMessages() strips reasoning-only messages
  3. Anthropic API receives thinking request without prior reasoning context → error

Solution

Implement context-aware filtering that preserves reasoning-only messages when needed:

  • Preserve reasoning-only partial messages for Anthropic with thinking enabled
  • Filter them in all other cases (existing behavior)

This gives Anthropic's thinking API the context it needs while keeping other flows clean.

Implementation

filterEmptyAssistantMessages() now accepts optional context:

  • provider - AI provider name
  • thinkingLevel - Thinking setting

Filtering logic:

  • Has text/tools → Keep (unchanged)
  • Anthropic + thinking enabled + partial → Keep (NEW)
  • Otherwise → Filter (unchanged)

Testing

New integration test with 5 tricky message scenarios:

  • Reasoning-only messages
  • Empty text content
  • Reasoning + empty text
  • Multiple reasoning blocks
  • Whitespace-only text

Tests both Anthropic and OpenAI to ensure the fix doesn't break other providers.

Uses direct HistoryService manipulation (not timing-dependent interrupts) for reliability.

Results:

  • ✅ All 297 unit tests pass
  • ✅ Typecheck passes
  • 🔄 Integration tests run on both providers

Related

This builds on #170 which changed the sentinel from [INTERRUPTED] to [CONTINUE] for better model understanding.

Generated with cmux

Tests various edge cases that could cause issues on resume:
- Reasoning-only messages
- Empty text content
- Reasoning followed by empty text
- Multiple reasoning blocks
- Whitespace-only text

Uses direct HistoryService manipulation instead of timing-sensitive
interrupts. Tests both Anthropic and OpenAI providers.
Add provider and thinking level context to filterEmptyAssistantMessages()
to handle edge case where Anthropic expects reasoning content when resuming
with thinking enabled.

The fix:
- Preserve reasoning-only messages for Anthropic when:
  * Provider is Anthropic
  * Thinking level is enabled (not 'off')
  * Message is partial (being resumed)
- Filter them out in all other cases (preserves existing behavior)

This prevents the error:
  "Expected `thinking` but found `text`"

When resuming an interrupted thinking stream with Anthropic.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

export function transformModelMessages(messages: ModelMessage[], provider: string): ModelMessage[] {
// Pass 0: Coalesce consecutive parts to reduce JSON overhead from streaming (applies to all providers)
const coalesced = coalesceConsecutiveParts(messages);
// Pass 1: Split mixed content messages (applies to all providers)
const split = splitMixedContentMessages(coalesced);
// Pass 2: Provider-specific reasoning handling
let reasoningHandled: ModelMessage[];
if (provider === "openai") {
// OpenAI: Strip all reasoning parts (Anthropic's text-based reasoning is incompatible with OpenAI's format)
reasoningHandled = stripReasoningParts(split);
// Then filter out any messages that became empty after stripping
reasoningHandled = filterReasoningOnlyMessages(reasoningHandled);
} else if (provider === "anthropic") {
// Anthropic: Filter out reasoning-only messages (API rejects messages with only reasoning)
reasoningHandled = filterReasoningOnlyMessages(split);
} else {

P1 Badge Preserve reasoning-only messages through all transforms

The new filterEmptyAssistantMessages keeps reasoning‑only partials when Anthropic thinking is enabled, but those preserved entries are still unconditionally removed a few steps later in transformModelMessages. The Anthropic branch at the provider transform (filterReasoningOnlyMessages) drops every assistant message whose content consists solely of reasoning regardless of thinkingLevel. As a result, resuming a thinking stream still sends no reasoning context to Anthropic, so the API can continue to return Expected thinkingbut foundtext``. The transform needs the same context-aware logic (e.g., skip filtering partial reasoning messages when thinking is on) or the earlier change is effectively a no-op.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant