Skip to content

Commit c2b99f5

Browse files
fix(openrouter): add JSON recovery for kimi-k2 truncation (#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
1 parent a15bfeb commit c2b99f5

File tree

1 file changed

+6
-21
lines changed

1 file changed

+6
-21
lines changed

web/src/llm-api/openrouter.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Agent } from 'undici'
21

32
import { PROFIT_MARGIN } from '@codebuff/common/constants/limits'
43
import { getErrorObject } from '@codebuff/common/util/error'
54
import { env } from '@codebuff/internal/env'
5+
import { Agent } from 'undici'
66

77
import {
88
consumeCreditsForMessage,
@@ -16,12 +16,11 @@ import {
1616

1717
import type { UsageData } from './helpers'
1818
import type { OpenRouterStreamChatCompletionChunk } from './type/openrouter'
19-
import type { InsertMessageBigqueryFn } from '@codebuff/common/types/contracts/bigquery'
20-
import type { Logger } from '@codebuff/common/types/contracts/logger'
2119
import type {
2220
ChatCompletionRequestBody,
23-
OpenRouterErrorMetadata,
2421
} from './types'
22+
import type { InsertMessageBigqueryFn } from '@codebuff/common/types/contracts/bigquery'
23+
import type { Logger } from '@codebuff/common/types/contracts/logger'
2524

2625
type StreamState = { responseText: string; reasoningText: string; ttftMs: number | null }
2726

@@ -545,22 +544,8 @@ async function handleLine({
545544
},
546545
'OpenRouter response matches error pattern but schema validation failed',
547546
)
548-
// Continue processing as error response
549-
return handleResponse({
550-
userId,
551-
stripeCustomerId,
552-
agentId,
553-
clientId,
554-
clientRequestId,
555-
costMode,
556-
byok,
557-
startTime,
558-
request,
559-
data: obj as OpenRouterStreamChatCompletionChunk,
560-
state,
561-
logger,
562-
insertMessage,
563-
})
547+
// handleResponse already returns { state } for error data, so skip processing
548+
return { state }
564549
}
565550

566551
// Not an error, likely a malformed chunk - log and skip
@@ -725,7 +710,7 @@ async function handleStreamChunk({
725710

726711
if (!data.choices || !Array.isArray(data.choices) || !data.choices.length) {
727712
logger.debug(
728-
{
713+
{
729714
hasChoices: 'choices' in data,
730715
choicesLength: Array.isArray(data.choices) ? data.choices.length : 'N/A',
731716
streamChunk: JSON.stringify(data).slice(0, 500),

0 commit comments

Comments
 (0)