feat(adapter-claude): support interleaved thinking with streaming#783
Conversation
Consolidate duplicated message conversion, tool formatting, and stream response handling logic across hunyuan, wenxin, and zhipu adapters into shared-adapter utilities. Remove 1000+ lines of duplicate code by leveraging buildChatCompletionParams, processStreamResponse, and other shared utilities. Adapters now focus on adapter-specific configuration and request/response translation.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (15)
📒 Files selected for processing (6)
Walkthrough统一重构多个适配器的请求与流处理(Claude、Hunyuan、Wenxin、Zhipu),扩展 Claude 类型与推理块支持,迁移到共享适配器工具链(buildChatCompletionParams、processStreamResponse 等),并为若干适配器新增 embeddings 路径与头部/用量元数据透传。 Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Adapter as Requester
participant Shared as SharedAdapter
participant API as ExternalAPI
participant Consumer
Client->>Adapter: 发起 completion / embeddings 请求
Adapter->>Shared: buildChatCompletionParams(request)
Shared-->>Adapter: 返回 requestPayload 与 requestContext
Adapter->>API: POST 请求 (含 headers 与 anthropic-beta)
API-->>Adapter: 返回 SSE / 流响应
Adapter->>Shared: processStreamResponse(iterable)
Shared-->>Adapter: 标准化 stream chunk (delta, usage, thinking)
Adapter->>Consumer: emit ChatGenerationChunk (包含 usage_metadata、reasoning_blocks、signature_delta)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the Claude adapter by introducing comprehensive support for interleaved thinking with streaming. It refactors the request and response processing to accurately capture and reconstruct reasoning blocks and usage metadata from streaming events. Furthermore, it consolidates common functionalities across other adapters (Hunyuan, Wenxin, Zhipu) into shared utilities, leading to a more robust and maintainable codebase. These changes improve the fidelity of model interactions, especially for advanced features like thinking processes and tool usage, while also streamlining development for future adapter integrations. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for interleaved thinking with streaming for Claude models, which is a significant feature addition. The implementation across the Claude adapter's requester, types, and utilities appears thorough and well-executed. A major part of this PR is also the large-scale refactoring of the Hunyuan, Wenxin, and Zhipu adapters to leverage a new shared adapter library. This refactoring greatly enhances code reuse and maintainability across the project. Overall, the code quality is high. I have identified one critical error handling issue in the Hunyuan adapter and one medium-severity maintainability concern in the Claude adapter that should be addressed.
41282ac to
d4127b7
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/adapter-zhipu/src/utils.ts (1)
139-149:⚠️ Potential issue | 🟠 Major逻辑错误:web_search 工具被添加后立即移除
第 140-146 行添加了
web_search工具,但第 148 行立即将其过滤掉。这使得整个代码块无效。请确认预期行为:
- 如果应该添加
web_search,删除第 148 行- 如果不应该添加
web_search,删除整个 if 块🔧 如果应该添加 web_search
if (clientConfig.webSearch && model.includes('tools')) { result.push({ type: 'web_search', web_search: { enable: true, search_engine: 'search_std' } } satisfies ChatCompletionTool) - - result = result.filter((tool) => tool.type !== 'web_search') }🔧 如果不应该添加 web_search
- if (clientConfig.webSearch && model.includes('tools')) { - result.push({ - type: 'web_search', - web_search: { - enable: true, - search_engine: 'search_std' - } - } satisfies ChatCompletionTool) - - result = result.filter((tool) => tool.type !== 'web_search') - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-zhipu/src/utils.ts` around lines 139 - 149, The if block that checks clientConfig.webSearch && model.includes('tools') (in packages/adapter-zhipu/src/utils.ts) adds a web_search tool to result and then immediately removes it with result = result.filter((tool) => tool.type !== 'web_search'), so the addition is a no-op; decide the intended behavior and apply one of two fixes: if web_search should be added, remove the filter line that removes 'web_search' (the filter expression); if web_search should not be added, remove the entire if block that pushes the web_search object (including the push of the ChatCompletionTool-like object) to result.packages/adapter-wenxin/src/requester.ts (1)
155-161:⚠️ Potential issue | 🟡 Minor删除
buildHeaders()中的空appid字段
appid字段被硬编码为空字符串,且Config类型中不存在相应的配置字段。由于文心 API 使用 Bearer Token 认证,该字段应该被移除。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-wenxin/src/requester.ts` around lines 155 - 161, Remove the hard-coded empty appid property from the buildHeaders() return object in the requester (function buildHeaders), since Config has no appid and the Wenxin API uses Bearer token auth; update buildHeaders() to only return Content-Type and Authorization: `Bearer ${this._config.value.apiKey}` (keep Authorization intact) and run a quick compile/type-check to ensure no references to appid remain.
🧹 Nitpick comments (4)
packages/adapter-wenxin/src/utils.ts (1)
60-65: 冗余检查:第 60-65 行的逻辑永远不会执行第 33-38 行已经处理了第一条消息是
assistant的情况,会在其前面添加user消息。因此第 60 行的检查result[0]?.role === 'assistant'永远不会为真。🔧 建议删除冗余代码
if (result[result.length - 1]?.role === 'assistant') { result.push({ role: 'user', content: CONTINUE_PROMPT }) } - if (result[0]?.role === 'assistant') { - result.unshift({ - role: 'user', - content: CONTINUE_PROMPT - }) - } - return result }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-wenxin/src/utils.ts` around lines 60 - 65, Remove the redundant branch that checks result[0]?.role === 'assistant' and unshifts a user message with CONTINUE_PROMPT into result; that case is already handled earlier when the first message is assistant (the logic that prepends a user CONTINUE_PROMPT when the first message is assistant), so delete the duplicate block referencing result and CONTINUE_PROMPT to avoid unreachable code.packages/adapter-zhipu/src/requester.ts (1)
90-90: 格式问题:不必要的括号静态分析工具标记了第 90 行的不必要括号。
🔧 建议修复
- request.user = isToolModel ? undefined : (params.user ?? 'user') + request.user = isToolModel ? undefined : params.user ?? 'user'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-zhipu/src/requester.ts` at line 90, Line uses unnecessary parentheses around (params.user ?? 'user'); update the assignment in requester.ts so request.user is set without extra parens: use the ternary expression with the nullish coalescing directly (i.e., request.user = isToolModel ? undefined : params.user ?? 'user'), targeting the expression that sets request.user and referencing isToolModel and params.user.packages/shared-adapter/src/utils.ts (1)
260-261: 注释与函数名不一致函数已重命名为
processInterleavedThinkMessages以支持通用的交织思考处理,但注释仍然引用 "DeepSeek docs"。建议更新注释以反映通用用途。🔧 建议修复
// Find the start of the last turn by locating the last user message - // According to DeepSeek docs: a turn starts with a user message + // A turn starts with a user message let lastTurnStartIndex = -1🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/shared-adapter/src/utils.ts` around lines 260 - 261, 更新 processInterleavedThinkMessages 上方的注释以反映函数通用用途:删除对 "DeepSeek docs" 的引用并改为描述该函数用于处理通用的交织(interleaved)思考消息流,说明其行为——通过定位最后一个用户消息来确定最后一轮的起点(即把一轮视为以用户消息开始的消息序列),并简要列出输入/输出预期(例如接受混合的 system/assistant/user 消息数组并返回切分后的最后一轮消息)。保证注释和函数名一致且不包含特定实现来源引用。packages/adapter-hunyuan/src/requester.ts (1)
97-101: JSON 解析错误被静默忽略当
JSON.parse失败且异常不是ChatLunaError时,错误被静默忽略。这可能会隐藏流处理中的问题。考虑添加日志记录以便调试。🔧 建议添加日志记录
} catch (e) { if (e instanceof ChatLunaError) { throw e } + // JSON parse failed for non-data chunk, continue processing }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-hunyuan/src/requester.ts` around lines 97 - 101, The catch block in packages/adapter-hunyuan/src/requester.ts currently swallows non-ChatLunaError exceptions (e.g., JSON.parse failures) which hides parsing issues; update the catch around the JSON.parse call so that if the thrown error is not an instance of ChatLunaError you log the full error and relevant context (response body or raw text) using the project logger (or console.error if no logger is available) and then rethrow the error so callers can handle it; reference the ChatLunaError type and the JSON.parse usage in the same try/catch when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/adapter-claude/src/requester.ts`:
- Around line 127-132: The code currently only pushes reasoning blocks into
reasoningState.blocks and emits text/tool_use blocks separately, which loses the
original interleaved order; update the logic around reasoningState (the
reasoningState object and its .blocks array, and any code that emits blocks in
the requester flow) to store the entire sequence of blocks with their original
index/position (keep full block objects including type, content, and an index)
instead of extracting only reasoning blocks, and then use that preserved ordered
array when constructing reasoning_blocks for replay; apply the same change to
the other similar sections referenced (the blocks handling around the other
occurrences) so interleaved sequences like thinking->text->tool_use->thinking
are reconstructed in original order when sending messages back to Claude.
- Around line 48-80: The merged request currently allows callers to override
stream behavior which breaks completionStreamInternal/sseIterable; after
deepAssign into request (the ClaudeRequest built with deepAssign and
params.overrideRequestParams), forcibly set request.stream = true (or remove any
stream property from override) so the request variable used by
completionStreamInternal and sseIterable always remains streaming; update the
logic around deepAssign/ClaudeRequest in requester.ts (the request construction)
to enforce this invariant and reference the request variable, deepAssign,
ClaudeRequest, and params.overrideRequestParams when making the change.
In `@packages/adapter-claude/src/utils.ts`:
- Around line 91-105: The ToolMessage branch currently rebuilds
tool_result.content from rawMessage.content and calls processMessageContent
again, which discards the multimodal content (images) assembled earlier; instead
reuse the already-constructed result.content (the merged rawMessage.content +
additional_kwargs.images) when creating the tool_result entry so
images/attachments are preserved, keep setting tool_use_id to
rawMessage.tool_call_id, and remove the redundant processing call to
processMessageContent in the ToolMessage handling block.
- Around line 270-275: The warn log currently outputs the raw message.file_url
via the rawUrl variable which may contain signed object-storage URLs or
sensitive local paths; change the catch block (where rawUrl and logger.warn are
used) to never log the full URL — instead log a fixed/descriptive message or a
safely redacted identifier (e.g., "file_url present" or a hashed/partial token)
and/or the message ID or filename only; remove rawUrl from logger.warn and
replace it with the sanitized or constant text to avoid credential/path leakage.
In `@packages/adapter-zhipu/src/utils.ts`:
- Around line 46-67: In the glm-4v-flash special-case branch (the if (model ===
'glm-4v-flash') block) the code assumes msg.content.find(item => item.type ===
'text') always returns an object and reads .text, which can throw; change the
assignment to safely handle missing matches by first storing the result of
msg.content.find(...) into a variable, check it is truthy before reading .text,
and fall back to a safe value (e.g., empty string or leave msg.content
unchanged) so no runtime error occurs when no {type: 'text'} item exists.
---
Outside diff comments:
In `@packages/adapter-wenxin/src/requester.ts`:
- Around line 155-161: Remove the hard-coded empty appid property from the
buildHeaders() return object in the requester (function buildHeaders), since
Config has no appid and the Wenxin API uses Bearer token auth; update
buildHeaders() to only return Content-Type and Authorization: `Bearer
${this._config.value.apiKey}` (keep Authorization intact) and run a quick
compile/type-check to ensure no references to appid remain.
In `@packages/adapter-zhipu/src/utils.ts`:
- Around line 139-149: The if block that checks clientConfig.webSearch &&
model.includes('tools') (in packages/adapter-zhipu/src/utils.ts) adds a
web_search tool to result and then immediately removes it with result =
result.filter((tool) => tool.type !== 'web_search'), so the addition is a no-op;
decide the intended behavior and apply one of two fixes: if web_search should be
added, remove the filter line that removes 'web_search' (the filter expression);
if web_search should not be added, remove the entire if block that pushes the
web_search object (including the push of the ChatCompletionTool-like object) to
result.
---
Nitpick comments:
In `@packages/adapter-hunyuan/src/requester.ts`:
- Around line 97-101: The catch block in
packages/adapter-hunyuan/src/requester.ts currently swallows non-ChatLunaError
exceptions (e.g., JSON.parse failures) which hides parsing issues; update the
catch around the JSON.parse call so that if the thrown error is not an instance
of ChatLunaError you log the full error and relevant context (response body or
raw text) using the project logger (or console.error if no logger is available)
and then rethrow the error so callers can handle it; reference the ChatLunaError
type and the JSON.parse usage in the same try/catch when making this change.
In `@packages/adapter-wenxin/src/utils.ts`:
- Around line 60-65: Remove the redundant branch that checks result[0]?.role ===
'assistant' and unshifts a user message with CONTINUE_PROMPT into result; that
case is already handled earlier when the first message is assistant (the logic
that prepends a user CONTINUE_PROMPT when the first message is assistant), so
delete the duplicate block referencing result and CONTINUE_PROMPT to avoid
unreachable code.
In `@packages/adapter-zhipu/src/requester.ts`:
- Line 90: Line uses unnecessary parentheses around (params.user ?? 'user');
update the assignment in requester.ts so request.user is set without extra
parens: use the ternary expression with the nullish coalescing directly (i.e.,
request.user = isToolModel ? undefined : params.user ?? 'user'), targeting the
expression that sets request.user and referencing isToolModel and params.user.
In `@packages/shared-adapter/src/utils.ts`:
- Around line 260-261: 更新 processInterleavedThinkMessages 上方的注释以反映函数通用用途:删除对
"DeepSeek docs"
的引用并改为描述该函数用于处理通用的交织(interleaved)思考消息流,说明其行为——通过定位最后一个用户消息来确定最后一轮的起点(即把一轮视为以用户消息开始的消息序列),并简要列出输入/输出预期(例如接受混合的
system/assistant/user 消息数组并返回切分后的最后一轮消息)。保证注释和函数名一致且不包含特定实现来源引用。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 430eb48a-2e09-4586-9cd0-fe484d5d1b3f
📒 Files selected for processing (11)
packages/adapter-claude/src/requester.tspackages/adapter-claude/src/types.tspackages/adapter-claude/src/utils.tspackages/adapter-hunyuan/src/requester.tspackages/adapter-hunyuan/src/types.tspackages/adapter-hunyuan/src/utils.tspackages/adapter-wenxin/src/requester.tspackages/adapter-wenxin/src/utils.tspackages/adapter-zhipu/src/requester.tspackages/adapter-zhipu/src/utils.tspackages/shared-adapter/src/utils.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/adapter-claude/src/utils.ts (1)
217-224:⚠️ Potential issue | 🟡 Minor图片 URL 也不应输出到日志。
与
processFileContent类似,image_url也可能包含签名 URL 或敏感数据。建议统一处理,避免泄露。🛡️ 建议修改
} catch (e) { - const rawUrl = - typeof message.image_url === 'string' - ? message.image_url - : message.image_url.url - logger.warn(`Failed to fetch image url: ${rawUrl}`, e) + logger.warn('Failed to fetch image content', e) return null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 217 - 224, The catch block logs the raw image URL which may contain sensitive/signed data; update the error handling in the try/catch around fetching images (the block that accesses message.image_url) to avoid emitting the URL itself—use a redacted placeholder or omit the URL and include only contextual info (e.g., "Failed to fetch image url for message id/attachment") and the error object in the logger.warn call; follow the same redaction approach used in processFileContent to ensure consistent handling.
♻️ Duplicate comments (3)
packages/adapter-claude/src/utils.ts (2)
91-105:⚠️ Potential issue | 🟠 Major
ToolMessage分支丢弃了前面处理好的多模态内容。Lines 49-84 已经将
rawMessage.content和additional_kwargs.images合并处理到content变量中。但这里又从rawMessage.content重新生成tool_result.content,会丢弃图片附件。应直接复用前面组装好的content。🐛 建议修改
if (rawMessage instanceof ToolMessage) { result.content = [ { type: 'tool_result', - content: - typeof rawMessage.content === 'string' - ? rawMessage.content - : await processMessageContent( - plugin, - rawMessage.content - ), + content, tool_use_id: rawMessage.tool_call_id } ] return result }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 91 - 105, The ToolMessage branch in the function handling messages currently rebuilds tool_result.content from rawMessage.content (and calls processMessageContent) which discards previously merged multimodal data (the local variable content assembled earlier from rawMessage.content and additional_kwargs.images); update the ToolMessage branch to reuse that assembled content variable for tool_result.content (keep tool_use_id set from rawMessage.tool_call_id) instead of reprocessing rawMessage.content so attached images and multimodal parts are preserved (check code around ToolMessage, processMessageContent, tool_call_id and the local content variable).
270-276:⚠️ Potential issue | 🟠 Major不要将原始
file_url输出到日志。
file_url可能包含签名的对象存储 URL 或敏感路径。在 warn 日志中原样输出会泄露凭证或文件位置。建议只记录固定文案或脱敏后的标识。🛡️ 建议修改
} catch (e) { - const rawUrl = - typeof message.file_url === 'string' - ? message.file_url - : message.file_url.url - logger.warn(`Failed to fetch file url: ${rawUrl}`, e) + logger.warn('Failed to fetch Claude file content', e) return null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 270 - 276, The catch block currently logs the raw file_url (see message.file_url and rawUrl) which can leak signed URLs or sensitive paths; change the logger.warn call in that catch block to avoid emitting the full URL—either log a fixed message like "Failed to fetch file URL (redacted)" or log a non-sensitive identifier (e.g. derive and log only a masked/hashed token or the filename without query params), and include the error object for debugging (keep using logger.warn but remove rawUrl from the message and replace with the redacted identifier).packages/adapter-claude/src/requester.ts (1)
48-80:⚠️ Potential issue | 🟠 Major
overrideRequestParams可能覆盖stream: true导致 SSE 解析失败。
deepAssign会将params.overrideRequestParams合并到请求中。如果调用方传入{ stream: false },后续的sseIterable(response)会将普通 JSON 响应当作 SSE 流处理,导致解析错误。应在合并后强制设置request.stream = true。🐛 建议修改
const request = deepAssign( {}, { model, max_tokens: maxTokens, // ... other fields } satisfies ClaudeRequest, params.overrideRequestParams ?? {} ) as ClaudeRequest + request.stream = true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/requester.ts` around lines 48 - 80, deepAssign merges params.overrideRequestParams into the ClaudeRequest which allows callers to set stream:false and break SSE parsing; after building the request (the deepAssign call that produces the request variable typed ClaudeRequest in requester.ts), explicitly set request.stream = true to ensure SSE remains enabled regardless of overrides (keep the rest of override merging intact), and similarly ensure any downstream uses (e.g., sseIterable(response)) can rely on request.stream always being true.
🧹 Nitpick comments (1)
packages/adapter-claude/src/utils.ts (1)
368-395:content_block_start中的thinking类型未被处理。当
delta.content_block.type是thinking或redacted_thinking时,函数会返回undefined。虽然requester.ts中单独处理了这些类型,但建议在此处添加显式 case 或注释说明,避免将来维护时产生困惑。💡 可选:添加显式处理或注释
if (delta.content_block.type === 'text') { const content = delta.content_block.text if (content !== undefined) { return new AIMessageChunk({ content, additional_kwargs: {} }) } } + // thinking and redacted_thinking blocks are handled separately in requester.ts return }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 368 - 395, The handler for content_block_start in packages/adapter-claude/src/utils.ts currently only handles 'tool_use' and 'text' but silently returns undefined for 'thinking' and 'redacted_thinking'; update the delta.content_block.type switch in the function (the block that constructs AIMessageChunk) to explicitly handle 'thinking' and 'redacted_thinking'—either by adding a clear case that returns undefined with a comment pointing to requester.ts for actual processing, or by returning an explicit AIMessageChunk shape if needed—so future readers see intended behavior for these types (refer to delta.content_block.type, AIMessageChunk, and the content_block_start handling).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/adapter-claude/src/utils.ts`:
- Around line 334-342: The function formatToolsToClaudeTools currently declares
a return type of ClaudeTool[] but returns undefined when tools.length < 1;
change the behavior to return an empty array instead of undefined (or
alternatively widen the return type to ClaudeTool[] | undefined) so the declared
signature matches actual return values; update the early-return branch in
formatToolsToClaudeTools and ensure callers expecting an array still work
(formatToolToClaudeTool remains used for mapping).
---
Outside diff comments:
In `@packages/adapter-claude/src/utils.ts`:
- Around line 217-224: The catch block logs the raw image URL which may contain
sensitive/signed data; update the error handling in the try/catch around
fetching images (the block that accesses message.image_url) to avoid emitting
the URL itself—use a redacted placeholder or omit the URL and include only
contextual info (e.g., "Failed to fetch image url for message id/attachment")
and the error object in the logger.warn call; follow the same redaction approach
used in processFileContent to ensure consistent handling.
---
Duplicate comments:
In `@packages/adapter-claude/src/requester.ts`:
- Around line 48-80: deepAssign merges params.overrideRequestParams into the
ClaudeRequest which allows callers to set stream:false and break SSE parsing;
after building the request (the deepAssign call that produces the request
variable typed ClaudeRequest in requester.ts), explicitly set request.stream =
true to ensure SSE remains enabled regardless of overrides (keep the rest of
override merging intact), and similarly ensure any downstream uses (e.g.,
sseIterable(response)) can rely on request.stream always being true.
In `@packages/adapter-claude/src/utils.ts`:
- Around line 91-105: The ToolMessage branch in the function handling messages
currently rebuilds tool_result.content from rawMessage.content (and calls
processMessageContent) which discards previously merged multimodal data (the
local variable content assembled earlier from rawMessage.content and
additional_kwargs.images); update the ToolMessage branch to reuse that assembled
content variable for tool_result.content (keep tool_use_id set from
rawMessage.tool_call_id) instead of reprocessing rawMessage.content so attached
images and multimodal parts are preserved (check code around ToolMessage,
processMessageContent, tool_call_id and the local content variable).
- Around line 270-276: The catch block currently logs the raw file_url (see
message.file_url and rawUrl) which can leak signed URLs or sensitive paths;
change the logger.warn call in that catch block to avoid emitting the full
URL—either log a fixed message like "Failed to fetch file URL (redacted)" or log
a non-sensitive identifier (e.g. derive and log only a masked/hashed token or
the filename without query params), and include the error object for debugging
(keep using logger.warn but remove rawUrl from the message and replace with the
redacted identifier).
---
Nitpick comments:
In `@packages/adapter-claude/src/utils.ts`:
- Around line 368-395: The handler for content_block_start in
packages/adapter-claude/src/utils.ts currently only handles 'tool_use' and
'text' but silently returns undefined for 'thinking' and 'redacted_thinking';
update the delta.content_block.type switch in the function (the block that
constructs AIMessageChunk) to explicitly handle 'thinking' and
'redacted_thinking'—either by adding a clear case that returns undefined with a
comment pointing to requester.ts for actual processing, or by returning an
explicit AIMessageChunk shape if needed—so future readers see intended behavior
for these types (refer to delta.content_block.type, AIMessageChunk, and the
content_block_start handling).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 1d6683b6-f783-49ba-b573-6e8f1550c14a
📒 Files selected for processing (3)
packages/adapter-claude/src/requester.tspackages/adapter-claude/src/types.tspackages/adapter-claude/src/utils.ts
Add interleaved-thinking-2025-05-14 beta support for Claude models with: - Streaming thinking content extraction from SSE events - Reasoning blocks with thinking content and signatures - Proper message content handling for tool results - Beta header management with model detection
d4127b7 to
569777c
Compare
- Enable streaming in Claude requester by setting stream=true - Simplify tool result content handling with proper type casting - Fix formatToolsToClaudeTools to return empty array instead of undefined - Add missing ClaudeToolResultContentBlockParam import for type safety
…nto constants Extract hardcoded model checks into INTERLEAVED_THINKING_SUPPORTED_MODELS and INTERLEAVED_THINKING_EXCLUDED_MODELS constants for improved maintainability and reusability.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/adapter-claude/src/utils.ts (1)
213-221:⚠️ Potential issue | 🟠 Major不要把原始
image_url写进日志。
image_url可能是签名对象存储地址,甚至是内联data:URL。这里失败时把完整值打到 warn,会把凭证或大段二进制内容泄露到日志系统。建议修改
} catch (e) { - const rawUrl = - typeof message.image_url === 'string' - ? message.image_url - : message.image_url.url - logger.warn(`Failed to fetch image url: ${rawUrl}`, e) + logger.warn('Failed to fetch Claude image content', e) return null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 213 - 221, The current catch block in the image fetching flow (around fetchImageUrl and logger.warn) logs the raw message.image_url (variable rawUrl), which can leak credentials or large data URLs; change the warning to avoid printing the full image_url — instead log a safe redaction or metadata (e.g., indicate whether image_url is a string/object, its length or the scheme like "data:" vs "https", or simply "image_url omitted for security"), and include the error object; update the logger.warn call in the catch for fetchImageUrl to use the redacted/metadata value rather than rawUrl.
♻️ Duplicate comments (2)
packages/adapter-claude/src/utils.ts (1)
268-274:⚠️ Potential issue | 🟠 Major不要把原始
file_url写进日志。
file_url很可能是签名 URL 或本地敏感路径。这里会把完整地址写进 warn,失败一次就会把凭证或路径泄露到日志系统。建议修改
} catch (e) { - const rawUrl = - typeof message.file_url === 'string' - ? message.file_url - : message.file_url.url - logger.warn(`Failed to fetch file url: ${rawUrl}`, e) + logger.warn('Failed to fetch Claude file content', e) return null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 268 - 274, The catch block currently logs the full rawUrl derived from message.file_url which may expose signed URLs or local sensitive paths; update the logger.warn in that catch to avoid emitting the full URL (reference the variable message.file_url and the logger.warn call) by either logging a redacted/obfuscated form (e.g., mask query string or show only hostname) or logging a generic message like "Failed to fetch file url" with no raw URL and optionally include a non-sensitive identifier (e.g., message.id); leave the return null behavior unchanged.packages/adapter-claude/src/requester.ts (1)
138-143:⚠️ Potential issue | 🟠 Major
reasoning_blocks仍然没有保住 interleaved 的原始顺序。
reasoningState.blocks的元素类型还是ClaudeReasoningBlockParam[],后面也只把这些块写回additional_kwargs.reasoning_blocks。只要流里出现thinking -> text/tool_use -> thinking,中间的 text/tool_use 就不会进入这个序列;下一轮在packages/adapter-claude/src/utils.ts的langchainMessageToClaudeMessage()里只能把 reasoning 全部前置,原始顺序会被重排。Also applies to: 202-250, 278-305
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/requester.ts` around lines 138 - 143, The current implementation only stores ClaudeReasoningBlockParam objects in reasoningState.blocks and writes them back to additional_kwargs.reasoning_blocks, which loses the original interleaved order when non-reasoning events (text/tool_use) occur between thinking blocks; update the logic so reasoningState.blocks records ordering information (e.g., make it an array of discriminated union entries or include an index/timestamp and message kind) instead of only ClaudeReasoningBlockParam, and when emitting additional_kwargs.reasoning_blocks or when langchainMessageToClaudeMessage() reconstructs the stream, merge/restore blocks according to that recorded order so thinking and text/tool_use elements remain in original interleaved sequence; reference reasoningState.blocks, ClaudeReasoningBlockParam, additional_kwargs.reasoning_blocks and langchainMessageToClaudeMessage() when changing types and reconstruction logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/adapter-claude/src/utils.ts`:
- Around line 59-85: The image-handling branch uses model?.includes('claude-4')
which misses variants like claude-sonnet-4 / claude-opus-4* / claude-haiku-4 and
thus silently drops additional_kwargs.images; update the condition in the block
that builds mappedImages/nextContent (the model check wrapping images processing
inside packages/adapter-claude/src/utils.ts) to match the same Claude 4
detection used in packages/adapter-claude/src/requester.ts (e.g., reuse the
shared helper or change the check to a regex/pattern that matches all claude-*4
variants such as /^claude.*-?4/ or the existing isClaude4Model function), so
images are processed by processImageContent and merged into content
(mappedImages, nextContent) for all Claude 4 variants.
---
Outside diff comments:
In `@packages/adapter-claude/src/utils.ts`:
- Around line 213-221: The current catch block in the image fetching flow
(around fetchImageUrl and logger.warn) logs the raw message.image_url (variable
rawUrl), which can leak credentials or large data URLs; change the warning to
avoid printing the full image_url — instead log a safe redaction or metadata
(e.g., indicate whether image_url is a string/object, its length or the scheme
like "data:" vs "https", or simply "image_url omitted for security"), and
include the error object; update the logger.warn call in the catch for
fetchImageUrl to use the redacted/metadata value rather than rawUrl.
---
Duplicate comments:
In `@packages/adapter-claude/src/requester.ts`:
- Around line 138-143: The current implementation only stores
ClaudeReasoningBlockParam objects in reasoningState.blocks and writes them back
to additional_kwargs.reasoning_blocks, which loses the original interleaved
order when non-reasoning events (text/tool_use) occur between thinking blocks;
update the logic so reasoningState.blocks records ordering information (e.g.,
make it an array of discriminated union entries or include an index/timestamp
and message kind) instead of only ClaudeReasoningBlockParam, and when emitting
additional_kwargs.reasoning_blocks or when langchainMessageToClaudeMessage()
reconstructs the stream, merge/restore blocks according to that recorded order
so thinking and text/tool_use elements remain in original interleaved sequence;
reference reasoningState.blocks, ClaudeReasoningBlockParam,
additional_kwargs.reasoning_blocks and langchainMessageToClaudeMessage() when
changing types and reconstruction logic.
In `@packages/adapter-claude/src/utils.ts`:
- Around line 268-274: The catch block currently logs the full rawUrl derived
from message.file_url which may expose signed URLs or local sensitive paths;
update the logger.warn in that catch to avoid emitting the full URL (reference
the variable message.file_url and the logger.warn call) by either logging a
redacted/obfuscated form (e.g., mask query string or show only hostname) or
logging a generic message like "Failed to fetch file url" with no raw URL and
optionally include a non-sensitive identifier (e.g., message.id); leave the
return null behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ceaae035-d048-40c6-b6bf-2c66da317539
📒 Files selected for processing (3)
packages/adapter-claude/src/requester.tspackages/adapter-claude/src/types.tspackages/adapter-claude/src/utils.ts
Let Claude models accept read_files inline documents and images so native Anthropic file blocks are used instead of dropping supported inputs.
Move Claude shared content and interleaved-thinking definitions into its types module, and centralize Hunyuan and Wenxin request shapes so requester implementations can reuse the same adapter types.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
packages/adapter-claude/src/utils.ts (2)
277-283:⚠️ Potential issue | 🟠 Major敏感 URL 不应直接记录到日志。
file_url可能包含带签名的对象存储 URL 或敏感本地路径。将其原样输出到 warn 日志会泄露凭证或文件位置。建议修改
} catch (e) { - const rawUrl = - typeof message.file_url === 'string' - ? message.file_url - : message.file_url.url - logger.warn(`Failed to fetch file url: ${rawUrl}`, e) + logger.warn('Failed to fetch Claude file content', e) return null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 277 - 283, The catch block in packages/adapter-claude/src/utils.ts currently builds rawUrl from message.file_url and logs it via logger.warn (see variable rawUrl and the logger.warn call); change this to avoid printing sensitive URLs/paths directly—either redact or replace the URL with a fixed placeholder (e.g., "<REDACTED_FILE_URL>"), or log a deterministic hash/UUID of the URL instead, and include minimal context like the message ID or error without the raw path; ensure the logger.warn invocation uses the sanitized placeholder/hash and still logs the caught error object e for diagnostics.
68-70:⚠️ Potential issue | 🟠 MajorClaude 4 模型变体的图片处理会被静默跳过。
model?.includes('claude-4')无法匹配claude-sonnet-4、claude-opus-4*、claude-haiku-4等变体(参考client.ts中的 fallbackModels 列表)。这些模型名称中包含的是sonnet-4、opus-4而非claude-4,导致additional_kwargs.images会被丢弃。建议修改
if ( - (model?.includes('claude-3') || model?.includes('claude-4')) && - images != null + images != null && + (model?.includes('claude-3') || + model?.includes('claude-sonnet-4') || + model?.includes('claude-opus-4') || + model?.includes('claude-haiku-4')) ) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/adapter-claude/src/utils.ts` around lines 68 - 70, The check in utils.ts that uses model?.includes('claude-4') misses Claude v4 variants (e.g., claude-sonnet-4, claude-opus-4, claude-haiku-4) so images get dropped; update the conditional around model (the if that gates additional_kwargs.images) to detect any Claude v4 variant instead of only the literal "claude-4" (for example use a regex that matches "claude" with any suffix ending in "-4" or reuse the fallbackModels logic from client.ts), then ensure additional_kwargs.images is preserved when that updated match succeeds; touch the condition that references model and additional_kwargs.images in utils.ts only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/adapter-claude/src/utils.ts`:
- Around line 277-283: The catch block in packages/adapter-claude/src/utils.ts
currently builds rawUrl from message.file_url and logs it via logger.warn (see
variable rawUrl and the logger.warn call); change this to avoid printing
sensitive URLs/paths directly—either redact or replace the URL with a fixed
placeholder (e.g., "<REDACTED_FILE_URL>"), or log a deterministic hash/UUID of
the URL instead, and include minimal context like the message ID or error
without the raw path; ensure the logger.warn invocation uses the sanitized
placeholder/hash and still logs the caught error object e for diagnostics.
- Around line 68-70: The check in utils.ts that uses model?.includes('claude-4')
misses Claude v4 variants (e.g., claude-sonnet-4, claude-opus-4, claude-haiku-4)
so images get dropped; update the conditional around model (the if that gates
additional_kwargs.images) to detect any Claude v4 variant instead of only the
literal "claude-4" (for example use a regex that matches "claude" with any
suffix ending in "-4" or reuse the fallbackModels logic from client.ts), then
ensure additional_kwargs.images is preserved when that updated match succeeds;
touch the condition that references model and additional_kwargs.images in
utils.ts only.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: cafd61f6-bcae-4455-991f-4b56a3051e0d
📒 Files selected for processing (3)
packages/adapter-claude/src/client.tspackages/adapter-claude/src/index.tspackages/adapter-claude/src/utils.ts
Update adapter package dependencies to use @chatluna/v1-shared-adapter 1.0.30 and bump affected package versions so the release metadata stays aligned.
Summary
This PR adds comprehensive support for interleaved thinking in Claude models with streaming capabilities.
New Features
interleaved-thinking-2025-05-14beta support for compatible Claude models (claude-3-7-, claude-sonnet-4, claude-opus-4-, claude-haiku-4)content_block_startevents for thinking blockscontent_block_deltaevents for incremental thinking updatessignature_deltaevents for thinking signaturesBug Fixes
CluadeTool→ClaudeToolthroughout adapter codebaseOther Changes
deepAssignutilityClaudeThinkingConfigClaudeToolChoiceClaudeReasoningBlockParamClaudeServerToolUseBlockParamClaudeStreamContentBlockParamClaudeContentBlockDeltafetchFileLikeUrlutility for PDF and text documentsAuthorizationheader alongsidex-api-keyfor improved compatibilityFiles Changed
packages/adapter-claude/src/requester.ts: Enhanced streaming and thinking supportpackages/adapter-claude/src/types.ts: Comprehensive type definitions for new featurespackages/adapter-claude/src/utils.ts: Improved message processing and tool handlingpackages/adapter-hunyuan/src/*: Adapter consolidation for shared patternspackages/adapter-wenxin/src/*: Adapter consolidation for shared patternspackages/adapter-zhipu/src/*: Adapter consolidation for shared patternspackages/shared-adapter/src/utils.ts: Minor shared utility updates