Skip to content

fix(reasoning): sanitize streamed K3 think tags - #8821

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.49from
rinseaid:codex/fix-kimi-apikey-think-tags
Jul 28, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.49from
rinseaid:codex/fix-kimi-apikey-think-tags

Conversation

@rinseaid

@rinseaid rinseaid commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Problem

K3 can stream its reasoning as literal tags in delta.content:

<think>inspect the request</think>Final answer

OpenAI-compatible clients render content as user-visible text, so the tags and chain-of-thought leak into the response. The existing non-streaming sanitizer does not run for passthrough SSE streams.

Change

  • Treat K3 as a textual-reasoning-tag route by model identity.
  • Apply the existing stateful tag parser to passthrough SSE deltas.
  • Emit the reasoning in reasoning_content and only Final answer in visible content.
  • Flush a tag fragment split across SSE chunks without losing the final text.

Regression coverage

The added tests prove both the non-streaming and streamed forms. The stream fixture splits the response as <think>reasoning followed by </think>final answer and asserts that the client receives no raw tags, reasoning as reasoning_content, and final answer as visible content.

Validation

  • node --import tsx/esm --test tests/unit/stream-utilities.test.ts
  • node --import tsx/esm --test tests/unit/responsesanitizer-reasoning-split.test.ts
  • ESLint, Prettier, and npm run typecheck:core

@rinseaid rinseaid changed the title fix(reasoning): support Kimi Code API-key streams fix(reasoning): sanitize streamed K3 think tags Jul 28, 2026
@rinseaid
rinseaid force-pushed the codex/fix-kimi-apikey-think-tags branch from c3c3b83 to 1ee0aa5 Compare July 28, 2026 02:41
open-sse/utils/stream.ts is frozen at 2887 lines with zero headroom, and the
kimi-coding-apikey think-tag handling added here pushed it to 2951. The parsing
half of that work has no SSE dependency, so it moves to thinkTagParser.ts (an
unfrozen leaf): the open-tag lookahead predicate and the end-of-stream flush
delta assembly. stream.ts keeps only the SSE envelope - enqueue, payload
collection, logging.

Getting back under the frozen ceiling needed more than just the PR's own
added lines, since even the fully self-gating helpers still cost a handful of
call-site lines stream.ts has zero room for. Along the way this also
deduplicates a synthetic chat-completion-chunk literal that was copy-pasted
three times in stream.ts (textual tool-call flush, think-tag flush, terminal
finish_reason synthesis) into one buildSyntheticChatChunk() in
streamHelpers.ts - pure DRY, no behavior change.

Behavior is unchanged: same tests, same counts.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw marked this pull request as ready for review July 28, 2026 03:05
@diegosouzapw
diegosouzapw self-requested a review as a code owner July 28, 2026 03:05
@diegosouzapw
diegosouzapw merged commit d30f484 into cheaper-inference:release/v3.8.49 Jul 28, 2026
22 of 24 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jul 28, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
* fix(reasoning): sanitize streamed K3 think tags

* refactor(stream): move think-tag helpers into thinkTagParser leaf module

open-sse/utils/stream.ts is frozen at 2887 lines with zero headroom, and the
kimi-coding-apikey think-tag handling added here pushed it to 2951. The parsing
half of that work has no SSE dependency, so it moves to thinkTagParser.ts (an
unfrozen leaf): the open-tag lookahead predicate and the end-of-stream flush
delta assembly. stream.ts keeps only the SSE envelope - enqueue, payload
collection, logging.

Getting back under the frozen ceiling needed more than just the PR's own
added lines, since even the fully self-gating helpers still cost a handful of
call-site lines stream.ts has zero room for. Along the way this also
deduplicates a synthetic chat-completion-chunk literal that was copy-pasted
three times in stream.ts (textual tool-call flush, think-tag flush, terminal
finish_reason synthesis) into one buildSyntheticChatChunk() in
streamHelpers.ts - pure DRY, no behavior change.

Behavior is unchanged: same tests, same counts.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: rinseaid <rinseaid@rinseaid.net>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
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.

2 participants