mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 13:21:07 -05:00
Merge branch 'main' into vs/support-unbound
This commit is contained in:
@@ -6,6 +6,7 @@ import { McpServer } from "./mcp"
|
||||
import { GitCommit } from "../utils/git"
|
||||
import { Mode, CustomModePrompts, ModeConfig } from "./modes"
|
||||
import { CustomSupportPrompts } from "./support-prompt"
|
||||
import { ExperimentId } from "./experiments"
|
||||
|
||||
export interface LanguageModelChatSelector {
|
||||
vendor?: string
|
||||
@@ -91,6 +92,7 @@ export interface ExtensionState {
|
||||
alwaysAllowBrowser?: boolean
|
||||
alwaysAllowMcp?: boolean
|
||||
alwaysApproveResubmit?: boolean
|
||||
alwaysAllowModeSwitch?: boolean
|
||||
requestDelaySeconds: number
|
||||
uriScheme?: string
|
||||
allowedCommands?: string[]
|
||||
@@ -107,7 +109,7 @@ export interface ExtensionState {
|
||||
mode: Mode
|
||||
modeApiConfigs?: Record<Mode, string>
|
||||
enhancementApiConfigId?: string
|
||||
experimentalDiffStrategy?: boolean
|
||||
experiments: Record<ExperimentId, boolean> // Map of experiment IDs to their enabled state
|
||||
autoApprovalEnabled?: boolean
|
||||
customModes: ModeConfig[]
|
||||
toolRequirements?: Record<string, boolean> // Map of tool names to their requirements (e.g. {"apply_diff": true} if diffEnabled)
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface WebviewMessage {
|
||||
| "refreshOpenAiModels"
|
||||
| "alwaysAllowBrowser"
|
||||
| "alwaysAllowMcp"
|
||||
| "alwaysAllowModeSwitch"
|
||||
| "playSound"
|
||||
| "soundEnabled"
|
||||
| "soundVolume"
|
||||
@@ -74,7 +75,7 @@ export interface WebviewMessage {
|
||||
| "getSystemPrompt"
|
||||
| "systemPrompt"
|
||||
| "enhancementApiConfigId"
|
||||
| "experimentalDiffStrategy"
|
||||
| "updateExperimental"
|
||||
| "autoApprovalEnabled"
|
||||
| "updateCustomMode"
|
||||
| "deleteCustomMode"
|
||||
|
||||
@@ -14,6 +14,12 @@ describe("isToolAllowedForMode", () => {
|
||||
roleDefinition: "You are a CSS editor",
|
||||
groups: ["read", ["edit", { fileRegex: "\\.css$" }], "browser"],
|
||||
},
|
||||
{
|
||||
slug: "test-exp-mode",
|
||||
name: "Test Exp Mode",
|
||||
roleDefinition: "You are an experimental tester",
|
||||
groups: ["read", "edit", "browser"],
|
||||
},
|
||||
]
|
||||
|
||||
it("allows always available tools", () => {
|
||||
@@ -240,6 +246,87 @@ describe("isToolAllowedForMode", () => {
|
||||
|
||||
expect(isToolAllowedForMode("write_to_file", "markdown-editor", customModes, toolRequirements)).toBe(false)
|
||||
})
|
||||
|
||||
describe("experimental tools", () => {
|
||||
it("disables tools when experiment is disabled", () => {
|
||||
const experiments = {
|
||||
search_and_replace: false,
|
||||
insert_code_block: false,
|
||||
}
|
||||
|
||||
expect(
|
||||
isToolAllowedForMode(
|
||||
"search_and_replace",
|
||||
"test-exp-mode",
|
||||
customModes,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
),
|
||||
).toBe(false)
|
||||
|
||||
expect(
|
||||
isToolAllowedForMode(
|
||||
"insert_code_block",
|
||||
"test-exp-mode",
|
||||
customModes,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it("allows tools when experiment is enabled", () => {
|
||||
const experiments = {
|
||||
search_and_replace: true,
|
||||
insert_code_block: true,
|
||||
}
|
||||
|
||||
expect(
|
||||
isToolAllowedForMode(
|
||||
"search_and_replace",
|
||||
"test-exp-mode",
|
||||
customModes,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
),
|
||||
).toBe(true)
|
||||
|
||||
expect(
|
||||
isToolAllowedForMode(
|
||||
"insert_code_block",
|
||||
"test-exp-mode",
|
||||
customModes,
|
||||
undefined,
|
||||
undefined,
|
||||
experiments,
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it("allows non-experimental tools when experiments are disabled", () => {
|
||||
const experiments = {
|
||||
search_and_replace: false,
|
||||
insert_code_block: false,
|
||||
}
|
||||
|
||||
expect(
|
||||
isToolAllowedForMode("read_file", "markdown-editor", customModes, undefined, undefined, experiments),
|
||||
).toBe(true)
|
||||
expect(
|
||||
isToolAllowedForMode(
|
||||
"write_to_file",
|
||||
"markdown-editor",
|
||||
customModes,
|
||||
undefined,
|
||||
{ path: "test.md" },
|
||||
experiments,
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("FileRestrictionError", () => {
|
||||
|
||||
69
src/shared/experiments.ts
Normal file
69
src/shared/experiments.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
export const EXPERIMENT_IDS = {
|
||||
DIFF_STRATEGY: "experimentalDiffStrategy",
|
||||
SEARCH_AND_REPLACE: "search_and_replace",
|
||||
INSERT_BLOCK: "insert_code_block",
|
||||
} as const
|
||||
|
||||
export type ExperimentKey = keyof typeof EXPERIMENT_IDS
|
||||
export type ExperimentId = valueof<typeof EXPERIMENT_IDS>
|
||||
|
||||
export interface ExperimentConfig {
|
||||
name: string
|
||||
description: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
type valueof<X> = X[keyof X]
|
||||
|
||||
export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
|
||||
DIFF_STRATEGY: {
|
||||
name: "Use experimental unified diff strategy",
|
||||
description:
|
||||
"Enable the experimental unified diff strategy. This strategy might reduce the number of retries caused by model errors but may cause unexpected behavior or incorrect edits. Only enable if you understand the risks and are willing to carefully review all changes.",
|
||||
enabled: false,
|
||||
},
|
||||
SEARCH_AND_REPLACE: {
|
||||
name: "Use experimental search and replace tool",
|
||||
description:
|
||||
"Enable the experimental search and replace tool, allowing Roo to replace multiple instances of a search term in one request.",
|
||||
enabled: false,
|
||||
},
|
||||
INSERT_BLOCK: {
|
||||
name: "Use experimental insert block tool",
|
||||
|
||||
description:
|
||||
"Enable the experimental insert block tool, allowing Roo to insert multiple code blocks at once at specific line numbers without needing to create a diff.",
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const experimentDefault = Object.fromEntries(
|
||||
Object.entries(experimentConfigsMap).map(([_, config]) => [
|
||||
EXPERIMENT_IDS[_ as keyof typeof EXPERIMENT_IDS] as ExperimentId,
|
||||
config.enabled,
|
||||
]),
|
||||
) as Record<ExperimentId, boolean>
|
||||
|
||||
export const experiments = {
|
||||
get: (id: ExperimentKey): ExperimentConfig | undefined => {
|
||||
return experimentConfigsMap[id]
|
||||
},
|
||||
isEnabled: (experimentsConfig: Record<ExperimentId, boolean>, id: ExperimentId): boolean => {
|
||||
return experimentsConfig[id] ?? experimentDefault[id]
|
||||
},
|
||||
} as const
|
||||
|
||||
// Expose experiment details for UI - pre-compute from map for better performance
|
||||
export const experimentLabels = Object.fromEntries(
|
||||
Object.entries(experimentConfigsMap).map(([_, config]) => [
|
||||
EXPERIMENT_IDS[_ as keyof typeof EXPERIMENT_IDS] as ExperimentId,
|
||||
config.name,
|
||||
]),
|
||||
) as Record<string, string>
|
||||
|
||||
export const experimentDescriptions = Object.fromEntries(
|
||||
Object.entries(experimentConfigsMap).map(([_, config]) => [
|
||||
EXPERIMENT_IDS[_ as keyof typeof EXPERIMENT_IDS] as ExperimentId,
|
||||
config.description,
|
||||
]),
|
||||
) as Record<string, string>
|
||||
@@ -6,6 +6,7 @@ interface ApiMetrics {
|
||||
totalCacheWrites?: number
|
||||
totalCacheReads?: number
|
||||
totalCost: number
|
||||
contextTokens: number // Total tokens in conversation (last message's tokensIn + tokensOut)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,8 +33,22 @@ export function getApiMetrics(messages: ClineMessage[]): ApiMetrics {
|
||||
totalCacheWrites: undefined,
|
||||
totalCacheReads: undefined,
|
||||
totalCost: 0,
|
||||
contextTokens: 0,
|
||||
}
|
||||
|
||||
// Find the last api_req_started message that has valid token information
|
||||
const lastApiReq = [...messages].reverse().find((message) => {
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
const parsedData = JSON.parse(message.text)
|
||||
return typeof parsedData.tokensIn === "number" && typeof parsedData.tokensOut === "number"
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
messages.forEach((message) => {
|
||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||
try {
|
||||
@@ -55,6 +70,14 @@ export function getApiMetrics(messages: ClineMessage[]): ApiMetrics {
|
||||
if (typeof cost === "number") {
|
||||
result.totalCost += cost
|
||||
}
|
||||
|
||||
// If this is the last api request, use its tokens for context size
|
||||
if (message === lastApiReq) {
|
||||
// Only update context tokens if both input and output tokens are non-zero
|
||||
if (tokensIn > 0 && tokensOut > 0) {
|
||||
result.contextTokens = tokensIn + tokensOut
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error)
|
||||
}
|
||||
|
||||
@@ -160,12 +160,19 @@ export function isToolAllowedForMode(
|
||||
customModes: ModeConfig[],
|
||||
toolRequirements?: Record<string, boolean>,
|
||||
toolParams?: Record<string, any>, // All tool parameters
|
||||
experiments?: Record<string, boolean>,
|
||||
): boolean {
|
||||
// Always allow these tools
|
||||
if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (experiments && tool in experiments) {
|
||||
if (!experiments[tool]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check tool requirements if any exist
|
||||
if (toolRequirements && tool in toolRequirements) {
|
||||
if (!toolRequirements[tool]) {
|
||||
@@ -198,7 +205,7 @@ export function isToolAllowedForMode(
|
||||
const filePath = toolParams?.path
|
||||
if (
|
||||
filePath &&
|
||||
(toolParams.diff || toolParams.content) &&
|
||||
(toolParams.diff || toolParams.content || toolParams.operations) &&
|
||||
!doesFileMatchRegex(filePath, options.fileRegex)
|
||||
) {
|
||||
throw new FileRestrictionError(mode.name, options.fileRegex, options.description, filePath)
|
||||
|
||||
@@ -21,7 +21,7 @@ export const TOOL_DISPLAY_NAMES = {
|
||||
// Define available tool groups
|
||||
export const TOOL_GROUPS: Record<string, ToolGroupValues> = {
|
||||
read: ["read_file", "search_files", "list_files", "list_code_definition_names"],
|
||||
edit: ["write_to_file", "apply_diff"],
|
||||
edit: ["write_to_file", "apply_diff", "insert_code_block", "search_and_replace"],
|
||||
browser: ["browser_action"],
|
||||
command: ["execute_command"],
|
||||
mcp: ["use_mcp_tool", "access_mcp_resource"],
|
||||
|
||||
Reference in New Issue
Block a user