mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Merge branch 'RooCodeInc:main' into feat/finer-grained-control-gemini
This commit is contained in:
commit
8d48fcc2b2
39 changed files with 617 additions and 124 deletions
|
|
@ -53,12 +53,18 @@ export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
|
|||
* ProviderSettings
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default value for consecutive mistake limit
|
||||
*/
|
||||
export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
|
||||
|
||||
const baseProviderSettingsSchema = z.object({
|
||||
includeMaxTokens: z.boolean().optional(),
|
||||
diffEnabled: z.boolean().optional(),
|
||||
fuzzyMatchThreshold: z.number().optional(),
|
||||
modelTemperature: z.number().nullish(),
|
||||
rateLimitSeconds: z.number().optional(),
|
||||
consecutiveMistakeLimit: z.number().min(0).optional(),
|
||||
|
||||
// Model reasoning.
|
||||
enableReasoningEffort: z.boolean().optional(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
type ProviderSettingsEntry,
|
||||
providerSettingsSchema,
|
||||
providerSettingsSchemaDiscriminated,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ export const providerProfilesSchema = z.object({
|
|||
rateLimitSecondsMigrated: z.boolean().optional(),
|
||||
diffSettingsMigrated: z.boolean().optional(),
|
||||
openAiHeadersMigrated: z.boolean().optional(),
|
||||
consecutiveMistakeLimitMigrated: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
|
@ -48,6 +50,7 @@ export class ProviderSettingsManager {
|
|||
rateLimitSecondsMigrated: true, // Mark as migrated on fresh installs
|
||||
diffSettingsMigrated: true, // Mark as migrated on fresh installs
|
||||
openAiHeadersMigrated: true, // Mark as migrated on fresh installs
|
||||
consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +116,7 @@ export class ProviderSettingsManager {
|
|||
rateLimitSecondsMigrated: false,
|
||||
diffSettingsMigrated: false,
|
||||
openAiHeadersMigrated: false,
|
||||
consecutiveMistakeLimitMigrated: false,
|
||||
} // Initialize with default values
|
||||
isDirty = true
|
||||
}
|
||||
|
|
@ -135,6 +139,12 @@ export class ProviderSettingsManager {
|
|||
isDirty = true
|
||||
}
|
||||
|
||||
if (!providerProfiles.migrations.consecutiveMistakeLimitMigrated) {
|
||||
await this.migrateConsecutiveMistakeLimit(providerProfiles)
|
||||
providerProfiles.migrations.consecutiveMistakeLimitMigrated = true
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
if (isDirty) {
|
||||
await this.store(providerProfiles)
|
||||
}
|
||||
|
|
@ -228,6 +238,18 @@ export class ProviderSettingsManager {
|
|||
}
|
||||
}
|
||||
|
||||
private async migrateConsecutiveMistakeLimit(providerProfiles: ProviderProfiles) {
|
||||
try {
|
||||
for (const [name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
|
||||
if (apiConfig.consecutiveMistakeLimit == null) {
|
||||
apiConfig.consecutiveMistakeLimit = DEFAULT_CONSECUTIVE_MISTAKE_LIMIT
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[MigrateConsecutiveMistakeLimit] Failed to migrate consecutive mistake limit:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available configs with metadata.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ describe("ProviderSettingsManager", () => {
|
|||
rateLimitSecondsMigrated: true,
|
||||
diffSettingsMigrated: true,
|
||||
openAiHeadersMigrated: true,
|
||||
consecutiveMistakeLimitMigrated: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
|
@ -144,6 +145,47 @@ describe("ProviderSettingsManager", () => {
|
|||
expect(storedConfig.apiConfigs.existing.rateLimitSeconds).toEqual(43)
|
||||
})
|
||||
|
||||
it("should call migrateConsecutiveMistakeLimit if it has not done so already", async () => {
|
||||
mockSecrets.get.mockResolvedValue(
|
||||
JSON.stringify({
|
||||
currentApiConfigName: "default",
|
||||
apiConfigs: {
|
||||
default: {
|
||||
config: {},
|
||||
id: "default",
|
||||
consecutiveMistakeLimit: undefined,
|
||||
},
|
||||
test: {
|
||||
apiProvider: "anthropic",
|
||||
consecutiveMistakeLimit: 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
|
||||
consecutiveMistakeLimit: 5,
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
rateLimitSecondsMigrated: true,
|
||||
diffSettingsMigrated: true,
|
||||
openAiHeadersMigrated: true,
|
||||
consecutiveMistakeLimitMigrated: 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.consecutiveMistakeLimit).toEqual(3)
|
||||
expect(storedConfig.apiConfigs.test.consecutiveMistakeLimit).toEqual(3)
|
||||
expect(storedConfig.apiConfigs.existing.consecutiveMistakeLimit).toEqual(5)
|
||||
expect(storedConfig.migrations.consecutiveMistakeLimitMigrated).toEqual(true)
|
||||
})
|
||||
|
||||
it("should throw error if secrets storage fails", async () => {
|
||||
mockSecrets.get.mockRejectedValue(new Error("Storage failed"))
|
||||
|
||||
|
|
|
|||
|
|
@ -179,22 +179,12 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
|
|||
// Add current time information with timezone.
|
||||
const now = new Date()
|
||||
|
||||
const formatter = new Intl.DateTimeFormat(undefined, {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
hour12: true,
|
||||
})
|
||||
|
||||
const timeZone = formatter.resolvedOptions().timeZone
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
const timeZoneOffset = -now.getTimezoneOffset() / 60 // Convert to hours and invert sign to match conventional notation
|
||||
const timeZoneOffsetHours = Math.floor(Math.abs(timeZoneOffset))
|
||||
const timeZoneOffsetMinutes = Math.abs(Math.round((Math.abs(timeZoneOffset) - timeZoneOffsetHours) * 60))
|
||||
const timeZoneOffsetStr = `${timeZoneOffset >= 0 ? "+" : "-"}${timeZoneOffsetHours}:${timeZoneOffsetMinutes.toString().padStart(2, "0")}`
|
||||
details += `\n\n# Current Time\n${formatter.format(now)} (${timeZone}, UTC${timeZoneOffsetStr})`
|
||||
details += `\n\n# Current Time\nCurrent time in ISO 8601 UTC format: ${now.toISOString()}\nUser time zone: ${timeZone}, UTC${timeZoneOffsetStr}`
|
||||
|
||||
// Add context tokens information.
|
||||
const { contextTokens, totalCost } = getApiMetrics(cline.clineMessages)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type ClineMessage,
|
||||
type ClineSay,
|
||||
type ToolProgressStatus,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
type HistoryItem,
|
||||
TelemetryEventName,
|
||||
TodoItem,
|
||||
|
|
@ -216,7 +217,7 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
enableDiff = false,
|
||||
enableCheckpoints = true,
|
||||
fuzzyMatchThreshold = 1.0,
|
||||
consecutiveMistakeLimit = 3,
|
||||
consecutiveMistakeLimit = DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
task,
|
||||
images,
|
||||
historyItem,
|
||||
|
|
@ -255,7 +256,7 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
this.browserSession = new BrowserSession(provider.context)
|
||||
this.diffEnabled = enableDiff
|
||||
this.fuzzyMatchThreshold = fuzzyMatchThreshold
|
||||
this.consecutiveMistakeLimit = consecutiveMistakeLimit
|
||||
this.consecutiveMistakeLimit = consecutiveMistakeLimit ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT
|
||||
this.providerRef = new WeakRef(provider)
|
||||
this.globalStoragePath = provider.context.globalStorageUri.fsPath
|
||||
this.diffViewProvider = new DiffViewProvider(this.cwd)
|
||||
|
|
@ -1159,7 +1160,7 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
throw new Error(`[RooCode#recursivelyMakeRooRequests] task ${this.taskId}.${this.instanceId} aborted`)
|
||||
}
|
||||
|
||||
if (this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) {
|
||||
if (this.consecutiveMistakeLimit > 0 && this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) {
|
||||
const { response, text, images } = await this.ask(
|
||||
"mistake_limit_reached",
|
||||
t("common:errors.mistake_limit_guidance"),
|
||||
|
|
|
|||
|
|
@ -320,6 +320,70 @@ describe("Cline", () => {
|
|||
expect(cline.diffStrategy).toBeDefined()
|
||||
})
|
||||
|
||||
it("should use default consecutiveMistakeLimit when not provided", () => {
|
||||
const cline = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
expect(cline.consecutiveMistakeLimit).toBe(3)
|
||||
})
|
||||
|
||||
it("should respect provided consecutiveMistakeLimit", () => {
|
||||
const cline = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
consecutiveMistakeLimit: 5,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
expect(cline.consecutiveMistakeLimit).toBe(5)
|
||||
})
|
||||
|
||||
it("should keep consecutiveMistakeLimit of 0 as 0 for unlimited", () => {
|
||||
const cline = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
consecutiveMistakeLimit: 0,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
expect(cline.consecutiveMistakeLimit).toBe(0)
|
||||
})
|
||||
|
||||
it("should pass 0 to ToolRepetitionDetector for unlimited mode", () => {
|
||||
const cline = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
consecutiveMistakeLimit: 0,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// The toolRepetitionDetector should be initialized with 0 for unlimited mode
|
||||
expect(cline.toolRepetitionDetector).toBeDefined()
|
||||
// Verify the limit remains as 0
|
||||
expect(cline.consecutiveMistakeLimit).toBe(0)
|
||||
})
|
||||
|
||||
it("should pass consecutiveMistakeLimit to ToolRepetitionDetector", () => {
|
||||
const cline = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
consecutiveMistakeLimit: 5,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
|
||||
// The toolRepetitionDetector should be initialized with the same limit
|
||||
expect(cline.toolRepetitionDetector).toBeDefined()
|
||||
expect(cline.consecutiveMistakeLimit).toBe(5)
|
||||
})
|
||||
|
||||
it("should require either task or historyItem", () => {
|
||||
expect(() => {
|
||||
new Task({ provider: mockProvider, apiConfiguration: mockApiConfig })
|
||||
|
|
|
|||
|
|
@ -43,8 +43,11 @@ export class ToolRepetitionDetector {
|
|||
this.previousToolCallJson = currentToolCallJson
|
||||
}
|
||||
|
||||
// Check if limit is reached
|
||||
if (this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit) {
|
||||
// Check if limit is reached (0 means unlimited)
|
||||
if (
|
||||
this.consecutiveIdenticalToolCallLimit > 0 &&
|
||||
this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit
|
||||
) {
|
||||
// Reset counters to allow recovery if user guides the AI past this point
|
||||
this.consecutiveIdenticalToolCallCount = 0
|
||||
this.previousToolCallJson = null
|
||||
|
|
|
|||
|
|
@ -301,5 +301,61 @@ describe("ToolRepetitionDetector", () => {
|
|||
expect(result3.allowExecution).toBe(false)
|
||||
expect(result3.askUser).toBeDefined()
|
||||
})
|
||||
|
||||
it("should never block when limit is 0 (unlimited)", () => {
|
||||
const detector = new ToolRepetitionDetector(0)
|
||||
|
||||
// Try many identical calls
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const result = detector.check(createToolUse("tool", "tool-name"))
|
||||
expect(result.allowExecution).toBe(true)
|
||||
expect(result.askUser).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle different limits correctly", () => {
|
||||
// Test with limit of 5
|
||||
const detector5 = new ToolRepetitionDetector(5)
|
||||
const tool = createToolUse("tool", "tool-name")
|
||||
|
||||
// First 4 calls should be allowed
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const result = detector5.check(tool)
|
||||
expect(result.allowExecution).toBe(true)
|
||||
expect(result.askUser).toBeUndefined()
|
||||
}
|
||||
|
||||
// 5th call should be blocked
|
||||
const result5 = detector5.check(tool)
|
||||
expect(result5.allowExecution).toBe(false)
|
||||
expect(result5.askUser).toBeDefined()
|
||||
expect(result5.askUser?.messageKey).toBe("mistake_limit_reached")
|
||||
})
|
||||
|
||||
it("should reset counter after blocking and allow new attempts", () => {
|
||||
const detector = new ToolRepetitionDetector(2)
|
||||
const tool = createToolUse("tool", "tool-name")
|
||||
|
||||
// First call allowed
|
||||
expect(detector.check(tool).allowExecution).toBe(true)
|
||||
|
||||
// Second call should block (limit is 2)
|
||||
const blocked = detector.check(tool)
|
||||
expect(blocked.allowExecution).toBe(false)
|
||||
|
||||
// After blocking, counter should reset and allow new attempts
|
||||
expect(detector.check(tool).allowExecution).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle negative limits as 0 (unlimited)", () => {
|
||||
const detector = new ToolRepetitionDetector(-1)
|
||||
|
||||
// Should behave like unlimited
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const result = detector.check(createToolUse("tool", "tool-name"))
|
||||
expect(result.allowExecution).toBe(true)
|
||||
expect(result.askUser).toBeUndefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -553,6 +553,7 @@ export class ClineProvider
|
|||
enableDiff,
|
||||
enableCheckpoints,
|
||||
fuzzyMatchThreshold,
|
||||
consecutiveMistakeLimit: apiConfiguration.consecutiveMistakeLimit,
|
||||
task,
|
||||
images,
|
||||
experiments,
|
||||
|
|
@ -589,6 +590,7 @@ export class ClineProvider
|
|||
enableDiff,
|
||||
enableCheckpoints,
|
||||
fuzzyMatchThreshold,
|
||||
consecutiveMistakeLimit: apiConfiguration.consecutiveMistakeLimit,
|
||||
historyItem,
|
||||
experiments,
|
||||
rootTask: historyItem.rootTask,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ const getBuildArtifactPatterns = () => [
|
|||
".next/",
|
||||
".nuxt/",
|
||||
".sass-cache/",
|
||||
".terraform/",
|
||||
".terragrunt-cache/",
|
||||
".vs/",
|
||||
".vscode/",
|
||||
"Pods/",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const QDRANT_CODE_BLOCK_NAMESPACE = "f47ac10b-58cc-4372-a567-0e02b2c3d479
|
|||
export const MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024 // 1MB
|
||||
|
||||
/**Directory Scanner */
|
||||
export const MAX_LIST_FILES_LIMIT = 3_000
|
||||
export const MAX_LIST_FILES_LIMIT_CODE_INDEX = 50_000
|
||||
export const BATCH_SEGMENT_THRESHOLD = 60 // Number of code segments to batch for embeddings/upserts
|
||||
export const MAX_BATCH_RETRIES = 3
|
||||
export const INITIAL_RETRY_DELAY_MS = 500
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import { withValidationErrorHandling, sanitizeErrorMessage } from "../shared/val
|
|||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
import { TelemetryEventName } from "@roo-code/types"
|
||||
|
||||
// Timeout constants for Ollama API requests
|
||||
const OLLAMA_EMBEDDING_TIMEOUT_MS = 60000 // 60 seconds for embedding requests
|
||||
const OLLAMA_VALIDATION_TIMEOUT_MS = 30000 // 30 seconds for validation requests
|
||||
|
||||
/**
|
||||
* Implements the IEmbedder interface using a local Ollama instance.
|
||||
*/
|
||||
|
|
@ -61,7 +65,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
|
||||
// Add timeout to prevent indefinite hanging
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout
|
||||
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_EMBEDDING_TIMEOUT_MS)
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
|
|
@ -140,7 +144,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
|
||||
// Add timeout to prevent indefinite hanging
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
|
||||
const timeoutId = setTimeout(() => controller.abort(), OLLAMA_VALIDATION_TIMEOUT_MS)
|
||||
|
||||
const modelsResponse = await fetch(modelsUrl, {
|
||||
method: "GET",
|
||||
|
|
@ -197,7 +201,7 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
|
|||
|
||||
// Add timeout for test request too
|
||||
const testController = new AbortController()
|
||||
const testTimeoutId = setTimeout(() => testController.abort(), 5000)
|
||||
const testTimeoutId = setTimeout(() => testController.abort(), OLLAMA_VALIDATION_TIMEOUT_MS)
|
||||
|
||||
const testResponse = await fetch(testUrl, {
|
||||
method: "POST",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export interface IDirectoryScanner {
|
|||
onBlocksIndexed?: (indexedCount: number) => void,
|
||||
onFileParsed?: (fileBlockCount: number) => void,
|
||||
): Promise<{
|
||||
codeBlocks: CodeBlock[]
|
||||
stats: {
|
||||
processed: number
|
||||
skipped: number
|
||||
|
|
|
|||
|
|
@ -168,7 +168,16 @@ describe("DirectoryScanner", () => {
|
|||
expect(mockCodeParser.parseFile).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should parse changed files and return code blocks", async () => {
|
||||
it("should parse changed files and return empty codeBlocks array", async () => {
|
||||
// Create scanner without embedder to test the non-embedding path
|
||||
const scannerNoEmbeddings = new DirectoryScanner(
|
||||
null as any, // No embedder
|
||||
null as any, // No vector store
|
||||
mockCodeParser,
|
||||
mockCacheManager,
|
||||
mockIgnoreInstance,
|
||||
)
|
||||
|
||||
const { listFiles } = await import("../../../glob/list-files")
|
||||
vi.mocked(listFiles).mockResolvedValue([["test/file1.js"], false])
|
||||
const mockBlocks: any[] = [
|
||||
|
|
@ -185,8 +194,7 @@ describe("DirectoryScanner", () => {
|
|||
]
|
||||
;(mockCodeParser.parseFile as any).mockResolvedValue(mockBlocks)
|
||||
|
||||
const result = await scanner.scanDirectory("/test")
|
||||
expect(result.codeBlocks).toEqual(mockBlocks)
|
||||
const result = await scannerNoEmbeddings.scanDirectory("/test")
|
||||
expect(result.stats.processed).toBe(1)
|
||||
})
|
||||
|
||||
|
|
@ -252,6 +260,15 @@ describe("DirectoryScanner", () => {
|
|||
})
|
||||
|
||||
it("should process markdown files alongside code files", async () => {
|
||||
// Create scanner without embedder to test the non-embedding path
|
||||
const scannerNoEmbeddings = new DirectoryScanner(
|
||||
null as any, // No embedder
|
||||
null as any, // No vector store
|
||||
mockCodeParser,
|
||||
mockCacheManager,
|
||||
mockIgnoreInstance,
|
||||
)
|
||||
|
||||
const { listFiles } = await import("../../../glob/list-files")
|
||||
vi.mocked(listFiles).mockResolvedValue([["test/README.md", "test/app.js", "docs/guide.markdown"], false])
|
||||
|
||||
|
|
@ -306,7 +323,7 @@ describe("DirectoryScanner", () => {
|
|||
return []
|
||||
})
|
||||
|
||||
const result = await scanner.scanDirectory("/test")
|
||||
const result = await scannerNoEmbeddings.scanDirectory("/test")
|
||||
|
||||
// Verify all files were processed
|
||||
expect(mockCodeParser.parseFile).toHaveBeenCalledTimes(3)
|
||||
|
|
@ -314,16 +331,7 @@ describe("DirectoryScanner", () => {
|
|||
expect(mockCodeParser.parseFile).toHaveBeenCalledWith("test/app.js", expect.any(Object))
|
||||
expect(mockCodeParser.parseFile).toHaveBeenCalledWith("docs/guide.markdown", expect.any(Object))
|
||||
|
||||
// Verify code blocks include both markdown and code content
|
||||
expect(result.codeBlocks).toHaveLength(3)
|
||||
expect(result.codeBlocks).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ type: "markdown_header_h1" }),
|
||||
expect.objectContaining({ type: "function" }),
|
||||
expect.objectContaining({ type: "markdown_header_h2" }),
|
||||
]),
|
||||
)
|
||||
|
||||
// Verify processing still works without codeBlocks accumulation
|
||||
expect(result.stats.processed).toBe(3)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { t } from "../../../i18n"
|
|||
import {
|
||||
QDRANT_CODE_BLOCK_NAMESPACE,
|
||||
MAX_FILE_SIZE_BYTES,
|
||||
MAX_LIST_FILES_LIMIT,
|
||||
MAX_LIST_FILES_LIMIT_CODE_INDEX,
|
||||
BATCH_SEGMENT_THRESHOLD,
|
||||
MAX_BATCH_RETRIES,
|
||||
INITIAL_RETRY_DELAY_MS,
|
||||
|
|
@ -51,13 +51,13 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
onError?: (error: Error) => void,
|
||||
onBlocksIndexed?: (indexedCount: number) => void,
|
||||
onFileParsed?: (fileBlockCount: number) => void,
|
||||
): Promise<{ codeBlocks: CodeBlock[]; stats: { processed: number; skipped: number }; totalBlockCount: number }> {
|
||||
): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> {
|
||||
const directoryPath = directory
|
||||
// Capture workspace context at scan start
|
||||
const scanWorkspace = getWorkspacePathForContext(directoryPath)
|
||||
|
||||
// Get all files recursively (handles .gitignore automatically)
|
||||
const [allPaths, _] = await listFiles(directoryPath, true, MAX_LIST_FILES_LIMIT)
|
||||
const [allPaths, _] = await listFiles(directoryPath, true, MAX_LIST_FILES_LIMIT_CODE_INDEX)
|
||||
|
||||
// Filter out directories (marked with trailing '/')
|
||||
const filePaths = allPaths.filter((p) => !p.endsWith("/"))
|
||||
|
|
@ -85,7 +85,6 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
|
||||
// Initialize tracking variables
|
||||
const processedFiles = new Set<string>()
|
||||
const codeBlocks: CodeBlock[] = []
|
||||
let processedCount = 0
|
||||
let skippedCount = 0
|
||||
|
||||
|
|
@ -98,7 +97,7 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
let currentBatchBlocks: CodeBlock[] = []
|
||||
let currentBatchTexts: string[] = []
|
||||
let currentBatchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[] = []
|
||||
const activeBatchPromises: Promise<void>[] = []
|
||||
const activeBatchPromises = new Set<Promise<void>>()
|
||||
|
||||
// Initialize block counter
|
||||
let totalBlockCount = 0
|
||||
|
|
@ -125,6 +124,7 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
|
||||
// Check against cache
|
||||
const cachedFileHash = this.cacheManager.getHash(filePath)
|
||||
const isNewFile = !cachedFileHash
|
||||
if (cachedFileHash === currentFileHash) {
|
||||
// File is unchanged
|
||||
skippedCount++
|
||||
|
|
@ -135,7 +135,6 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
const blocks = await this.codeParser.parseFile(filePath, { content, fileHash: currentFileHash })
|
||||
const fileBlockCount = blocks.length
|
||||
onFileParsed?.(fileBlockCount)
|
||||
codeBlocks.push(...blocks)
|
||||
processedCount++
|
||||
|
||||
// Process embeddings if configured
|
||||
|
|
@ -146,20 +145,11 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
const trimmedContent = block.content.trim()
|
||||
if (trimmedContent) {
|
||||
const release = await mutex.acquire()
|
||||
totalBlockCount += fileBlockCount
|
||||
try {
|
||||
currentBatchBlocks.push(block)
|
||||
currentBatchTexts.push(trimmedContent)
|
||||
addedBlocksFromFile = true
|
||||
|
||||
if (addedBlocksFromFile) {
|
||||
currentBatchFileInfos.push({
|
||||
filePath,
|
||||
fileHash: currentFileHash,
|
||||
isNew: !this.cacheManager.getHash(filePath),
|
||||
})
|
||||
}
|
||||
|
||||
// Check if batch threshold is met
|
||||
if (currentBatchBlocks.length >= BATCH_SEGMENT_THRESHOLD) {
|
||||
// Copy current batch data and clear accumulators
|
||||
|
|
@ -181,13 +171,33 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
onBlocksIndexed,
|
||||
),
|
||||
)
|
||||
activeBatchPromises.push(batchPromise)
|
||||
activeBatchPromises.add(batchPromise)
|
||||
|
||||
// Clean up completed promises to prevent memory accumulation
|
||||
batchPromise.finally(() => {
|
||||
activeBatchPromises.delete(batchPromise)
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add file info once per file (outside the block loop)
|
||||
if (addedBlocksFromFile) {
|
||||
const release = await mutex.acquire()
|
||||
try {
|
||||
totalBlockCount += fileBlockCount
|
||||
currentBatchFileInfos.push({
|
||||
filePath,
|
||||
fileHash: currentFileHash,
|
||||
isNew: isNewFile,
|
||||
})
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only update hash if not being processed in a batch
|
||||
await this.cacheManager.updateHash(filePath, currentFileHash)
|
||||
|
|
@ -232,7 +242,12 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
const batchPromise = batchLimiter(() =>
|
||||
this.processBatch(batchBlocks, batchTexts, batchFileInfos, scanWorkspace, onError, onBlocksIndexed),
|
||||
)
|
||||
activeBatchPromises.push(batchPromise)
|
||||
activeBatchPromises.add(batchPromise)
|
||||
|
||||
// Clean up completed promises to prevent memory accumulation
|
||||
batchPromise.finally(() => {
|
||||
activeBatchPromises.delete(batchPromise)
|
||||
})
|
||||
} finally {
|
||||
release()
|
||||
}
|
||||
|
|
@ -280,7 +295,6 @@ export class DirectoryScanner implements IDirectoryScanner {
|
|||
}
|
||||
|
||||
return {
|
||||
codeBlocks,
|
||||
stats: {
|
||||
processed: processedCount,
|
||||
skipped: skippedCount,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
|
|||
import { convertHeadersToObject } from "./utils/headers"
|
||||
import { useDebounce } from "react-use"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ExternalLinkIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import {
|
||||
type ProviderName,
|
||||
type ProviderSettings,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
openRouterDefaultModelId,
|
||||
requestyDefaultModelId,
|
||||
glamaDefaultModelId,
|
||||
|
|
@ -30,8 +32,22 @@ import { useAppTranslation } from "@src/i18n/TranslationContext"
|
|||
import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
|
||||
import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import {
|
||||
useOpenRouterModelProviders,
|
||||
OPENROUTER_DEFAULT_PROVIDER_NAME,
|
||||
} from "@src/components/ui/hooks/useOpenRouterModelProviders"
|
||||
import { filterProviders, filterModels } from "./utils/organizationFilters"
|
||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SearchableSelect } from "@src/components/ui"
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SearchableSelect,
|
||||
Collapsible,
|
||||
CollapsibleTrigger,
|
||||
CollapsibleContent,
|
||||
} from "@src/components/ui"
|
||||
|
||||
import {
|
||||
Anthropic,
|
||||
|
|
@ -64,6 +80,7 @@ import { ThinkingBudget } from "./ThinkingBudget"
|
|||
import { DiffSettingsControl } from "./DiffSettingsControl"
|
||||
import { TemperatureControl } from "./TemperatureControl"
|
||||
import { RateLimitSecondsControl } from "./RateLimitSecondsControl"
|
||||
import { ConsecutiveMistakeLimitControl } from "./ConsecutiveMistakeLimitControl"
|
||||
import { BedrockCustomArn } from "./providers/BedrockCustomArn"
|
||||
import { buildDocLink } from "@src/utils/docLinks"
|
||||
|
||||
|
|
@ -119,6 +136,7 @@ const ApiOptions = ({
|
|||
)
|
||||
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
|
||||
const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = useState(false)
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
|
|
@ -139,12 +157,20 @@ const ApiOptions = ({
|
|||
|
||||
const { data: routerModels, refetch: refetchRouterModels } = useRouterModels()
|
||||
|
||||
const { data: openRouterModelProviders } = useOpenRouterModelProviders(apiConfiguration?.openRouterModelId, {
|
||||
enabled:
|
||||
!!apiConfiguration?.openRouterModelId &&
|
||||
routerModels?.openrouter &&
|
||||
Object.keys(routerModels.openrouter).length > 1 &&
|
||||
apiConfiguration.openRouterModelId in routerModels.openrouter,
|
||||
})
|
||||
|
||||
// Update `apiModelId` whenever `selectedModelId` changes.
|
||||
useEffect(() => {
|
||||
if (selectedModelId) {
|
||||
if (selectedModelId && apiConfiguration.apiModelId !== selectedModelId) {
|
||||
setApiConfigurationField("apiModelId", selectedModelId)
|
||||
}
|
||||
}, [selectedModelId, setApiConfigurationField])
|
||||
}, [selectedModelId, setApiConfigurationField, apiConfiguration.apiModelId])
|
||||
|
||||
// Debounced refresh model updates, only executed 250ms after the user
|
||||
// stops typing.
|
||||
|
|
@ -536,22 +562,78 @@ const ApiOptions = ({
|
|||
/>
|
||||
|
||||
{!fromWelcomeView && (
|
||||
<>
|
||||
<DiffSettingsControl
|
||||
diffEnabled={apiConfiguration.diffEnabled}
|
||||
fuzzyMatchThreshold={apiConfiguration.fuzzyMatchThreshold}
|
||||
onChange={(field, value) => setApiConfigurationField(field, value)}
|
||||
/>
|
||||
<TemperatureControl
|
||||
value={apiConfiguration.modelTemperature}
|
||||
onChange={handleInputChange("modelTemperature", noTransform)}
|
||||
maxValue={2}
|
||||
/>
|
||||
<RateLimitSecondsControl
|
||||
value={apiConfiguration.rateLimitSeconds || 0}
|
||||
onChange={(value) => setApiConfigurationField("rateLimitSeconds", value)}
|
||||
/>
|
||||
</>
|
||||
<Collapsible open={isAdvancedSettingsOpen} onOpenChange={setIsAdvancedSettingsOpen}>
|
||||
<CollapsibleTrigger className="flex items-center gap-1 w-full cursor-pointer hover:opacity-80 mb-2">
|
||||
<span className={`codicon codicon-chevron-${isAdvancedSettingsOpen ? "down" : "right"}`}></span>
|
||||
<span className="font-medium">{t("settings:advancedSettings.title")}</span>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="space-y-3">
|
||||
<DiffSettingsControl
|
||||
diffEnabled={apiConfiguration.diffEnabled}
|
||||
fuzzyMatchThreshold={apiConfiguration.fuzzyMatchThreshold}
|
||||
onChange={(field, value) => setApiConfigurationField(field, value)}
|
||||
/>
|
||||
<TemperatureControl
|
||||
value={apiConfiguration.modelTemperature}
|
||||
onChange={handleInputChange("modelTemperature", noTransform)}
|
||||
maxValue={2}
|
||||
/>
|
||||
<RateLimitSecondsControl
|
||||
value={apiConfiguration.rateLimitSeconds || 0}
|
||||
onChange={(value) => setApiConfigurationField("rateLimitSeconds", value)}
|
||||
/>
|
||||
<ConsecutiveMistakeLimitControl
|
||||
value={
|
||||
apiConfiguration.consecutiveMistakeLimit !== undefined
|
||||
? apiConfiguration.consecutiveMistakeLimit
|
||||
: DEFAULT_CONSECUTIVE_MISTAKE_LIMIT
|
||||
}
|
||||
onChange={(value) => setApiConfigurationField("consecutiveMistakeLimit", value)}
|
||||
/>
|
||||
{selectedProvider === "openrouter" &&
|
||||
openRouterModelProviders &&
|
||||
Object.keys(openRouterModelProviders).length > 0 && (
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.openRouter.providerRouting.title")}
|
||||
</label>
|
||||
<a href={`https://openrouter.ai/${selectedModelId}/providers`}>
|
||||
<ExternalLinkIcon className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
<Select
|
||||
value={
|
||||
apiConfiguration?.openRouterSpecificProvider ||
|
||||
OPENROUTER_DEFAULT_PROVIDER_NAME
|
||||
}
|
||||
onValueChange={(value) =>
|
||||
setApiConfigurationField("openRouterSpecificProvider", value)
|
||||
}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={OPENROUTER_DEFAULT_PROVIDER_NAME}>
|
||||
{OPENROUTER_DEFAULT_PROVIDER_NAME}
|
||||
</SelectItem>
|
||||
{Object.entries(openRouterModelProviders).map(([value, { label }]) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-1">
|
||||
{t("settings:providers.openRouter.providerRouting.description")}{" "}
|
||||
<a href="https://openrouter.ai/docs/features/provider-routing">
|
||||
{t("settings:providers.openRouter.providerRouting.learnMore")}.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import React, { useCallback } from "react"
|
||||
import { Slider } from "@/components/ui"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { DEFAULT_CONSECUTIVE_MISTAKE_LIMIT } from "@roo-code/types"
|
||||
|
||||
interface ConsecutiveMistakeLimitControlProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
export const ConsecutiveMistakeLimitControl: React.FC<ConsecutiveMistakeLimitControlProps> = ({ value, onChange }) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(newValue: number) => {
|
||||
// Ensure value is not negative
|
||||
const validValue = Math.max(0, newValue)
|
||||
onChange(validValue)
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.consecutiveMistakeLimit.label")}</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
value={[value ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT]}
|
||||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
onValueChange={(newValue) => handleValueChange(newValue[0])}
|
||||
/>
|
||||
<span className="w-10">{Math.max(0, value ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT)}</span>
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
{value === 0
|
||||
? t("settings:providers.consecutiveMistakeLimit.unlimitedDescription")
|
||||
: t("settings:providers.consecutiveMistakeLimit.description", {
|
||||
value: value ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
})}
|
||||
</div>
|
||||
{value === 0 && (
|
||||
<div className="text-sm text-vscode-errorForeground mt-1">
|
||||
{t("settings:providers.consecutiveMistakeLimit.warning")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -218,7 +218,15 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
return prevState
|
||||
}
|
||||
|
||||
setChangeDetected(true)
|
||||
const previousValue = prevState.apiConfiguration?.[field]
|
||||
|
||||
// Don't treat initial sync from undefined to a defined value as a user change
|
||||
// This prevents the dirty state when the component initializes and auto-syncs the model ID
|
||||
const isInitialSync = previousValue === undefined && value !== undefined
|
||||
|
||||
if (!isInitialSync) {
|
||||
setChangeDetected(true)
|
||||
}
|
||||
return { ...prevState, apiConfiguration: { ...prevState.apiConfiguration, [field]: value } }
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -100,6 +100,20 @@ vi.mock("@/components/ui", () => ({
|
|||
</select>
|
||||
</div>
|
||||
),
|
||||
// Add Collapsible components
|
||||
Collapsible: ({ children, open }: any) => (
|
||||
<div className="collapsible-mock" data-open={open}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
CollapsibleTrigger: ({ children, className, onClick }: any) => (
|
||||
<div className={`collapsible-trigger-mock ${className || ""}`} onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
CollapsibleContent: ({ children, className }: any) => (
|
||||
<div className={`collapsible-content-mock ${className || ""}`}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("../TemperatureControl", () => ({
|
||||
|
|
|
|||
|
|
@ -179,6 +179,20 @@ vi.mock("@/components/ui", () => ({
|
|||
{children}
|
||||
</button>
|
||||
),
|
||||
// Add Collapsible components
|
||||
Collapsible: ({ children, open }: any) => (
|
||||
<div className="collapsible-mock" data-open={open}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
CollapsibleTrigger: ({ children, className, onClick }: any) => (
|
||||
<div className={`collapsible-trigger-mock ${className || ""}`} onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
CollapsibleContent: ({ children, className }: any) => (
|
||||
<div className={`collapsible-content-mock ${className || ""}`}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock window.postMessage to trigger state hydration
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { useCallback, useState } from "react"
|
|||
import { Trans } from "react-i18next"
|
||||
import { Checkbox } from "vscrui"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ExternalLinkIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import { type ProviderSettings, type OrganizationAllowList, openRouterDefaultModelId } from "@roo-code/types"
|
||||
|
||||
|
|
@ -10,12 +9,7 @@ import type { RouterModels } from "@roo/api"
|
|||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { getOpenRouterAuthUrl } from "@src/oauth/urls"
|
||||
import {
|
||||
useOpenRouterModelProviders,
|
||||
OPENROUTER_DEFAULT_PROVIDER_NAME,
|
||||
} from "@src/components/ui/hooks/useOpenRouterModelProviders"
|
||||
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"
|
||||
|
||||
import { inputEventTransform, noTransform } from "../transforms"
|
||||
|
||||
|
|
@ -37,7 +31,6 @@ export const OpenRouter = ({
|
|||
apiConfiguration,
|
||||
setApiConfigurationField,
|
||||
routerModels,
|
||||
selectedModelId,
|
||||
uriScheme,
|
||||
fromWelcomeView,
|
||||
organizationAllowList,
|
||||
|
|
@ -58,14 +51,6 @@ export const OpenRouter = ({
|
|||
[setApiConfigurationField],
|
||||
)
|
||||
|
||||
const { data: openRouterModelProviders } = useOpenRouterModelProviders(apiConfiguration?.openRouterModelId, {
|
||||
enabled:
|
||||
!!apiConfiguration?.openRouterModelId &&
|
||||
routerModels?.openrouter &&
|
||||
Object.keys(routerModels.openrouter).length > 1 &&
|
||||
apiConfiguration.openRouterModelId in routerModels.openrouter,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
|
|
@ -139,41 +124,6 @@ export const OpenRouter = ({
|
|||
organizationAllowList={organizationAllowList}
|
||||
errorMessage={modelValidationError}
|
||||
/>
|
||||
{openRouterModelProviders && Object.keys(openRouterModelProviders).length > 0 && (
|
||||
<div>
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.openRouter.providerRouting.title")}
|
||||
</label>
|
||||
<a href={`https://openrouter.ai/${selectedModelId}/providers`}>
|
||||
<ExternalLinkIcon className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
<Select
|
||||
value={apiConfiguration?.openRouterSpecificProvider || OPENROUTER_DEFAULT_PROVIDER_NAME}
|
||||
onValueChange={(value) => setApiConfigurationField("openRouterSpecificProvider", value)}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={OPENROUTER_DEFAULT_PROVIDER_NAME}>
|
||||
{OPENROUTER_DEFAULT_PROVIDER_NAME}
|
||||
</SelectItem>
|
||||
{Object.entries(openRouterModelProviders).map(([value, { label }]) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-sm text-vscode-descriptionForeground mt-1">
|
||||
{t("settings:providers.openRouter.providerRouting.description")}{" "}
|
||||
<a href="https://openrouter.ai/docs/features/provider-routing">
|
||||
{t("settings:providers.openRouter.providerRouting.learnMore")}.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -399,6 +399,12 @@
|
|||
"label": "Límit de freqüència",
|
||||
"description": "Temps mínim entre sol·licituds d'API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Límit d'errors i repeticions",
|
||||
"description": "Nombre d'errors consecutius o accions repetides abans de mostrar el diàleg 'En Roo està tenint problemes'",
|
||||
"unlimitedDescription": "Reintents il·limitats habilitats (procediment automàtic). El diàleg no apareixerà mai.",
|
||||
"warning": "⚠️ Establir a 0 permet reintents il·limitats que poden consumir un ús significatiu de l'API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esforç de raonament del model",
|
||||
"high": "Alt",
|
||||
|
|
@ -568,6 +574,9 @@
|
|||
"description": "Quan està habilitat, el terminal hereta les variables d'entorn del procés pare de VSCode, com ara la configuració d'integració del shell definida al perfil d'usuari. Això commuta directament la configuració global de VSCode `terminal.integrated.inheritEnv`. <0>Més informació</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Configuració avançada"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Habilitar edició mitjançant diffs",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Ratenbegrenzung",
|
||||
"description": "Minimale Zeit zwischen API-Anfragen."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Fehler- & Wiederholungslimit",
|
||||
"description": "Anzahl aufeinanderfolgender Fehler oder wiederholter Aktionen, bevor der Dialog 'Roo hat Probleme' angezeigt wird",
|
||||
"unlimitedDescription": "Unbegrenzte Wiederholungen aktiviert (automatisches Fortfahren). Der Dialog wird niemals angezeigt.",
|
||||
"warning": "⚠️ Das Setzen auf 0 erlaubt unbegrenzte Wiederholungen, was zu erheblichem API-Verbrauch führen kann"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Modell-Denkaufwand",
|
||||
"high": "Hoch",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Wenn aktiviert, erbt das Terminal Umgebungsvariablen aus dem übergeordneten Prozess von VSCode, wie z.B. benutzerdefinierte Shell-Integrationseinstellungen. Dies schaltet direkt die globale VSCode-Einstellung `terminal.integrated.inheritEnv` um. <0>Mehr erfahren</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Erweiterte Einstellungen"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Bearbeitung durch Diffs aktivieren",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Rate limit",
|
||||
"description": "Minimum time between API requests."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Error & Repetition Limit",
|
||||
"description": "Number of consecutive errors or repeated actions before showing 'Roo is having trouble' dialog",
|
||||
"unlimitedDescription": "Unlimited retries enabled (auto-proceed). The dialog will never appear.",
|
||||
"warning": "⚠️ Setting to 0 allows unlimited retries which may consume significant API usage"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model Reasoning Effort",
|
||||
"high": "High",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "When enabled, the terminal will inherit environment variables from VSCode's parent process, such as user-profile-defined shell integration settings. This directly toggles VSCode global setting `terminal.integrated.inheritEnv`. <0>Learn more</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Advanced settings"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Enable editing through diffs",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Límite de tasa",
|
||||
"description": "Tiempo mínimo entre solicitudes de API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Límite de errores y repeticiones",
|
||||
"description": "Número de errores consecutivos o acciones repetidas antes de mostrar el diálogo 'Roo está teniendo problemas'",
|
||||
"unlimitedDescription": "Reintentos ilimitados habilitados (proceder automáticamente). El diálogo nunca aparecerá.",
|
||||
"warning": "⚠️ Establecer en 0 permite reintentos ilimitados que pueden consumir un uso significativo de la API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esfuerzo de razonamiento del modelo",
|
||||
"high": "Alto",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Cuando está habilitado, el terminal hereda las variables de entorno del proceso padre de VSCode, como la configuración de integración del shell definida en el perfil del usuario. Esto alterna directamente la configuración global de VSCode `terminal.integrated.inheritEnv`. <0>Más información</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Configuración avanzada"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Habilitar edición a través de diffs",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Limite de débit",
|
||||
"description": "Temps minimum entre les requêtes API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Limite d'erreurs et de répétitions",
|
||||
"description": "Nombre d'erreurs consécutives ou d'actions répétées avant d'afficher la boîte de dialogue 'Roo a des difficultés'",
|
||||
"unlimitedDescription": "Réessais illimités activés (poursuite automatique). La boîte de dialogue n'apparaîtra jamais.",
|
||||
"warning": "⚠️ Mettre à 0 autorise des réessais illimités, ce qui peut consommer une utilisation importante de l'API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Effort de raisonnement du modèle",
|
||||
"high": "Élevé",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Lorsqu'activé, le terminal hérite des variables d'environnement du processus parent VSCode, comme les paramètres d'intégration du shell définis dans le profil utilisateur. Cela bascule directement le paramètre global VSCode `terminal.integrated.inheritEnv`. <0>En savoir plus</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Paramètres avancés"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Activer l'édition via des diffs",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "दर सीमा",
|
||||
"description": "API अनुरोधों के बीच न्यूनतम समय।"
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "त्रुटि और पुनरावृत्ति सीमा",
|
||||
"description": "'रू को समस्या हो रही है' संवाद दिखाने से पहले लगातार त्रुटियों या दोहराए गए कार्यों की संख्या",
|
||||
"unlimitedDescription": "असीमित पुनः प्रयास सक्षम (स्वतः आगे बढ़ें)। संवाद कभी नहीं दिखाई देगा।",
|
||||
"warning": "⚠️ 0 पर सेट करने से असीमित पुनः प्रयास की अनुमति मिलती है जिससे महत्वपूर्ण एपीआई उपयोग हो सकता है"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "मॉडल तर्क प्रयास",
|
||||
"high": "उच्च",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "सक्षम होने पर, टर्मिनल VSCode के मूल प्रक्रिया से पर्यावरण चर विरासत में लेता है, जैसे उपयोगकर्ता प्रोफ़ाइल में परिभाषित शेल एकीकरण सेटिंग्स। यह VSCode की वैश्विक सेटिंग `terminal.integrated.inheritEnv` को सीधे टॉगल करता है। <0>अधिक जानें</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "उन्नत सेटिंग्स"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "diffs के माध्यम से संपादन सक्षम करें",
|
||||
|
|
|
|||
|
|
@ -399,6 +399,12 @@
|
|||
"label": "Rate limit",
|
||||
"description": "Waktu minimum antara permintaan API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Batas Kesalahan & Pengulangan",
|
||||
"description": "Jumlah kesalahan berturut-turut atau tindakan berulang sebelum menampilkan dialog 'Roo mengalami masalah'",
|
||||
"unlimitedDescription": "Percobaan ulang tak terbatas diaktifkan (lanjut otomatis). Dialog tidak akan pernah muncul.",
|
||||
"warning": "⚠️ Mengatur ke 0 memungkinkan percobaan ulang tak terbatas yang dapat menghabiskan penggunaan API yang signifikan"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Upaya Reasoning Model",
|
||||
"high": "Tinggi",
|
||||
|
|
@ -568,6 +574,9 @@
|
|||
"description": "Ketika diaktifkan, terminal akan mewarisi variabel environment dari proses parent VSCode, seperti pengaturan integrasi shell yang didefinisikan user-profile. Ini secara langsung mengalihkan pengaturan global VSCode `terminal.integrated.inheritEnv`. <0>Pelajari lebih lanjut</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Pengaturan lanjutan"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Aktifkan editing melalui diff",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Limite di frequenza",
|
||||
"description": "Tempo minimo tra le richieste API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Limite di errori e ripetizioni",
|
||||
"description": "Numero di errori consecutivi o azioni ripetute prima di mostrare la finestra di dialogo 'Roo sta riscontrando problemi'",
|
||||
"unlimitedDescription": "Tentativi illimitati abilitati (procedi automaticamente). La finestra di dialogo non verrà mai visualizzata.",
|
||||
"warning": "⚠️ L'impostazione a 0 consente tentativi illimitati che possono consumare un notevole utilizzo dell'API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Sforzo di ragionamento del modello",
|
||||
"high": "Alto",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Quando abilitato, il terminale eredita le variabili d'ambiente dal processo padre di VSCode, come le impostazioni di integrazione della shell definite nel profilo utente. Questo attiva direttamente l'impostazione globale di VSCode `terminal.integrated.inheritEnv`. <0>Scopri di più</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Impostazioni avanzate"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Abilita modifica tramite diff",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "レート制限",
|
||||
"description": "APIリクエスト間の最小時間。"
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "エラーと繰り返しの制限",
|
||||
"description": "「Rooが問題を抱えています」ダイアログを表示するまでの連続エラーまたは繰り返しアクションの数",
|
||||
"unlimitedDescription": "無制限のリトライが有効です(自動進行)。ダイアログは表示されません。",
|
||||
"warning": "⚠️ 0に設定すると無制限のリトライが可能になり、API使用量が大幅に増加する可能性があります"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "モデル推論の労力",
|
||||
"high": "高",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "有効にすると、ターミナルは VSCode の親プロセスから環境変数を継承します。ユーザープロファイルで定義されたシェル統合設定などが含まれます。これは VSCode のグローバル設定 `terminal.integrated.inheritEnv` を直接切り替えます。 <0>詳細情報</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "詳細設定"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "diff経由の編集を有効化",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "속도 제한",
|
||||
"description": "API 요청 간 최소 시간."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "오류 및 반복 제한",
|
||||
"description": "'Roo에 문제가 발생했습니다' 대화 상자를 표시하기 전의 연속 오류 또는 반복 작업 수",
|
||||
"unlimitedDescription": "무제한 재시도 활성화 (자동 진행). 대화 상자가 나타나지 않습니다.",
|
||||
"warning": "⚠️ 0으로 설정하면 무제한 재시도가 허용되어 상당한 API 사용량이 발생할 수 있습니다"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "모델 추론 노력",
|
||||
"high": "높음",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "활성화하면 터미널이 VSCode 부모 프로세스로부터 환경 변수를 상속받습니다. 사용자 프로필에 정의된 셸 통합 설정 등이 포함됩니다. 이는 VSCode 전역 설정 `terminal.integrated.inheritEnv`를 직접 전환합니다. <0>더 알아보기</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "고급 설정"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "diff를 통한 편집 활성화",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Snelheidslimiet",
|
||||
"description": "Minimale tijd tussen API-verzoeken."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Fout- & Herhalingslimiet",
|
||||
"description": "Aantal opeenvolgende fouten of herhaalde acties voordat het dialoogvenster 'Roo ondervindt problemen' wordt weergegeven",
|
||||
"unlimitedDescription": "Onbeperkt aantal nieuwe pogingen ingeschakeld (automatisch doorgaan). Het dialoogvenster zal nooit verschijnen.",
|
||||
"warning": "⚠️ Instellen op 0 staat onbeperkte nieuwe pogingen toe, wat aanzienlijk API-gebruik kan verbruiken"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model redeneervermogen",
|
||||
"high": "Hoog",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Indien ingeschakeld, neemt de terminal omgevingsvariabelen over van het bovenliggende VSCode-proces, zoals shell-integratie-instellingen uit het gebruikersprofiel. Dit schakelt direct de VSCode-instelling `terminal.integrated.inheritEnv` om. <0>Meer informatie</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Geavanceerde instellingen"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Bewerken via diffs inschakelen",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Limit szybkości",
|
||||
"description": "Minimalny czas między żądaniami API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Limit błędów i powtórzeń",
|
||||
"description": "Liczba kolejnych błędów lub powtórzonych akcji przed wyświetleniem okna dialogowego 'Roo ma problemy'",
|
||||
"unlimitedDescription": "Włączono nieograniczone próby (automatyczne kontynuowanie). Okno dialogowe nigdy się nie pojawi.",
|
||||
"warning": "⚠️ Ustawienie na 0 pozwala na nieograniczone próby, co może zużyć znaczną ilość API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Wysiłek rozumowania modelu",
|
||||
"high": "Wysoki",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Po włączeniu terminal dziedziczy zmienne środowiskowe z procesu nadrzędnego VSCode, takie jak ustawienia integracji powłoki zdefiniowane w profilu użytkownika. Przełącza to bezpośrednio globalne ustawienie VSCode `terminal.integrated.inheritEnv`. <0>Dowiedz się więcej</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Ustawienia zaawansowane"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Włącz edycję przez różnice",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Limite de taxa",
|
||||
"description": "Tempo mínimo entre requisições de API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Limite de Erros e Repetições",
|
||||
"description": "Número de erros consecutivos ou ações repetidas antes de exibir o diálogo 'Roo está com problemas'",
|
||||
"unlimitedDescription": "Tentativas ilimitadas ativadas (prosseguimento automático). O diálogo nunca aparecerá.",
|
||||
"warning": "⚠️ Definir como 0 permite tentativas ilimitadas, o que pode consumir um uso significativo da API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Esforço de raciocínio do modelo",
|
||||
"high": "Alto",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Quando ativado, o terminal herda variáveis de ambiente do processo pai do VSCode, como configurações de integração do shell definidas no perfil do usuário. Isso alterna diretamente a configuração global do VSCode `terminal.integrated.inheritEnv`. <0>Saiba mais</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Configurações avançadas"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Ativar edição através de diffs",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Лимит скорости",
|
||||
"description": "Минимальное время между запросами к API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Лимит ошибок и повторений",
|
||||
"description": "Количество последовательных ошибок или повторных действий перед показом диалогового окна 'У Roo возникли проблемы'",
|
||||
"unlimitedDescription": "Включены неограниченные повторные попытки (автоматическое продолжение). Диалоговое окно никогда не появится.",
|
||||
"warning": "⚠️ Установка значения 0 разрешает неограниченные повторные попытки, что может значительно увеличить использование API"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Усилия по рассуждению модели",
|
||||
"high": "Высокие",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Если включено, терминал будет наследовать переменные среды от родительского процесса VSCode, такие как настройки интеграции оболочки, определённые в профиле пользователя. Напрямую переключает глобальную настройку VSCode `terminal.integrated.inheritEnv`. <0>Подробнее</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Дополнительные настройки"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Включить редактирование через диффы",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Hız sınırı",
|
||||
"description": "API istekleri arasındaki minimum süre."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Hata ve Tekrar Limiti",
|
||||
"description": "'Roo sorun yaşıyor' iletişim kutusunu göstermeden önceki ardışık hata veya tekrarlanan eylem sayısı",
|
||||
"unlimitedDescription": "Sınırsız yeniden deneme etkin (otomatik devam et). Diyalog asla görünmeyecek.",
|
||||
"warning": "⚠️ 0'a ayarlamak, önemli API kullanımına neden olabilecek sınırsız yeniden denemeye izin verir"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Model Akıl Yürütme Çabası",
|
||||
"high": "Yüksek",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Etkinleştirildiğinde, terminal VSCode üst işleminden ortam değişkenlerini devralır, örneğin kullanıcı profilinde tanımlanan kabuk entegrasyon ayarları gibi. Bu, VSCode'un global ayarı olan `terminal.integrated.inheritEnv` değerini doğrudan değiştirir. <0>Daha fazla bilgi</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Gelişmiş ayarlar"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Diff'ler aracılığıyla düzenlemeyi etkinleştir",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "Giới hạn tốc độ",
|
||||
"description": "Thời gian tối thiểu giữa các yêu cầu API."
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "Giới hạn lỗi và lặp lại",
|
||||
"description": "Số lỗi liên tiếp hoặc hành động lặp lại trước khi hiển thị hộp thoại 'Roo đang gặp sự cố'",
|
||||
"unlimitedDescription": "Đã bật thử lại không giới hạn (tự động tiếp tục). Hộp thoại sẽ không bao giờ xuất hiện.",
|
||||
"warning": "⚠️ Đặt thành 0 cho phép thử lại không giới hạn, điều này có thể tiêu tốn mức sử dụng API đáng kể"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "Nỗ lực suy luận của mô hình",
|
||||
"high": "Cao",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "Khi được bật, terminal sẽ kế thừa các biến môi trường từ tiến trình cha của VSCode, như các cài đặt tích hợp shell được định nghĩa trong hồ sơ người dùng. Điều này trực tiếp chuyển đổi cài đặt toàn cục của VSCode `terminal.integrated.inheritEnv`. <0>Tìm hiểu thêm</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "Cài đặt nâng cao"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "Bật chỉnh sửa qua diff",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "API 请求频率限制",
|
||||
"description": "设置API请求的最小间隔时间"
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "错误和重复限制",
|
||||
"description": "在显示“Roo遇到问题”对话框前允许的连续错误或重复操作次数",
|
||||
"unlimitedDescription": "已启用无限重试(自动继续)。对话框将永远不会出现。",
|
||||
"warning": "⚠️ 设置为 0 允许无限重试,这可能会消耗大量 API 使用量"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "模型推理强度",
|
||||
"high": "高",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "启用后,终端将从 VSCode 父进程继承环境变量,如用户配置文件中定义的 shell 集成设置。这直接切换 VSCode 全局设置 `terminal.integrated.inheritEnv`。 <0>了解更多</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "高级设置"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "启用diff更新",
|
||||
|
|
|
|||
|
|
@ -395,6 +395,12 @@
|
|||
"label": "速率限制",
|
||||
"description": "API 請求間的最短時間"
|
||||
},
|
||||
"consecutiveMistakeLimit": {
|
||||
"label": "錯誤和重複限制",
|
||||
"description": "在顯示「Roo 遇到問題」對話方塊前允許的連續錯誤或重複操作次數",
|
||||
"unlimitedDescription": "已啟用無限重試(自動繼續)。對話方塊將永遠不會出現。",
|
||||
"warning": "⚠️ 設定為 0 允許無限重試,這可能會消耗大量 API 使用量"
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"label": "模型推理強度",
|
||||
"high": "高",
|
||||
|
|
@ -564,6 +570,9 @@
|
|||
"description": "啟用後,終端機將從 VSCode 父程序繼承環境變數,如使用者設定檔中定義的 shell 整合設定。這直接切換 VSCode 全域設定 `terminal.integrated.inheritEnv`。 <0>瞭解更多</0>"
|
||||
}
|
||||
},
|
||||
"advancedSettings": {
|
||||
"title": "進階設定"
|
||||
},
|
||||
"advanced": {
|
||||
"diff": {
|
||||
"label": "透過差異比對編輯",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue