Merge branch 'RooCodeInc:main' into main

This commit is contained in:
Murilo Pires 2025-07-22 12:33:07 -03:00 committed by GitHub
commit ed3a077c81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 1135 additions and 62 deletions

View file

@ -494,7 +494,7 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
signal: AbortSignal.timeout(10000),
})
if (response.status >= 400 && response.status < 500) {
if (response.status === 401 || response.status === 404) {
throw new InvalidClientTokenError()
} else if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)

View file

@ -12,6 +12,12 @@ describe("getApiProtocol", () => {
expect(getApiProtocol("claude-code")).toBe("anthropic")
expect(getApiProtocol("claude-code", "some-model")).toBe("anthropic")
})
it("should return 'anthropic' for bedrock provider", () => {
expect(getApiProtocol("bedrock")).toBe("anthropic")
expect(getApiProtocol("bedrock", "gpt-4")).toBe("anthropic")
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
})
})
describe("Vertex provider with Claude models", () => {
@ -27,25 +33,14 @@ describe("getApiProtocol", () => {
expect(getApiProtocol("vertex", "gemini-pro")).toBe("openai")
expect(getApiProtocol("vertex", "llama-2")).toBe("openai")
})
})
describe("Bedrock provider with Claude models", () => {
it("should return 'anthropic' for bedrock provider with claude models", () => {
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("bedrock", "Claude-3-Sonnet")).toBe("anthropic")
expect(getApiProtocol("bedrock", "CLAUDE-instant")).toBe("anthropic")
expect(getApiProtocol("bedrock", "anthropic.claude-v2")).toBe("anthropic")
})
it("should return 'openai' for bedrock provider with non-claude models", () => {
expect(getApiProtocol("bedrock", "gpt-4")).toBe("openai")
expect(getApiProtocol("bedrock", "titan-text")).toBe("openai")
expect(getApiProtocol("bedrock", "llama-2")).toBe("openai")
it("should return 'openai' for vertex provider without model", () => {
expect(getApiProtocol("vertex")).toBe("openai")
})
})
describe("Other providers with Claude models", () => {
it("should return 'openai' for non-vertex/bedrock providers with claude models", () => {
describe("Other providers", () => {
it("should return 'openai' for non-anthropic providers regardless of model", () => {
expect(getApiProtocol("openrouter", "claude-3-opus")).toBe("openai")
expect(getApiProtocol("openai", "claude-3-sonnet")).toBe("openai")
expect(getApiProtocol("litellm", "claude-instant")).toBe("openai")
@ -59,20 +54,13 @@ describe("getApiProtocol", () => {
expect(getApiProtocol(undefined, "claude-3-opus")).toBe("openai")
})
it("should return 'openai' when model is undefined", () => {
expect(getApiProtocol("openai")).toBe("openai")
expect(getApiProtocol("vertex")).toBe("openai")
expect(getApiProtocol("bedrock")).toBe("openai")
})
it("should handle empty strings", () => {
expect(getApiProtocol("vertex", "")).toBe("openai")
expect(getApiProtocol("bedrock", "")).toBe("openai")
})
it("should be case-insensitive for claude detection", () => {
expect(getApiProtocol("vertex", "CLAUDE-3-OPUS")).toBe("anthropic")
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("vertex", "ClAuDe-InStAnT")).toBe("anthropic")
})
})

View file

@ -159,6 +159,7 @@ export const SECRET_STATE_KEYS = [
"geminiApiKey",
"openAiNativeApiKey",
"deepSeekApiKey",
"moonshotApiKey",
"mistralApiKey",
"unboundApiKey",
"requestyApiKey",

View file

@ -22,6 +22,7 @@ export const providerNames = [
"gemini-cli",
"openai-native",
"mistral",
"moonshot",
"deepseek",
"unbound",
"requesty",
@ -61,6 +62,7 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
const baseProviderSettingsSchema = z.object({
includeMaxTokens: z.boolean().optional(),
diffEnabled: z.boolean().optional(),
todoListEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
modelTemperature: z.number().nullish(),
rateLimitSeconds: z.number().optional(),
@ -186,6 +188,13 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({
deepSeekApiKey: z.string().optional(),
})
const moonshotSchema = apiModelIdProviderModelSchema.extend({
moonshotBaseUrl: z
.union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")])
.optional(),
moonshotApiKey: z.string().optional(),
})
const unboundSchema = baseProviderSettingsSchema.extend({
unboundApiKey: z.string().optional(),
unboundModelId: z.string().optional(),
@ -240,6 +249,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })),
mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
unboundSchema.merge(z.object({ apiProvider: z.literal("unbound") })),
requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
humanRelaySchema.merge(z.object({ apiProvider: z.literal("human-relay") })),
@ -268,6 +278,7 @@ export const providerSettingsSchema = z.object({
...openAiNativeSchema.shape,
...mistralSchema.shape,
...deepSeekSchema.shape,
...moonshotSchema.shape,
...unboundSchema.shape,
...requestySchema.shape,
...humanRelaySchema.shape,
@ -301,7 +312,7 @@ export const getModelId = (settings: ProviderSettings): string | undefined => {
}
// Providers that use Anthropic-style API protocol
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code"]
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock"]
// Helper function to determine API protocol for a provider and model
export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => {
@ -310,13 +321,8 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str
return "anthropic"
}
// For vertex and bedrock providers, check if the model ID contains "claude" (case-insensitive)
if (
provider &&
(provider === "vertex" || provider === "bedrock") &&
modelId &&
modelId.toLowerCase().includes("claude")
) {
// For vertex provider, check if the model ID contains "claude" (case-insensitive)
if (provider && provider === "vertex" && modelId && modelId.toLowerCase().includes("claude")) {
return "anthropic"
}

View file

@ -9,6 +9,7 @@ export * from "./groq.js"
export * from "./lite-llm.js"
export * from "./lm-studio.js"
export * from "./mistral.js"
export * from "./moonshot.js"
export * from "./ollama.js"
export * from "./openai.js"
export * from "./openrouter.js"

View file

@ -0,0 +1,22 @@
import type { ModelInfo } from "../model.js"
// https://platform.moonshot.ai/
export type MoonshotModelId = keyof typeof moonshotModels
export const moonshotDefaultModelId: MoonshotModelId = "kimi-k2-0711-preview"
export const moonshotModels = {
"kimi-k2-0711-preview": {
maxTokens: 32_000,
contextWindow: 131_072,
supportsImages: false,
supportsPromptCache: true,
inputPrice: 0.6, // $0.60 per million tokens (cache miss)
outputPrice: 2.5, // $2.50 per million tokens
cacheWritesPrice: 0, // $0 per million tokens (cache miss)
cacheReadsPrice: 0.15, // $0.15 per million tokens (cache hit)
description: `Kimi K2 is a state-of-the-art mixture-of-experts (MoE) language model with 32 billion activated parameters and 1 trillion total parameters.`,
},
} as const satisfies Record<string, ModelInfo>
export const MOONSHOT_DEFAULT_TEMPERATURE = 0.6

View file

@ -17,6 +17,7 @@ import {
GeminiHandler,
OpenAiNativeHandler,
DeepSeekHandler,
MoonshotHandler,
MistralHandler,
VsCodeLmHandler,
UnboundHandler,
@ -89,6 +90,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new OpenAiNativeHandler(options)
case "deepseek":
return new DeepSeekHandler(options)
case "moonshot":
return new MoonshotHandler(options)
case "vscode-lm":
return new VsCodeLmHandler(options)
case "mistral":
@ -110,6 +113,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
case "litellm":
return new LiteLLMHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)
}
}

View file

@ -0,0 +1,297 @@
// Mocks must come first, before imports
const mockCreate = vi.fn()
vi.mock("openai", () => {
return {
__esModule: true,
default: vi.fn().mockImplementation(() => ({
chat: {
completions: {
create: mockCreate.mockImplementation(async (options) => {
if (!options.stream) {
return {
id: "test-completion",
choices: [
{
message: { role: "assistant", content: "Test response", refusal: null },
finish_reason: "stop",
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
cached_tokens: 2,
},
}
}
// Return async iterator for streaming
return {
[Symbol.asyncIterator]: async function* () {
yield {
choices: [
{
delta: { content: "Test response" },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: {},
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
cached_tokens: 2,
},
}
},
}
}),
},
},
})),
}
})
import OpenAI from "openai"
import type { Anthropic } from "@anthropic-ai/sdk"
import { moonshotDefaultModelId } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../../shared/api"
import { MoonshotHandler } from "../moonshot"
describe("MoonshotHandler", () => {
let handler: MoonshotHandler
let mockOptions: ApiHandlerOptions
beforeEach(() => {
mockOptions = {
moonshotApiKey: "test-api-key",
apiModelId: "moonshot-chat",
moonshotBaseUrl: "https://api.moonshot.ai/v1",
}
handler = new MoonshotHandler(mockOptions)
vi.clearAllMocks()
})
describe("constructor", () => {
it("should initialize with provided options", () => {
expect(handler).toBeInstanceOf(MoonshotHandler)
expect(handler.getModel().id).toBe(mockOptions.apiModelId)
})
it.skip("should throw error if API key is missing", () => {
expect(() => {
new MoonshotHandler({
...mockOptions,
moonshotApiKey: undefined,
})
}).toThrow("Moonshot API key is required")
})
it("should use default model ID if not provided", () => {
const handlerWithoutModel = new MoonshotHandler({
...mockOptions,
apiModelId: undefined,
})
expect(handlerWithoutModel.getModel().id).toBe(moonshotDefaultModelId)
})
it("should use default base URL if not provided", () => {
const handlerWithoutBaseUrl = new MoonshotHandler({
...mockOptions,
moonshotBaseUrl: undefined,
})
expect(handlerWithoutBaseUrl).toBeInstanceOf(MoonshotHandler)
// The base URL is passed to OpenAI client internally
expect(OpenAI).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "https://api.moonshot.ai/v1",
}),
)
})
it("should use chinese base URL if provided", () => {
const customBaseUrl = "https://api.moonshot.cn/v1"
const handlerWithCustomUrl = new MoonshotHandler({
...mockOptions,
moonshotBaseUrl: customBaseUrl,
})
expect(handlerWithCustomUrl).toBeInstanceOf(MoonshotHandler)
// The custom base URL is passed to OpenAI client
expect(OpenAI).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: customBaseUrl,
}),
)
})
it("should set includeMaxTokens to true", () => {
// Create a new handler and verify OpenAI client was called with includeMaxTokens
const _handler = new MoonshotHandler(mockOptions)
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: mockOptions.moonshotApiKey }))
})
})
describe("getModel", () => {
it("should return model info for valid model ID", () => {
const model = handler.getModel()
expect(model.id).toBe(mockOptions.apiModelId)
expect(model.info).toBeDefined()
expect(model.info.maxTokens).toBe(32_000)
expect(model.info.contextWindow).toBe(131_072)
expect(model.info.supportsImages).toBe(false)
expect(model.info.supportsPromptCache).toBe(true) // Should be true now
})
it("should return provided model ID with default model info if model does not exist", () => {
const handlerWithInvalidModel = new MoonshotHandler({
...mockOptions,
apiModelId: "invalid-model",
})
const model = handlerWithInvalidModel.getModel()
expect(model.id).toBe("invalid-model") // Returns provided ID
expect(model.info).toBeDefined()
// With the current implementation, it's the same object reference when using default model info
expect(model.info).toBe(handler.getModel().info)
// Should have the same base properties
expect(model.info.contextWindow).toBe(handler.getModel().info.contextWindow)
// And should have supportsPromptCache set to true
expect(model.info.supportsPromptCache).toBe(true)
})
it("should return default model if no model ID is provided", () => {
const handlerWithoutModel = new MoonshotHandler({
...mockOptions,
apiModelId: undefined,
})
const model = handlerWithoutModel.getModel()
expect(model.id).toBe(moonshotDefaultModelId)
expect(model.info).toBeDefined()
expect(model.info.supportsPromptCache).toBe(true)
})
it("should include model parameters from getModelParams", () => {
const model = handler.getModel()
expect(model).toHaveProperty("temperature")
expect(model).toHaveProperty("maxTokens")
})
})
describe("createMessage", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
{
role: "user",
content: [
{
type: "text" as const,
text: "Hello!",
},
],
},
]
it("should handle streaming responses", async () => {
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
expect(chunks.length).toBeGreaterThan(0)
const textChunks = chunks.filter((chunk) => chunk.type === "text")
expect(textChunks).toHaveLength(1)
expect(textChunks[0].text).toBe("Test response")
})
it("should include usage information", async () => {
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
const usageChunks = chunks.filter((chunk) => chunk.type === "usage")
expect(usageChunks.length).toBeGreaterThan(0)
expect(usageChunks[0].inputTokens).toBe(10)
expect(usageChunks[0].outputTokens).toBe(5)
})
it("should include cache metrics in usage information", async () => {
const stream = handler.createMessage(systemPrompt, messages)
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
const usageChunks = chunks.filter((chunk) => chunk.type === "usage")
expect(usageChunks.length).toBeGreaterThan(0)
expect(usageChunks[0].cacheWriteTokens).toBe(0)
expect(usageChunks[0].cacheReadTokens).toBe(2)
})
})
describe("processUsageMetrics", () => {
it("should correctly process usage metrics including cache information", () => {
// We need to access the protected method, so we'll create a test subclass
class TestMoonshotHandler extends MoonshotHandler {
public testProcessUsageMetrics(usage: any) {
return this.processUsageMetrics(usage)
}
}
const testHandler = new TestMoonshotHandler(mockOptions)
const usage = {
prompt_tokens: 100,
completion_tokens: 50,
total_tokens: 150,
cached_tokens: 20,
}
const result = testHandler.testProcessUsageMetrics(usage)
expect(result.type).toBe("usage")
expect(result.inputTokens).toBe(100)
expect(result.outputTokens).toBe(50)
expect(result.cacheWriteTokens).toBe(0)
expect(result.cacheReadTokens).toBe(20)
})
it("should handle missing cache metrics gracefully", () => {
class TestMoonshotHandler extends MoonshotHandler {
public testProcessUsageMetrics(usage: any) {
return this.processUsageMetrics(usage)
}
}
const testHandler = new TestMoonshotHandler(mockOptions)
const usage = {
prompt_tokens: 100,
completion_tokens: 50,
total_tokens: 150,
// No cached_tokens
}
const result = testHandler.testProcessUsageMetrics(usage)
expect(result.type).toBe("usage")
expect(result.inputTokens).toBe(100)
expect(result.outputTokens).toBe(50)
expect(result.cacheWriteTokens).toBe(0)
expect(result.cacheReadTokens).toBeUndefined()
})
})
})

View file

@ -4,6 +4,7 @@ export { AwsBedrockHandler } from "./bedrock"
export { ChutesHandler } from "./chutes"
export { ClaudeCodeHandler } from "./claude-code"
export { DeepSeekHandler } from "./deepseek"
export { MoonshotHandler } from "./moonshot"
export { FakeAIHandler } from "./fake-ai"
export { GeminiHandler } from "./gemini"
export { GlamaHandler } from "./glama"

View file

@ -0,0 +1,39 @@
import { moonshotModels, moonshotDefaultModelId } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
import type { ApiStreamUsageChunk } from "../transform/stream"
import { getModelParams } from "../transform/model-params"
import { OpenAiHandler } from "./openai"
export class MoonshotHandler extends OpenAiHandler {
constructor(options: ApiHandlerOptions) {
super({
...options,
openAiApiKey: options.moonshotApiKey ?? "not-provided",
openAiModelId: options.apiModelId ?? moonshotDefaultModelId,
openAiBaseUrl: options.moonshotBaseUrl ?? "https://api.moonshot.ai/v1",
openAiStreamingEnabled: true,
includeMaxTokens: true,
})
}
override getModel() {
const id = this.options.apiModelId ?? moonshotDefaultModelId
const info = moonshotModels[id as keyof typeof moonshotModels] || moonshotModels[moonshotDefaultModelId]
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
return { id, info, ...params }
}
// Override to handle Moonshot's usage metrics, including caching.
protected override processUsageMetrics(usage: any): ApiStreamUsageChunk {
return {
type: "usage",
inputTokens: usage?.prompt_tokens || 0,
outputTokens: usage?.completion_tokens || 0,
cacheWriteTokens: 0,
cacheReadTokens: usage?.cached_tokens,
}
}
}

View file

@ -28,6 +28,7 @@ export const providerProfilesSchema = z.object({
diffSettingsMigrated: z.boolean().optional(),
openAiHeadersMigrated: z.boolean().optional(),
consecutiveMistakeLimitMigrated: z.boolean().optional(),
todoListEnabledMigrated: z.boolean().optional(),
})
.optional(),
})
@ -51,6 +52,7 @@ export class ProviderSettingsManager {
diffSettingsMigrated: true, // Mark as migrated on fresh installs
openAiHeadersMigrated: true, // Mark as migrated on fresh installs
consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs
todoListEnabledMigrated: true, // Mark as migrated on fresh installs
},
}
@ -117,6 +119,7 @@ export class ProviderSettingsManager {
diffSettingsMigrated: false,
openAiHeadersMigrated: false,
consecutiveMistakeLimitMigrated: false,
todoListEnabledMigrated: false,
} // Initialize with default values
isDirty = true
}
@ -145,6 +148,12 @@ export class ProviderSettingsManager {
isDirty = true
}
if (!providerProfiles.migrations.todoListEnabledMigrated) {
await this.migrateTodoListEnabled(providerProfiles)
providerProfiles.migrations.todoListEnabledMigrated = true
isDirty = true
}
if (isDirty) {
await this.store(providerProfiles)
}
@ -250,6 +259,18 @@ export class ProviderSettingsManager {
}
}
private async migrateTodoListEnabled(providerProfiles: ProviderProfiles) {
try {
for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
if (apiConfig.todoListEnabled === undefined) {
apiConfig.todoListEnabled = true
}
}
} catch (error) {
console.error(`[MigrateTodoListEnabled] Failed to migrate todo list enabled setting:`, error)
}
}
/**
* List all available configs with metadata.
*/

