Skip to content

Kimi K2.5 returns undefined finishReason after tool calls, breaking agentic loops #420

@konard

Description

@konard

Description

When using Kimi K2.5 model via OpenRouter (or OpenRouter-compatible APIs like OpenCode), the finishReason field is undefined in the finish-step event, even when the model made tool calls. This causes agentic applications to incorrectly determine the conversation has ended.

Steps to Reproduce

  1. Configure the OpenRouter provider with Kimi K2.5 model (moonshotai/kimi-k2.5)
  2. Send a request that triggers tool calls
  3. Observe the finish-step event

Expected Behavior

The finishReason should be "tool-calls" when the model response includes tool calls:

```json
{
"type": "finish-step",
"finishReason": "tool-calls"
}
```

Or "stop" when the response is complete text.

Actual Behavior

The finishReason is undefined:

```json
{
"type": "step_finish",
"part": {
"reason": "unknown",
"tokens": { "input": 0, "output": 0, "reasoning": 0 },
"model": {
"providerID": "opencode",
"requestedModelID": "kimi-k2.5-free",
"respondedModelID": "kimi-k2.5"
}
}
}
```

Impact

This breaks agentic loops that depend on finishReason to determine whether to continue. For example:

```typescript
// In an agentic loop
if (lastAssistant?.finish && lastAssistant.finish !== 'tool-calls') {
break; // Exit loop
}
```

When finishReason is undefined (converted to "unknown"), the loop incorrectly exits even though tool calls were made and the agent should continue working.

Environment

  • @openrouter/ai-sdk-provider version: 1.5.4
  • Model: moonshotai/kimi-k2.5 (also affects opencode/kimi-k2.5-free)
  • Using streaming mode with tool calls

Possible Causes

  1. Kimi K2.5 API might not populate finish_reason in SSE chunks
  2. The SDK might not be extracting it from the correct location in the response

Suggested Fix

Similar to issue #419, the SDK could infer finishReason when it's undefined:

```typescript
// In flush() or finish-step handling
if (finishReason === undefined) {
// If we had tool calls, finish reason should be tool-calls
if (hasToolCalls) {
finishReason = 'tool-calls';
}
// If we have usage data, assume it completed successfully
else if (openrouterUsage?.totalTokens > 0) {
finishReason = 'stop';
}
}
```

Related Issues

Workaround

Applications can work around this by treating undefined/"unknown" finish reasons as incomplete when tool calls exist:

```typescript
const isComplete = finishReason !== undefined &&
finishReason !== 'unknown' &&
finishReason !== 'tool-calls';
```

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions