Skip to content

fix(translation): report truncation from response.incomplete as max_tokens/refusal - #877

Merged
SsuJojo merged 1 commit into
devfrom
fix/anthropic-translator-completeness
Oct 21, 2026
Merged

SsuJojo merged 1 commit into
devfrom
fix/anthropic-translator-completeness

Conversation

@SsuJojo

@SsuJojo SsuJojo commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

response.incomplete is a terminal event, but the Anthropic Messages translator never handled it: usage was dropped, the stream closed via the generic close path, and a truncated answer was reported as a clean end_turn — the client believes the model finished on its own.

Changes

  • codex-event-extractor.ts: surface incomplete_details.reason from response.incomplete (official values: max_output_tokens, content_filter) as incompleteReason.
  • incomplete-stop-reason.ts (new): pure mapping max_output_tokens -> max_tokens, content_filter -> refusal, unknown/missing -> null (callers keep their existing tool_use/end_turn priority).
  • codex-to-anthropic.ts: handle response.incomplete in both streaming (final message_delta stop_reason + usage) and non-streaming (response body) paths; truncation/filter wins over the tool_use/end_turn priority; reuse the shared captureUsage closure; same empty-stream guard as response.completed.
  • types: accept refusal in AnthropicMessagesResponse.stop_reason; parse incomplete_details in CodexResponseData.
  • tests: 5 unit tests for the pure mapping.

Verification

  • npx vitest run tests/unit/translation: 472/472 passed (incl. 5 new)
  • npx tsc --noEmit: clean
  • Full npm run test:unit: 2944 passed / 0 test failures (2 pre-existing collection failures in tests/unit/web/* reproduced on the clean base — they require npm run build artifacts)

Note

max_tokens -> max_output_tokens forwarding was deliberately not included: the existing test does not forward max_tokens to Codex pins that omission as intentional for the Codex backend.

…okens/refusal

response.incomplete is a terminal event, but the Anthropic translator
never handled it: usage was dropped, the stream closed via the generic
path, and a truncated answer was reported as a clean end_turn — the
client believes the model finished on its own.

- extract incomplete_details.reason from response.incomplete
  (official values: max_output_tokens, content_filter)
- map it via a dedicated pure helper (incompleteReasonToAnthropicStopReason):
  max_output_tokens -> max_tokens, content_filter -> refusal
- apply in both streaming (final message_delta) and non-streaming
  (response body) paths; truncation wins over the tool_use/end_turn priority
- add the same empty-stream guard as response.completed
- accept refusal in AnthropicMessagesResponse stop_reason type
- unit tests for the pure mapping (5 cases)
@SsuJojo
SsuJojo merged commit 0543eaa into dev Oct 21, 2026
5 checks passed
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