mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Merge pull request #563 from Szpadel/r1-display-reason
Display reasoning for supported openrouter models
This commit is contained in:
@@ -10,6 +10,7 @@ import delay from "delay"
|
||||
// Add custom interface for OpenRouter params
|
||||
type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
|
||||
transforms?: string[]
|
||||
include_reasoning?: boolean
|
||||
}
|
||||
|
||||
// Add custom interface for OpenRouter usage chunk
|
||||
@@ -110,14 +111,23 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
||||
maxTokens = 8_192
|
||||
break
|
||||
}
|
||||
|
||||
let temperature = 0
|
||||
switch (this.getModel().id) {
|
||||
case "deepseek/deepseek-r1":
|
||||
// Recommended temperature for DeepSeek reasoning models
|
||||
temperature = 0.6
|
||||
}
|
||||
|
||||
// https://openrouter.ai/docs/transforms
|
||||
let fullResponseText = ""
|
||||
const stream = await this.client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
max_tokens: maxTokens,
|
||||
temperature: 0,
|
||||
temperature: temperature,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
include_reasoning: true,
|
||||
// This way, the transforms field will only be included in the parameters when openRouterUseMiddleOutTransform is true.
|
||||
...(this.options.openRouterUseMiddleOutTransform && { transforms: ["middle-out"] }),
|
||||
} as OpenRouterChatCompletionParams)
|
||||
@@ -137,6 +147,12 @@ export class OpenRouterHandler implements ApiHandler, SingleCompletionHandler {
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta
|
||||
if ("reasoning" in delta && delta.reasoning) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: delta.reasoning,
|
||||
} as ApiStreamChunk
|
||||
}
|
||||
if (delta?.content) {
|
||||
fullResponseText += delta.content
|
||||
yield {
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
export type ApiStream = AsyncGenerator<ApiStreamChunk>
|
||||
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamUsageChunk
|
||||
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamUsageChunk | ApiStreamReasoningChunk
|
||||
|
||||
export interface ApiStreamTextChunk {
|
||||
type: "text"
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface ApiStreamReasoningChunk {
|
||||
type: "reasoning"
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface ApiStreamUsageChunk {
|
||||
type: "usage"
|
||||
inputTokens: number
|
||||
|
||||
@@ -2391,9 +2391,14 @@ export class Cline {
|
||||
|
||||
const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
|
||||
let assistantMessage = ""
|
||||
let reasoningMessage = ""
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
switch (chunk.type) {
|
||||
case "reasoning":
|
||||
reasoningMessage += chunk.text
|
||||
await this.say("reasoning", reasoningMessage, undefined, true)
|
||||
break
|
||||
case "usage":
|
||||
inputTokens += chunk.inputTokens
|
||||
outputTokens += chunk.outputTokens
|
||||
|
||||
@@ -121,6 +121,7 @@ export interface ClineMessage {
|
||||
text?: string
|
||||
images?: string[]
|
||||
partial?: boolean
|
||||
reasoning?: string
|
||||
}
|
||||
|
||||
export type ClineAsk =
|
||||
@@ -142,6 +143,7 @@ export type ClineSay =
|
||||
| "api_req_started"
|
||||
| "api_req_finished"
|
||||
| "text"
|
||||
| "reasoning"
|
||||
| "completion_result"
|
||||
| "user_feedback"
|
||||
| "user_feedback_diff"
|
||||
|
||||
@@ -15,6 +15,7 @@ import { vscode } from "../../utils/vscode"
|
||||
import CodeAccordian, { removeLeadingNonAlphanumeric } from "../common/CodeAccordian"
|
||||
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
import ReasoningBlock from "./ReasoningBlock"
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
import McpResourceRow from "../mcp/McpResourceRow"
|
||||
import McpToolRow from "../mcp/McpToolRow"
|
||||
@@ -79,6 +80,14 @@ export const ChatRowContent = ({
|
||||
isStreaming,
|
||||
}: ChatRowContentProps) => {
|
||||
const { mcpServers, alwaysAllowMcp } = useExtensionState()
|
||||
const [reasoningCollapsed, setReasoningCollapsed] = useState(false)
|
||||
|
||||
// Auto-collapse reasoning when new messages arrive
|
||||
useEffect(() => {
|
||||
if (!isLast && message.say === "reasoning") {
|
||||
setReasoningCollapsed(true)
|
||||
}
|
||||
}, [isLast, message.say])
|
||||
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
|
||||
if (message.text != null && message.say === "api_req_started") {
|
||||
const info: ClineApiReqInfo = JSON.parse(message.text)
|
||||
@@ -472,6 +481,14 @@ export const ChatRowContent = ({
|
||||
switch (message.type) {
|
||||
case "say":
|
||||
switch (message.say) {
|
||||
case "reasoning":
|
||||
return (
|
||||
<ReasoningBlock
|
||||
content={message.text || ""}
|
||||
isCollapsed={reasoningCollapsed}
|
||||
onToggleCollapse={() => setReasoningCollapsed(!reasoningCollapsed)}
|
||||
/>
|
||||
)
|
||||
case "api_req_started":
|
||||
return (
|
||||
<>
|
||||
|
||||
70
webview-ui/src/components/chat/ReasoningBlock.tsx
Normal file
70
webview-ui/src/components/chat/ReasoningBlock.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useRef } from "react"
|
||||
import { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
|
||||
import MarkdownBlock from "../common/MarkdownBlock"
|
||||
|
||||
interface ReasoningBlockProps {
|
||||
content: string
|
||||
isCollapsed?: boolean
|
||||
onToggleCollapse?: () => void
|
||||
autoHeight?: boolean
|
||||
}
|
||||
|
||||
const ReasoningBlock: React.FC<ReasoningBlockProps> = ({
|
||||
content,
|
||||
isCollapsed = false,
|
||||
onToggleCollapse,
|
||||
autoHeight = false,
|
||||
}) => {
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Scroll to bottom when content updates
|
||||
useEffect(() => {
|
||||
if (contentRef.current && !isCollapsed) {
|
||||
contentRef.current.scrollTop = contentRef.current.scrollHeight
|
||||
}
|
||||
}, [content, isCollapsed])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
border: "1px solid var(--vscode-editorGroup-border)",
|
||||
borderRadius: "3px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<div
|
||||
onClick={onToggleCollapse}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderBottom: isCollapsed ? "none" : "1px solid var(--vscode-editorGroup-border)",
|
||||
}}>
|
||||
<span style={{ fontWeight: "bold" }}>Reasoning</span>
|
||||
<span className={`codicon codicon-chevron-${isCollapsed ? "right" : "down"}`}></span>
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<div
|
||||
ref={contentRef}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
maxHeight: autoHeight ? "none" : "160px",
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
opacity: 0.9,
|
||||
}}>
|
||||
<MarkdownBlock markdown={content} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReasoningBlock
|
||||
Reference in New Issue
Block a user