Commit 69ee454
* Fix issue #177: Transform reasoning to thinking blocks when using tools
Fixes #177
## Problem
When using Claude Sonnet 4 with extended thinking mode alongside tool use,
the API returned an error: "Expected `thinking` or `redacted_thinking`, but
found `text`". This occurred because Anthropic's API requires a specific
message format when both features are used together.
## Root Cause
The provider was sending reasoning_details with type "reasoning.text" in a
flat message structure, but Anthropic requires:
1. Message content as an array (not string)
2. Thinking blocks with type "thinking" (not "reasoning.text")
3. Thinking blocks must appear first, before text/tool_use blocks
## Solution
- Added `Thinking` type to ReasoningDetailType enum
- Created ReasoningDetailThinkingSchema for the thinking block type
- Updated ChatCompletionContentPart types to support thinking blocks
- Modified convert-to-openrouter-chat-messages to conditionally transform
reasoning.text to thinking blocks when both reasoning and tool calls present
- Updated response parsing to handle thinking blocks in both streaming and
non-streaming modes
- Maintains backward compatibility for single-feature usage
## Changes
- src/schemas/reasoning-details.ts: Add thinking type support
- src/types/openrouter-chat-completions-input.ts: Update type definitions
- src/chat/convert-to-openrouter-chat-messages.ts: Implement transformation
- src/chat/index.ts: Handle thinking blocks in response parsing
- src/chat/convert-to-openrouter-chat-messages.test.ts: Add unit tests
- e2e/reasoning-with-tools.test.ts: Add e2e tests
## Testing
- All 73 unit tests pass (Node + Edge environments)
- TypeScript compilation succeeds with no errors
- Lint checks pass
- Build succeeds
* Fix: Use reasoning_details format instead of thinking blocks for OpenRouter API
OpenRouter API expects reasoning_details at the root level, not "thinking"
content blocks. This fix ensures messages are sent in the correct format even
when both reasoning and tool calls are present.
Changes:
- Remove incorrect thinking blocks conversion in message formatting
- Always use reasoning_details format for OpenRouter API
- Add handling for AI SDK "thinking" type, converting to reasoning_details
- Update tests to validate correct OpenRouter API message shape
All unit tests (22/22) and e2e tests (3/3) passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix: Remove unused 'thinking' case to resolve typecheck errors
* Fix: Update reasoning_details to match OpenRouter specification
- Add required common fields (id, format, index) to all reasoning detail types
- Add ReasoningFormat enum with supported format values
- Remove incorrect ChatCompletionContentPartThinking type
- Update convert-to-openrouter-chat-messages to populate all required fields
- Remove Thinking type handling from chat index
- Update all tests to use new schema with required fields
- All tests passing and typecheck clean
* remove duplicate test
* Add e2e test for streamText with reasoning and tools
- Tests streamText with reasoning enabled and tool calls
- Verifies reasoning deltas are received correctly
- Verifies tool calls work with reasoning
- Test passes successfully with updated reasoning_details format
* Fix TypeScript errors in stream-with-reasoning-and-tools test
- Add required 'effort' property to reasoning configuration
- Fix error type handling with proper type guards
- Remove deprecated experimental_continueSteps and maxSteps options
- Update stream part property names (text instead of textDelta/delta)
- Remove non-existent step-finish event type
These changes align the test with AI SDK v5 types and ensure CI passes.
* Improve test assertions and reduce console logging
- Add proper expect assertions for reasoning text length (>1)
- Add assertions for tool calls and verify getWeather is called
- Add assertions for text content length
- Remove excessive console.log statements
- Keep only error logging for debugging failures
- Calculate total reasoning and text content for validation
The test now properly validates that reasoning content is present
and meaningful, rather than just warning when it's missing.
* Reproduce thinking block error with AI SDK 5.0.76
- Update AI SDK to version 5.0.76 to match user environment
- Update e2e test to use stopWhen(stepCountIs(5)) which triggers multi-step execution
- Successfully reproduces error: "messages.1.content.0.type: Expected `thinking` or `redacted_thinking`, but found `tool_use`"
- Add raw API test to demonstrate the error condition
- Revert src/ changes to match main branch (no implementation changes)
The error occurs when reasoning is enabled and the assistant message contains tool calls without thinking blocks preceding them in the content array.
* Remove raw API test file
* Fix reasoning token conversion for multi-turn conversations with tools
When assistant messages with both reasoning and tool calls were sent back
to OpenRouter in subsequent turns, the format was incompatible with
Anthropic's requirements, causing validation errors.
Changes:
- Updated reasoning detail schemas to include id, format, and index fields
- Preserve reasoning_details in providerMetadata for both doGenerate and doStream
- Modified message conversion to only send reasoning_details when we have the
exact preserved version from OpenRouter's response
- Use legacy reasoning field as fallback when preserved version unavailable
- Improved e2e test to properly capture and report streaming errors
Fixes issue where OpenRouter/Anthropic rejected messages with:
"messages.1.content.0.type: Expected `thinking` or `redacted_thinking`,
but found `tool_use`"
The AI SDK doesn't preserve providerMetadata across turns, so we rely on
the legacy reasoning field which OpenRouter correctly translates to
provider-specific formats.
* Fix TypeScript compilation errors
- Add missing ReasoningDetailUnion import in src/chat/index.ts
- Add type assertion for providerMetadata.openrouter to include reasoning_details
- Fix type assertion for preserved reasoning_details in convert function
- Remove unused imports (ReasoningDetailType, UIMessage, convertToModelMessages)
- Fix e2e test to use stopWhen with stepCountIs instead of maxSteps
- Add max_tokens to reasoning config in e2e test
- Fix error handling in e2e test with proper type assertion
* Replace type assertions with Zod schemas for provider metadata
- Add complete ReasoningSchema from main project including format types and reasoning details
- Create OpenRouterProviderMetadataSchema for type-safe validation
- Replace unsafe type assertions with schema validation in chat/index.ts and convert-to-openrouter-chat-messages.ts
- Add type-guards utility for filtering null/undefined values
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
1 parent d49948d commit 69ee454
File tree
11 files changed
+333
-51
lines changed- e2e
- src
- chat
- schemas
- utils
11 files changed
+333
-51
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | 8 | | |
10 | 9 | | |
11 | 10 | | |
12 | 11 | | |
13 | 12 | | |
14 | | - | |
| 13 | + | |
15 | 14 | | |
16 | 15 | | |
17 | 16 | | |
| |||
157 | 156 | | |
158 | 157 | | |
159 | 158 | | |
160 | | - | |
161 | 159 | | |
162 | 160 | | |
163 | 161 | | |
| |||
183 | 181 | | |
184 | 182 | | |
185 | 183 | | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | 184 | | |
192 | 185 | | |
193 | 186 | | |
| |||
199 | 192 | | |
200 | 193 | | |
201 | 194 | | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
202 | 208 | | |
203 | 209 | | |
204 | 210 | | |
205 | 211 | | |
206 | 212 | | |
207 | | - | |
208 | | - | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
209 | 217 | | |
210 | 218 | | |
211 | 219 | | |
| |||
0 commit comments