View file

@ -67,6 +67,7 @@ describe("ProviderSettingsManager", () => {
diffSettingsMigrated: true,
openAiHeadersMigrated: true,
consecutiveMistakeLimitMigrated: true,
todoListEnabledMigrated: true,
},
}),
)
@ -186,6 +187,48 @@ describe("ProviderSettingsManager", () => {
expect(storedConfig.migrations.consecutiveMistakeLimitMigrated).toEqual(true)
})
it("should call migrateTodoListEnabled if it has not done so already", async () => {
mockSecrets.get.mockResolvedValue(
JSON.stringify({
currentApiConfigName: "default",
apiConfigs: {
default: {
config: {},
id: "default",
todoListEnabled: undefined,
},
test: {
apiProvider: "anthropic",
todoListEnabled: undefined,
},
existing: {
apiProvider: "anthropic",
// this should not really be possible, unless someone has loaded a hand edited config,
// but we don't overwrite so we'll check that
todoListEnabled: false,
},
},
migrations: {
rateLimitSecondsMigrated: true,
diffSettingsMigrated: true,
openAiHeadersMigrated: true,
consecutiveMistakeLimitMigrated: true,
todoListEnabledMigrated: false,
},
}),
)
await providerSettingsManager.initialize()
// Get the last call to store, which should contain the migrated config
const calls = mockSecrets.store.mock.calls
const storedConfig = JSON.parse(calls[calls.length - 1][1])
expect(storedConfig.apiConfigs.default.todoListEnabled).toEqual(true)
expect(storedConfig.apiConfigs.test.todoListEnabled).toEqual(true)
expect(storedConfig.apiConfigs.existing.todoListEnabled).toEqual(false)
expect(storedConfig.migrations.todoListEnabledMigrated).toEqual(true)
})
it("should throw error if secrets storage fails", async () => {
mockSecrets.get.mockRejectedValue(new Error("Storage failed"))

View file

@ -555,6 +555,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -555,6 +555,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -623,6 +623,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -560,6 +560,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -555,6 +555,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -611,6 +611,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -555,6 +555,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -643,6 +643,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -555,6 +555,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -611,6 +611,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -623,6 +623,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -555,6 +555,8 @@ Mode-specific Instructions:
- Focused on a single, well-defined outcome
- Clear enough that another mode could execute it independently
**Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.
4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.
5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.

View file

@ -79,7 +79,7 @@ __setMockImplementation(
globalCustomInstructions: string,
cwd: string,
mode: string,
options?: { language?: string },
options?: { language?: string; rooIgnoreInstructions?: string; settings?: Record<string, any> },
) => {
const sections = []
@ -575,6 +575,94 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt.indexOf(modes[0].roleDefinition)).toBeLessThan(prompt.indexOf("TOOL USE"))
})
it("should exclude update_todo_list tool when todoListEnabled is false", async () => {
const settings = {
todoListEnabled: false,
}
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)
// Should not contain the tool description
expect(prompt).not.toContain("## update_todo_list")
// Mode instructions will still reference the tool with a fallback to markdown
})
it("should include update_todo_list tool when todoListEnabled is true", async () => {
const settings = {
todoListEnabled: true,
}
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)
expect(prompt).toContain("update_todo_list")
expect(prompt).toContain("## update_todo_list")
})
it("should include update_todo_list tool when todoListEnabled is undefined", async () => {
const settings = {
// todoListEnabled not set
}
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)
expect(prompt).toContain("update_todo_list")
expect(prompt).toContain("## update_todo_list")
})
afterAll(() => {
vi.restoreAllMocks()
})

