mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Merge branch 'main' into new_unified
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import cloneDeep from "clone-deep"
|
||||
import { DiffStrategy, getDiffStrategy, UnifiedDiffStrategy } from "./diff/DiffStrategy"
|
||||
import { validateToolUse, isToolAllowedForMode } from "./mode-validator"
|
||||
import delay from "delay"
|
||||
import fs from "fs/promises"
|
||||
import os from "os"
|
||||
@@ -44,7 +45,7 @@ import { arePathsEqual, getReadablePath } from "../utils/path"
|
||||
import { parseMentions } from "./mentions"
|
||||
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message"
|
||||
import { formatResponse } from "./prompts/responses"
|
||||
import { addCustomInstructions, SYSTEM_PROMPT } from "./prompts/system"
|
||||
import { addCustomInstructions, codeMode, SYSTEM_PROMPT } from "./prompts/system"
|
||||
import { truncateHalfConversation } from "./sliding-window"
|
||||
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
|
||||
import { detectCodeOmission } from "../integrations/editor/detect-omission"
|
||||
@@ -784,8 +785,24 @@ export class Cline {
|
||||
})
|
||||
}
|
||||
|
||||
const { browserViewportSize, preferredLanguage } = await this.providerRef.deref()?.getState() ?? {}
|
||||
const systemPrompt = await SYSTEM_PROMPT(cwd, this.api.getModel().info.supportsComputerUse ?? false, mcpHub, this.diffStrategy, browserViewportSize) + await addCustomInstructions(this.customInstructions ?? '', cwd, preferredLanguage)
|
||||
const { browserViewportSize, preferredLanguage, mode, customPrompts } = await this.providerRef.deref()?.getState() ?? {}
|
||||
const systemPrompt = await SYSTEM_PROMPT(
|
||||
cwd,
|
||||
this.api.getModel().info.supportsComputerUse ?? false,
|
||||
mcpHub,
|
||||
this.diffStrategy,
|
||||
browserViewportSize,
|
||||
mode,
|
||||
customPrompts
|
||||
) + await addCustomInstructions(
|
||||
{
|
||||
customInstructions: this.customInstructions,
|
||||
customPrompts,
|
||||
preferredLanguage
|
||||
},
|
||||
cwd,
|
||||
mode
|
||||
)
|
||||
|
||||
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
|
||||
if (previousApiReqIndex >= 0) {
|
||||
@@ -804,8 +821,30 @@ export class Cline {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to Anthropic.MessageParam by spreading only the API-required properties
|
||||
const cleanConversationHistory = this.apiConversationHistory.map(({ role, content }) => ({ role, content }))
|
||||
// Clean conversation history by:
|
||||
// 1. Converting to Anthropic.MessageParam by spreading only the API-required properties
|
||||
// 2. Converting image blocks to text descriptions if model doesn't support images
|
||||
const cleanConversationHistory = this.apiConversationHistory.map(({ role, content }) => {
|
||||
// Handle array content (could contain image blocks)
|
||||
if (Array.isArray(content)) {
|
||||
if (!this.api.getModel().info.supportsImages) {
|
||||
// Convert image blocks to text descriptions
|
||||
content = content.map(block => {
|
||||
if (block.type === 'image') {
|
||||
// Convert image blocks to text descriptions
|
||||
// Note: We can't access the actual image content/url due to API limitations,
|
||||
// but we can indicate that an image was present in the conversation
|
||||
return {
|
||||
type: 'text',
|
||||
text: '[Referenced image in conversation]'
|
||||
};
|
||||
}
|
||||
return block;
|
||||
});
|
||||
}
|
||||
}
|
||||
return { role, content }
|
||||
})
|
||||
const stream = this.api.createMessage(systemPrompt, cleanConversationHistory)
|
||||
const iterator = stream[Symbol.asyncIterator]()
|
||||
|
||||
@@ -816,15 +855,15 @@ export class Cline {
|
||||
} catch (error) {
|
||||
// note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely.
|
||||
if (alwaysApproveResubmit) {
|
||||
const errorMsg = error.message ?? "Unknown error"
|
||||
const requestDelay = requestDelaySeconds || 5
|
||||
// Automatically retry with delay
|
||||
await this.say(
|
||||
"error",
|
||||
`${error.message ?? "Unknown error"} ↺ Retrying in ${requestDelay} seconds...`,
|
||||
)
|
||||
await this.say("api_req_retry_delayed")
|
||||
await delay(requestDelay * 1000)
|
||||
await this.say("api_req_retried")
|
||||
// Show countdown timer in error color
|
||||
for (let i = requestDelay; i > 0; i--) {
|
||||
await this.say("api_req_retry_delayed", `${errorMsg}\n\nRetrying in ${i} seconds...`, undefined, true)
|
||||
await delay(1000)
|
||||
}
|
||||
await this.say("api_req_retry_delayed", `${errorMsg}\n\nRetrying now...`, undefined, false)
|
||||
// delegate generator output from the recursive call
|
||||
yield* this.attemptApiRequest(previousApiReqIndex)
|
||||
return
|
||||
@@ -1069,6 +1108,16 @@ export class Cline {
|
||||
await this.browserSession.closeBrowser()
|
||||
}
|
||||
|
||||
// Validate tool use based on current mode
|
||||
const { mode } = await this.providerRef.deref()?.getState() ?? {}
|
||||
try {
|
||||
validateToolUse(block.name, mode ?? codeMode)
|
||||
} catch (error) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(formatResponse.toolError(error.message))
|
||||
break
|
||||
}
|
||||
|
||||
switch (block.name) {
|
||||
case "write_to_file": {
|
||||
const relPath: string | undefined = block.params.path
|
||||
@@ -2344,22 +2393,30 @@ export class Cline {
|
||||
// 2. ToolResultBlockParam's content/context text arrays if it contains "<feedback>" (see formatToolDeniedFeedback, attemptCompletion, executeCommand, and consecutiveMistakeCount >= 3) or "<answer>" (see askFollowupQuestion), we place all user generated content in these tags so they can effectively be used as markers for when we should parse mentions)
|
||||
Promise.all(
|
||||
userContent.map(async (block) => {
|
||||
const shouldProcessMentions = (text: string) =>
|
||||
text.includes("<task>") || text.includes("<feedback>");
|
||||
|
||||
if (block.type === "text") {
|
||||
return {
|
||||
...block,
|
||||
text: await parseMentions(block.text, cwd, this.urlContentFetcher),
|
||||
}
|
||||
} else if (block.type === "tool_result") {
|
||||
const isUserMessage = (text: string) => text.includes("<feedback>") || text.includes("<answer>")
|
||||
if (typeof block.content === "string" && isUserMessage(block.content)) {
|
||||
if (shouldProcessMentions(block.text)) {
|
||||
return {
|
||||
...block,
|
||||
content: await parseMentions(block.content, cwd, this.urlContentFetcher),
|
||||
text: await parseMentions(block.text, cwd, this.urlContentFetcher),
|
||||
}
|
||||
}
|
||||
return block;
|
||||
} else if (block.type === "tool_result") {
|
||||
if (typeof block.content === "string") {
|
||||
if (shouldProcessMentions(block.content)) {
|
||||
return {
|
||||
...block,
|
||||
content: await parseMentions(block.content, cwd, this.urlContentFetcher),
|
||||
}
|
||||
}
|
||||
return block;
|
||||
} else if (Array.isArray(block.content)) {
|
||||
const parsedContent = await Promise.all(
|
||||
block.content.map(async (contentBlock) => {
|
||||
if (contentBlock.type === "text" && isUserMessage(contentBlock.text)) {
|
||||
if (contentBlock.type === "text" && shouldProcessMentions(contentBlock.text)) {
|
||||
return {
|
||||
...contentBlock,
|
||||
text: await parseMentions(contentBlock.text, cwd, this.urlContentFetcher),
|
||||
@@ -2373,6 +2430,7 @@ export class Cline {
|
||||
content: parsedContent,
|
||||
}
|
||||
}
|
||||
return block;
|
||||
}
|
||||
return block
|
||||
}),
|
||||
@@ -2511,6 +2569,16 @@ export class Cline {
|
||||
const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? '+' : ''}${timeZoneOffset}:00`
|
||||
details += `\n\n# Current Time\n${formatter.format(now)} (${timeZone}, UTC${timeZoneOffsetStr})`
|
||||
|
||||
// Add current mode and any mode-specific warnings
|
||||
const { mode } = await this.providerRef.deref()?.getState() ?? {}
|
||||
const currentMode = mode ?? codeMode
|
||||
details += `\n\n# Current Mode\n${currentMode}`
|
||||
|
||||
// Add warning if not in code mode
|
||||
if (!isToolAllowedForMode('write_to_file', currentMode) || !isToolAllowedForMode('execute_command', currentMode)) {
|
||||
details += `\n\nNOTE: You are currently in '${currentMode}' mode which only allows read-only operations. To write files or execute commands, the user will need to switch to 'code' mode. Note that only the user can switch modes.`
|
||||
}
|
||||
|
||||
if (includeFileDetails) {
|
||||
details += `\n\n# Current Working Directory (${cwd.toPosix()}) Files\n`
|
||||
const isDesktop = arePathsEqual(cwd, path.join(os.homedir(), "Desktop"))
|
||||
|
||||
Reference in New Issue
Block a user