Skip to content

Fix/issue 367 kimi k2 streaming - #477

Open
ayorindeadunse wants to merge 4 commits into
CodebuffAI:mainfrom
ayorindeadunse:fix/issue-367-kimi-k2-streaming
Open

Fix/issue 367 kimi k2 streaming#477
ayorindeadunse wants to merge 4 commits into
CodebuffAI:mainfrom
ayorindeadunse:fix/issue-367-kimi-k2-streaming

Conversation

@ayorindeadunse

Copy link
Copy Markdown
Contributor

Description

Fixes #367 - The "kimi-k2" Model breaks off after use at the beginning

Problem

The kimi-k2 model was breaking off during streaming responses due to truncated JSON chunks. When JSON is sent at newline boundaries, it can arrive incomplete, causing parsing failures and stream termination.

Solution

This PR adds defensive JSON parsing and error recovery mechanisms to gracefully handle truncated or malformed JSON responses from OpenRouter:

  1. attemptJSONParse() function - Implements multi-stage JSON recovery:

    • Attempts to parse JSON as-is
    • Removes trailing newlines from truncated responses
    • Tries closing incomplete JSON structures (}, ]}, }]}, etc.)
    • Returns null only after all recovery attempts fail
  2. Enhanced error handling - Improved logging and validation:

    • Better detection of truncated chunks vs actual errors
    • Graceful fallback when parsing fails (logs and continues)
    • Detailed debugging information for troubleshooting
  3. Import cleanup - Reorganized imports for consistency and removed unused types

Testing

  • Build succeeds with no TypeScript/type errors
  • All existing tests pass
  • No regressions detected

Related

Fixes #367

…runtime errors

- Add safety check for lineInfo.lineStartCols existence before accessing it
- Prevents 'undefined is not an object' error when OpenTUI TextBufferView doesn't populate lineStartCols
- Fixes issue where CLI would crash on startup with TypeError
- Fixed incorrect property name reference at line 804
- All references use correct lineStartCols (not lineStarts)
- Resolves TypeScript compilation errors
…reaming responses

- Add attemptJSONParse() function with 4 fallback recovery strategies for truncated JSON
  1. Try parsing as-is
  2. Try trimming trailing whitespace
  3. Try closing incomplete JSON structures ({}, ]}, }]}, [])
  4. Return null if all attempts fail

- Integrate defensive parsing into handleLine() function:
  - Replace simple JSON.parse() with attemptJSONParse() with null checks
  - Add error-like pattern detection to handle error responses with malformed schema
  - Enhanced logging showing raw response preview and recovery attempts
  - Detailed debug logs for truncated/malformed chunks

- Improve handleStreamChunk() robustness:
  - Add defensive check for null/undefined/non-array choices
  - Better logging with diagnostic information for empty choices

This fixes issue CodebuffAI#367 where kimi-k2 and similar models that send truncated JSON at newline boundaries would cause silent parsing failures and agent crashes.
Comment thread web/src/llm-api/openrouter.ts
Comment thread cli/src/components/multiline-input.tsx
Comment thread cli/src/components/multiline-input.tsx
Comment thread web/src/llm-api/openrouter.ts Outdated
…#367)

- Add attemptJSONParse() function with recovery strategies for truncated JSON
- Attempts to parse as-is, then trims whitespace, then closes incomplete structures
- Handles truncated newline boundaries gracefully
- Remove redundant error-like fallback path (handleResponse already returns {state} for errors)
- Improve logging for schema validation failures
- Fixes issue where kimi-k2 model breaks due to JSON chunk truncation
@ayorindeadunse
ayorindeadunse force-pushed the fix/issue-367-kimi-k2-streaming branch from 74d22f3 to c2b99f5 Compare March 22, 2026 21:52

hiSandog commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

One concern with the new attemptJSONParse() recovery in web/src/llm-api/openrouter.ts: appending }, ]}, etc. can turn a truncated delta frame into syntactically valid JSON, and handleStreamChunk() will append that delta to state.responseText / state.reasoningText immediately.

If the real complete SSE frame arrives on the next read, the same token span can be counted twice or with corrupted content. That feels riskier than just skipping the malformed fragment. A safer recovery path would be to keep an unfinished data: payload in a pending buffer and retry parsing on the next read, or to limit recovery to terminal usage/error frames where duplicate token emission cannot corrupt the accumulated state.

@codebuff-team

Copy link
Copy Markdown
Contributor

Thanks for digging into #367 — streaming breakage with kimi-k2 is a real and annoying bug. Unfortunately this PR can't be ported as-is for a few reasons:

  1. Out of scope: the actual fix is in web/src/llm-api/openrouter.ts. This mirror explicitly excludes changes under web/, so this diff can't be reviewed/merged here regardless of correctness.

  2. Unrelated changes bundled in: package.json/bun.lock add a geoip-lite dependency that has nothing to do with kimi-k2 streaming, and cli/src/components/multiline-input.tsx has a large block of pure reformatting (indentation/whitespace) plus an unexplained behavioral tweak to cursorRow/lineInfo.lineStartCols guards. None of this belongs in a PR titled "fix kimi-k2 streaming" — it makes the diff much harder to review and suggests a messy branch/rebase.

  3. The core approach is risky even on its own merits. attemptJSONParse() guesses at closing truncated JSON by blindly appending }, ]}, }]}, [] to the raw string. Appending brackets to a genuinely truncated fragment doesn't reliably reconstruct the original object — it can produce JSON that parses successfully but represents different data than what OpenRouter intended, and that gets silently fed into stream state. If the real problem is that JSON chunks arrive split across newline-delimited SSE boundaries, the correct fix is in the chunk-buffering/line-splitting logic that assembles fragments before parse, not in speculative JSON repair after the fact.

If you want to pursue this further, I'd suggest: (a) isolate the fix to just the streaming/parsing logic, no unrelated dependency or formatting changes, (b) find where lines are split on newlines and confirm whether partial JSON is being handed to JSON.parse before a full line is buffered — that's almost certainly the actual bug, and (c) add a regression test reproducing a truncated OpenRouter SSE chunk.

@codebuff-team codebuff-team added bot:triaged Classified by the community triage bot pr:out-of-scope Touches paths the public mirror does not accept labels Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:triaged Classified by the community triage bot pr:out-of-scope Touches paths the public mirror does not accept

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The “kimi-k2” Model breaks off after use at the beginning

4 participants