View file

@ -1033,6 +1033,157 @@ describe("Rules directory reading", () => {
expect(result).toContain("content of file3")
})
it("should return files in alphabetical order by filename", async () => {
// Simulate .roo/rules directory exists
statMock.mockResolvedValueOnce({
isDirectory: vi.fn().mockReturnValue(true),
} as any)
// Simulate listing files in non-alphabetical order to test sorting
readdirMock.mockResolvedValueOnce([
{ name: "zebra.txt", isFile: () => true, parentPath: "/fake/path/.roo/rules" },
{ name: "alpha.txt", isFile: () => true, parentPath: "/fake/path/.roo/rules" },
{ name: "Beta.txt", isFile: () => true, parentPath: "/fake/path/.roo/rules" }, // Test case-insensitive sorting
] as any)
statMock.mockImplementation((path) => {
return Promise.resolve({
isFile: vi.fn().mockReturnValue(true),
}) as any
})
readFileMock.mockImplementation((filePath: PathLike) => {
const pathStr = filePath.toString()
const normalizedPath = pathStr.replace(/\\/g, "/")
if (normalizedPath === "/fake/path/.roo/rules/zebra.txt") {
return Promise.resolve("zebra content")
}
if (normalizedPath === "/fake/path/.roo/rules/alpha.txt") {
return Promise.resolve("alpha content")
}
if (normalizedPath === "/fake/path/.roo/rules/Beta.txt") {
return Promise.resolve("beta content")
}
return Promise.reject({ code: "ENOENT" })
})
const result = await loadRuleFiles("/fake/path")
// Files should appear in alphabetical order: alpha.txt, Beta.txt, zebra.txt
const alphaIndex = result.indexOf("alpha content")
const betaIndex = result.indexOf("beta content")
const zebraIndex = result.indexOf("zebra content")
expect(alphaIndex).toBeLessThan(betaIndex)
expect(betaIndex).toBeLessThan(zebraIndex)
// Verify the expected file paths are in the result
const expectedAlphaPath =
process.platform === "win32" ? "\\fake\\path\\.roo\\rules\\alpha.txt" : "/fake/path/.roo/rules/alpha.txt"
const expectedBetaPath =
process.platform === "win32" ? "\\fake\\path\\.roo\\rules\\Beta.txt" : "/fake/path/.roo/rules/Beta.txt"
const expectedZebraPath =
process.platform === "win32" ? "\\fake\\path\\.roo\\rules\\zebra.txt" : "/fake/path/.roo/rules/zebra.txt"
expect(result).toContain(`# Rules from ${expectedAlphaPath}:`)
expect(result).toContain(`# Rules from ${expectedBetaPath}:`)
expect(result).toContain(`# Rules from ${expectedZebraPath}:`)
})
it("should sort symlinks by their symlink names, not target names", async () => {
// Reset mocks
statMock.mockReset()
readdirMock.mockReset()
readlinkMock.mockReset()
readFileMock.mockReset()
// First call: check if .roo/rules directory exists
statMock.mockResolvedValueOnce({
isDirectory: vi.fn().mockReturnValue(true),
} as any)
// Simulate listing files with symlinks that point to files with different names
readdirMock.mockResolvedValueOnce([
{
name: "01-first.link",
isFile: () => false,
isSymbolicLink: () => true,
parentPath: "/fake/path/.roo/rules",
},
{
name: "02-second.link",
isFile: () => false,
isSymbolicLink: () => true,
parentPath: "/fake/path/.roo/rules",
},
{
name: "03-third.link",
isFile: () => false,
isSymbolicLink: () => true,
parentPath: "/fake/path/.roo/rules",
},
] as any)
// Mock readlink to return target paths that would sort differently than symlink names
readlinkMock
.mockResolvedValueOnce("../../targets/zzz-last.txt") // 01-first.link -> zzz-last.txt
.mockResolvedValueOnce("../../targets/aaa-first.txt") // 02-second.link -> aaa-first.txt
.mockResolvedValueOnce("../../targets/mmm-middle.txt") // 03-third.link -> mmm-middle.txt
// Set up stat mock for the remaining calls
statMock.mockImplementation((path) => {
const normalizedPath = path.toString().replace(/\\/g, "/")
// Target files exist and are files
if (normalizedPath.endsWith(".txt")) {
return Promise.resolve({
isFile: vi.fn().mockReturnValue(true),
isDirectory: vi.fn().mockReturnValue(false),
} as any)
}
return Promise.resolve({
isFile: vi.fn().mockReturnValue(false),
isDirectory: vi.fn().mockReturnValue(false),
} as any)
})
readFileMock.mockImplementation((filePath: PathLike) => {
const pathStr = filePath.toString()
const normalizedPath = pathStr.replace(/\\/g, "/")
if (normalizedPath.endsWith("zzz-last.txt")) {
return Promise.resolve("content from zzz-last.txt")
}
if (normalizedPath.endsWith("aaa-first.txt")) {
return Promise.resolve("content from aaa-first.txt")
}
if (normalizedPath.endsWith("mmm-middle.txt")) {
return Promise.resolve("content from mmm-middle.txt")
}
return Promise.reject({ code: "ENOENT" })
})
const result = await loadRuleFiles("/fake/path")
// Content should appear in order of symlink names (01-first, 02-second, 03-third)
// NOT in order of target names (aaa-first, mmm-middle, zzz-last)
const firstIndex = result.indexOf("content from zzz-last.txt") // from 01-first.link
const secondIndex = result.indexOf("content from aaa-first.txt") // from 02-second.link
const thirdIndex = result.indexOf("content from mmm-middle.txt") // from 03-third.link
// All content should be found
expect(firstIndex).toBeGreaterThan(-1)
expect(secondIndex).toBeGreaterThan(-1)
expect(thirdIndex).toBeGreaterThan(-1)
// And they should be in the order of symlink names, not target names
expect(firstIndex).toBeLessThan(secondIndex)
expect(secondIndex).toBeLessThan(thirdIndex)
// Verify the target paths are shown (not symlink paths)
expect(result).toContain("zzz-last.txt")
expect(result).toContain("aaa-first.txt")
expect(result).toContain("mmm-middle.txt")
})
it("should handle empty file list gracefully", async () => {
// Simulate .roo/rules directory exists
statMock.mockResolvedValueOnce({

View file

@ -44,7 +44,7 @@ const MAX_DEPTH = 5
async function resolveDirectoryEntry(
entry: Dirent,
dirPath: string,
filePaths: string[],
fileInfo: Array<{ originalPath: string; resolvedPath: string }>,
depth: number,
): Promise<void> {
// Avoid cyclic symlinks
@ -54,44 +54,49 @@ async function resolveDirectoryEntry(
const fullPath = path.resolve(entry.parentPath || dirPath, entry.name)
if (entry.isFile()) {
// Regular file
filePaths.push(fullPath)
// Regular file - both original and resolved paths are the same
fileInfo.push({ originalPath: fullPath, resolvedPath: fullPath })
} else if (entry.isSymbolicLink()) {
// Await the resolution of the symbolic link
await resolveSymLink(fullPath, filePaths, depth + 1)
await resolveSymLink(fullPath, fileInfo, depth + 1)
}
}
/**
* Recursively resolve a symbolic link and collect file paths
*/
async function resolveSymLink(fullPath: string, filePaths: string[], depth: number): Promise<void> {
async function resolveSymLink(
symlinkPath: string,
fileInfo: Array<{ originalPath: string; resolvedPath: string }>,
depth: number,
): Promise<void> {
// Avoid cyclic symlinks
if (depth > MAX_DEPTH) {
return
}
try {
// Get the symlink target
const linkTarget = await fs.readlink(fullPath)
const linkTarget = await fs.readlink(symlinkPath)
// Resolve the target path (relative to the symlink location)
const resolvedTarget = path.resolve(path.dirname(fullPath), linkTarget)
const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget)
// Check if the target is a file
const stats = await fs.stat(resolvedTarget)
if (stats.isFile()) {
filePaths.push(resolvedTarget)
// For symlinks to files, store the symlink path as original and target as resolved
fileInfo.push({ originalPath: symlinkPath, resolvedPath: resolvedTarget })
} else if (stats.isDirectory()) {
const anotherEntries = await fs.readdir(resolvedTarget, { withFileTypes: true, recursive: true })
// Collect promises for recursive calls within the directory
const directoryPromises: Promise<void>[] = []
for (const anotherEntry of anotherEntries) {
directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, filePaths, depth + 1))
directoryPromises.push(resolveDirectoryEntry(anotherEntry, resolvedTarget, fileInfo, depth + 1))
}
// Wait for all entries in the resolved directory to be processed
await Promise.all(directoryPromises)
} else if (stats.isSymbolicLink()) {
// Handle nested symlinks by awaiting the recursive call
await resolveSymLink(resolvedTarget, filePaths, depth + 1)
await resolveSymLink(resolvedTarget, fileInfo, depth + 1)
}
} catch (err) {
// Skip invalid symlinks
@ -106,29 +111,31 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
const entries = await fs.readdir(dirPath, { withFileTypes: true, recursive: true })
// Process all entries - regular files and symlinks that might point to files
const filePaths: string[] = []
// Store both original path (for sorting) and resolved path (for reading)
const fileInfo: Array<{ originalPath: string; resolvedPath: string }> = []
// Collect promises for the initial resolution calls
const initialPromises: Promise<void>[] = []
for (const entry of entries) {
initialPromises.push(resolveDirectoryEntry(entry, dirPath, filePaths, 0))
initialPromises.push(resolveDirectoryEntry(entry, dirPath, fileInfo, 0))
}
// Wait for all asynchronous operations (including recursive ones) to complete
await Promise.all(initialPromises)
const fileContents = await Promise.all(
filePaths.map(async (file) => {
fileInfo.map(async ({ originalPath, resolvedPath }) => {
try {
// Check if it's a file (not a directory)
const stats = await fs.stat(file)
const stats = await fs.stat(resolvedPath)
if (stats.isFile()) {
// Filter out cache files and system files that shouldn't be in rules
if (!shouldIncludeRuleFile(file)) {
if (!shouldIncludeRuleFile(resolvedPath)) {
return null
}
const content = await safeReadFile(file)
return { filename: file, content }
const content = await safeReadFile(resolvedPath)
// Use resolvedPath for display to maintain existing behavior
return { filename: resolvedPath, content, sortKey: originalPath }
}
return null
} catch (err) {
@ -138,7 +145,19 @@ async function readTextFilesFromDirectory(dirPath: string): Promise<Array<{ file
)
// Filter out null values (directories, failed reads, or excluded files)
return fileContents.filter((item): item is { filename: string; content: string } => item !== null)
const filteredFiles = fileContents.filter(
(item): item is { filename: string; content: string; sortKey: string } => item !== null,
)
// Sort files alphabetically by the original filename (case-insensitive) to ensure consistent order
// For symlinks, this will use the symlink name, not the target name
return filteredFiles
.sort((a, b) => {
const filenameA = path.basename(a.sortKey).toLowerCase()
const filenameB = path.basename(b.sortKey).toLowerCase()
return filenameA.localeCompare(filenameB)
})
.map(({ filename, content }) => ({ filename, content }))
} catch (err) {
return []
}
@ -200,7 +219,7 @@ export async function addCustomInstructions(
globalCustomInstructions: string,
cwd: string,
mode: string,
options: { language?: string; rooIgnoreInstructions?: string } = {},
options: { language?: string; rooIgnoreInstructions?: string; settings?: Record<string, any> } = {},
): Promise<string> {
const sections = []

View file

@ -119,7 +119,7 @@ ${getSystemInfoSection(cwd)}
${getObjectiveSection(codeIndexManager, experiments)}
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions })}`
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings })}`
return basePrompt
}
@ -177,7 +177,7 @@ export const SYSTEM_PROMPT = async (
globalCustomInstructions || "",
cwd,
mode,
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions },
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings },
)
// For file-based prompts, don't include the tool sections

View file

@ -109,6 +109,11 @@ export function getToolDescriptionsForMode(
tools.delete("codebase_search")
}
// Conditionally exclude update_todo_list if disabled in settings
if (settings?.todoListEnabled === false) {
tools.delete("update_todo_list")
}
// Map tool descriptions for allowed tools
const descriptions = Array.from(tools).map((toolName) => {
const descriptionFn = toolDescriptionMap[toolName]

View file

@ -1103,9 +1103,9 @@ describe("Sliding Window", () => {
expect(result2.prevContextTokens).toBe(50001)
})
it("should use 20% of context window as buffer when maxTokens is undefined", async () => {
it("should use ANTHROPIC_DEFAULT_MAX_TOKENS as buffer when maxTokens is undefined", async () => {
const modelInfo = createModelInfo(100000, undefined)
// Max tokens = 100000 - (100000 * 0.2) = 80000
// Max tokens = 100000 - ANTHROPIC_DEFAULT_MAX_TOKENS = 100000 - 8192 = 91808
// Create messages with very small content in the last one to avoid token overflow
const messagesWithSmallContent = [
@ -1117,7 +1117,7 @@ describe("Sliding Window", () => {
// Below max tokens and buffer - no truncation
const result1 = await truncateConversationIfNeeded({
messages: messagesWithSmallContent,
totalTokens: 69999, // Well below threshold + dynamic buffer
totalTokens: 81807, // Well below threshold + dynamic buffer (91808 - 10000 = 81808)
contextWindow: modelInfo.contextWindow,
maxTokens: modelInfo.maxTokens,
apiHandler: mockApiHandler,
@ -1132,13 +1132,13 @@ describe("Sliding Window", () => {
messages: messagesWithSmallContent,
summary: "",
cost: 0,
prevContextTokens: 69999,
prevContextTokens: 81807,
})
// Above max tokens - truncate
const result2 = await truncateConversationIfNeeded({
messages: messagesWithSmallContent,
totalTokens: 80001, // Above threshold
totalTokens: 81809, // Above threshold (81808)
contextWindow: modelInfo.contextWindow,
maxTokens: modelInfo.maxTokens,
apiHandler: mockApiHandler,
@ -1153,7 +1153,7 @@ describe("Sliding Window", () => {
expect(result2.messages.length).toBe(3) // Truncated with 0.5 fraction
expect(result2.summary).toBe("")
expect(result2.cost).toBe(0)
expect(result2.prevContextTokens).toBe(80001)
expect(result2.prevContextTokens).toBe(81809)
})
it("should handle small context windows appropriately", async () => {

View file

@ -5,6 +5,7 @@ import { TelemetryService } from "@roo-code/telemetry"
import { ApiHandler } from "../../api"
import { MAX_CONDENSE_THRESHOLD, MIN_CONDENSE_THRESHOLD, summarizeConversation, SummarizeResponse } from "../condense"
import { ApiMessage } from "../task-persistence/apiMessages"
import { ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types"
/**
* Default percentage of the context window to use as a buffer when deciding when to truncate
@ -105,7 +106,7 @@ export async function truncateConversationIfNeeded({
let error: string | undefined
let cost = 0
// Calculate the maximum tokens reserved for response
const reservedTokens = maxTokens || contextWindow * 0.2
const reservedTokens = maxTokens || ANTHROPIC_DEFAULT_MAX_TOKENS
// Estimate tokens for the last message (which is always a user message)
const lastMessage = messages[messages.length - 1]

View file

@ -53,6 +53,7 @@ describe("checkExistKey", () => {
geminiApiKey: undefined,
openAiNativeApiKey: undefined,
deepSeekApiKey: undefined,
moonshotApiKey: undefined,
mistralApiKey: undefined,
vsCodeLmModelSelector: undefined,
requestyApiKey: undefined,

View file

@ -72,7 +72,7 @@ export const modes: readonly ModeConfig[] = [
description: "Plan and design before implementation",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
customInstructions:
"1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**",
"1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**",
},
{
slug: "code",

View file

@ -18,6 +18,7 @@ import {
claudeCodeDefaultModelId,
geminiDefaultModelId,
deepSeekDefaultModelId,
moonshotDefaultModelId,
mistralDefaultModelId,
xaiDefaultModelId,
groqDefaultModelId,
@ -61,6 +62,7 @@ import {
LMStudio,
LiteLLM,
Mistral,
Moonshot,
Ollama,
OpenAI,
OpenAICompatible,
@ -78,6 +80,7 @@ import { ModelInfoView } from "./ModelInfoView"
import { ApiErrorMessage } from "./ApiErrorMessage"
import { ThinkingBudget } from "./ThinkingBudget"
import { DiffSettingsControl } from "./DiffSettingsControl"
import { TodoListSettingsControl } from "./TodoListSettingsControl"
import { TemperatureControl } from "./TemperatureControl"
import { RateLimitSecondsControl } from "./RateLimitSecondsControl"
import { ConsecutiveMistakeLimitControl } from "./ConsecutiveMistakeLimitControl"
@ -286,6 +289,7 @@ const ApiOptions = ({
"openai-native": { field: "apiModelId", default: openAiNativeDefaultModelId },
gemini: { field: "apiModelId", default: geminiDefaultModelId },
deepseek: { field: "apiModelId", default: deepSeekDefaultModelId },
moonshot: { field: "apiModelId", default: moonshotDefaultModelId },
mistral: { field: "apiModelId", default: mistralDefaultModelId },
xai: { field: "apiModelId", default: xaiDefaultModelId },
groq: { field: "apiModelId", default: groqDefaultModelId },
@ -463,6 +467,10 @@ const ApiOptions = ({
<DeepSeek apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "moonshot" && (
<Moonshot apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProvider === "vscode-lm" && (
<VSCodeLM apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
@ -564,6 +572,10 @@ const ApiOptions = ({
<span className="font-medium">{t("settings:advancedSettings.title")}</span>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-3">
<TodoListSettingsControl
todoListEnabled={apiConfiguration.todoListEnabled}
onChange={(field, value) => setApiConfigurationField(field, value)}
/>
<DiffSettingsControl
diffEnabled={apiConfiguration.diffEnabled}
fuzzyMatchThreshold={apiConfiguration.fuzzyMatchThreshold}

View file

@ -0,0 +1,35 @@
import React, { useCallback } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
interface TodoListSettingsControlProps {
todoListEnabled?: boolean
onChange: (field: "todoListEnabled", value: any) => void
}
export const TodoListSettingsControl: React.FC<TodoListSettingsControlProps> = ({
todoListEnabled = true,
onChange,
}) => {
const { t } = useAppTranslation()
const handleTodoListEnabledChange = useCallback(
(e: any) => {
onChange("todoListEnabled", e.target.checked)
},
[onChange],
)
return (
<div className="flex flex-col gap-1">
<div>
<VSCodeCheckbox checked={todoListEnabled} onChange={handleTodoListEnabledChange}>
<span className="font-medium">{t("settings:advanced.todoList.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm">
{t("settings:advanced.todoList.description")}
</div>
</div>
</div>
)
}

View file

@ -21,6 +21,16 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeRadio: ({ value, checked }: any) => <input type="radio" value={value} checked={checked} />,
VSCodeRadioGroup: ({ children }: any) => <div>{children}</div>,
VSCodeButton: ({ children }: any) => <div>{children}</div>,
VSCodeCheckbox: ({ children, checked, onChange }: any) => (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange && onChange({ target: { checked: e.target.checked } })}
/>
{children}
</label>
),
}))
// Mock other components
@ -173,6 +183,22 @@ vi.mock("../DiffSettingsControl", () => ({
),
}))
// Mock TodoListSettingsControl for tests
vi.mock("../TodoListSettingsControl", () => ({
TodoListSettingsControl: ({ todoListEnabled, onChange }: any) => (
<div data-testid="todo-list-settings-control">
<label>
Enable todo list tool
<input
type="checkbox"
checked={todoListEnabled}
onChange={(e) => onChange("todoListEnabled", e.target.checked)}
/>
</label>
</div>
),
}))
// Mock ThinkingBudget component
vi.mock("../ThinkingBudget", () => ({
ThinkingBudget: ({ modelInfo }: any) => {

View file

@ -0,0 +1,77 @@
import React from "react"
import { render, screen, fireEvent } from "@testing-library/react"
import { describe, it, expect, vi } from "vitest"
import { TodoListSettingsControl } from "../TodoListSettingsControl"
// Mock the translation hook
vi.mock("@/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
"settings:advanced.todoList.label": "Enable todo list tool",
"settings:advanced.todoList.description":
"When enabled, Roo can create and manage todo lists to track task progress. This helps organize complex tasks into manageable steps.",
}
return translations[key] || key
},
}),
}))
// Mock VSCodeCheckbox
vi.mock("@vscode/webview-ui-toolkit/react", () => ({
VSCodeCheckbox: ({ children, onChange, checked, ...props }: any) => (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange({ target: { checked: e.target.checked } })}
{...props}
/>
{children}
</label>
),
}))
describe("TodoListSettingsControl", () => {
it("renders with default props", () => {
const onChange = vi.fn()
render(<TodoListSettingsControl onChange={onChange} />)
const checkbox = screen.getByRole("checkbox")
const label = screen.getByText("Enable todo list tool")
const description = screen.getByText(/When enabled, Roo can create and manage todo lists/)
expect(checkbox).toBeInTheDocument()
expect(checkbox).toBeChecked() // Default is true
expect(label).toBeInTheDocument()
expect(description).toBeInTheDocument()
})
it("renders with todoListEnabled set to false", () => {
const onChange = vi.fn()
render(<TodoListSettingsControl todoListEnabled={false} onChange={onChange} />)
const checkbox = screen.getByRole("checkbox")
expect(checkbox).not.toBeChecked()
})
it("calls onChange when checkbox is clicked", () => {
const onChange = vi.fn()
render(<TodoListSettingsControl todoListEnabled={true} onChange={onChange} />)
const checkbox = screen.getByRole("checkbox")
fireEvent.click(checkbox)
expect(onChange).toHaveBeenCalledWith("todoListEnabled", false)
})
it("toggles from unchecked to checked", () => {
const onChange = vi.fn()
render(<TodoListSettingsControl todoListEnabled={false} onChange={onChange} />)
const checkbox = screen.getByRole("checkbox")
fireEvent.click(checkbox)
expect(onChange).toHaveBeenCalledWith("todoListEnabled", true)
})
})

View file

@ -5,6 +5,7 @@ import {
bedrockModels,
claudeCodeModels,
deepSeekModels,
moonshotModels,
geminiModels,
mistralModels,
openAiNativeModels,
@ -19,6 +20,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
"claude-code": claudeCodeModels,
bedrock: bedrockModels,
deepseek: deepSeekModels,
moonshot: moonshotModels,
gemini: geminiModels,
mistral: mistralModels,
"openai-native": openAiNativeModels,
@ -34,6 +36,7 @@ export const PROVIDERS = [
{ value: "claude-code", label: "Claude Code" },
{ value: "gemini", label: "Google Gemini" },
{ value: "deepseek", label: "DeepSeek" },
{ value: "moonshot", label: "Moonshot" },
{ value: "openai-native", label: "OpenAI" },
{ value: "openai", label: "OpenAI Compatible" },
{ value: "vertex", label: "GCP Vertex AI" },

View file

@ -0,0 +1,73 @@
import { useCallback } from "react"
import { VSCodeTextField, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
import { inputEventTransform } from "../transforms"
import { cn } from "@/lib/utils"
type MoonshotProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const Moonshot = ({ apiConfiguration, setApiConfigurationField }: MoonshotProps) => {
const { t } = useAppTranslation()
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))
},
[setApiConfigurationField],
)
return (
<>
<div>
<label className="block font-medium mb-1">{t("settings:providers.moonshotBaseUrl")}</label>
<VSCodeDropdown
value={apiConfiguration.moonshotBaseUrl}
onChange={handleInputChange("moonshotBaseUrl")}
className={cn("w-full")}>
<VSCodeOption value="https://api.moonshot.ai/v1" className="p-2">
api.moonshot.ai
</VSCodeOption>
<VSCodeOption value="https://api.moonshot.cn/v1" className="p-2">
api.moonshot.cn
</VSCodeOption>
</VSCodeDropdown>
</div>
<div>
<VSCodeTextField
value={apiConfiguration?.moonshotApiKey || ""}
type="password"
onInput={handleInputChange("moonshotApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.moonshotApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.moonshotApiKey && (
<VSCodeButtonLink
href={
apiConfiguration.moonshotBaseUrl === "https://api.moonshot.cn/v1"
? "https://platform.moonshot.cn/console/api-keys"
: "https://platform.moonshot.ai/console/api-keys"
}
appearance="secondary">
{t("settings:providers.getMoonshotApiKey")}
</VSCodeButtonLink>
)}
</div>
</>
)
}

View file

@ -8,6 +8,7 @@ export { Glama } from "./Glama"
export { Groq } from "./Groq"
export { LMStudio } from "./LMStudio"
export { Mistral } from "./Mistral"
export { Moonshot } from "./Moonshot"
export { Ollama } from "./Ollama"
export { OpenAI } from "./OpenAI"
export { OpenAICompatible } from "./OpenAICompatible"

View file

@ -8,6 +8,8 @@ import {
bedrockModels,
deepSeekDefaultModelId,
deepSeekModels,
moonshotDefaultModelId,
moonshotModels,
geminiDefaultModelId,
geminiModels,
mistralDefaultModelId,
@ -162,6 +164,11 @@ function getSelectedModel({
const info = deepSeekModels[id as keyof typeof deepSeekModels]
return { id, info }
}
case "moonshot": {
const id = apiConfiguration.apiModelId ?? moonshotDefaultModelId
const info = moonshotModels[id as keyof typeof moonshotModels]
return { id, info }
}
case "openai-native": {
const id = apiConfiguration.apiModelId ?? openAiNativeDefaultModelId
const info = openAiNativeModels[id as keyof typeof openAiNativeModels]
@ -211,6 +218,7 @@ function getSelectedModel({
// case "human-relay":
// case "fake-ai":
default: {
provider satisfies "anthropic" | "gemini-cli" | "human-relay" | "fake-ai"
const id = apiConfiguration.apiModelId ?? anthropicDefaultModelId
const info = anthropicModels[id as keyof typeof anthropicModels]
return { id, info }

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obtenir clau API de Chutes",
"deepSeekApiKey": "Clau API de DeepSeek",
"getDeepSeekApiKey": "Obtenir clau API de DeepSeek",
"moonshotApiKey": "Clau API de Moonshot",
"getMoonshotApiKey": "Obtenir clau API de Moonshot",
"moonshotBaseUrl": "Punt d'entrada de Moonshot",
"geminiApiKey": "Clau API de Gemini",
"getGroqApiKey": "Obtenir clau API de Groq",
"groqApiKey": "Clau API de Groq",
@ -577,6 +580,10 @@
"label": "Precisió de coincidència",
"description": "Aquest control lliscant controla amb quina precisió han de coincidir les seccions de codi en aplicar diffs. Valors més baixos permeten coincidències més flexibles però augmenten el risc de reemplaçaments incorrectes. Utilitzeu valors per sota del 100% amb extrema precaució."
}
},
"todoList": {
"label": "Habilitar eina de llista de tasques",
"description": "Quan està habilitat, Roo pot crear i gestionar llistes de tasques per fer el seguiment del progrés de les tasques. Això ajuda a organitzar tasques complexes en passos manejables."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API-Schlüssel erhalten",
"deepSeekApiKey": "DeepSeek API-Schlüssel",
"getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten",
"moonshotApiKey": "Moonshot API-Schlüssel",
"getMoonshotApiKey": "Moonshot API-Schlüssel erhalten",
"moonshotBaseUrl": "Moonshot-Einstiegspunkt",
"geminiApiKey": "Gemini API-Schlüssel",
"getGroqApiKey": "Groq API-Schlüssel erhalten",
"groqApiKey": "Groq API-Schlüssel",
@ -577,6 +580,10 @@
"label": "Übereinstimmungspräzision",
"description": "Dieser Schieberegler steuert, wie genau Codeabschnitte bei der Anwendung von Diffs übereinstimmen müssen. Niedrigere Werte ermöglichen eine flexiblere Übereinstimmung, erhöhen aber das Risiko falscher Ersetzungen. Verwenden Sie Werte unter 100 % mit äußerster Vorsicht."
}
},
"todoList": {
"label": "Todo-Listen-Tool aktivieren",
"description": "Wenn aktiviert, kann Roo Todo-Listen erstellen und verwalten, um den Aufgabenfortschritt zu verfolgen. Dies hilft, komplexe Aufgaben in überschaubare Schritte zu organisieren."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Get Chutes API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Get DeepSeek API Key",
"moonshotApiKey": "Moonshot API Key",
"getMoonshotApiKey": "Get Moonshot API Key",
"moonshotBaseUrl": "Moonshot Entrypoint",
"geminiApiKey": "Gemini API Key",
"getGroqApiKey": "Get Groq API Key",
"groqApiKey": "Groq API Key",
@ -577,6 +580,10 @@
"label": "Match precision",
"description": "This slider controls how precisely code sections must match when applying diffs. Lower values allow more flexible matching but increase the risk of incorrect replacements. Use values below 100% with extreme caution."
}
},
"todoList": {
"label": "Enable todo list tool",
"description": "When enabled, Roo can create and manage todo lists to track task progress. This helps organize complex tasks into manageable steps."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obtener clave API de Chutes",
"deepSeekApiKey": "Clave API de DeepSeek",
"getDeepSeekApiKey": "Obtener clave API de DeepSeek",
"moonshotApiKey": "Clave API de Moonshot",
"getMoonshotApiKey": "Obtener clave API de Moonshot",
"moonshotBaseUrl": "Punto de entrada de Moonshot",
"geminiApiKey": "Clave API de Gemini",
"getGroqApiKey": "Obtener clave API de Groq",
"groqApiKey": "Clave API de Groq",
@ -577,6 +580,10 @@
"label": "Precisión de coincidencia",
"description": "Este control deslizante controla cuán precisamente deben coincidir las secciones de código al aplicar diffs. Valores más bajos permiten coincidencias más flexibles pero aumentan el riesgo de reemplazos incorrectos. Use valores por debajo del 100% con extrema precaución."
}
},
"todoList": {
"label": "Habilitar herramienta de lista de tareas",
"description": "Cuando está habilitado, Roo puede crear y gestionar listas de tareas para hacer seguimiento del progreso. Esto ayuda a organizar tareas complejas en pasos manejables."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obtenir la clé API Chutes",
"deepSeekApiKey": "Clé API DeepSeek",
"getDeepSeekApiKey": "Obtenir la clé API DeepSeek",
"moonshotApiKey": "Clé API Moonshot",
"getMoonshotApiKey": "Obtenir la clé API Moonshot",
"moonshotBaseUrl": "Point d'entrée Moonshot",
"geminiApiKey": "Clé API Gemini",
"getGroqApiKey": "Obtenir la clé API Groq",
"groqApiKey": "Clé API Groq",
@ -577,6 +580,10 @@
"label": "Précision de correspondance",
"description": "Ce curseur contrôle la précision avec laquelle les sections de code doivent correspondre lors de l'application des diffs. Des valeurs plus basses permettent des correspondances plus flexibles mais augmentent le risque de remplacements incorrects. Utilisez des valeurs inférieures à 100 % avec une extrême prudence."
}
},
"todoList": {
"label": "Activer l'outil de liste de tâches",
"description": "Lorsqu'activé, Roo peut créer et gérer des listes de tâches pour suivre la progression. Cela aide à organiser les tâches complexes en étapes gérables."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API कुंजी प्राप्त करें",
"deepSeekApiKey": "DeepSeek API कुंजी",
"getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें",
"moonshotApiKey": "Moonshot API कुंजी",
"getMoonshotApiKey": "Moonshot API कुंजी प्राप्त करें",
"moonshotBaseUrl": "Moonshot प्रवेश बिंदु",
"geminiApiKey": "Gemini API कुंजी",
"getGroqApiKey": "Groq API कुंजी प्राप्त करें",
"groqApiKey": "Groq API कुंजी",
@ -577,6 +580,10 @@
"label": "मिलान सटीकता",
"description": "यह स्लाइडर नियंत्रित करता है कि diffs लागू करते समय कोड अनुभागों को कितनी सटीकता से मेल खाना चाहिए। निम्न मान अधिक लचीले मिलान की अनुमति देते हैं लेकिन गलत प्रतिस्थापन का जोखिम बढ़ाते हैं। 100% से नीचे के मानों का उपयोग अत्यधिक सावधानी के साथ करें।"
}
},
"todoList": {
"label": "टूडू सूची टूल सक्षम करें",
"description": "जब सक्षम हो, तो Roo कार्य प्रगति को ट्रैक करने के लिए टूडू सूचियाँ बना और प्रबंधित कर सकता है। यह जटिल कार्यों को प्रबंधनीय चरणों में व्यवस्थित करने में मदद करता है।"
}
},
"experimental": {

View file

@ -257,6 +257,9 @@
"getChutesApiKey": "Dapatkan Chutes API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Dapatkan DeepSeek API Key",
"moonshotApiKey": "Kunci API Moonshot",
"getMoonshotApiKey": "Dapatkan Kunci API Moonshot",
"moonshotBaseUrl": "Titik Masuk Moonshot",
"geminiApiKey": "Gemini API Key",
"getGroqApiKey": "Dapatkan Groq API Key",
"groqApiKey": "Groq API Key",
@ -581,6 +584,10 @@
"label": "Presisi pencocokan",
"description": "Slider ini mengontrol seberapa tepat bagian kode harus cocok saat menerapkan diff. Nilai yang lebih rendah memungkinkan pencocokan yang lebih fleksibel tetapi meningkatkan risiko penggantian yang salah. Gunakan nilai di bawah 100% dengan sangat hati-hati."
}
},
"todoList": {
"label": "Aktifkan alat daftar tugas",
"description": "Saat diaktifkan, Roo dapat membuat dan mengelola daftar tugas untuk melacak kemajuan tugas. Ini membantu mengatur tugas kompleks menjadi langkah-langkah yang dapat dikelola."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Ottieni chiave API Chutes",
"deepSeekApiKey": "Chiave API DeepSeek",
"getDeepSeekApiKey": "Ottieni chiave API DeepSeek",
"moonshotApiKey": "Chiave API Moonshot",
"getMoonshotApiKey": "Ottieni chiave API Moonshot",
"moonshotBaseUrl": "Punto di ingresso Moonshot",
"geminiApiKey": "Chiave API Gemini",
"getGroqApiKey": "Ottieni chiave API Groq",
"groqApiKey": "Chiave API Groq",
@ -577,6 +580,10 @@
"label": "Precisione corrispondenza",
"description": "Questo cursore controlla quanto precisamente le sezioni di codice devono corrispondere quando si applicano i diff. Valori più bassi consentono corrispondenze più flessibili ma aumentano il rischio di sostituzioni errate. Usa valori inferiori al 100% con estrema cautela."
}
},
"todoList": {
"label": "Abilita strumento lista di cose da fare",
"description": "Quando abilitato, Roo può creare e gestire liste di cose da fare per tracciare il progresso delle attività. Questo aiuta a organizzare attività complesse in passaggi gestibili."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes APIキーを取得",
"deepSeekApiKey": "DeepSeek APIキー",
"getDeepSeekApiKey": "DeepSeek APIキーを取得",
"moonshotApiKey": "Moonshot APIキー",
"getMoonshotApiKey": "Moonshot APIキーを取得",
"moonshotBaseUrl": "Moonshot エントリーポイント",
"geminiApiKey": "Gemini APIキー",
"getGroqApiKey": "Groq APIキーを取得",
"groqApiKey": "Groq APIキー",
@ -577,6 +580,10 @@
"label": "マッチ精度",
"description": "このスライダーは、diffを適用する際にコードセクションがどれだけ正確に一致する必要があるかを制御します。低い値はより柔軟なマッチングを可能にしますが、誤った置換のリスクが高まります。100%未満の値は細心の注意を払って使用してください。"
}
},
"todoList": {
"label": "ToDoリストツールを有効にする",
"description": "有効にすると、Rooはタスクの進捗を追跡するためのToDoリストを作成・管理できます。これにより、複雑なタスクを管理しやすいステップに整理できます。"
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API 키 받기",
"deepSeekApiKey": "DeepSeek API 키",
"getDeepSeekApiKey": "DeepSeek API 키 받기",
"moonshotApiKey": "Moonshot API 키",
"getMoonshotApiKey": "Moonshot API 키 받기",
"moonshotBaseUrl": "Moonshot 엔트리포인트",
"geminiApiKey": "Gemini API 키",
"getGroqApiKey": "Groq API 키 받기",
"groqApiKey": "Groq API 키",
@ -577,6 +580,10 @@
"label": "일치 정확도",
"description": "이 슬라이더는 diff를 적용할 때 코드 섹션이 얼마나 정확하게 일치해야 하는지 제어합니다. 낮은 값은 더 유연한 일치를 허용하지만 잘못된 교체 위험이 증가합니다. 100% 미만의 값은 극도로 주의해서 사용하세요."
}
},
"todoList": {
"label": "할 일 목록 도구 활성화",
"description": "활성화하면 Roo가 작업 진행 상황을 추적하기 위한 할 일 목록을 만들고 관리할 수 있습니다. 이는 복잡한 작업을 관리 가능한 단계로 구성하는 데 도움이 됩니다."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API-sleutel ophalen",
"deepSeekApiKey": "DeepSeek API-sleutel",
"getDeepSeekApiKey": "DeepSeek API-sleutel ophalen",
"moonshotApiKey": "Moonshot API-sleutel",
"getMoonshotApiKey": "Moonshot API-sleutel ophalen",
"moonshotBaseUrl": "Moonshot-ingangspunt",
"geminiApiKey": "Gemini API-sleutel",
"getGroqApiKey": "Groq API-sleutel ophalen",
"groqApiKey": "Groq API-sleutel",
@ -577,6 +580,10 @@
"label": "Matchnauwkeurigheid",
"description": "Deze schuifregelaar bepaalt hoe nauwkeurig codeblokken moeten overeenkomen bij het toepassen van diffs. Lagere waarden laten flexibelere matching toe maar verhogen het risico op verkeerde vervangingen. Gebruik waarden onder 100% met uiterste voorzichtigheid."
}
},
"todoList": {
"label": "Takenlijst-tool inschakelen",
"description": "Wanneer ingeschakeld, kan Roo takenlijsten maken en beheren om de voortgang van taken bij te houden. Dit helpt complexe taken te organiseren in beheersbare stappen."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Uzyskaj klucz API Chutes",
"deepSeekApiKey": "Klucz API DeepSeek",
"getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek",
"moonshotApiKey": "Klucz API Moonshot",
"getMoonshotApiKey": "Uzyskaj klucz API Moonshot",
"moonshotBaseUrl": "Punkt wejścia Moonshot",
"geminiApiKey": "Klucz API Gemini",
"getGroqApiKey": "Uzyskaj klucz API Groq",
"groqApiKey": "Klucz API Groq",
@ -577,6 +580,10 @@
"label": "Precyzja dopasowania",
"description": "Ten suwak kontroluje, jak dokładnie sekcje kodu muszą pasować podczas stosowania różnic. Niższe wartości umożliwiają bardziej elastyczne dopasowywanie, ale zwiększają ryzyko nieprawidłowych zamian. Używaj wartości poniżej 100% z najwyższą ostrożnością."
}
},
"todoList": {
"label": "Włącz narzędzie listy zadań",
"description": "Po włączeniu Roo może tworzyć i zarządzać listami zadań do śledzenia postępu zadań. Pomaga to organizować złożone zadania w łatwe do zarządzania kroki."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Obter chave de API Chutes",
"deepSeekApiKey": "Chave de API DeepSeek",
"getDeepSeekApiKey": "Obter chave de API DeepSeek",
"moonshotApiKey": "Chave de API Moonshot",
"getMoonshotApiKey": "Obter chave de API Moonshot",
"moonshotBaseUrl": "Ponto de entrada Moonshot",
"geminiApiKey": "Chave de API Gemini",
"getGroqApiKey": "Obter chave de API Groq",
"groqApiKey": "Chave de API Groq",
@ -577,6 +580,10 @@
"label": "Precisão de correspondência",
"description": "Este controle deslizante controla quão precisamente as seções de código devem corresponder ao aplicar diffs. Valores mais baixos permitem correspondências mais flexíveis, mas aumentam o risco de substituições incorretas. Use valores abaixo de 100% com extrema cautela."
}
},
"todoList": {
"label": "Habilitar ferramenta de lista de tarefas",
"description": "Quando habilitado, o Roo pode criar e gerenciar listas de tarefas para acompanhar o progresso das tarefas. Isso ajuda a organizar tarefas complexas em etapas gerenciáveis."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Получить Chutes API-ключ",
"deepSeekApiKey": "DeepSeek API-ключ",
"getDeepSeekApiKey": "Получить DeepSeek API-ключ",
"moonshotApiKey": "Moonshot API-ключ",
"getMoonshotApiKey": "Получить Moonshot API-ключ",
"moonshotBaseUrl": "Точка входа Moonshot",
"geminiApiKey": "Gemini API-ключ",
"getGroqApiKey": "Получить Groq API-ключ",
"groqApiKey": "Groq API-ключ",
@ -577,6 +580,10 @@
"label": "Точность совпадения",
"description": "Этот ползунок управляет точностью совпадения секций кода при применении диффов. Меньшие значения позволяют более гибкое совпадение, но увеличивают риск неверной замены. Используйте значения ниже 100% с осторожностью."
}
},
"todoList": {
"label": "Включить инструмент списка задач",
"description": "При включении Roo может создавать и управлять списками задач для отслеживания прогресса. Это помогает организовать сложные задачи в управляемые шаги."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Chutes API Anahtarı Al",
"deepSeekApiKey": "DeepSeek API Anahtarı",
"getDeepSeekApiKey": "DeepSeek API Anahtarı Al",
"moonshotApiKey": "Moonshot API Anahtarı",
"getMoonshotApiKey": "Moonshot API Anahtarı Al",
"moonshotBaseUrl": "Moonshot Giriş Noktası",
"geminiApiKey": "Gemini API Anahtarı",
"getGroqApiKey": "Groq API Anahtarı Al",
"groqApiKey": "Groq API Anahtarı",
@ -577,6 +580,10 @@
"label": "Eşleşme hassasiyeti",
"description": "Bu kaydırıcı, diff'ler uygulanırken kod bölümlerinin ne kadar hassas bir şekilde eşleşmesi gerektiğini kontrol eder. Daha düşük değerler daha esnek eşleşmeye izin verir ancak yanlış değiştirme riskini artırır. %100'ün altındaki değerleri son derece dikkatli kullanın."
}
},
"todoList": {
"label": "Yapılacaklar listesi aracını etkinleştir",
"description": "Etkinleştirildiğinde, Roo görev ilerlemesini takip etmek için yapılacaklar listeleri oluşturabilir ve yönetebilir. Bu, karmaşık görevleri yönetilebilir adımlara organize etmeye yardımcı olur."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "Lấy khóa API Chutes",
"deepSeekApiKey": "Khóa API DeepSeek",
"getDeepSeekApiKey": "Lấy khóa API DeepSeek",
"moonshotApiKey": "Khóa API Moonshot",
"getMoonshotApiKey": "Lấy khóa API Moonshot",
"moonshotBaseUrl": "Điểm vào Moonshot",
"geminiApiKey": "Khóa API Gemini",
"getGroqApiKey": "Lấy khóa API Groq",
"groqApiKey": "Khóa API Groq",
@ -577,6 +580,10 @@
"label": "Độ chính xác khớp",
"description": "Thanh trượt này kiểm soát mức độ chính xác các phần mã phải khớp khi áp dụng diff. Giá trị thấp hơn cho phép khớp linh hoạt hơn nhưng tăng nguy cơ thay thế không chính xác. Sử dụng giá trị dưới 100% với sự thận trọng cao."
}
},
"todoList": {
"label": "Bật công cụ danh sách việc cần làm",
"description": "Khi được bật, Roo có thể tạo và quản lý danh sách việc cần làm để theo dõi tiến độ công việc. Điều này giúp tổ chức các tác vụ phức tạp thành các bước có thể quản lý được."
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "获取 Chutes API 密钥",
"deepSeekApiKey": "DeepSeek API 密钥",
"getDeepSeekApiKey": "获取 DeepSeek API 密钥",
"moonshotApiKey": "Moonshot API 密钥",
"getMoonshotApiKey": "获取 Moonshot API 密钥",
"moonshotBaseUrl": "Moonshot 服务站点",
"geminiApiKey": "Gemini API 密钥",
"getGroqApiKey": "获取 Groq API 密钥",
"groqApiKey": "Groq API 密钥",
@ -577,6 +580,10 @@
"label": "匹配精度",
"description": "控制代码匹配的精确程度。数值越低匹配越宽松容错率高但风险大建议保持100%以确保安全。"
}
},
"todoList": {
"label": "启用任务清单工具",
"description": "启用后Roo 可以创建和管理任务清单来跟踪任务进度。这有助于将复杂任务组织成可管理的步骤。"
}
},
"experimental": {

View file

@ -253,6 +253,9 @@
"getChutesApiKey": "取得 Chutes API 金鑰",
"deepSeekApiKey": "DeepSeek API 金鑰",
"getDeepSeekApiKey": "取得 DeepSeek API 金鑰",
"moonshotApiKey": "Moonshot API 金鑰",
"getMoonshotApiKey": "取得 Moonshot API 金鑰",
"moonshotBaseUrl": "Moonshot 服務站點",
"geminiApiKey": "Gemini API 金鑰",
"getGroqApiKey": "取得 Groq API 金鑰",
"groqApiKey": "Groq API 金鑰",
@ -577,6 +580,10 @@
"label": "比對精確度",
"description": "此滑桿控制套用差異時程式碼區段的比對精確度。較低的數值允許更彈性的比對,但也會增加錯誤取代的風險。使用低於 100% 的數值時請特別謹慎。"
}
},
"todoList": {
"label": "啟用待辦事項清單工具",
"description": "啟用後Roo 可以建立和管理待辦事項清單來追蹤任務進度。這有助於將複雜任務組織成可管理的步驟。"
}
},
"experimental": {