From 02ff4bd9410e2b7db5d53da479590ee3f0449132 Mon Sep 17 00:00:00 2001 From: R00-B0T <110429663+R00-B0T@users.noreply.github.com> Date: Fri, 2 May 2025 20:03:48 -0700 Subject: [PATCH 001/228] Changeset version bump (#3134) * changeset version bump * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Chris Estreich --- .changeset/chilled-books-relate.md | 5 ----- .changeset/famous-jars-dance.md | 5 ----- .changeset/great-sheep-speak.md | 5 ----- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 .changeset/chilled-books-relate.md delete mode 100644 .changeset/famous-jars-dance.md delete mode 100644 .changeset/great-sheep-speak.md diff --git a/.changeset/chilled-books-relate.md b/.changeset/chilled-books-relate.md deleted file mode 100644 index 281d55d1046..00000000000 --- a/.changeset/chilled-books-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Fix empty command bug diff --git a/.changeset/famous-jars-dance.md b/.changeset/famous-jars-dance.md deleted file mode 100644 index 030f640be14..00000000000 --- a/.changeset/famous-jars-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -v3.15.3 diff --git a/.changeset/great-sheep-speak.md b/.changeset/great-sheep-speak.md deleted file mode 100644 index 727e72c8ee6..00000000000 --- a/.changeset/great-sheep-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -More robust process killing diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e7a9766160..d2b44282d69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Roo Code Changelog +## [3.15.3] - 2025-05-02 + +- Terminal: Fix empty command bug +- Terminal: More robust process killing +- Optimize Gemini prompt caching for OpenRouter +- Chat view performance improvements + ## [3.15.2] - 2025-05-02 - Fix terminal performance issues diff --git a/package-lock.json b/package-lock.json index 396d799518b..1f6ad6dc9a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.15.2", + "version": "3.15.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.15.2", + "version": "3.15.3", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index d60089fee96..48dcd53674f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.15.2", + "version": "3.15.3", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 8641b215c6c82d50e53b9953e4155ebf6d944e26 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 2 May 2025 23:16:21 -0400 Subject: [PATCH 002/228] Add isSubtask to telemetry (#3141) --- src/core/webview/ClineProvider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 167c5d327e3..ca9b63d4ae2 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1517,6 +1517,11 @@ export class ClineProvider extends EventEmitter implements properties.diffStrategy = currentCline.diffStrategy.getName() } + // Add isSubtask property that indicates whether this task is a subtask + if (currentCline) { + properties.isSubtask = !!currentCline.parentTask + } + return properties } } From 98adb04f98374f991bffdff0f204fc60778ff30f Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Sat, 3 May 2025 07:55:35 -0700 Subject: [PATCH 003/228] Gemini caching tweaks (#3142) --- .changeset/tiny-mugs-give.md | 5 + src/api/providers/__tests__/gemini.test.ts | 381 +++++++++++++++++++++ src/api/providers/gemini.ts | 127 +++++-- 3 files changed, 475 insertions(+), 38 deletions(-) create mode 100644 .changeset/tiny-mugs-give.md diff --git a/.changeset/tiny-mugs-give.md b/.changeset/tiny-mugs-give.md new file mode 100644 index 00000000000..7aff925bac3 --- /dev/null +++ b/.changeset/tiny-mugs-give.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Improve Gemini caching efficiency diff --git a/src/api/providers/__tests__/gemini.test.ts b/src/api/providers/__tests__/gemini.test.ts index af32b1bebbf..e994bf0edfd 100644 --- a/src/api/providers/__tests__/gemini.test.ts +++ b/src/api/providers/__tests__/gemini.test.ts @@ -247,3 +247,384 @@ describe("GeminiHandler", () => { }) }) }) + +describe("Caching Logic", () => { + const systemPrompt = "System prompt" + const longContent = "a".repeat(5 * 4096) // Ensure content is long enough for caching + const mockMessagesLong: Anthropic.Messages.MessageParam[] = [ + { role: "user", content: longContent }, + { role: "assistant", content: "OK" }, + ] + const cacheKey = "test-cache-key" + const mockCacheName = "generated/caches/mock-cache-name" + const mockCacheTokens = 5000 + + let handlerWithCache: GeminiHandler + let mockGenerateContentStream: jest.Mock + let mockCreateCache: jest.Mock + let mockDeleteCache: jest.Mock + let mockCacheGet: jest.Mock + let mockCacheSet: jest.Mock + + beforeEach(() => { + mockGenerateContentStream = jest.fn().mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { text: "Response" } + yield { + usageMetadata: { + promptTokenCount: 100, // Uncached input + candidatesTokenCount: 50, // Output + cachedContentTokenCount: 0, // Default, override in tests + }, + } + }, + }) + mockCreateCache = jest.fn().mockResolvedValue({ + name: mockCacheName, + usageMetadata: { totalTokenCount: mockCacheTokens }, + }) + mockDeleteCache = jest.fn().mockResolvedValue({}) + mockCacheGet = jest.fn().mockReturnValue(undefined) // Default: cache miss + mockCacheSet = jest.fn() + + handlerWithCache = new GeminiHandler({ + apiKey: "test-key", + apiModelId: "gemini-1.5-flash-latest", // Use a model that supports caching + geminiApiKey: "test-key", + promptCachingEnabled: true, // Enable caching for these tests + }) + + handlerWithCache["client"] = { + models: { + generateContentStream: mockGenerateContentStream, + }, + caches: { + create: mockCreateCache, + delete: mockDeleteCache, + }, + } as any + handlerWithCache["contentCaches"] = { + get: mockCacheGet, + set: mockCacheSet, + } as any + }) + + it("should not use cache if promptCachingEnabled is false", async () => { + handlerWithCache["options"].promptCachingEnabled = false + const stream = handlerWithCache.createMessage(systemPrompt, mockMessagesLong, cacheKey) + + for await (const _ of stream) { + } + + expect(mockCacheGet).not.toHaveBeenCalled() + expect(mockGenerateContentStream).toHaveBeenCalledWith( + expect.objectContaining({ + config: expect.objectContaining({ + cachedContent: undefined, + systemInstruction: systemPrompt, + }), + }), + ) + expect(mockCreateCache).not.toHaveBeenCalled() + }) + + it("should not use cache if content length is below threshold", async () => { + const shortMessages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "short" }] + const stream = handlerWithCache.createMessage(systemPrompt, shortMessages, cacheKey) + for await (const _ of stream) { + /* consume stream */ + } + + expect(mockCacheGet).not.toHaveBeenCalled() // Doesn't even check cache if too short + expect(mockGenerateContentStream).toHaveBeenCalledWith( + expect.objectContaining({ + config: expect.objectContaining({ + cachedContent: undefined, + systemInstruction: systemPrompt, + }), + }), + ) + expect(mockCreateCache).not.toHaveBeenCalled() + }) + + it("should perform cache write on miss when conditions met", async () => { + const stream = handlerWithCache.createMessage(systemPrompt, mockMessagesLong, cacheKey) + const chunks = [] + + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(mockCacheGet).toHaveBeenCalledWith(cacheKey) + expect(mockGenerateContentStream).toHaveBeenCalledWith( + expect.objectContaining({ + config: expect.objectContaining({ + cachedContent: undefined, + systemInstruction: systemPrompt, + }), + }), + ) + + await new Promise(process.nextTick) // Allow microtasks (like the async writeCache) to run + + expect(mockCreateCache).toHaveBeenCalledTimes(1) + expect(mockCreateCache).toHaveBeenCalledWith( + expect.objectContaining({ + model: expect.stringContaining("gemini-2.0-flash-001"), // Adjusted expectation based on test run + config: expect.objectContaining({ + systemInstruction: systemPrompt, + contents: expect.any(Array), // Verify contents structure if needed + ttl: expect.stringContaining("300s"), + }), + }), + ) + expect(mockCacheSet).toHaveBeenCalledWith( + cacheKey, + expect.objectContaining({ + key: mockCacheName, + count: mockMessagesLong.length, + tokens: mockCacheTokens, + }), + ) + expect(mockDeleteCache).not.toHaveBeenCalled() // No previous cache to delete + + const usageChunk = chunks.find((c) => c.type === "usage") + + expect(usageChunk).toEqual( + expect.objectContaining({ + cacheWriteTokens: 100, // Should match promptTokenCount when write is queued + cacheReadTokens: 0, + }), + ) + }) + + it("should use cache on hit and not send system prompt", async () => { + const cachedMessagesCount = 1 + const cacheReadTokensCount = 4000 + mockCacheGet.mockReturnValue({ key: mockCacheName, count: cachedMessagesCount, tokens: cacheReadTokensCount }) + + mockGenerateContentStream.mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { text: "Response" } + yield { + usageMetadata: { + promptTokenCount: 10, // Uncached input tokens + candidatesTokenCount: 50, + cachedContentTokenCount: cacheReadTokensCount, // Simulate cache hit reporting + }, + } + }, + }) + + // Only send the second message (index 1) as uncached + const stream = handlerWithCache.createMessage(systemPrompt, mockMessagesLong, cacheKey) + const chunks = [] + + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(mockCacheGet).toHaveBeenCalledWith(cacheKey) + expect(mockGenerateContentStream).toHaveBeenCalledWith( + expect.objectContaining({ + contents: expect.any(Array), // Should contain only the *uncached* messages + config: expect.objectContaining({ + cachedContent: mockCacheName, // Cache name provided + systemInstruction: undefined, // System prompt NOT sent on hit + }), + }), + ) + + // Check that the contents sent are only the *new* messages + const calledContents = mockGenerateContentStream.mock.calls[0][0].contents + expect(calledContents.length).toBe(mockMessagesLong.length - cachedMessagesCount) // Only new messages sent + + // Wait for potential async cache write (shouldn't happen here) + await new Promise(process.nextTick) + expect(mockCreateCache).not.toHaveBeenCalled() + expect(mockCacheSet).not.toHaveBeenCalled() // No write occurred + + // Check usage data for cache read tokens + const usageChunk = chunks.find((c) => c.type === "usage") + expect(usageChunk).toEqual( + expect.objectContaining({ + inputTokens: 10, // Uncached tokens + outputTokens: 50, + cacheWriteTokens: undefined, // No write queued + cacheReadTokens: cacheReadTokensCount, // Read tokens reported + }), + ) + }) + + it("should trigger cache write and delete old cache on hit with enough new messages", async () => { + const previousCacheName = "generated/caches/old-cache-name" + const previousCacheTokens = 3000 + const previousMessageCount = 1 + + mockCacheGet.mockReturnValue({ + key: previousCacheName, + count: previousMessageCount, + tokens: previousCacheTokens, + }) + + // Simulate enough new messages to trigger write (>= CACHE_WRITE_FREQUENCY) + const newMessagesCount = 10 + + const messagesForCacheWrite = [ + mockMessagesLong[0], // Will be considered cached + ...Array(newMessagesCount).fill({ role: "user", content: "new message" }), + ] as Anthropic.Messages.MessageParam[] + + // Mock generateContentStream to report some uncached tokens + mockGenerateContentStream.mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { text: "Response" } + yield { + usageMetadata: { + promptTokenCount: 500, // Uncached input tokens for the 10 new messages + candidatesTokenCount: 50, + cachedContentTokenCount: previousCacheTokens, + }, + } + }, + }) + + const stream = handlerWithCache.createMessage(systemPrompt, messagesForCacheWrite, cacheKey) + const chunks = [] + + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(mockCacheGet).toHaveBeenCalledWith(cacheKey) + + expect(mockGenerateContentStream).toHaveBeenCalledWith( + expect.objectContaining({ + contents: expect.any(Array), // Should contain only the *new* messages + config: expect.objectContaining({ + cachedContent: previousCacheName, // Old cache name used for reading + systemInstruction: undefined, // System prompt NOT sent + }), + }), + ) + const calledContents = mockGenerateContentStream.mock.calls[0][0].contents + expect(calledContents.length).toBe(newMessagesCount) // Only new messages sent + + // Wait for async cache write and delete + await new Promise(process.nextTick) + await new Promise(process.nextTick) // Needs extra tick for delete promise chain? + + expect(mockCreateCache).toHaveBeenCalledTimes(1) + expect(mockCreateCache).toHaveBeenCalledWith( + expect.objectContaining({ + // New cache uses *all* messages + config: expect.objectContaining({ + contents: expect.any(Array), // Should contain *all* messagesForCacheWrite + systemInstruction: systemPrompt, // System prompt included in *new* cache + }), + }), + ) + const createCallContents = mockCreateCache.mock.calls[0][0].config.contents + expect(createCallContents.length).toBe(messagesForCacheWrite.length) // All messages in new cache + + expect(mockCacheSet).toHaveBeenCalledWith( + cacheKey, + expect.objectContaining({ + key: mockCacheName, // New cache name + count: messagesForCacheWrite.length, // New count + tokens: mockCacheTokens, + }), + ) + + expect(mockDeleteCache).toHaveBeenCalledTimes(1) + expect(mockDeleteCache).toHaveBeenCalledWith({ name: previousCacheName }) // Old cache deleted + + const usageChunk = chunks.find((c) => c.type === "usage") + + expect(usageChunk).toEqual( + expect.objectContaining({ + inputTokens: 500, // Uncached tokens + outputTokens: 50, + cacheWriteTokens: 500, // Write tokens match uncached input when write is queued on hit? No, should be total tokens for the *new* cache. Let's adjust mockCreateCache. + cacheReadTokens: previousCacheTokens, + }), + ) + + // Re-run with adjusted expectation after fixing mockCreateCache if needed + // Let's assume mockCreateCache returns the *total* tokens for the *new* cache (system + all messages) + const expectedNewCacheTotalTokens = 6000 // Example total tokens for the new cache + + mockCreateCache.mockResolvedValue({ + name: mockCacheName, + usageMetadata: { totalTokenCount: expectedNewCacheTotalTokens }, + }) + + // Re-run the stream consumption and checks if necessary, or adjust expectation: + // The cacheWriteTokens in usage should reflect the *input* tokens that triggered the write, + // which are the *uncached* tokens in this hit scenario. + // The cost calculation uses the token count from the *create* response though. + // Let's stick to the current implementation: cacheWriteTokens = inputTokens when write is queued. + expect(usageChunk?.cacheWriteTokens).toBe(500) // Matches the uncached promptTokenCount + }) + + it("should handle cache create error gracefully", async () => { + const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {}) + const createError = new Error("Failed to create cache") + mockCreateCache.mockRejectedValue(createError) + + const stream = handlerWithCache.createMessage(systemPrompt, mockMessagesLong, cacheKey) + + for await (const _ of stream) { + } + + // Wait for async cache write attempt + await new Promise(process.nextTick) + + expect(mockCreateCache).toHaveBeenCalledTimes(1) + expect(mockCacheSet).not.toHaveBeenCalled() // Set should not be called on error + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("[GeminiHandler] caches.create error"), + createError, + ) + consoleErrorSpy.mockRestore() + }) + + it("should handle cache delete error gracefully", async () => { + const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {}) + const deleteError = new Error("Failed to delete cache") + mockDeleteCache.mockRejectedValue(deleteError) + + // Setup for cache hit + write scenario to trigger delete + const previousCacheName = "generated/caches/old-cache-name" + mockCacheGet.mockReturnValue({ key: previousCacheName, count: 1, tokens: 3000 }) + + const newMessagesCount = 10 + + const messagesForCacheWrite = [ + mockMessagesLong[0], + ...Array(newMessagesCount).fill({ role: "user", content: "new message" }), + ] as Anthropic.Messages.MessageParam[] + + const stream = handlerWithCache.createMessage(systemPrompt, messagesForCacheWrite, cacheKey) + + for await (const _ of stream) { + } + + // Wait for async cache write and delete attempt + await new Promise(process.nextTick) + await new Promise(process.nextTick) + + expect(mockCreateCache).toHaveBeenCalledTimes(1) // Create still happens + expect(mockCacheSet).toHaveBeenCalledTimes(1) // Set still happens + expect(mockDeleteCache).toHaveBeenCalledTimes(1) // Delete was attempted + + // Expect a single string argument containing both parts + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining( + `[GeminiHandler] failed to delete stale cache entry ${previousCacheName} -> ${deleteError.message}`, + ), + ) + + consoleErrorSpy.mockRestore() + }) +}) diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index 5e9db97afea..38347fc903a 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -21,12 +21,13 @@ import type { ApiStream } from "../transform/stream" import { BaseProvider } from "./base-provider" const CACHE_TTL = 5 - +const CACHE_WRITE_FREQUENCY = 10 const CONTEXT_CACHE_TOKEN_MINIMUM = 4096 type CacheEntry = { key: string count: number + tokens?: number } type GeminiHandlerOptions = ApiHandlerOptions & { @@ -96,7 +97,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl cacheKey && contentsLength > 4 * CONTEXT_CACHE_TOKEN_MINIMUM - let cacheWrite = false + let isCacheWriteQueued = false if (isCacheAvailable) { const cacheEntry = this.contentCaches.get(cacheKey) @@ -104,43 +105,16 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl if (cacheEntry) { uncachedContent = contents.slice(cacheEntry.count, contents.length) cachedContent = cacheEntry.key - console.log( - `[GeminiHandler] using ${cacheEntry.count} cached messages (${cacheEntry.key}) and ${uncachedContent.length} uncached messages`, - ) + // console.log( + // `[GeminiHandler] using cache entry ${cacheEntry.key} -> ${cacheEntry.count} messages, ${cacheEntry.tokens} tokens (+${uncachedContent.length} uncached messages)`, + // ) } - if (!this.isCacheBusy) { - this.isCacheBusy = true - const timestamp = Date.now() - - this.client.caches - .create({ - model, - config: { - contents, - systemInstruction, - ttl: `${CACHE_TTL * 60}s`, - httpOptions: { timeout: 120_000 }, - }, - }) - .then((result) => { - const { name, usageMetadata } = result - - if (name) { - this.contentCaches.set(cacheKey, { key: name, count: contents.length }) - console.log( - `[GeminiHandler] cached ${contents.length} messages (${usageMetadata?.totalTokenCount ?? "-"} tokens) in ${Date.now() - timestamp}ms`, - ) - } - }) - .catch((error) => { - console.error(`[GeminiHandler] caches.create error`, error) - }) - .finally(() => { - this.isCacheBusy = false - }) - - cacheWrite = true + // If `CACHE_WRITE_FREQUENCY` messages have been appended since the + // last cache write then write a new cache entry. + // TODO: Use a token count instead. + if (!cacheEntry || (uncachedContent && uncachedContent.length >= CACHE_WRITE_FREQUENCY)) { + isCacheWriteQueued = true } } @@ -163,6 +137,10 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl const result = await this.client.models.generateContentStream(params) + if (cacheKey && isCacheWriteQueued) { + this.writeCache({ cacheKey, model, systemInstruction, contents }) + } + let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined for await (const chunk of result) { @@ -178,7 +156,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl if (lastUsageMetadata) { const inputTokens = lastUsageMetadata.promptTokenCount ?? 0 const outputTokens = lastUsageMetadata.candidatesTokenCount ?? 0 - const cacheWriteTokens = cacheWrite ? inputTokens : undefined + const cacheWriteTokens = isCacheWriteQueued ? inputTokens : undefined const cacheReadTokens = lastUsageMetadata.cachedContentTokenCount const reasoningTokens = lastUsageMetadata.thoughtsTokenCount @@ -338,4 +316,77 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl return totalCost } + + private writeCache({ + cacheKey, + model, + systemInstruction, + contents, + }: { + cacheKey: string + model: string + systemInstruction: string + contents: Content[] + }) { + // TODO: https://www.npmjs.com/package/p-queue + if (this.isCacheBusy) { + return + } + + this.isCacheBusy = true + // const timestamp = Date.now() + + const previousCacheEntry = this.contentCaches.get(cacheKey) + + this.client.caches + .create({ + model, + config: { + contents, + systemInstruction, + ttl: `${CACHE_TTL * 60}s`, + httpOptions: { timeout: 120_000 }, + }, + }) + .then((result) => { + const { name, usageMetadata } = result + + if (name) { + const newCacheEntry: CacheEntry = { + key: name, + count: contents.length, + tokens: usageMetadata?.totalTokenCount, + } + + this.contentCaches.set(cacheKey, newCacheEntry) + + // console.log( + // `[GeminiHandler] created cache entry ${newCacheEntry.key} -> ${newCacheEntry.count} messages, ${newCacheEntry.tokens} tokens (${Date.now() - timestamp}ms)`, + // ) + + if (previousCacheEntry) { + // const timestamp = Date.now() + + this.client.caches + .delete({ name: previousCacheEntry.key }) + .then(() => { + // console.log( + // `[GeminiHandler] deleted cache entry ${previousCacheEntry.key} -> ${previousCacheEntry.count} messages, ${previousCacheEntry.tokens} tokens (${Date.now() - timestamp}ms)`, + // ) + }) + .catch((error) => { + console.error( + `[GeminiHandler] failed to delete stale cache entry ${previousCacheEntry.key} -> ${error instanceof Error ? error.message : String(error)}`, + ) + }) + } + } + }) + .catch((error) => { + console.error(`[GeminiHandler] caches.create error`, error) + }) + .finally(() => { + this.isCacheBusy = false + }) + } } From 40ee5cf154a28b788e07ae8d0f60fa9fd853e185 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 3 May 2025 13:45:12 -0400 Subject: [PATCH 004/228] Remove help button from title bar (#3150) --- package.json | 15 --------------- src/activate/registerCommands.ts | 5 ----- 2 files changed, 20 deletions(-) diff --git a/package.json b/package.json index 48dcd53674f..4c3c8f11d6e 100644 --- a/package.json +++ b/package.json @@ -110,11 +110,6 @@ "title": "%command.settings.title%", "icon": "$(settings-gear)" }, - { - "command": "roo-cline.helpButtonClicked", - "title": "%command.documentation.title%", - "icon": "$(question)" - }, { "command": "roo-cline.openInNewTab", "title": "%command.openInNewTab.title%", @@ -247,11 +242,6 @@ "command": "roo-cline.settingsButtonClicked", "group": "navigation@6", "when": "view == roo-cline.SidebarProvider" - }, - { - "command": "roo-cline.helpButtonClicked", - "group": "navigation@7", - "when": "view == roo-cline.SidebarProvider" } ], "editor/title": [ @@ -284,11 +274,6 @@ "command": "roo-cline.settingsButtonClicked", "group": "navigation@6", "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" - }, - { - "command": "roo-cline.helpButtonClicked", - "group": "navigation@7", - "when": "activeWebviewPanelId == roo-cline.TabPanelProvider" } ] }, diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index b6d2edb00d4..c1712a80419 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -128,11 +128,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" }) }, - "roo-cline.helpButtonClicked": () => { - telemetryService.captureTitleButtonClicked("help") - - vscode.env.openExternal(vscode.Uri.parse("https://docs.roocode.com")) - }, "roo-cline.showHumanRelayDialog": (params: { requestId: string; promptText: string }) => { const panel = getPanel() From 756c0a00146cf99d5e305351ae3d1fe3b3c9fbf4 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 4 May 2025 02:57:19 -0400 Subject: [PATCH 005/228] Fix issues with subtasks attempting completion along with commands (#3156) --- .changeset/dull-flowers-trade.md | 5 ++++ src/core/Cline.ts | 2 +- src/core/tools/attemptCompletionTool.ts | 6 ++--- src/core/tools/executeCommandTool.ts | 4 +-- src/exports/roo-code.d.ts | 2 -- src/exports/types.ts | 2 -- src/schemas/index.ts | 1 - webview-ui/src/components/chat/ChatRow.tsx | 2 +- webview-ui/src/components/chat/ChatView.tsx | 26 +++++++++++++++---- .../src/components/chat/CommandExecution.tsx | 6 +---- 10 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 .changeset/dull-flowers-trade.md diff --git a/.changeset/dull-flowers-trade.md b/.changeset/dull-flowers-trade.md new file mode 100644 index 00000000000..7cbe6d9f8ed --- /dev/null +++ b/.changeset/dull-flowers-trade.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix auto-approvals diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 4779275ff34..eeb17df08d9 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -161,7 +161,7 @@ export class Cline extends EventEmitter { private askResponse?: ClineAskResponse private askResponseText?: string private askResponseImages?: string[] - private lastMessageTs?: number + public lastMessageTs?: number // Not private since it needs to be accessible by tools. consecutiveMistakeCount: number = 0 diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index b65ce34b19a..a911da98ec3 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -76,13 +76,13 @@ export async function attemptCompletionTool( } // Complete command message. - const executionId = Date.now().toString() - const didApprove = await askApproval("command", command, { id: executionId }) + const didApprove = await askApproval("command", command) if (!didApprove) { return } + const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString() const options: ExecuteCommandOptions = { executionId, command } const [userRejected, execCommandResult] = await executeCommand(cline, options) @@ -108,7 +108,7 @@ export async function attemptCompletionTool( } // tell the provider to remove the current subtask and resume the previous task in the stack - await cline.providerRef.deref()?.finishSubTask(lastMessage?.text ?? "") + await cline.providerRef.deref()?.finishSubTask(result) return } diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 4bd303904f7..97ec0e6ffca 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -48,14 +48,14 @@ export async function executeCommandTool( cline.consecutiveMistakeCount = 0 - const executionId = Date.now().toString() command = unescapeHtmlEntities(command) // Unescape HTML entities. - const didApprove = await askApproval("command", command, { id: executionId }) + const didApprove = await askApproval("command", command) if (!didApprove) { return } + const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString() const clineProvider = await cline.providerRef.deref() const clineProviderState = await clineProvider?.getState() const { terminalOutputLineLimit = 500, terminalShellIntegrationDisabled = false } = clineProviderState ?? {} diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 70376cf9483..386c983cc88 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -353,7 +353,6 @@ type ClineMessage = { | undefined progressStatus?: | { - id?: string | undefined icon?: string | undefined text?: string | undefined } @@ -429,7 +428,6 @@ type RooCodeEvents = { | undefined progressStatus?: | { - id?: string | undefined icon?: string | undefined text?: string | undefined } diff --git a/src/exports/types.ts b/src/exports/types.ts index c2da70391f2..330f9c8c21e 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -358,7 +358,6 @@ type ClineMessage = { | undefined progressStatus?: | { - id?: string | undefined icon?: string | undefined text?: string | undefined } @@ -438,7 +437,6 @@ type RooCodeEvents = { | undefined progressStatus?: | { - id?: string | undefined icon?: string | undefined text?: string | undefined } diff --git a/src/schemas/index.ts b/src/schemas/index.ts index ff8c01f0cff..c6e34fe3955 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -817,7 +817,6 @@ export type ClineSay = z.infer */ export const toolProgressStatusSchema = z.object({ - id: z.string().optional(), icon: z.string().optional(), text: z.string().optional(), }) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 714c47ee5b1..efc720ba76e 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -985,7 +985,7 @@ export const ChatRowContent = ({ {icon} {title} - + ) case "use_mcp_server": diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 4dabee8ade9..c2fb21353bb 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -735,7 +735,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - if (!autoApprovalEnabled || !message || message.type !== "ask") return false + if (!autoApprovalEnabled || !message || message.type !== "ask") { + return false + } if (message.ask === "browser_action_launch") { return alwaysAllowBrowser @@ -749,7 +751,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - if (isAutoApproved(lastMessage)) { + if (lastMessage?.ask && isAutoApproved(lastMessage)) { + // Note that `isAutoApproved` can only return true if + // lastMessage is an ask of type "browser_action_launch", + // "use_mcp_server", "command", or "tool". + // Add delay for write operations. - if (lastMessage?.ask === "tool" && isWriteToolAction(lastMessage)) { + if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) { await new Promise((resolve) => setTimeout(resolve, writeDelayMs)) } - handlePrimaryButtonClick() + vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" }) + + // This is copied from `handlePrimaryButtonClick`, which we used + // to call from `autoApprove`. I'm not sure how many of these + // things are actually needed. + setInputValue("") + setSelectedImages([]) + setTextAreaDisabled(true) + setClineAsk(undefined) + setEnableButtons(false) } } autoApprove() diff --git a/webview-ui/src/components/chat/CommandExecution.tsx b/webview-ui/src/components/chat/CommandExecution.tsx index 827e7119489..005484c36db 100644 --- a/webview-ui/src/components/chat/CommandExecution.tsx +++ b/webview-ui/src/components/chat/CommandExecution.tsx @@ -14,7 +14,7 @@ import { cn } from "@src/lib/utils" import { Button } from "@src/components/ui" interface CommandExecutionProps { - executionId?: string + executionId: string text?: string } @@ -36,10 +36,6 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) = const onMessage = useCallback( (event: MessageEvent) => { - if (!executionId) { - return - } - const message: ExtensionMessage = event.data if (message.type === "commandExecutionStatus") { From c3b85978984574bf3545fd4664d2a944cdd07af3 Mon Sep 17 00:00:00 2001 From: R00-B0T <110429663+R00-B0T@users.noreply.github.com> Date: Sun, 4 May 2025 00:01:32 -0700 Subject: [PATCH 006/228] Changeset version bump (#3149) * changeset version bump * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Chris Estreich --- .changeset/dull-flowers-trade.md | 5 ----- .changeset/tiny-mugs-give.md | 5 ----- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) delete mode 100644 .changeset/dull-flowers-trade.md delete mode 100644 .changeset/tiny-mugs-give.md diff --git a/.changeset/dull-flowers-trade.md b/.changeset/dull-flowers-trade.md deleted file mode 100644 index 7cbe6d9f8ed..00000000000 --- a/.changeset/dull-flowers-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Fix auto-approvals diff --git a/.changeset/tiny-mugs-give.md b/.changeset/tiny-mugs-give.md deleted file mode 100644 index 7aff925bac3..00000000000 --- a/.changeset/tiny-mugs-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Improve Gemini caching efficiency diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b44282d69..97c7dbafc1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Roo Code Changelog +## [3.15.4] - 2025-05-04 + +- Fix a nasty bug that would cause Roo Code to hang, particularly in orchestrator mode +- Improve Gemini caching efficiency + ## [3.15.3] - 2025-05-02 - Terminal: Fix empty command bug diff --git a/package-lock.json b/package-lock.json index 1f6ad6dc9a7..ca5891871de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.15.3", + "version": "3.15.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.15.3", + "version": "3.15.4", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index 4c3c8f11d6e..b73e3610eaf 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.15.3", + "version": "3.15.4", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From d27b22fa59630d757b515732d0a62d086b2bd698 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Sun, 4 May 2025 13:20:11 -0700 Subject: [PATCH 007/228] Update @google/genai package (#3166) --- .changeset/lovely-nails-fly.md | 5 +++++ package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/lovely-nails-fly.md diff --git a/.changeset/lovely-nails-fly.md b/.changeset/lovely-nails-fly.md new file mode 100644 index 00000000000..772f073abd8 --- /dev/null +++ b/.changeset/lovely-nails-fly.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Update @google/genai to 0.12 diff --git a/package-lock.json b/package-lock.json index ca5891871de..656df2420d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@anthropic-ai/sdk": "^0.37.0", "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.779.0", - "@google/genai": "^0.9.0", + "@google/genai": "^0.12.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.7.0", "@types/clone-deep": "^4.0.4", @@ -5775,9 +5775,9 @@ } }, "node_modules/@google/genai": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.9.0.tgz", - "integrity": "sha512-FD2RizYGInsvfjeaN6O+wQGpRnGVglS1XWrGQr8K7D04AfMmvPodDSw94U9KyFtsVLzWH9kmlPyFM+G4jbmkqg==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.12.0.tgz", + "integrity": "sha512-SJtCHac+HPgmwELpJpPKbaV4rk397bS2D42XgFR2NBEARDKd/79RcaRUFFd55pYUJ+gfaz9Bv6KYoiz/P6eZKA==", "license": "Apache-2.0", "dependencies": { "google-auth-library": "^9.14.2", diff --git a/package.json b/package.json index b73e3610eaf..a073cb76a6b 100644 --- a/package.json +++ b/package.json @@ -367,7 +367,7 @@ "@anthropic-ai/sdk": "^0.37.0", "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.779.0", - "@google/genai": "^0.9.0", + "@google/genai": "^0.12.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.7.0", "@types/clone-deep": "^4.0.4", From bd3a42ed23308450655c97ee42d0c92a8e354b6d Mon Sep 17 00:00:00 2001 From: KJ7LNW <93454819+KJ7LNW@users.noreply.github.com> Date: Sun, 4 May 2025 13:20:33 -0700 Subject: [PATCH 008/228] perf: optimize code block rendering performance (#3135) feat: optimize code block rendering performance Memoize CodeBlock components to prevent unnecessary re-renders: - Add MemoizedCodeContent for syntax highlighted HTML - Add MemoizedStyledPre for container element - Properly type all component props - Reduce React reconciliation work for complex code blocks Signed-off-by: Eric Wheeler Co-authored-by: Eric Wheeler --- .../src/components/common/CodeBlock.tsx | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/webview-ui/src/components/common/CodeBlock.tsx b/webview-ui/src/components/common/CodeBlock.tsx index afd87c248fc..1b295e7d1a3 100644 --- a/webview-ui/src/components/common/CodeBlock.tsx +++ b/webview-ui/src/components/common/CodeBlock.tsx @@ -604,16 +604,15 @@ const CodeBlock = memo( return ( - updateCodeBlockButtonPosition(true)} - onMouseUp={() => updateCodeBlockButtonPosition(false)}> -
- + highlightedCode={highlightedCode} + updateCodeBlockButtonPosition={updateCodeBlockButtonPosition} + /> {!isSelecting && (
) + +// Memoized StyledPre component +const MemoizedStyledPre = memo( + ({ + preRef, + preStyle, + wordWrap, + windowShade, + collapsedHeight, + highlightedCode, + updateCodeBlockButtonPosition, + }: { + preRef: React.RefObject + preStyle?: React.CSSProperties + wordWrap: boolean + windowShade: boolean + collapsedHeight?: number + highlightedCode: string + updateCodeBlockButtonPosition: (forceHide?: boolean) => void + }) => ( + updateCodeBlockButtonPosition(true)} + onMouseUp={() => updateCodeBlockButtonPosition(false)}> + + + ), +) + export default CodeBlock From aceff7dfe8216e6e787813789ae56eb89d9d157e Mon Sep 17 00:00:00 2001 From: R00-B0T <110429663+R00-B0T@users.noreply.github.com> Date: Sun, 4 May 2025 18:05:22 -0700 Subject: [PATCH 009/228] Changeset version bump (#3167) * changeset version bump * Updating CHANGELOG.md format * Update CHANGELOG.md --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: R00-B0T Co-authored-by: Chris Estreich --- .changeset/lovely-nails-fly.md | 5 ----- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 .changeset/lovely-nails-fly.md diff --git a/.changeset/lovely-nails-fly.md b/.changeset/lovely-nails-fly.md deleted file mode 100644 index 772f073abd8..00000000000 --- a/.changeset/lovely-nails-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Update @google/genai to 0.12 diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c7dbafc1c..d32b9e54e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Roo Code Changelog +## [3.15.5] - 2025-05-05 + +- Update @google/genai to 0.12 (includes some streaming completion bug fixes) +- Rendering performance improvements for code blocks in chat (thanks @KJ7LNW) + ## [3.15.4] - 2025-05-04 - Fix a nasty bug that would cause Roo Code to hang, particularly in orchestrator mode diff --git a/package-lock.json b/package-lock.json index 656df2420d0..e0366d7f9cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.15.4", + "version": "3.15.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.15.4", + "version": "3.15.5", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index a073cb76a6b..1329db92c15 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.15.4", + "version": "3.15.5", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 11ed7d7d46c9330582b471cd57ddb79b9bfc61a4 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 5 May 2025 08:21:40 -0700 Subject: [PATCH 010/228] Add support for tests that use ESM libraries (#3172) * Add support for tests that use ESM libraries * Disable win32 for this test for now --- .github/workflows/code-qa.yml | 4 +- package-lock.json | 1114 ++++++++++++++++- package.json | 4 +- scripts/run-tests.js | 8 - .../terminal/__tests__/ExecaTerminal.spec.ts | 35 + vitest.config.ts | 7 + 6 files changed, 1114 insertions(+), 58 deletions(-) delete mode 100644 scripts/run-tests.js create mode 100644 src/integrations/terminal/__tests__/ExecaTerminal.spec.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/code-qa.yml b/.github/workflows/code-qa.yml index 7e027d0fc9a..667d0b6bf80 100644 --- a/.github/workflows/code-qa.yml +++ b/.github/workflows/code-qa.yml @@ -78,8 +78,10 @@ jobs: run: npm run install:all - name: Compile (to build and copy WASM files) run: npm run compile - - name: Run unit tests + - name: Run jest unit tests run: npx jest --silent + - name: Run vitest unit tests + run: npx vitest run --silent test-webview: runs-on: ${{ matrix.os }} diff --git a/package-lock.json b/package-lock.json index e0366d7f9cb..9b6691d7840 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,6 +104,7 @@ "tsup": "^8.4.0", "tsx": "^4.19.3", "typescript": "^5.4.5", + "vitest": "^3.1.2", "zod-to-ts": "^1.2.0" }, "engines": { @@ -9316,6 +9317,119 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vitest/expect": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.2.tgz", + "integrity": "sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.2", + "@vitest/utils": "3.1.2", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.2.tgz", + "integrity": "sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.2", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.2.tgz", + "integrity": "sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.2.tgz", + "integrity": "sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.2", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.2.tgz", + "integrity": "sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.2.tgz", + "integrity": "sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.2.tgz", + "integrity": "sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.2", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vscode/codicons": { "version": "0.0.36", "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", @@ -9988,6 +10102,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -10642,6 +10766,23 @@ } ] }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -10685,6 +10826,16 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/cheerio": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", @@ -11442,6 +11593,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -12104,6 +12265,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -12434,6 +12602,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -12622,6 +12800,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", @@ -16465,6 +16653,13 @@ "underscore": "^1.13.1" } }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -16485,6 +16680,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -16816,6 +17021,25 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -17844,6 +18068,23 @@ "node": ">=16" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -18011,6 +18252,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", @@ -19223,6 +19493,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -19405,6 +19682,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -19495,6 +19782,13 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -19504,6 +19798,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -20134,6 +20435,13 @@ "integrity": "sha512-/kqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/bH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -20142,13 +20450,13 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2" }, "engines": { @@ -20159,9 +20467,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -20186,6 +20494,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -21756,50 +22094,713 @@ "node": ">= 0.8" } }, - "node_modules/vscode-material-icons": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/vscode-material-icons/-/vscode-material-icons-0.1.1.tgz", - "integrity": "sha512-GsoEEF8Tbb0yUFQ6N6FPvh11kFkL9F95x0FkKlbbfRQN9eFms67h+L3t6b9cUv58dSn2gu8kEhNfoESVCrz4ag==", - "license": "MIT" - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "node_modules/vite": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", + "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">= 14" - } - }, - "node_modules/web-tree-sitter": { - "version": "0.22.6", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.22.6.tgz", - "integrity": "sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q==" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-encoding": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.2.tgz", + "integrity": "sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.2.tgz", + "integrity": "sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.2", + "@vitest/mocker": "3.1.2", + "@vitest/pretty-format": "^3.1.2", + "@vitest/runner": "3.1.2", + "@vitest/snapshot": "3.1.2", + "@vitest/spy": "3.1.2", + "@vitest/utils": "3.1.2", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.2", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.2", + "@vitest/ui": "3.1.2", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-material-icons": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/vscode-material-icons/-/vscode-material-icons-0.1.1.tgz", + "integrity": "sha512-GsoEEF8Tbb0yUFQ6N6FPvh11kFkL9F95x0FkKlbbfRQN9eFms67h+L3t6b9cUv58dSn2gu8kEhNfoESVCrz4ag==", + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "optional": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.22.6", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.22.6.tgz", + "integrity": "sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", @@ -21930,6 +22931,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 1329db92c15..0f7aed1416d 100644 --- a/package.json +++ b/package.json @@ -337,8 +337,9 @@ "package": "npm-run-all -l -p build:webview build:esbuild check-types lint", "pretest": "npm run compile", "dev": "cd webview-ui && npm run dev", - "test": "node scripts/run-tests.js", + "test": "npm-run-all test:*", "test:extension": "jest -w=40%", + "test:extension-esm": "vitest run", "test:webview": "cd webview-ui && npm run test", "prepare": "husky", "publish:marketplace": "vsce publish && ovsx publish", @@ -459,6 +460,7 @@ "tsup": "^8.4.0", "tsx": "^4.19.3", "typescript": "^5.4.5", + "vitest": "^3.1.2", "zod-to-ts": "^1.2.0" }, "lint-staged": { diff --git a/scripts/run-tests.js b/scripts/run-tests.js deleted file mode 100644 index 9ea8a835dd2..00000000000 --- a/scripts/run-tests.js +++ /dev/null @@ -1,8 +0,0 @@ -// run-tests.js -const { execSync } = require("child_process") - -if (process.platform === "win32") { - execSync("npm-run-all test:*", { stdio: "inherit" }) -} else { - execSync("npm-run-all -p test:*", { stdio: "inherit" }) -} diff --git a/src/integrations/terminal/__tests__/ExecaTerminal.spec.ts b/src/integrations/terminal/__tests__/ExecaTerminal.spec.ts new file mode 100644 index 00000000000..314238a94ad --- /dev/null +++ b/src/integrations/terminal/__tests__/ExecaTerminal.spec.ts @@ -0,0 +1,35 @@ +// npx vitest run src/integrations/terminal/__tests__/ExecaTerminal.spec.ts + +import { vi, describe, it, expect } from "vitest" + +import { RooTerminalCallbacks } from "../types" +import { ExecaTerminal } from "../ExecaTerminal" + +describe("ExecaTerminal", () => { + it("should run terminal commands and collect output", async () => { + // TODO: Run the equivalent test for Windows. + if (process.platform === "win32") { + return + } + + const terminal = new ExecaTerminal(1, "/tmp") + let result + + const callbacks: RooTerminalCallbacks = { + onLine: vi.fn(), + onCompleted: (output) => (result = output), + onShellExecutionStarted: vi.fn(), + onShellExecutionComplete: vi.fn(), + } + + const subprocess = terminal.runCommand("ls -al", callbacks) + await subprocess + + expect(callbacks.onLine).toHaveBeenCalled() + expect(callbacks.onShellExecutionStarted).toHaveBeenCalled() + expect(callbacks.onShellExecutionComplete).toHaveBeenCalled() + + expect(result).toBeTypeOf("string") + expect(result).toContain("total") + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000000..778f7bc32c9 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + include: ["**/__tests__/**/*.spec.ts"], + }, +}) From bf38b830b84e5da1d513b9df14aa409ecda5660a Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 5 May 2025 09:11:47 -0700 Subject: [PATCH 011/228] Tidy up the Cline class a bit (#3100) * Tidy up the Cline class a bit * Clean up more comments --- src/core/Cline.ts | 1944 ++++++++++++++++-------------- src/core/__tests__/Cline.test.ts | 13 +- 2 files changed, 1019 insertions(+), 938 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index eeb17df08d9..c188e222453 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -296,11 +296,7 @@ export class Cline extends EventEmitter { return [instance, promise] } - get cwd() { - return this.workspacePath - } - - // Storing task to disk for history + // API Messages private async getSavedApiConversationHistory(): Promise { return readApiMessages({ taskId: this.taskId, globalStoragePath: this.globalStoragePath }) @@ -325,11 +321,13 @@ export class Cline extends EventEmitter { globalStoragePath: this.globalStoragePath, }) } catch (error) { - // in the off chance this fails, we don't want to stop the task + // In the off chance this fails, we don't want to stop the task. console.error("Failed to save API conversation history:", error) } } + // Cline Messages + private async getSavedClineMessages(): Promise { return readTaskMessages({ taskId: this.taskId, globalStoragePath: this.globalStoragePath }) } @@ -377,9 +375,9 @@ export class Cline extends EventEmitter { } } - // Communicate with webview - - // partial has three valid states true (partial message), false (completion of partial message), undefined (individual complete message) + // Note that `partial` has three valid states true (partial message), + // false (completion of partial message), undefined (individual complete + // message). async ask( type: ClineAsk, text?: string, @@ -402,8 +400,10 @@ export class Cline extends EventEmitter { if (partial !== undefined) { const lastMessage = this.clineMessages.at(-1) + const isUpdatingPreviousPartial = lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type + if (partial) { if (isUpdatingPreviousPartial) { // Existing partial message, so update it. @@ -432,15 +432,19 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined - /* - Bug for the history books: - In the webview we use the ts as the chatrow key for the virtuoso list. Since we would update this ts right at the end of streaming, it would cause the view to flicker. The key prop has to be stable otherwise react has trouble reconciling items between renders, causing unmounting and remounting of components (flickering). - The lesson here is if you see flickering when rendering lists, it's likely because the key prop is not stable. - So in this case we must make sure that the message ts is never altered after first setting it. - */ + // Bug for the history books: + // In the webview we use the ts as the chatrow key for the + // virtuoso list. Since we would update this ts right at the + // end of streaming, it would cause the view to flicker. The + // key prop has to be stable otherwise react has trouble + // reconciling items between renders, causing unmounting and + // remounting of components (flickering). + // The lesson here is if you see flickering when rendering + // lists, it's likely because the key prop is not stable. + // So in this case we must make sure that the message ts is + // never altered after first setting it. askTs = lastMessage.ts this.lastMessageTs = askTs - // lastMessage.ts = askTs lastMessage.text = text lastMessage.partial = false lastMessage.progressStatus = progressStatus @@ -568,7 +572,7 @@ export class Cline extends EventEmitter { return formatResponse.toolError(formatResponse.missingToolParameterError(paramName)) } - // Task lifecycle + // Start / Abort / Resume private async startTask(task?: string, images?: string[]): Promise { // conversationHistory (for API) and clineMessages (for webview) need to be in sync @@ -593,7 +597,7 @@ export class Cline extends EventEmitter { ]) } - async resumePausedTask(lastMessage: string) { + public async resumePausedTask(lastMessage: string) { // release this Cline instance from paused state this.isPaused = false this.emit("taskUnpaused") @@ -856,6 +860,59 @@ export class Cline extends EventEmitter { await this.initiateTaskLoop(newUserContent) } + public async abortTask(isAbandoned = false) { + console.log(`[subtasks] aborting task ${this.taskId}.${this.instanceId}`) + + // Will stop any autonomously running promises. + if (isAbandoned) { + this.abandoned = true + } + + this.abort = true + this.emit("taskAborted") + + // Stop waiting for child task completion. + if (this.pauseInterval) { + clearInterval(this.pauseInterval) + this.pauseInterval = undefined + } + + // Release any terminals associated with this task. + TerminalRegistry.releaseTerminalsForTask(this.taskId) + + this.urlContentFetcher.closeBrowser() + this.browserSession.closeBrowser() + this.rooIgnoreController?.dispose() + this.fileContextTracker.dispose() + + // If we're not streaming then `abortStream` (which reverts the diff + // view changes) won't be called, so we need to revert the changes here. + if (this.isStreaming && this.diffViewProvider.isEditing) { + await this.diffViewProvider.revertChanges() + } + + // Save the countdown message in the automatic retry or other content. + await this.saveClineMessages() + } + + // Used when a sub-task is launched and the parent task is waiting for it to + // finish. + // TBD: The 1s should be added to the settings, also should add a timeout to + // prevent infinite waiting. + public async waitForResume() { + await new Promise((resolve) => { + this.pauseInterval = setInterval(() => { + if (!this.isPaused) { + clearInterval(this.pauseInterval) + this.pauseInterval = undefined + resolve() + } + }, 1000) + }) + } + + // Task Loop + private async initiateTaskLoop(userContent: UserContent): Promise { // Kicks off the checkpoints initialization process in the background. this.getCheckpointService() @@ -891,1052 +948,1073 @@ export class Cline extends EventEmitter { } } - async abortTask(isAbandoned = false) { - console.log(`[subtasks] aborting task ${this.taskId}.${this.instanceId}`) - - // Will stop any autonomously running promises. - if (isAbandoned) { - this.abandoned = true + public async recursivelyMakeClineRequests( + userContent: UserContent, + includeFileDetails: boolean = false, + ): Promise { + if (this.abort) { + throw new Error(`[Cline#recursivelyMakeClineRequests] task ${this.taskId}.${this.instanceId} aborted`) } - this.abort = true - this.emit("taskAborted") + if (this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) { + const { response, text, images } = await this.ask( + "mistake_limit_reached", + this.api.getModel().id.includes("claude") + ? `This may indicate a failure in his thought process or inability to use a tool properly, which can be mitigated with some user guidance (e.g. "Try breaking down the task into smaller steps").` + : "Roo Code uses complex prompts and iterative task execution that may be challenging for less capable models. For best results, it's recommended to use Claude 3.7 Sonnet for its advanced agentic coding capabilities.", + ) - // Stop waiting for child task completion. - if (this.pauseInterval) { - clearInterval(this.pauseInterval) - this.pauseInterval = undefined - } + if (response === "messageResponse") { + userContent.push( + ...[ + { type: "text" as const, text: formatResponse.tooManyMistakes(text) }, + ...formatResponse.imageBlocks(images), + ], + ) - // Release any terminals associated with this task. - TerminalRegistry.releaseTerminalsForTask(this.taskId) + await this.say("user_feedback", text, images) - this.urlContentFetcher.closeBrowser() - this.browserSession.closeBrowser() - this.rooIgnoreController?.dispose() - this.fileContextTracker.dispose() + // Track consecutive mistake errors in telemetry. + telemetryService.captureConsecutiveMistakeError(this.taskId) + } - // If we're not streaming then `abortStream` (which reverts the diff - // view changes) won't be called, so we need to revert the changes here. - if (this.isStreaming && this.diffViewProvider.isEditing) { - await this.diffViewProvider.revertChanges() + this.consecutiveMistakeCount = 0 } - // Save the countdown message in the automatic retry or other content - await this.saveClineMessages() - } - // Tools + // Get previous api req's index to check token usage and determine if we + // need to truncate conversation history. + const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") - async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream { - let mcpHub: McpHub | undefined + // In this Cline request loop, we need to check if this task instance + // has been asked to wait for a subtask to finish before continuing. + const provider = this.providerRef.deref() - const { apiConfiguration, mcpEnabled, autoApprovalEnabled, alwaysApproveResubmit, requestDelaySeconds } = - (await this.providerRef.deref()?.getState()) ?? {} + if (this.isPaused && provider) { + provider.log(`[subtasks] paused ${this.taskId}.${this.instanceId}`) + await this.waitForResume() + provider.log(`[subtasks] resumed ${this.taskId}.${this.instanceId}`) + const currentMode = (await provider.getState())?.mode ?? defaultModeSlug - let rateLimitDelay = 0 + if (currentMode !== this.pausedModeSlug) { + // The mode has changed, we need to switch back to the paused mode. + await provider.handleModeSwitch(this.pausedModeSlug) - // Only apply rate limiting if this isn't the first request - if (this.lastApiRequestTime) { - const now = Date.now() - const timeSinceLastRequest = now - this.lastApiRequestTime - const rateLimit = apiConfiguration?.rateLimitSeconds || 0 - rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000) - } + // Delay to allow mode change to take effect before next tool is executed. + await delay(500) - // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there. - if (rateLimitDelay > 0 && retryAttempt === 0) { - // Show countdown timer - for (let i = rateLimitDelay; i > 0; i--) { - const delayMessage = `Rate limiting for ${i} seconds...` - await this.say("api_req_retry_delayed", delayMessage, undefined, true) - await delay(1000) + provider.log( + `[subtasks] task ${this.taskId}.${this.instanceId} has switched back to '${this.pausedModeSlug}' from '${currentMode}'`, + ) } } - // Update last request time before making the request - this.lastApiRequestTime = Date.now() - - if (mcpEnabled ?? true) { - const provider = this.providerRef.deref() - - if (!provider) { - throw new Error("Provider reference lost during view transition") - } + // Getting verbose details is an expensive operation, it uses ripgrep to + // top-down build file structure of project which for large projects can + // take a few seconds. For the best UX we show a placeholder api_req_started + // message with a loading spinner as this happens. + await this.say( + "api_req_started", + JSON.stringify({ + request: + userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...", + }), + ) - // Wait for MCP hub initialization through McpServerManager - mcpHub = await McpServerManager.getInstance(provider.context, provider) + const parsedUserContent = await this.parseUserContent(userContent) + const environmentDetails = await this.getEnvironmentDetails(includeFileDetails) - if (!mcpHub) { - throw new Error("Failed to get MCP hub from server manager") - } + // Add environment details as its own text block, separate from tool + // results. + const finalUserContent = [...parsedUserContent, { type: "text" as const, text: environmentDetails }] - // Wait for MCP servers to be connected before generating system prompt - await pWaitFor(() => !mcpHub!.isConnecting, { timeout: 10_000 }).catch(() => { - console.error("MCP servers failed to connect in time") - }) - } + await this.addToApiConversationHistory({ role: "user", content: finalUserContent }) + telemetryService.captureConversationMessage(this.taskId, "user") - const rooIgnoreInstructions = this.rooIgnoreController?.getInstructions() + // Since we sent off a placeholder api_req_started message to update the + // webview while waiting to actually start the API request (to load + // potential details for example), we need to update the text of that + // message. + const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") - const { - browserViewportSize, - mode, - customModePrompts, - experiments, - enableMcpServerCreation, - browserToolEnabled, - language, - } = (await this.providerRef.deref()?.getState()) ?? {} + this.clineMessages[lastApiReqIndex].text = JSON.stringify({ + request: finalUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"), + } satisfies ClineApiReqInfo) - const { customModes } = (await this.providerRef.deref()?.getState()) ?? {} + await this.saveClineMessages() + await provider?.postStateToWebview() - const systemPrompt = await (async () => { - const provider = this.providerRef.deref() + try { + let cacheWriteTokens = 0 + let cacheReadTokens = 0 + let inputTokens = 0 + let outputTokens = 0 + let totalCost: number | undefined - if (!provider) { - throw new Error("Provider not available") + // We can't use `api_req_finished` anymore since it's a unique case + // where it could come after a streaming message (i.e. in the middle + // of being updated or executed). + // Fortunately `api_req_finished` was always parsed out for the GUI + // anyways, so it remains solely for legacy purposes to keep track + // of prices in tasks from history (it's worth removing a few months + // from now). + const updateApiReqMsg = (cancelReason?: ClineApiReqCancelReason, streamingFailedMessage?: string) => { + this.clineMessages[lastApiReqIndex].text = JSON.stringify({ + ...JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}"), + tokensIn: inputTokens, + tokensOut: outputTokens, + cacheWrites: cacheWriteTokens, + cacheReads: cacheReadTokens, + cost: + totalCost ?? + calculateApiCostAnthropic( + this.api.getModel().info, + inputTokens, + outputTokens, + cacheWriteTokens, + cacheReadTokens, + ), + cancelReason, + streamingFailedMessage, + } satisfies ClineApiReqInfo) } - return SYSTEM_PROMPT( - provider.context, - this.cwd, - (this.api.getModel().info.supportsComputerUse ?? false) && (browserToolEnabled ?? true), - mcpHub, - this.diffStrategy, - browserViewportSize, - mode, - customModePrompts, - customModes, - this.customInstructions, - this.diffEnabled, - experiments, - enableMcpServerCreation, - language, - rooIgnoreInstructions, - ) - })() + const abortStream = async (cancelReason: ClineApiReqCancelReason, streamingFailedMessage?: string) => { + if (this.diffViewProvider.isEditing) { + await this.diffViewProvider.revertChanges() // closes diff view + } - // If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request - if (previousApiReqIndex >= 0) { - const previousRequest = this.clineMessages[previousApiReqIndex]?.text + // if last message is a partial we need to update and save it + const lastMessage = this.clineMessages.at(-1) - if (!previousRequest) { - return - } + if (lastMessage && lastMessage.partial) { + // lastMessage.ts = Date.now() DO NOT update ts since it is used as a key for virtuoso list + lastMessage.partial = false + // instead of streaming partialMessage events, we do a save and post like normal to persist to disk + console.log("updating partial message", lastMessage) + // await this.saveClineMessages() + } - const { - tokensIn = 0, - tokensOut = 0, - cacheWrites = 0, - cacheReads = 0, - }: ClineApiReqInfo = JSON.parse(previousRequest) + // Let assistant know their response was interrupted for when task is resumed + await this.addToApiConversationHistory({ + role: "assistant", + content: [ + { + type: "text", + text: + assistantMessage + + `\n\n[${ + cancelReason === "streaming_failed" + ? "Response interrupted by API Error" + : "Response interrupted by user" + }]`, + }, + ], + }) - const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads + // Update `api_req_started` to have cancelled and cost, so that + // we can display the cost of the partial stream. + updateApiReqMsg(cancelReason, streamingFailedMessage) + await this.saveClineMessages() - // Default max tokens value for thinking models when no specific value is set - const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384 + // Signals to provider that it can retrieve the saved messages + // from disk, as abortTask can not be awaited on in nature. + this.didFinishAbortingStream = true + } - const modelInfo = this.api.getModel().info + // Reset streaming state. + this.currentStreamingContentIndex = 0 + this.assistantMessageContent = [] + this.didCompleteReadingStream = false + this.userMessageContent = [] + this.userMessageContentReady = false + this.didRejectTool = false + this.didAlreadyUseTool = false + this.presentAssistantMessageLocked = false + this.presentAssistantMessageHasPendingUpdates = false - const maxTokens = modelInfo.thinking - ? this.apiConfiguration.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS - : modelInfo.maxTokens + await this.diffViewProvider.reset() - const contextWindow = modelInfo.contextWindow + // Yields only if the first chunk is successful, otherwise will + // allow the user to retry the request (most likely due to rate + // limit error, which gets thrown on the first chunk). + const stream = this.attemptApiRequest(previousApiReqIndex) + let assistantMessage = "" + let reasoningMessage = "" + this.isStreaming = true - const trimmedMessages = await truncateConversationIfNeeded({ - messages: this.apiConversationHistory, - totalTokens, - maxTokens, - contextWindow, - apiHandler: this.api, - }) + try { + for await (const chunk of stream) { + if (!chunk) { + // Sometimes chunk is undefined, no idea that can cause + // it, but this workaround seems to fix it. + continue + } - if (trimmedMessages !== this.apiConversationHistory) { - await this.overwriteApiConversationHistory(trimmedMessages) - } - } + switch (chunk.type) { + case "reasoning": + reasoningMessage += chunk.text + await this.say("reasoning", reasoningMessage, undefined, true) + break + case "usage": + inputTokens += chunk.inputTokens + outputTokens += chunk.outputTokens + cacheWriteTokens += chunk.cacheWriteTokens ?? 0 + cacheReadTokens += chunk.cacheReadTokens ?? 0 + totalCost = chunk.totalCost + break + case "text": + assistantMessage += chunk.text - // Clean conversation history by: - // 1. Converting to Anthropic.MessageParam by spreading only the API-required properties - // 2. Converting image blocks to text descriptions if model doesn't support images - const cleanConversationHistory = this.apiConversationHistory.map(({ role, content }) => { - // Handle array content (could contain image blocks) - if (Array.isArray(content)) { - if (!this.api.getModel().info.supportsImages) { - // Convert image blocks to text descriptions - content = content.map((block) => { - if (block.type === "image") { - // Convert image blocks to text descriptions - // Note: We can't access the actual image content/url due to API limitations, - // but we can indicate that an image was present in the conversation - return { - type: "text", - text: "[Referenced image in conversation]", + // Parse raw assistant message into content blocks. + const prevLength = this.assistantMessageContent.length + this.assistantMessageContent = parseAssistantMessage(assistantMessage) + + if (this.assistantMessageContent.length > prevLength) { + // New content we need to present, reset to + // false in case previous content set this to true. + this.userMessageContentReady = false } - } - return block - }) - } - } - return { role, content } - }) - const stream = this.api.createMessage(systemPrompt, cleanConversationHistory, this.promptCacheKey) - const iterator = stream[Symbol.asyncIterator]() + // Present content to user. + this.presentAssistantMessage() + break + } - try { - // Awaiting first chunk to see if it will throw an error. - this.isWaitingForFirstChunk = true - const firstChunk = await iterator.next() - yield firstChunk.value - this.isWaitingForFirstChunk = false - } catch (error) { - // note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely. - if (autoApprovalEnabled && alwaysApproveResubmit) { - let errorMsg + if (this.abort) { + console.log(`aborting stream, this.abandoned = ${this.abandoned}`) - if (error.error?.metadata?.raw) { - errorMsg = JSON.stringify(error.error.metadata.raw, null, 2) - } else if (error.message) { - errorMsg = error.message - } else { - errorMsg = "Unknown error" - } + if (!this.abandoned) { + // Only need to gracefully abort if this instance + // isn't abandoned (sometimes OpenRouter stream + // hangs, in which case this would affect future + // instances of Cline). + await abortStream("user_cancelled") + } - const baseDelay = requestDelaySeconds || 5 - let exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt)) + break // Aborts the stream. + } - // If the error is a 429, and the error details contain a retry delay, use that delay instead of exponential backoff - if (error.status === 429) { - const geminiRetryDetails = error.errorDetails?.find( - (detail: any) => detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo", - ) - if (geminiRetryDetails) { - const match = geminiRetryDetails?.retryDelay?.match(/^(\d+)s$/) - if (match) { - exponentialDelay = Number(match[1]) + 1 - } + if (this.didRejectTool) { + // `userContent` has a tool rejection, so interrupt the + // assistant's response to present the user's feedback. + assistantMessage += "\n\n[Response interrupted by user feedback]" + // Instead of setting this premptively, we allow the + // present iterator to finish and set + // userMessageContentReady when its ready. + // this.userMessageContentReady = true + break } - } - // Wait for the greater of the exponential delay or the rate limit delay - const finalDelay = Math.max(exponentialDelay, rateLimitDelay) + // PREV: We need to let the request finish for openrouter to + // get generation details. + // UPDATE: It's better UX to interrupt the request at the + // cost of the API cost not being retrieved. + if (this.didAlreadyUseTool) { + assistantMessage += + "\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]" + break + } + } + } catch (error) { + // Abandoned happens when extension is no longer waiting for the + // Cline instance to finish aborting (error is thrown here when + // any function in the for loop throws due to this.abort). + if (!this.abandoned) { + // If the stream failed, there's various states the task + // could be in (i.e. could have streamed some tools the user + // may have executed), so we just resort to replicating a + // cancel task. + this.abortTask() - // Show countdown timer with exponential backoff - for (let i = finalDelay; i > 0; i--) { - await this.say( - "api_req_retry_delayed", - `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying in ${i} seconds...`, - undefined, - true, + await abortStream( + "streaming_failed", + error.message ?? JSON.stringify(serializeError(error), null, 2), ) - await delay(1000) + + const history = await provider?.getTaskWithId(this.taskId) + + if (history) { + await provider?.initClineWithHistoryItem(history.historyItem) + } } + } finally { + this.isStreaming = false + } - await this.say( - "api_req_retry_delayed", - `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying now...`, - undefined, - false, - ) + // Need to call here in case the stream was aborted. + if (this.abort || this.abandoned) { + throw new Error(`[Cline#recursivelyMakeClineRequests] task ${this.taskId}.${this.instanceId} aborted`) + } - // delegate generator output from the recursive call with incremented retry count - yield* this.attemptApiRequest(previousApiReqIndex, retryAttempt + 1) - return - } else { - const { response } = await this.ask( - "api_req_failed", - error.message ?? JSON.stringify(serializeError(error), null, 2), - ) + this.didCompleteReadingStream = true - if (response !== "yesButtonClicked") { - // this will never happen since if noButtonClicked, we will clear current task, aborting this instance - throw new Error("API request failed") - } + // Set any blocks to be complete to allow `presentAssistantMessage` + // to finish and set `userMessageContentReady` to true. + // (Could be a text block that had no subsequent tool uses, or a + // text block at the very end, or an invalid tool use, etc. Whatever + // the case, `presentAssistantMessage` relies on these blocks either + // to be completed or the user to reject a block in order to proceed + // and eventually set userMessageContentReady to true.) + const partialBlocks = this.assistantMessageContent.filter((block) => block.partial) + partialBlocks.forEach((block) => (block.partial = false)) - await this.say("api_req_retried") + // Can't just do this b/c a tool could be in the middle of executing. + // this.assistantMessageContent.forEach((e) => (e.partial = false)) - // delegate generator output from the recursive call - yield* this.attemptApiRequest(previousApiReqIndex) - return + if (partialBlocks.length > 0) { + // If there is content to update then it will complete and + // update `this.userMessageContentReady` to true, which we + // `pWaitFor` before making the next request. All this is really + // doing is presenting the last partial message that we just set + // to complete. + this.presentAssistantMessage() } - } - // no error, so we can continue to yield all remaining chunks - // (needs to be placed outside of try/catch since it we want caller to handle errors not with api_req_failed as that is reserved for first chunk failures only) - // this delegates to another generator or iterable object. In this case, it's saying "yield all remaining values from this iterator". This effectively passes along all subsequent chunks from the original stream. - yield* iterator - } + updateApiReqMsg() + await this.saveClineMessages() + await this.providerRef.deref()?.postStateToWebview() - async presentAssistantMessage() { - if (this.abort) { - throw new Error(`[Cline#presentAssistantMessage] task ${this.taskId}.${this.instanceId} aborted`) - } + // Now add to apiConversationHistory. + // Need to save assistant responses to file before proceeding to + // tool use since user can exit at any moment and we wouldn't be + // able to save the assistant's response. + let didEndLoop = false - if (this.presentAssistantMessageLocked) { - this.presentAssistantMessageHasPendingUpdates = true - return - } - this.presentAssistantMessageLocked = true - this.presentAssistantMessageHasPendingUpdates = false + if (assistantMessage.length > 0) { + await this.addToApiConversationHistory({ + role: "assistant", + content: [{ type: "text", text: assistantMessage }], + }) - if (this.currentStreamingContentIndex >= this.assistantMessageContent.length) { - // this may happen if the last content block was completed before streaming could finish. if streaming is finished, and we're out of bounds then this means we already presented/executed the last content block and are ready to continue to next request - if (this.didCompleteReadingStream) { - this.userMessageContentReady = true - } - // console.log("no more content blocks to stream! this shouldn't happen?") - this.presentAssistantMessageLocked = false - return - //throw new Error("No more content blocks to stream! This shouldn't happen...") // remove and just return after testing - } + telemetryService.captureConversationMessage(this.taskId, "assistant") - const block = cloneDeep(this.assistantMessageContent[this.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too + // NOTE: This comment is here for future reference - this was a + // workaround for `userMessageContent` not getting set to true. + // It was due to it not recursively calling for partial blocks + // when `didRejectTool`, so it would get stuck waiting for a + // partial block to complete before it could continue. + // In case the content blocks finished it may be the api stream + // finished after the last parsed content block was executed, so + // we are able to detect out of bounds and set + // `userMessageContentReady` to true (note you should not call + // `presentAssistantMessage` since if the last block i + // completed it will be presented again). + // const completeBlocks = this.assistantMessageContent.filter((block) => !block.partial) // If there are any partial blocks after the stream ended we can consider them invalid. + // if (this.currentStreamingContentIndex >= completeBlocks.length) { + // this.userMessageContentReady = true + // } - switch (block.type) { - case "text": { - if (this.didRejectTool || this.didAlreadyUseTool) { - break + await pWaitFor(() => this.userMessageContentReady) + + // If the model did not tool use, then we need to tell it to + // either use a tool or attempt_completion. + const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use") + + if (!didToolUse) { + this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed() }) + this.consecutiveMistakeCount++ } - let content = block.content - if (content) { - // (have to do this for partial and complete since sending content in thinking tags to markdown renderer will automatically be removed) - // Remove end substrings of (with optional line break after) and (with optional line break before) - // - Needs to be separate since we dont want to remove the line break before the first tag - // - Needs to happen before the xml parsing below - content = content.replace(/\s?/g, "") - content = content.replace(/\s?<\/thinking>/g, "") - // Remove partial XML tag at the very end of the content (for tool use and thinking tags) - // (prevents scrollview from jumping when tags are automatically removed) - const lastOpenBracketIndex = content.lastIndexOf("<") - if (lastOpenBracketIndex !== -1) { - const possibleTag = content.slice(lastOpenBracketIndex) - // Check if there's a '>' after the last '<' (i.e., if the tag is complete) (complete thinking and tool tags will have been removed by now) - const hasCloseBracket = possibleTag.includes(">") - if (!hasCloseBracket) { - // Extract the potential tag name - let tagContent: string - if (possibleTag.startsWith(" { - switch (block.name) { - case "execute_command": - return `[${block.name} for '${block.params.command}']` - case "read_file": - return `[${block.name} for '${block.params.path}']` - case "fetch_instructions": - return `[${block.name} for '${block.params.task}']` - case "write_to_file": - return `[${block.name} for '${block.params.path}']` - case "apply_diff": - return `[${block.name} for '${block.params.path}']` - case "search_files": - return `[${block.name} for '${block.params.regex}'${ - block.params.file_pattern ? ` in '${block.params.file_pattern}'` : "" - }]` - case "insert_content": - return `[${block.name} for '${block.params.path}']` - case "search_and_replace": - return `[${block.name} for '${block.params.path}']` - case "list_files": - return `[${block.name} for '${block.params.path}']` - case "list_code_definition_names": - return `[${block.name} for '${block.params.path}']` - case "browser_action": - return `[${block.name} for '${block.params.action}']` - case "use_mcp_tool": - return `[${block.name} for '${block.params.server_name}']` - case "access_mcp_resource": - return `[${block.name} for '${block.params.server_name}']` - case "ask_followup_question": - return `[${block.name} for '${block.params.question}']` - case "attempt_completion": - return `[${block.name}]` - case "switch_mode": - return `[${block.name} to '${block.params.mode_slug}'${block.params.reason ? ` because: ${block.params.reason}` : ""}]` - case "new_task": { - const mode = block.params.mode ?? defaultModeSlug - const message = block.params.message ?? "(no message)" - const modeName = getModeBySlug(mode, customModes)?.name ?? mode - return `[${block.name} in ${modeName} mode: '${message}']` - } - } - } + const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent) + didEndLoop = recDidEndLoop + } else { + // If there's no assistant_responses, that means we got no text + // or tool_use content blocks from API which we should assume is + // an error. + await this.say( + "error", + "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.", + ) - if (this.didRejectTool) { - // ignore any tool content after user has rejected tool once - if (!block.partial) { - this.userMessageContent.push({ - type: "text", - text: `Skipping tool ${toolDescription()} due to user rejecting a previous tool.`, - }) - } else { - // partial tool after user rejected a previous tool - this.userMessageContent.push({ - type: "text", - text: `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.`, - }) - } - break - } + await this.addToApiConversationHistory({ + role: "assistant", + content: [{ type: "text", text: "Failure: I did not provide a response." }], + }) + } - if (this.didAlreadyUseTool) { - // ignore any content after a tool has already been used - this.userMessageContent.push({ - type: "text", - text: `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.`, - }) - break - } + return didEndLoop // Will always be false for now. + } catch (error) { + // This should never happen since the only thing that can throw an + // error is the attemptApiRequest, which is wrapped in a try catch + // that sends an ask where if noButtonClicked, will clear current + // task and destroy this instance. However to avoid unhandled + // promise rejection, we will end this loop which will end execution + // of this instance (see `startTask`). + return true // Needs to be true so parent loop knows to end task. + } + } - const pushToolResult = (content: ToolResponse) => { - this.userMessageContent.push({ - type: "text", - text: `${toolDescription()} Result:`, - }) - if (typeof content === "string") { - this.userMessageContent.push({ - type: "text", - text: content || "(tool did not return anything)", - }) - } else { - this.userMessageContent.push(...content) - } - // once a tool result has been collected, ignore all other tool uses since we should only ever present one tool result per message - this.didAlreadyUseTool = true + public async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream { + let mcpHub: McpHub | undefined - // Flag a checkpoint as possible since we've used a tool - // which may have changed the file system. - } + const { apiConfiguration, mcpEnabled, autoApprovalEnabled, alwaysApproveResubmit, requestDelaySeconds } = + (await this.providerRef.deref()?.getState()) ?? {} - const askApproval = async ( - type: ClineAsk, - partialMessage?: string, - progressStatus?: ToolProgressStatus, - ) => { - const { response, text, images } = await this.ask(type, partialMessage, false, progressStatus) - if (response !== "yesButtonClicked") { - // Handle both messageResponse and noButtonClicked with text - if (text) { - await this.say("user_feedback", text, images) - pushToolResult( - formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images), - ) - } else { - pushToolResult(formatResponse.toolDenied()) - } - this.didRejectTool = true - return false - } - // Handle yesButtonClicked with text - if (text) { - await this.say("user_feedback", text, images) - pushToolResult(formatResponse.toolResult(formatResponse.toolApprovedWithFeedback(text), images)) - } - return true - } + let rateLimitDelay = 0 - const askFinishSubTaskApproval = async () => { - // ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished - // and return control to the parent task to continue running the rest of the sub-tasks - const toolMessage = JSON.stringify({ - tool: "finishTask", - }) + // Only apply rate limiting if this isn't the first request + if (this.lastApiRequestTime) { + const now = Date.now() + const timeSinceLastRequest = now - this.lastApiRequestTime + const rateLimit = apiConfiguration?.rateLimitSeconds || 0 + rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000) + } - return await askApproval("tool", toolMessage) - } + // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there. + if (rateLimitDelay > 0 && retryAttempt === 0) { + // Show countdown timer + for (let i = rateLimitDelay; i > 0; i--) { + const delayMessage = `Rate limiting for ${i} seconds...` + await this.say("api_req_retry_delayed", delayMessage, undefined, true) + await delay(1000) + } + } - const handleError = async (action: string, error: Error) => { - const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` - await this.say( - "error", - `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, - ) - // this.toolResults.push({ - // type: "tool_result", - // tool_use_id: toolUseId, - // content: await this.formatToolError(errorString), - // }) - pushToolResult(formatResponse.toolError(errorString)) - } + // Update last request time before making the request + this.lastApiRequestTime = Date.now() - // If block is partial, remove partial closing tag so its not presented to user - const removeClosingTag = (tag: ToolParamName, text?: string): string => { - if (!block.partial) { - return text || "" - } - if (!text) { - return "" - } - // This regex dynamically constructs a pattern to match the closing tag: - // - Optionally matches whitespace before the tag - // - Matches '<' or ' `(?:${char})?`) - .join("")}$`, - "g", - ) - return text.replace(tagRegex, "") - } + if (mcpEnabled ?? true) { + const provider = this.providerRef.deref() - if (block.name !== "browser_action") { - await this.browserSession.closeBrowser() - } + if (!provider) { + throw new Error("Provider reference lost during view transition") + } - if (!block.partial) { - this.recordToolUsage(block.name) - telemetryService.captureToolUsage(this.taskId, block.name) - } + // Wait for MCP hub initialization through McpServerManager + mcpHub = await McpServerManager.getInstance(provider.context, provider) - // Validate tool use before execution - const { mode, customModes } = (await this.providerRef.deref()?.getState()) ?? {} - try { - validateToolUse( - block.name as ToolName, - mode ?? defaultModeSlug, - customModes ?? [], - { - apply_diff: this.diffEnabled, - }, - block.params, - ) - } catch (error) { - this.consecutiveMistakeCount++ - pushToolResult(formatResponse.toolError(error.message)) - break - } + if (!mcpHub) { + throw new Error("Failed to get MCP hub from server manager") + } - switch (block.name) { - case "write_to_file": - await writeToFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "apply_diff": - await applyDiffTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "insert_content": - await insertContentTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "search_and_replace": - await searchAndReplaceTool( - this, - block, - askApproval, - handleError, - pushToolResult, - removeClosingTag, - ) - break - case "read_file": - await readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + // Wait for MCP servers to be connected before generating system prompt + await pWaitFor(() => !mcpHub!.isConnecting, { timeout: 10_000 }).catch(() => { + console.error("MCP servers failed to connect in time") + }) + } - break - case "fetch_instructions": - await fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) - break - case "list_files": - await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "list_code_definition_names": - await listCodeDefinitionNamesTool( - this, - block, - askApproval, - handleError, - pushToolResult, - removeClosingTag, - ) - break - case "search_files": - await searchFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "browser_action": - await browserActionTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "execute_command": - await executeCommandTool( - this, - block, - askApproval, - handleError, - pushToolResult, - removeClosingTag, - ) - break - case "use_mcp_tool": - await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "access_mcp_resource": - await accessMcpResourceTool( - this, - block, - askApproval, - handleError, - pushToolResult, - removeClosingTag, - ) - break - case "ask_followup_question": - await askFollowupQuestionTool( - this, - block, - askApproval, - handleError, - pushToolResult, - removeClosingTag, - ) - break - case "switch_mode": - await switchModeTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "new_task": - await newTaskTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - break - case "attempt_completion": - await attemptCompletionTool( - this, - block, - askApproval, - handleError, - pushToolResult, - removeClosingTag, - toolDescription, - askFinishSubTaskApproval, - ) - break - } + const rooIgnoreInstructions = this.rooIgnoreController?.getInstructions() - break - } + const { + browserViewportSize, + mode, + customModePrompts, + experiments, + enableMcpServerCreation, + browserToolEnabled, + language, + } = (await this.providerRef.deref()?.getState()) ?? {} - const recentlyModifiedFiles = this.fileContextTracker.getAndClearCheckpointPossibleFile() + const { customModes } = (await this.providerRef.deref()?.getState()) ?? {} - if (recentlyModifiedFiles.length > 0) { - // TODO: We can track what file changes were made and only - // checkpoint those files, this will be save storage. - await this.checkpointSave() - } + const systemPrompt = await (async () => { + const provider = this.providerRef.deref() - /* - Seeing out of bounds is fine, it means that the next too call is being built up and ready to add to assistantMessageContent to present. - When you see the UI inactive during this, it means that a tool is breaking without presenting any UI. For example the write_to_file tool was breaking when relpath was undefined, and for invalid relpath it never presented UI. - */ - this.presentAssistantMessageLocked = false // this needs to be placed here, if not then calling this.presentAssistantMessage below would fail (sometimes) since it's locked - // NOTE: when tool is rejected, iterator stream is interrupted and it waits for userMessageContentReady to be true. Future calls to present will skip execution since didRejectTool and iterate until contentIndex is set to message length and it sets userMessageContentReady to true itself (instead of preemptively doing it in iterator) - if (!block.partial || this.didRejectTool || this.didAlreadyUseTool) { - // block is finished streaming and executing - if (this.currentStreamingContentIndex === this.assistantMessageContent.length - 1) { - // its okay that we increment if !didCompleteReadingStream, it'll just return bc out of bounds and as streaming continues it will call presentAssitantMessage if a new block is ready. if streaming is finished then we set userMessageContentReady to true when out of bounds. This gracefully allows the stream to continue on and all potential content blocks be presented. - // last block is complete and it is finished executing - this.userMessageContentReady = true // will allow pwaitfor to continue + if (!provider) { + throw new Error("Provider not available") } - // call next block if it exists (if not then read stream will call it when its ready) - this.currentStreamingContentIndex++ // need to increment regardless, so when read stream calls this function again it will be streaming the next block + return SYSTEM_PROMPT( + provider.context, + this.cwd, + (this.api.getModel().info.supportsComputerUse ?? false) && (browserToolEnabled ?? true), + mcpHub, + this.diffStrategy, + browserViewportSize, + mode, + customModePrompts, + customModes, + this.customInstructions, + this.diffEnabled, + experiments, + enableMcpServerCreation, + language, + rooIgnoreInstructions, + ) + })() - if (this.currentStreamingContentIndex < this.assistantMessageContent.length) { - // there are already more content blocks to stream, so we'll call this function ourselves - // await this.presentAssistantContent() + // If the previous API request's total token usage is close to the + // context window, truncate the conversation history to free up space + // for the new request. + if (previousApiReqIndex >= 0) { + const previousRequest = this.clineMessages[previousApiReqIndex]?.text - this.presentAssistantMessage() + if (!previousRequest) { return } - } - // block is partial, but the read stream may have finished - if (this.presentAssistantMessageHasPendingUpdates) { - this.presentAssistantMessage() - } - } - // Used when a sub-task is launched and the parent task is waiting for it to - // finish. - // TBD: The 1s should be added to the settings, also should add a timeout to - // prevent infinite waiting. - async waitForResume() { - await new Promise((resolve) => { - this.pauseInterval = setInterval(() => { - if (!this.isPaused) { - clearInterval(this.pauseInterval) - this.pauseInterval = undefined - resolve() - } - }, 1000) - }) - } + const { + tokensIn = 0, + tokensOut = 0, + cacheWrites = 0, + cacheReads = 0, + }: ClineApiReqInfo = JSON.parse(previousRequest) - async recursivelyMakeClineRequests( - userContent: UserContent, - includeFileDetails: boolean = false, - ): Promise { - if (this.abort) { - throw new Error(`[Cline#recursivelyMakeClineRequests] task ${this.taskId}.${this.instanceId} aborted`) - } + const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads - if (this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) { - const { response, text, images } = await this.ask( - "mistake_limit_reached", - this.api.getModel().id.includes("claude") - ? `This may indicate a failure in his thought process or inability to use a tool properly, which can be mitigated with some user guidance (e.g. "Try breaking down the task into smaller steps").` - : "Roo Code uses complex prompts and iterative task execution that may be challenging for less capable models. For best results, it's recommended to use Claude 3.7 Sonnet for its advanced agentic coding capabilities.", - ) + // Default max tokens value for thinking models when no specific + // value is set. + const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384 - if (response === "messageResponse") { - userContent.push( - ...[ - { - type: "text", - text: formatResponse.tooManyMistakes(text), - } as Anthropic.Messages.TextBlockParam, - ...formatResponse.imageBlocks(images), - ], - ) + const modelInfo = this.api.getModel().info - await this.say("user_feedback", text, images) + const maxTokens = modelInfo.thinking + ? this.apiConfiguration.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS + : modelInfo.maxTokens - // Track consecutive mistake errors in telemetry - telemetryService.captureConsecutiveMistakeError(this.taskId) + const contextWindow = modelInfo.contextWindow + + const trimmedMessages = await truncateConversationIfNeeded({ + messages: this.apiConversationHistory, + totalTokens, + maxTokens, + contextWindow, + apiHandler: this.api, + }) + + if (trimmedMessages !== this.apiConversationHistory) { + await this.overwriteApiConversationHistory(trimmedMessages) } - this.consecutiveMistakeCount = 0 } - // Get previous api req's index to check token usage and determine if we - // need to truncate conversation history. - const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") + // Clean conversation history by: + // 1. Converting to Anthropic.MessageParam by spreading only the API-required properties. + // 2. Converting image blocks to text descriptions if model doesn't support images. + const cleanConversationHistory = this.apiConversationHistory.map(({ role, content }) => { + // Handle array content (could contain image blocks). + if (Array.isArray(content)) { + if (!this.api.getModel().info.supportsImages) { + // Convert image blocks to text descriptions. + content = content.map((block) => { + if (block.type === "image") { + // Convert image blocks to text descriptions. + // Note: We can't access the actual image content/url due to API limitations, + // but we can indicate that an image was present in the conversation. + return { + type: "text", + text: "[Referenced image in conversation]", + } + } + return block + }) + } + } - // In this Cline request loop, we need to check if this task instance - // has been asked to wait for a subtask to finish before continuing. - const provider = this.providerRef.deref() + return { role, content } + }) - if (this.isPaused && provider) { - provider.log(`[subtasks] paused ${this.taskId}.${this.instanceId}`) - await this.waitForResume() - provider.log(`[subtasks] resumed ${this.taskId}.${this.instanceId}`) - const currentMode = (await provider.getState())?.mode ?? defaultModeSlug + const stream = this.api.createMessage(systemPrompt, cleanConversationHistory, this.promptCacheKey) + const iterator = stream[Symbol.asyncIterator]() - if (currentMode !== this.pausedModeSlug) { - // The mode has changed, we need to switch back to the paused mode. - await provider.handleModeSwitch(this.pausedModeSlug) + try { + // Awaiting first chunk to see if it will throw an error. + this.isWaitingForFirstChunk = true + const firstChunk = await iterator.next() + yield firstChunk.value + this.isWaitingForFirstChunk = false + } catch (error) { + // note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely. + if (autoApprovalEnabled && alwaysApproveResubmit) { + let errorMsg - // Delay to allow mode change to take effect before next tool is executed. - await delay(500) + if (error.error?.metadata?.raw) { + errorMsg = JSON.stringify(error.error.metadata.raw, null, 2) + } else if (error.message) { + errorMsg = error.message + } else { + errorMsg = "Unknown error" + } - provider.log( - `[subtasks] task ${this.taskId}.${this.instanceId} has switched back to '${this.pausedModeSlug}' from '${currentMode}'`, + const baseDelay = requestDelaySeconds || 5 + let exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt)) + + // If the error is a 429, and the error details contain a retry delay, use that delay instead of exponential backoff + if (error.status === 429) { + const geminiRetryDetails = error.errorDetails?.find( + (detail: any) => detail["@type"] === "type.googleapis.com/google.rpc.RetryInfo", + ) + if (geminiRetryDetails) { + const match = geminiRetryDetails?.retryDelay?.match(/^(\d+)s$/) + if (match) { + exponentialDelay = Number(match[1]) + 1 + } + } + } + + // Wait for the greater of the exponential delay or the rate limit delay + const finalDelay = Math.max(exponentialDelay, rateLimitDelay) + + // Show countdown timer with exponential backoff + for (let i = finalDelay; i > 0; i--) { + await this.say( + "api_req_retry_delayed", + `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying in ${i} seconds...`, + undefined, + true, + ) + await delay(1000) + } + + await this.say( + "api_req_retry_delayed", + `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying now...`, + undefined, + false, ) - } - } - // Getting verbose details is an expensive operation, it uses ripgrep to - // top-down build file structure of project which for large projects can - // take a few seconds. For the best UX we show a placeholder api_req_started - // message with a loading spinner as this happens. - await this.say( - "api_req_started", - JSON.stringify({ - request: - userContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n") + "\n\nLoading...", - }), - ) + // Delegate generator output from the recursive call with + // incremented retry count. + yield* this.attemptApiRequest(previousApiReqIndex, retryAttempt + 1) - const [parsedUserContent, environmentDetails] = await this.loadContext(userContent, includeFileDetails) - // add environment details as its own text block, separate from tool results - const finalUserContent = [...parsedUserContent, { type: "text", text: environmentDetails }] as UserContent + return + } else { + const { response } = await this.ask( + "api_req_failed", + error.message ?? JSON.stringify(serializeError(error), null, 2), + ) - await this.addToApiConversationHistory({ role: "user", content: finalUserContent }) - telemetryService.captureConversationMessage(this.taskId, "user") + if (response !== "yesButtonClicked") { + // This will never happen since if noButtonClicked, we will + // clear current task, aborting this instance. + throw new Error("API request failed") + } - // since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message - const lastApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started") + await this.say("api_req_retried") - this.clineMessages[lastApiReqIndex].text = JSON.stringify({ - request: finalUserContent.map((block) => formatContentBlockToMarkdown(block)).join("\n\n"), - } satisfies ClineApiReqInfo) + // Delegate generator output from the recursive call. + yield* this.attemptApiRequest(previousApiReqIndex) + return + } + } - await this.saveClineMessages() - await this.providerRef.deref()?.postStateToWebview() + // No error, so we can continue to yield all remaining chunks. + // (Needs to be placed outside of try/catch since it we want caller to + // handle errors not with api_req_failed as that is reserved for first + // chunk failures only.) + // This delegates to another generator or iterable object. In this case, + // it's saying "yield all remaining values from this iterator". This + // effectively passes along all subsequent chunks from the original + // stream. + yield* iterator + } - try { - let cacheWriteTokens = 0 - let cacheReadTokens = 0 - let inputTokens = 0 - let outputTokens = 0 - let totalCost: number | undefined + public async presentAssistantMessage() { + if (this.abort) { + throw new Error(`[Cline#presentAssistantMessage] task ${this.taskId}.${this.instanceId} aborted`) + } - // update api_req_started. we can't use api_req_finished anymore since it's a unique case where it could come after a streaming message (ie in the middle of being updated or executed) - // fortunately api_req_finished was always parsed out for the gui anyways, so it remains solely for legacy purposes to keep track of prices in tasks from history - // (it's worth removing a few months from now) - const updateApiReqMsg = (cancelReason?: ClineApiReqCancelReason, streamingFailedMessage?: string) => { - this.clineMessages[lastApiReqIndex].text = JSON.stringify({ - ...JSON.parse(this.clineMessages[lastApiReqIndex].text || "{}"), - tokensIn: inputTokens, - tokensOut: outputTokens, - cacheWrites: cacheWriteTokens, - cacheReads: cacheReadTokens, - cost: - totalCost ?? - calculateApiCostAnthropic( - this.api.getModel().info, - inputTokens, - outputTokens, - cacheWriteTokens, - cacheReadTokens, - ), - cancelReason, - streamingFailedMessage, - } satisfies ClineApiReqInfo) + if (this.presentAssistantMessageLocked) { + this.presentAssistantMessageHasPendingUpdates = true + return + } + this.presentAssistantMessageLocked = true + this.presentAssistantMessageHasPendingUpdates = false + + if (this.currentStreamingContentIndex >= this.assistantMessageContent.length) { + // this may happen if the last content block was completed before streaming could finish. if streaming is finished, and we're out of bounds then this means we already presented/executed the last content block and are ready to continue to next request + if (this.didCompleteReadingStream) { + this.userMessageContentReady = true } + // console.log("no more content blocks to stream! this shouldn't happen?") + this.presentAssistantMessageLocked = false + return + //throw new Error("No more content blocks to stream! This shouldn't happen...") // remove and just return after testing + } + + const block = cloneDeep(this.assistantMessageContent[this.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too + + switch (block.type) { + case "text": { + if (this.didRejectTool || this.didAlreadyUseTool) { + break + } + let content = block.content + if (content) { + // (have to do this for partial and complete since sending content in thinking tags to markdown renderer will automatically be removed) + // Remove end substrings of (with optional line break after) and (with optional line break before) + // - Needs to be separate since we dont want to remove the line break before the first tag + // - Needs to happen before the xml parsing below + content = content.replace(/\s?/g, "") + content = content.replace(/\s?<\/thinking>/g, "") - const abortStream = async (cancelReason: ClineApiReqCancelReason, streamingFailedMessage?: string) => { - if (this.diffViewProvider.isEditing) { - await this.diffViewProvider.revertChanges() // closes diff view + // Remove partial XML tag at the very end of the content (for tool use and thinking tags) + // (prevents scrollview from jumping when tags are automatically removed) + const lastOpenBracketIndex = content.lastIndexOf("<") + if (lastOpenBracketIndex !== -1) { + const possibleTag = content.slice(lastOpenBracketIndex) + // Check if there's a '>' after the last '<' (i.e., if the tag is complete) (complete thinking and tool tags will have been removed by now) + const hasCloseBracket = possibleTag.includes(">") + if (!hasCloseBracket) { + // Extract the potential tag name + let tagContent: string + if (possibleTag.startsWith(" { + switch (block.name) { + case "execute_command": + return `[${block.name} for '${block.params.command}']` + case "read_file": + return `[${block.name} for '${block.params.path}']` + case "fetch_instructions": + return `[${block.name} for '${block.params.task}']` + case "write_to_file": + return `[${block.name} for '${block.params.path}']` + case "apply_diff": + return `[${block.name} for '${block.params.path}']` + case "search_files": + return `[${block.name} for '${block.params.regex}'${ + block.params.file_pattern ? ` in '${block.params.file_pattern}'` : "" + }]` + case "insert_content": + return `[${block.name} for '${block.params.path}']` + case "search_and_replace": + return `[${block.name} for '${block.params.path}']` + case "list_files": + return `[${block.name} for '${block.params.path}']` + case "list_code_definition_names": + return `[${block.name} for '${block.params.path}']` + case "browser_action": + return `[${block.name} for '${block.params.action}']` + case "use_mcp_tool": + return `[${block.name} for '${block.params.server_name}']` + case "access_mcp_resource": + return `[${block.name} for '${block.params.server_name}']` + case "ask_followup_question": + return `[${block.name} for '${block.params.question}']` + case "attempt_completion": + return `[${block.name}]` + case "switch_mode": + return `[${block.name} to '${block.params.mode_slug}'${block.params.reason ? ` because: ${block.params.reason}` : ""}]` + case "new_task": { + const mode = block.params.mode ?? defaultModeSlug + const message = block.params.message ?? "(no message)" + const modeName = getModeBySlug(mode, customModes)?.name ?? mode + return `[${block.name} in ${modeName} mode: '${message}']` + } + } } - // if last message is a partial we need to update and save it - const lastMessage = this.clineMessages.at(-1) + if (this.didRejectTool) { + // ignore any tool content after user has rejected tool once + if (!block.partial) { + this.userMessageContent.push({ + type: "text", + text: `Skipping tool ${toolDescription()} due to user rejecting a previous tool.`, + }) + } else { + // partial tool after user rejected a previous tool + this.userMessageContent.push({ + type: "text", + text: `Tool ${toolDescription()} was interrupted and not executed due to user rejecting a previous tool.`, + }) + } + break + } - if (lastMessage && lastMessage.partial) { - // lastMessage.ts = Date.now() DO NOT update ts since it is used as a key for virtuoso list - lastMessage.partial = false - // instead of streaming partialMessage events, we do a save and post like normal to persist to disk - console.log("updating partial message", lastMessage) - // await this.saveClineMessages() + if (this.didAlreadyUseTool) { + // ignore any content after a tool has already been used + this.userMessageContent.push({ + type: "text", + text: `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.`, + }) + break } - // Let assistant know their response was interrupted for when task is resumed - await this.addToApiConversationHistory({ - role: "assistant", - content: [ - { + const pushToolResult = (content: ToolResponse) => { + this.userMessageContent.push({ + type: "text", + text: `${toolDescription()} Result:`, + }) + if (typeof content === "string") { + this.userMessageContent.push({ type: "text", - text: - assistantMessage + - `\n\n[${ - cancelReason === "streaming_failed" - ? "Response interrupted by API Error" - : "Response interrupted by user" - }]`, - }, - ], - }) + text: content || "(tool did not return anything)", + }) + } else { + this.userMessageContent.push(...content) + } + // once a tool result has been collected, ignore all other tool uses since we should only ever present one tool result per message + this.didAlreadyUseTool = true - // update api_req_started to have cancelled and cost, so that we can display the cost of the partial stream - updateApiReqMsg(cancelReason, streamingFailedMessage) - await this.saveClineMessages() + // Flag a checkpoint as possible since we've used a tool + // which may have changed the file system. + } - // signals to provider that it can retrieve the saved messages from disk, as abortTask can not be awaited on in nature - this.didFinishAbortingStream = true - } + const askApproval = async ( + type: ClineAsk, + partialMessage?: string, + progressStatus?: ToolProgressStatus, + ) => { + const { response, text, images } = await this.ask(type, partialMessage, false, progressStatus) + if (response !== "yesButtonClicked") { + // Handle both messageResponse and noButtonClicked with text + if (text) { + await this.say("user_feedback", text, images) + pushToolResult( + formatResponse.toolResult(formatResponse.toolDeniedWithFeedback(text), images), + ) + } else { + pushToolResult(formatResponse.toolDenied()) + } + this.didRejectTool = true + return false + } + // Handle yesButtonClicked with text + if (text) { + await this.say("user_feedback", text, images) + pushToolResult(formatResponse.toolResult(formatResponse.toolApprovedWithFeedback(text), images)) + } + return true + } - // reset streaming state - this.currentStreamingContentIndex = 0 - this.assistantMessageContent = [] - this.didCompleteReadingStream = false - this.userMessageContent = [] - this.userMessageContentReady = false - this.didRejectTool = false - this.didAlreadyUseTool = false - this.presentAssistantMessageLocked = false - this.presentAssistantMessageHasPendingUpdates = false - await this.diffViewProvider.reset() + const askFinishSubTaskApproval = async () => { + // ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished + // and return control to the parent task to continue running the rest of the sub-tasks + const toolMessage = JSON.stringify({ + tool: "finishTask", + }) - // Yields only if the first chunk is successful, otherwise will - // allow the user to retry the request (most likely due to rate - // limit error, which gets thrown on the first chunk). - const stream = this.attemptApiRequest(previousApiReqIndex) - let assistantMessage = "" - let reasoningMessage = "" - this.isStreaming = true + return await askApproval("tool", toolMessage) + } - try { - for await (const chunk of stream) { - if (!chunk) { - // Sometimes chunk is undefined, no idea that can cause it, but this workaround seems to fix it. - continue - } + const handleError = async (action: string, error: Error) => { + const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` + await this.say( + "error", + `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, + ) + // this.toolResults.push({ + // type: "tool_result", + // tool_use_id: toolUseId, + // content: await this.formatToolError(errorString), + // }) + pushToolResult(formatResponse.toolError(errorString)) + } - switch (chunk.type) { - case "reasoning": - reasoningMessage += chunk.text - await this.say("reasoning", reasoningMessage, undefined, true) - break - case "usage": - inputTokens += chunk.inputTokens - outputTokens += chunk.outputTokens - cacheWriteTokens += chunk.cacheWriteTokens ?? 0 - cacheReadTokens += chunk.cacheReadTokens ?? 0 - totalCost = chunk.totalCost - break - case "text": - assistantMessage += chunk.text - // parse raw assistant message into content blocks - const prevLength = this.assistantMessageContent.length - this.assistantMessageContent = parseAssistantMessage(assistantMessage) - if (this.assistantMessageContent.length > prevLength) { - this.userMessageContentReady = false // new content we need to present, reset to false in case previous content set this to true - } - // present content to user - this.presentAssistantMessage() - break + // If block is partial, remove partial closing tag so its not presented to user + const removeClosingTag = (tag: ToolParamName, text?: string): string => { + if (!block.partial) { + return text || "" + } + if (!text) { + return "" } + // This regex dynamically constructs a pattern to match the closing tag: + // - Optionally matches whitespace before the tag + // - Matches '<' or ' `(?:${char})?`) + .join("")}$`, + "g", + ) + return text.replace(tagRegex, "") + } - if (this.abort) { - console.log(`aborting stream, this.abandoned = ${this.abandoned}`) + if (block.name !== "browser_action") { + await this.browserSession.closeBrowser() + } - if (!this.abandoned) { - // only need to gracefully abort if this instance isn't abandoned (sometimes openrouter stream hangs, in which case this would affect future instances of cline) - await abortStream("user_cancelled") - } + if (!block.partial) { + this.recordToolUsage(block.name) + telemetryService.captureToolUsage(this.taskId, block.name) + } - break // aborts the stream - } + // Validate tool use before execution + const { mode, customModes } = (await this.providerRef.deref()?.getState()) ?? {} + try { + validateToolUse( + block.name as ToolName, + mode ?? defaultModeSlug, + customModes ?? [], + { + apply_diff: this.diffEnabled, + }, + block.params, + ) + } catch (error) { + this.consecutiveMistakeCount++ + pushToolResult(formatResponse.toolError(error.message)) + break + } - if (this.didRejectTool) { - // userContent has a tool rejection, so interrupt the assistant's response to present the user's feedback - assistantMessage += "\n\n[Response interrupted by user feedback]" - // this.userMessageContentReady = true // instead of setting this premptively, we allow the present iterator to finish and set userMessageContentReady when its ready + switch (block.name) { + case "write_to_file": + await writeToFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } + case "apply_diff": + await applyDiffTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "insert_content": + await insertContentTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "search_and_replace": + await searchAndReplaceTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "read_file": + await readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) - // PREV: we need to let the request finish for openrouter to get generation details - // UPDATE: it's better UX to interrupt the request at the cost of the api cost not being retrieved - if (this.didAlreadyUseTool) { - assistantMessage += - "\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]" break - } + case "fetch_instructions": + await fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) + break + case "list_files": + await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "list_code_definition_names": + await listCodeDefinitionNamesTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "search_files": + await searchFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "browser_action": + await browserActionTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "execute_command": + await executeCommandTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "use_mcp_tool": + await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "access_mcp_resource": + await accessMcpResourceTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "ask_followup_question": + await askFollowupQuestionTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "switch_mode": + await switchModeTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "new_task": + await newTaskTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break + case "attempt_completion": + await attemptCompletionTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + toolDescription, + askFinishSubTaskApproval, + ) + break } - } catch (error) { - // abandoned happens when extension is no longer waiting for the cline instance to finish aborting (error is thrown here when any function in the for loop throws due to this.abort) - if (!this.abandoned) { - this.abortTask() // if the stream failed, there's various states the task could be in (i.e. could have streamed some tools the user may have executed), so we just resort to replicating a cancel task - - await abortStream( - "streaming_failed", - error.message ?? JSON.stringify(serializeError(error), null, 2), - ) - - const history = await this.providerRef.deref()?.getTaskWithId(this.taskId) - if (history) { - await this.providerRef.deref()?.initClineWithHistoryItem(history.historyItem) - // await this.providerRef.deref()?.postStateToWebview() - } - } - } finally { - this.isStreaming = false - } + break + } - // need to call here in case the stream was aborted - if (this.abort || this.abandoned) { - throw new Error(`[Cline#recursivelyMakeClineRequests] task ${this.taskId}.${this.instanceId} aborted`) - } + const recentlyModifiedFiles = this.fileContextTracker.getAndClearCheckpointPossibleFile() - this.didCompleteReadingStream = true + if (recentlyModifiedFiles.length > 0) { + // TODO: We can track what file changes were made and only + // checkpoint those files, this will be save storage. + await this.checkpointSave() + } - // set any blocks to be complete to allow presentAssistantMessage to finish and set userMessageContentReady to true - // (could be a text block that had no subsequent tool uses, or a text block at the very end, or an invalid tool use, etc. whatever the case, presentAssistantMessage relies on these blocks either to be completed or the user to reject a block in order to proceed and eventually set userMessageContentReady to true) - const partialBlocks = this.assistantMessageContent.filter((block) => block.partial) - partialBlocks.forEach((block) => { - block.partial = false - }) - // this.assistantMessageContent.forEach((e) => (e.partial = false)) // cant just do this bc a tool could be in the middle of executing () - if (partialBlocks.length > 0) { - this.presentAssistantMessage() // if there is content to update then it will complete and update this.userMessageContentReady to true, which we pwaitfor before making the next request. all this is really doing is presenting the last partial message that we just set to complete + /* + Seeing out of bounds is fine, it means that the next too call is being built up and ready to add to assistantMessageContent to present. + When you see the UI inactive during this, it means that a tool is breaking without presenting any UI. For example the write_to_file tool was breaking when relpath was undefined, and for invalid relpath it never presented UI. + */ + this.presentAssistantMessageLocked = false // this needs to be placed here, if not then calling this.presentAssistantMessage below would fail (sometimes) since it's locked + // NOTE: when tool is rejected, iterator stream is interrupted and it waits for userMessageContentReady to be true. Future calls to present will skip execution since didRejectTool and iterate until contentIndex is set to message length and it sets userMessageContentReady to true itself (instead of preemptively doing it in iterator) + if (!block.partial || this.didRejectTool || this.didAlreadyUseTool) { + // block is finished streaming and executing + if (this.currentStreamingContentIndex === this.assistantMessageContent.length - 1) { + // its okay that we increment if !didCompleteReadingStream, it'll just return bc out of bounds and as streaming continues it will call presentAssitantMessage if a new block is ready. if streaming is finished then we set userMessageContentReady to true when out of bounds. This gracefully allows the stream to continue on and all potential content blocks be presented. + // last block is complete and it is finished executing + this.userMessageContentReady = true // will allow pwaitfor to continue } - updateApiReqMsg() - await this.saveClineMessages() - await this.providerRef.deref()?.postStateToWebview() - - // now add to apiconversationhistory - // need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response - let didEndLoop = false - - if (assistantMessage.length > 0) { - await this.addToApiConversationHistory({ - role: "assistant", - content: [{ type: "text", text: assistantMessage }], - }) - - telemetryService.captureConversationMessage(this.taskId, "assistant") - - // NOTE: this comment is here for future reference - this was a workaround for userMessageContent not getting set to true. It was due to it not recursively calling for partial blocks when didRejectTool, so it would get stuck waiting for a partial block to complete before it could continue. - // in case the content blocks finished - // it may be the api stream finished after the last parsed content block was executed, so we are able to detect out of bounds and set userMessageContentReady to true (note you should not call presentAssistantMessage since if the last block is completed it will be presented again) - // const completeBlocks = this.assistantMessageContent.filter((block) => !block.partial) // if there are any partial blocks after the stream ended we can consider them invalid - // if (this.currentStreamingContentIndex >= completeBlocks.length) { - // this.userMessageContentReady = true - // } - - await pWaitFor(() => this.userMessageContentReady) + // call next block if it exists (if not then read stream will call it when its ready) + this.currentStreamingContentIndex++ // need to increment regardless, so when read stream calls this function again it will be streaming the next block - // if the model did not tool use, then we need to tell it to either use a tool or attempt_completion - const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use") - if (!didToolUse) { - this.userMessageContent.push({ - type: "text", - text: formatResponse.noToolsUsed(), - }) - this.consecutiveMistakeCount++ - } + if (this.currentStreamingContentIndex < this.assistantMessageContent.length) { + // there are already more content blocks to stream, so we'll call this function ourselves + // await this.presentAssistantContent() - const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent) - didEndLoop = recDidEndLoop - } else { - // if there's no assistant_responses, that means we got no text or tool_use content blocks from API which we should assume is an error - await this.say( - "error", - "Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.", - ) - await this.addToApiConversationHistory({ - role: "assistant", - content: [{ type: "text", text: "Failure: I did not provide a response." }], - }) + this.presentAssistantMessage() + return } - - return didEndLoop // will always be false for now - } catch (error) { - // This should never happen since the only thing that can throw an - // error is the attemptApiRequest, which is wrapped in a try catch - // that sends an ask where if noButtonClicked, will clear current - // task and destroy this instance. However to avoid unhandled - // promise rejection, we will end this loop which will end execution - // of this instance (see `startTask`). - return true // Needs to be true so parent loop knows to end task. + } + // block is partial, but the read stream may have finished + if (this.presentAssistantMessageHasPendingUpdates) { + this.presentAssistantMessage() } } - async loadContext(userContent: UserContent, includeFileDetails: boolean = false) { + // Transform + + public async parseUserContent(userContent: UserContent) { // Process userContent array, which contains various block types: // TextBlockParam, ImageBlockParam, ToolUseBlockParam, and ToolResultBlockParam. // We need to apply parseMentions() to: // 1. All TextBlockParam's text (first user message with task) - // 2. ToolResultBlockParam's content/context text arrays if it contains "" (see formatToolDeniedFeedback, attemptCompletion, executeCommand, and consecutiveMistakeCount >= 3) or "" (see askFollowupQuestion), we place all user generated content in these tags so they can effectively be used as markers for when we should parse mentions) - const parsedUserContent = await Promise.all( + // 2. ToolResultBlockParam's content/context text arrays if it contains + // "" (see formatToolDeniedFeedback, attemptCompletion, + // executeCommand, and consecutiveMistakeCount >= 3) or "" + // (see askFollowupQuestion), we place all user generated content in + // these tags so they can effectively be used as markers for when we + // should parse mentions). + return Promise.all( userContent.map(async (block) => { const shouldProcessMentions = (text: string) => text.includes("") || text.includes("") @@ -1952,6 +2030,7 @@ export class Cline extends EventEmitter { ), } } + return block } else if (block.type === "tool_result") { if (typeof block.content === "string") { @@ -1966,6 +2045,7 @@ export class Cline extends EventEmitter { ), } } + return block } else if (Array.isArray(block.content)) { const parsedContent = await Promise.all( @@ -1981,26 +2061,25 @@ export class Cline extends EventEmitter { ), } } + return contentBlock }), ) - return { - ...block, - content: parsedContent, - } + + return { ...block, content: parsedContent } } + return block } + return block }), ) - - const environmentDetails = await this.getEnvironmentDetails(includeFileDetails) - - return [parsedUserContent, environmentDetails] } - async getEnvironmentDetails(includeFileDetails: boolean = false) { + // Environment + + public async getEnvironmentDetails(includeFileDetails: boolean = false) { let details = "" const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } = @@ -2537,11 +2616,6 @@ export class Cline extends EventEmitter { } } - // Public accessor for fileContextTracker - public getFileContextTracker(): FileContextTracker { - return this.fileContextTracker - } - // Metrics public getTokenUsage() { @@ -2571,4 +2645,14 @@ export class Cline extends EventEmitter { public getToolUsage() { return this.toolUsage } + + // Getters + + public get cwd() { + return this.workspacePath + } + + public getFileContextTracker(): FileContextTracker { + return this.fileContextTracker + } } diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 00a9c4dc6bf..3de198acb38 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -424,7 +424,7 @@ describe("Cline", () => { jest.spyOn(cline as any, "getEnvironmentDetails").mockResolvedValue("") // Mock loadContext to return unmodified content. - jest.spyOn(cline as any, "loadContext").mockImplementation(async (content) => [content, ""]) + jest.spyOn(cline as any, "parseUserContent").mockImplementation(async (content) => [content, ""]) // Add test message to conversation history. cline.apiConversationHistory = [ @@ -577,11 +577,8 @@ describe("Cline", () => { // Mock environment details and context loading jest.spyOn(clineWithImages as any, "getEnvironmentDetails").mockResolvedValue("") jest.spyOn(clineWithoutImages as any, "getEnvironmentDetails").mockResolvedValue("") - jest.spyOn(clineWithImages as any, "loadContext").mockImplementation(async (content) => [content, ""]) - jest.spyOn(clineWithoutImages as any, "loadContext").mockImplementation(async (content) => [ - content, - "", - ]) + jest.spyOn(clineWithImages as any, "parseUserContent").mockImplementation(async (content) => content) + jest.spyOn(clineWithoutImages as any, "parseUserContent").mockImplementation(async (content) => content) // Set up mock streams const mockStreamWithImages = (async function* () { @@ -885,7 +882,7 @@ describe("Cline", () => { await task.catch(() => {}) }) - describe("loadContext", () => { + describe("parseUserContent", () => { it("should process mentions in task and feedback tags", async () => { const [cline, task] = Cline.create({ provider: mockProvider, @@ -929,7 +926,7 @@ describe("Cline", () => { ] // Process the content - const [processedContent] = await cline["loadContext"](userContent) + const processedContent = await cline.parseUserContent(userContent) // Regular text should not be processed expect((processedContent[0] as Anthropic.TextBlockParam).text).toBe("Regular text with @/some/path") From 01fec4c1dbf060ac09f9e6427ded50d19643565e Mon Sep 17 00:00:00 2001 From: Aljosa Asanovic Date: Mon, 5 May 2025 14:11:57 -0400 Subject: [PATCH 012/228] fix: migrate and persist modeApiConfigs for per-mode API profiles (#3071) --- src/core/config/ProviderSettingsManager.ts | 20 +++++++++++++++++-- .../__tests__/ProviderSettingsManager.test.ts | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index ca53cba8892..53ae585b3c0 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -79,6 +79,18 @@ export class ProviderSettingsManager { let isDirty = false + // Migrate existing installs to have per-mode API config map + if (!providerProfiles.modeApiConfigs) { + // Use the currently selected config for all modes initially + const currentName = providerProfiles.currentApiConfigName + const seedId = + providerProfiles.apiConfigs[currentName]?.id ?? + Object.values(providerProfiles.apiConfigs)[0]?.id ?? + this.defaultConfigId + providerProfiles.modeApiConfigs = Object.fromEntries(modes.map((m) => [m.slug, seedId])) + isDirty = true + } + // Ensure all configs have IDs. for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) { if (!apiConfig.id) { @@ -340,8 +352,12 @@ export class ProviderSettingsManager { try { return await this.lock(async () => { const providerProfiles = await this.load() - const { modeApiConfigs = {} } = providerProfiles - modeApiConfigs[mode] = configId + // Ensure the per-mode config map exists + if (!providerProfiles.modeApiConfigs) { + providerProfiles.modeApiConfigs = {} + } + // Assign the chosen config ID to this mode + providerProfiles.modeApiConfigs[mode] = configId await this.store(providerProfiles) }) } catch (error) { diff --git a/src/core/config/__tests__/ProviderSettingsManager.test.ts b/src/core/config/__tests__/ProviderSettingsManager.test.ts index 3cacc4c8b72..064157da028 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.test.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.test.ts @@ -53,6 +53,7 @@ describe("ProviderSettingsManager", () => { fuzzyMatchThreshold: 1.0, }, }, + modeApiConfigs: {}, migrations: { rateLimitSecondsMigrated: true, diffSettingsMigrated: true, From 305185cd883994d821c0f5325832cd208b2003b7 Mon Sep 17 00:00:00 2001 From: KJ7LNW <93454819+KJ7LNW@users.noreply.github.com> Date: Mon, 5 May 2025 11:33:18 -0700 Subject: [PATCH 013/228] feat: clickable code references in model responses navigate to source lines (#3087) Co-authored-by: Eric Wheeler --- .../__snapshots__/system.test.ts.snap | 72 +++++++++++++++++++ src/core/prompts/sections/index.ts | 1 + .../prompts/sections/markdown-formatting.ts | 7 ++ src/core/prompts/system.ts | 3 + src/core/webview/webviewMessageHandler.ts | 2 +- src/integrations/misc/open-file.ts | 8 ++- .../src/components/common/MarkdownBlock.tsx | 54 ++++++++++++-- 7 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 src/core/prompts/sections/markdown-formatting.ts diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap index ade0bd1f852..4aed3e9af57 100644 --- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap +++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap @@ -5,6 +5,12 @@ exports[`SYSTEM_PROMPT should exclude diff strategy tool description when diffEn ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -472,6 +478,12 @@ exports[`SYSTEM_PROMPT should exclude diff strategy tool description when diffEn ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -939,6 +951,12 @@ exports[`SYSTEM_PROMPT should explicitly handle undefined mcpHub 1`] = ` ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -1406,6 +1424,12 @@ exports[`SYSTEM_PROMPT should handle different browser viewport sizes 1`] = ` ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -1929,6 +1953,12 @@ exports[`SYSTEM_PROMPT should include MCP server info when mcpHub is provided 1` ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -2464,6 +2494,12 @@ exports[`SYSTEM_PROMPT should include browser actions when supportsComputerUse i ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -2987,6 +3023,12 @@ exports[`SYSTEM_PROMPT should include diff strategy tool description when diffEn ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -3544,6 +3586,12 @@ exports[`SYSTEM_PROMPT should maintain consistent system prompt 1`] = ` ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -4053,6 +4101,12 @@ exports[`addCustomInstructions should exclude MCP server creation info when disa ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -4597,6 +4651,12 @@ exports[`addCustomInstructions should generate correct prompt for architect mode ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -5055,6 +5115,12 @@ exports[`addCustomInstructions should generate correct prompt for ask mode 1`] = ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -5430,6 +5496,12 @@ exports[`addCustomInstructions should include MCP server creation info when enab ==== +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in + +==== + TOOL USE You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. diff --git a/src/core/prompts/sections/index.ts b/src/core/prompts/sections/index.ts index a9f8c9e4c6d..d06dbbfde1d 100644 --- a/src/core/prompts/sections/index.ts +++ b/src/core/prompts/sections/index.ts @@ -7,3 +7,4 @@ export { getMcpServersSection } from "./mcp-servers" export { getToolUseGuidelinesSection } from "./tool-use-guidelines" export { getCapabilitiesSection } from "./capabilities" export { getModesSection } from "./modes" +export { markdownFormattingSection } from "./markdown-formatting" diff --git a/src/core/prompts/sections/markdown-formatting.ts b/src/core/prompts/sections/markdown-formatting.ts new file mode 100644 index 00000000000..fe152a1a41d --- /dev/null +++ b/src/core/prompts/sections/markdown-formatting.ts @@ -0,0 +1,7 @@ +export function markdownFormattingSection(): string { + return `==== + +MARKDOWN RULES + +ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in ` +} diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index f56a9476637..92eba6bdc7c 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -24,6 +24,7 @@ import { getCapabilitiesSection, getModesSection, addCustomInstructions, + markdownFormattingSection, } from "./sections" import { loadSystemPromptFile } from "./sections/custom-system-prompt" import { formatLanguage } from "../../shared/language" @@ -65,6 +66,8 @@ async function generatePrompt( const basePrompt = `${roleDefinition} +${markdownFormattingSection()} + ${getSharedToolUseSection()} ${getToolDescriptionsForMode( diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index cee8ec76182..8528870a637 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -336,7 +336,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We openImage(message.text!) break case "openFile": - openFile(message.text!, message.values as { create?: boolean; content?: string }) + openFile(message.text!, message.values as { create?: boolean; content?: string; line?: number }) break case "openMention": openMention(message.text) diff --git a/src/integrations/misc/open-file.ts b/src/integrations/misc/open-file.ts index b3724068b62..52352314b3e 100644 --- a/src/integrations/misc/open-file.ts +++ b/src/integrations/misc/open-file.ts @@ -23,6 +23,7 @@ export async function openImage(dataUri: string) { interface OpenFileOptions { create?: boolean content?: string + line?: number } export async function openFile(filePath: string, options: OpenFileOptions = {}) { @@ -75,7 +76,12 @@ export async function openFile(filePath: string, options: OpenFileOptions = {}) } catch {} // not essential, sometimes tab operations fail const document = await vscode.workspace.openTextDocument(uri) - await vscode.window.showTextDocument(document, { preview: false }) + const selection = + options.line !== undefined ? new vscode.Selection(options.line - 1, 0, options.line - 1, 0) : undefined + await vscode.window.showTextDocument(document, { + preview: false, + selection, + }) } catch (error) { if (error instanceof Error) { vscode.window.showErrorMessage(`Could not open file: ${error.message}`) diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx index be7d5459802..a9c5ada19a9 100644 --- a/webview-ui/src/components/common/MarkdownBlock.tsx +++ b/webview-ui/src/components/common/MarkdownBlock.tsx @@ -3,6 +3,7 @@ import { useRemark } from "react-remark" import styled from "styled-components" import { visit } from "unist-util-visit" +import { vscode } from "@src/utils/vscode" import { useExtensionState } from "@src/context/ExtensionStateContext" import CodeBlock from "./CodeBlock" @@ -108,11 +109,14 @@ const StyledMarkdown = styled.div` } a { - text-decoration: none; - } - a { + color: var(--vscode-textLink-foreground); + text-decoration-line: underline; + text-decoration-style: dotted; + text-decoration-color: var(--vscode-textLink-foreground); &:hover { - text-decoration: underline; + color: var(--vscode-textLink-activeForeground); + text-decoration-style: solid; + text-decoration-color: var(--vscode-textLink-activeForeground); } } ` @@ -137,6 +141,48 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => { rehypePlugins: [], rehypeReactOptions: { components: { + a: ({ href, children }: any) => { + return ( + { + // Only process file:// protocol or local file paths + const isLocalPath = + href.startsWith("file://") || href.startsWith("/") || !href.includes("://") + + if (!isLocalPath) { + return + } + + e.preventDefault() + + // Handle absolute vs project-relative paths + let filePath = href.replace("file://", "") + + // Extract line number if present + const match = filePath.match(/(.*):(\d+)(-\d+)?$/) + let values = undefined + if (match) { + filePath = match[1] + values = { line: parseInt(match[2]) } + } + + // Add ./ prefix if needed + if (!filePath.startsWith("/") && !filePath.startsWith("./")) { + filePath = "./" + filePath + } + + vscode.postMessage({ + type: "openFile", + text: filePath, + values, + }) + }}> + {children} + + ) + }, pre: ({ node: _, children }: any) => { // Check for Mermaid diagrams first if (Array.isArray(children) && children.length === 1 && React.isValidElement(children[0])) { From 78beb718c876971e77a468ea8b4286108c4bbffd Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 5 May 2025 12:07:20 -0700 Subject: [PATCH 014/228] Move environment details to a separate module, add tests (#3078) --- .changeset/pretty-peaches-bake.md | 5 + src/core/Cline.ts | 265 +-------------- src/core/__tests__/Cline.test.ts | 99 +----- .../__tests__/getEnvironmentDetails.test.ts | 316 ++++++++++++++++++ src/core/environment/getEnvironmentDetails.ts | 270 +++++++++++++++ src/services/glob/list-files.ts | 1 + .../src/components/settings/ApiOptions.tsx | 8 +- 7 files changed, 606 insertions(+), 358 deletions(-) create mode 100644 .changeset/pretty-peaches-bake.md create mode 100644 src/core/environment/__tests__/getEnvironmentDetails.test.ts create mode 100644 src/core/environment/getEnvironmentDetails.ts diff --git a/.changeset/pretty-peaches-bake.md b/.changeset/pretty-peaches-bake.md new file mode 100644 index 00000000000..046228ea507 --- /dev/null +++ b/.changeset/pretty-peaches-bake.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Split Cline.getEnvironmentDetails out into a standalone function diff --git a/src/core/Cline.ts b/src/core/Cline.ts index c188e222453..f38da0b853a 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -33,14 +33,11 @@ import { import { getApiMetrics } from "../shared/getApiMetrics" import { HistoryItem } from "../shared/HistoryItem" import { ClineAskResponse } from "../shared/WebviewMessage" -import { defaultModeSlug, getModeBySlug, getFullModeDetails, isToolAllowedForMode } from "../shared/modes" -import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments" -import { formatLanguage } from "../shared/language" +import { defaultModeSlug, getModeBySlug } from "../shared/modes" import { ToolParamName, ToolResponse, DiffStrategy } from "../shared/tools" // services import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" -import { listFiles } from "../services/glob/list-files" import { BrowserSession } from "../services/browser/BrowserSession" import { McpHub } from "../services/mcp/McpHub" import { McpServerManager } from "../services/mcp/McpServerManager" @@ -51,12 +48,11 @@ import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../servi import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider" import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" import { RooTerminalProcess } from "../integrations/terminal/types" -import { Terminal } from "../integrations/terminal/Terminal" import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry" // utils import { calculateApiCostAnthropic } from "../utils/cost" -import { arePathsEqual, getWorkspacePath } from "../utils/path" +import { getWorkspacePath } from "../utils/path" // tools import { fetchInstructionsTool } from "./tools/fetchInstructionsTool" @@ -91,6 +87,7 @@ import { ClineProvider } from "./webview/ClineProvider" import { validateToolUse } from "./mode-validator" import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace" import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "./task-persistence" +import { getEnvironmentDetails } from "./environment/getEnvironmentDetails" type UserContent = Array @@ -145,7 +142,7 @@ export class Cline extends EventEmitter { private promptCacheKey: string rooIgnoreController?: RooIgnoreController - private fileContextTracker: FileContextTracker + fileContextTracker: FileContextTracker private urlContentFetcher: UrlContentFetcher browserSession: BrowserSession didEditFile: boolean = false @@ -1021,7 +1018,7 @@ export class Cline extends EventEmitter { ) const parsedUserContent = await this.parseUserContent(userContent) - const environmentDetails = await this.getEnvironmentDetails(includeFileDetails) + const environmentDetails = await getEnvironmentDetails(this, includeFileDetails) // Add environment details as its own text block, separate from tool // results. @@ -2077,258 +2074,6 @@ export class Cline extends EventEmitter { ) } - // Environment - - public async getEnvironmentDetails(includeFileDetails: boolean = false) { - let details = "" - - const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } = - (await this.providerRef.deref()?.getState()) ?? {} - - // It could be useful for cline to know if the user went from one or no file to another between messages, so we always include this context - details += "\n\n# VSCode Visible Files" - - const visibleFilePaths = vscode.window.visibleTextEditors - ?.map((editor) => editor.document?.uri?.fsPath) - .filter(Boolean) - .map((absolutePath) => path.relative(this.cwd, absolutePath)) - .slice(0, maxWorkspaceFiles) - - // Filter paths through rooIgnoreController - const allowedVisibleFiles = this.rooIgnoreController - ? this.rooIgnoreController.filterPaths(visibleFilePaths) - : visibleFilePaths.map((p) => p.toPosix()).join("\n") - - if (allowedVisibleFiles) { - details += `\n${allowedVisibleFiles}` - } else { - details += "\n(No visible files)" - } - - details += "\n\n# VSCode Open Tabs" - const { maxOpenTabsContext } = (await this.providerRef.deref()?.getState()) ?? {} - const maxTabs = maxOpenTabsContext ?? 20 - const openTabPaths = vscode.window.tabGroups.all - .flatMap((group) => group.tabs) - .map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath) - .filter(Boolean) - .map((absolutePath) => path.relative(this.cwd, absolutePath).toPosix()) - .slice(0, maxTabs) - - // Filter paths through rooIgnoreController - const allowedOpenTabs = this.rooIgnoreController - ? this.rooIgnoreController.filterPaths(openTabPaths) - : openTabPaths.map((p) => p.toPosix()).join("\n") - - if (allowedOpenTabs) { - details += `\n${allowedOpenTabs}` - } else { - details += "\n(No open tabs)" - } - - // Get task-specific and background terminals. - const busyTerminals = [ - ...TerminalRegistry.getTerminals(true, this.taskId), - ...TerminalRegistry.getBackgroundTerminals(true), - ] - - const inactiveTerminals = [ - ...TerminalRegistry.getTerminals(false, this.taskId), - ...TerminalRegistry.getBackgroundTerminals(false), - ] - - if (busyTerminals.length > 0) { - if (this.didEditFile) { - await delay(300) // Delay after saving file to let terminals catch up. - } - - // Wait for terminals to cool down. - await pWaitFor(() => busyTerminals.every((t) => !TerminalRegistry.isProcessHot(t.id)), { - interval: 100, - timeout: 5_000, - }).catch(() => {}) - } - - // Reset, this lets us know when to wait for saved files to update terminals. - this.didEditFile = false - - // Waiting for updated diagnostics lets terminal output be the most - // up-to-date possible. - let terminalDetails = "" - - if (busyTerminals.length > 0) { - // Terminals are cool, let's retrieve their output. - terminalDetails += "\n\n# Actively Running Terminals" - - for (const busyTerminal of busyTerminals) { - terminalDetails += `\n## Original command: \`${busyTerminal.getLastCommand()}\`` - let newOutput = TerminalRegistry.getUnretrievedOutput(busyTerminal.id) - - if (newOutput) { - newOutput = Terminal.compressTerminalOutput(newOutput, terminalOutputLineLimit) - terminalDetails += `\n### New Output\n${newOutput}` - } - } - } - - // First check if any inactive terminals in this task have completed - // processes with output. - const terminalsWithOutput = inactiveTerminals.filter((terminal) => { - const completedProcesses = terminal.getProcessesWithOutput() - return completedProcesses.length > 0 - }) - - // Only add the header if there are terminals with output. - if (terminalsWithOutput.length > 0) { - terminalDetails += "\n\n# Inactive Terminals with Completed Process Output" - - // Process each terminal with output. - for (const inactiveTerminal of terminalsWithOutput) { - let terminalOutputs: string[] = [] - - // Get output from completed processes queue. - const completedProcesses = inactiveTerminal.getProcessesWithOutput() - - for (const process of completedProcesses) { - let output = process.getUnretrievedOutput() - - if (output) { - output = Terminal.compressTerminalOutput(output, terminalOutputLineLimit) - terminalOutputs.push(`Command: \`${process.command}\`\n${output}`) - } - } - - // Clean the queue after retrieving output. - inactiveTerminal.cleanCompletedProcessQueue() - - // Add this terminal's outputs to the details. - if (terminalOutputs.length > 0) { - terminalDetails += `\n## Terminal ${inactiveTerminal.id}` - terminalOutputs.forEach((output) => { - terminalDetails += `\n### New Output\n${output}` - }) - } - } - } - - // console.log(`[Cline#getEnvironmentDetails] terminalDetails: ${terminalDetails}`) - - // Add recently modified files section. - const recentlyModifiedFiles = this.fileContextTracker.getAndClearRecentlyModifiedFiles() - - if (recentlyModifiedFiles.length > 0) { - details += - "\n\n# Recently Modified Files\nThese files have been modified since you last accessed them (file was just edited so you may need to re-read it before editing):" - for (const filePath of recentlyModifiedFiles) { - details += `\n${filePath}` - } - } - - if (terminalDetails) { - details += terminalDetails - } - - // 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 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})` - - // Add context tokens information. - const { contextTokens, totalCost } = getApiMetrics(this.clineMessages) - const modelInfo = this.api.getModel().info - const contextWindow = modelInfo.contextWindow - - const contextPercentage = - contextTokens && contextWindow ? Math.round((contextTokens / contextWindow) * 100) : undefined - - details += `\n\n# Current Context Size (Tokens)\n${contextTokens ? `${contextTokens.toLocaleString()} (${contextPercentage}%)` : "(Not available)"}` - details += `\n\n# Current Cost\n${totalCost !== null ? `$${totalCost.toFixed(2)}` : "(Not available)"}` - - // Add current mode and any mode-specific warnings. - const { - mode, - customModes, - apiModelId, - customModePrompts, - experiments = {} as Record, - customInstructions: globalCustomInstructions, - language, - } = (await this.providerRef.deref()?.getState()) ?? {} - - const currentMode = mode ?? defaultModeSlug - - const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, { - cwd: this.cwd, - globalCustomInstructions, - language: language ?? formatLanguage(vscode.env.language), - }) - - details += `\n\n# Current Mode\n` - details += `${currentMode}\n` - details += `${modeDetails.name}\n` - details += `${apiModelId}\n` - - if (Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)) { - details += `${modeDetails.roleDefinition}\n` - - if (modeDetails.customInstructions) { - details += `${modeDetails.customInstructions}\n` - } - } - - // Add warning if not in code mode. - if ( - !isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], { apply_diff: this.diffEnabled }) && - !isToolAllowedForMode("apply_diff", currentMode, customModes ?? [], { apply_diff: this.diffEnabled }) - ) { - const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode - const defaultModeName = getModeBySlug(defaultModeSlug, customModes)?.name ?? defaultModeSlug - details += `\n\nNOTE: You are currently in '${currentModeName}' mode, which does not allow write operations. To write files, the user will need to switch to a mode that supports file writing, such as '${defaultModeName}' mode.` - } - - if (includeFileDetails) { - details += `\n\n# Current Workspace Directory (${this.cwd.toPosix()}) Files\n` - const isDesktop = arePathsEqual(this.cwd, path.join(os.homedir(), "Desktop")) - - if (isDesktop) { - // Don't want to immediately access desktop since it would show - // permission popup. - details += "(Desktop files not shown automatically. Use list_files to explore if needed.)" - } else { - const maxFiles = maxWorkspaceFiles ?? 200 - const [files, didHitLimit] = await listFiles(this.cwd, true, maxFiles) - const { showRooIgnoredFiles = true } = (await this.providerRef.deref()?.getState()) ?? {} - - const result = formatResponse.formatFilesList( - this.cwd, - files, - didHitLimit, - this.rooIgnoreController, - showRooIgnoredFiles, - ) - - details += result - } - } - - return `\n${details.trim()}\n` - } - // Checkpoints private getCheckpointService() { diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 3de198acb38..14540c834c3 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -13,6 +13,10 @@ import { ApiConfiguration, ModelInfo } from "../../shared/api" import { ApiStreamChunk } from "../../api/transform/stream" import { ContextProxy } from "../config/ContextProxy" +jest.mock("../environment/getEnvironmentDetails", () => ({ + getEnvironmentDetails: jest.fn().mockResolvedValue(""), +})) + jest.mock("execa", () => ({ execa: jest.fn(), })) @@ -316,90 +320,7 @@ describe("Cline", () => { }) describe("getEnvironmentDetails", () => { - let originalDate: DateConstructor - let mockDate: Date - - beforeEach(() => { - originalDate = global.Date - const fixedTime = new Date("2024-01-01T12:00:00Z") - mockDate = new Date(fixedTime) - mockDate.getTimezoneOffset = jest.fn().mockReturnValue(420) // UTC-7 - - class MockDate extends Date { - constructor() { - super() - return mockDate - } - static override now() { - return mockDate.getTime() - } - } - - global.Date = MockDate as DateConstructor - - // Create a proper mock of Intl.DateTimeFormat - const mockDateTimeFormat = { - resolvedOptions: () => ({ - timeZone: "America/Los_Angeles", - }), - format: () => "1/1/2024, 5:00:00 AM", - } - - const MockDateTimeFormat = function (this: any) { - return mockDateTimeFormat - } as any - - MockDateTimeFormat.prototype = mockDateTimeFormat - MockDateTimeFormat.supportedLocalesOf = jest.fn().mockReturnValue(["en-US"]) - - global.Intl.DateTimeFormat = MockDateTimeFormat - }) - - afterEach(() => { - global.Date = originalDate - }) - - it("should include timezone information in environment details", async () => { - const cline = new Cline({ - provider: mockProvider, - apiConfiguration: mockApiConfig, - task: "test task", - startTask: false, - }) - - const details = await cline["getEnvironmentDetails"](false) - - // Verify timezone information is present and formatted correctly. - expect(details).toContain("America/Los_Angeles") - expect(details).toMatch(/UTC-7:00/) // Fixed offset for America/Los_Angeles. - expect(details).toContain("# Current Time") - expect(details).toMatch(/1\/1\/2024.*5:00:00 AM.*\(America\/Los_Angeles, UTC-7:00\)/) // Full time string format. - }) - describe("API conversation handling", () => { - /** - * Mock environment details retrieval to avoid filesystem access in tests - * - * This setup: - * 1. Prevents file listing operations that might cause test instability - * 2. Preserves test-specific mocks when they exist (via _mockGetEnvironmentDetails) - * 3. Provides a stable, empty environment by default - */ - beforeEach(() => { - // Mock the method with a stable implementation - jest.spyOn(Cline.prototype, "getEnvironmentDetails").mockImplementation( - // Use 'any' type to allow for dynamic test properties - async function (this: any, _verbose: boolean = false): Promise { - // Use test-specific mock if available - if (this._mockGetEnvironmentDetails) { - return this._mockGetEnvironmentDetails() - } - // Default to empty environment details for stability - return "" - }, - ) - }) - it("should clean conversation history before sending to API", async () => { // Cline.create will now use our mocked getEnvironmentDetails const [cline, task] = Cline.create({ @@ -420,12 +341,6 @@ describe("Cline", () => { const cleanMessageSpy = jest.fn().mockReturnValue(mockStreamForClean) jest.spyOn(cline.api, "createMessage").mockImplementation(cleanMessageSpy) - // Mock getEnvironmentDetails to return empty details. - jest.spyOn(cline as any, "getEnvironmentDetails").mockResolvedValue("") - - // Mock loadContext to return unmodified content. - jest.spyOn(cline as any, "parseUserContent").mockImplementation(async (content) => [content, ""]) - // Add test message to conversation history. cline.apiConversationHistory = [ { @@ -574,12 +489,6 @@ describe("Cline", () => { configurable: true, }) - // Mock environment details and context loading - jest.spyOn(clineWithImages as any, "getEnvironmentDetails").mockResolvedValue("") - jest.spyOn(clineWithoutImages as any, "getEnvironmentDetails").mockResolvedValue("") - jest.spyOn(clineWithImages as any, "parseUserContent").mockImplementation(async (content) => content) - jest.spyOn(clineWithoutImages as any, "parseUserContent").mockImplementation(async (content) => content) - // Set up mock streams const mockStreamWithImages = (async function* () { yield { type: "text", text: "test response" } diff --git a/src/core/environment/__tests__/getEnvironmentDetails.test.ts b/src/core/environment/__tests__/getEnvironmentDetails.test.ts new file mode 100644 index 00000000000..730055c3c1b --- /dev/null +++ b/src/core/environment/__tests__/getEnvironmentDetails.test.ts @@ -0,0 +1,316 @@ +// npx jest src/core/environment/__tests__/getEnvironmentDetails.test.ts + +import pWaitFor from "p-wait-for" +import delay from "delay" + +import { getEnvironmentDetails } from "../getEnvironmentDetails" +import { EXPERIMENT_IDS, experiments } from "../../../shared/experiments" +import { defaultModeSlug, getFullModeDetails, getModeBySlug, isToolAllowedForMode } from "../../../shared/modes" +import { getApiMetrics } from "../../../shared/getApiMetrics" +import { listFiles } from "../../../services/glob/list-files" +import { TerminalRegistry } from "../../../integrations/terminal/TerminalRegistry" +import { Terminal } from "../../../integrations/terminal/Terminal" +import { arePathsEqual } from "../../../utils/path" +import { FileContextTracker } from "../../context-tracking/FileContextTracker" +import { ApiHandler } from "../../../api/index" +import { ClineProvider } from "../../webview/ClineProvider" +import { RooIgnoreController } from "../../ignore/RooIgnoreController" +import { formatResponse } from "../../prompts/responses" +import { Cline } from "../../Cline" + +jest.mock("vscode", () => ({ + window: { + tabGroups: { all: [], onDidChangeTabs: jest.fn() }, + visibleTextEditors: [], + }, + env: { + language: "en-US", + }, +})) + +jest.mock("p-wait-for") + +jest.mock("delay") + +jest.mock("execa", () => ({ + execa: jest.fn(), +})) + +jest.mock("../../../shared/experiments") +jest.mock("../../../shared/modes") +jest.mock("../../../shared/getApiMetrics") +jest.mock("../../../services/glob/list-files") +jest.mock("../../../integrations/terminal/TerminalRegistry") +jest.mock("../../../integrations/terminal/Terminal") +jest.mock("../../../utils/path") +jest.mock("../../prompts/responses") + +describe("getEnvironmentDetails", () => { + const mockCwd = "/test/path" + const mockTaskId = "test-task-id" + + type MockTerminal = { + id: string + getLastCommand: jest.Mock + getProcessesWithOutput: jest.Mock + cleanCompletedProcessQueue?: jest.Mock + } + + let mockCline: Partial + let mockProvider: any + let mockState: any + + beforeEach(() => { + jest.clearAllMocks() + + mockState = { + terminalOutputLineLimit: 100, + maxWorkspaceFiles: 50, + maxOpenTabsContext: 10, + mode: "code", + customModes: [], + apiModelId: "test-model", + experiments: {}, + customInstructions: "test instructions", + language: "en", + showRooIgnoredFiles: true, + } + + mockProvider = { + getState: jest.fn().mockResolvedValue(mockState), + } + + mockCline = { + cwd: mockCwd, + taskId: mockTaskId, + didEditFile: false, + fileContextTracker: { + getAndClearRecentlyModifiedFiles: jest.fn().mockReturnValue([]), + } as unknown as FileContextTracker, + rooIgnoreController: { + filterPaths: jest.fn((paths: string[]) => paths.join("\n")), + cwd: mockCwd, + ignoreInstance: {}, + disposables: [], + rooIgnoreContent: "", + isPathIgnored: jest.fn(), + getIgnoreContent: jest.fn(), + updateIgnoreContent: jest.fn(), + addToIgnore: jest.fn(), + removeFromIgnore: jest.fn(), + dispose: jest.fn(), + } as unknown as RooIgnoreController, + clineMessages: [], + api: { + getModel: jest.fn().mockReturnValue({ info: { contextWindow: 100000 } }), + createMessage: jest.fn(), + countTokens: jest.fn(), + } as unknown as ApiHandler, + diffEnabled: true, + providerRef: { + deref: jest.fn().mockReturnValue(mockProvider), + [Symbol.toStringTag]: "WeakRef", + } as unknown as WeakRef, + } + + // Mock other dependencies. + ;(getApiMetrics as jest.Mock).mockReturnValue({ contextTokens: 50000, totalCost: 0.25 }) + ;(getFullModeDetails as jest.Mock).mockResolvedValue({ + name: "💻 Code", + roleDefinition: "You are a code assistant", + customInstructions: "Custom instructions", + }) + ;(isToolAllowedForMode as jest.Mock).mockReturnValue(true) + ;(listFiles as jest.Mock).mockResolvedValue([["file1.ts", "file2.ts"], false]) + ;(formatResponse.formatFilesList as jest.Mock).mockReturnValue("file1.ts\nfile2.ts") + ;(arePathsEqual as jest.Mock).mockReturnValue(false) + ;(Terminal.compressTerminalOutput as jest.Mock).mockImplementation((output: string) => output) + ;(TerminalRegistry.getTerminals as jest.Mock).mockReturnValue([]) + ;(TerminalRegistry.getBackgroundTerminals as jest.Mock).mockReturnValue([]) + ;(TerminalRegistry.isProcessHot as jest.Mock).mockReturnValue(false) + ;(TerminalRegistry.getUnretrievedOutput as jest.Mock).mockReturnValue("") + ;(pWaitFor as unknown as jest.Mock).mockResolvedValue(undefined) + ;(delay as jest.Mock).mockResolvedValue(undefined) + }) + + it("should return basic environment details", async () => { + const result = await getEnvironmentDetails(mockCline as Cline) + + expect(result).toContain("") + expect(result).toContain("") + expect(result).toContain("# VSCode Visible Files") + expect(result).toContain("# VSCode Open Tabs") + expect(result).toContain("# Current Time") + expect(result).toContain("# Current Context Size (Tokens)") + expect(result).toContain("# Current Cost") + expect(result).toContain("# Current Mode") + + expect(mockProvider.getState).toHaveBeenCalled() + + expect(getFullModeDetails).toHaveBeenCalledWith("code", [], undefined, { + cwd: mockCwd, + globalCustomInstructions: "test instructions", + language: "en", + }) + + expect(getApiMetrics).toHaveBeenCalledWith(mockCline.clineMessages) + }) + + it("should include file details when includeFileDetails is true", async () => { + const result = await getEnvironmentDetails(mockCline as Cline, true) + expect(result).toContain("# Current Workspace Directory") + expect(result).toContain("Files") + + expect(listFiles).toHaveBeenCalledWith(mockCwd, true, 50) + + expect(formatResponse.formatFilesList).toHaveBeenCalledWith( + mockCwd, + ["file1.ts", "file2.ts"], + false, + mockCline.rooIgnoreController, + true, + ) + }) + + it("should not include file details when includeFileDetails is false", async () => { + await getEnvironmentDetails(mockCline as Cline, false) + expect(listFiles).not.toHaveBeenCalled() + expect(formatResponse.formatFilesList).not.toHaveBeenCalled() + }) + + it("should handle desktop directory specially", async () => { + ;(arePathsEqual as jest.Mock).mockReturnValue(true) + const result = await getEnvironmentDetails(mockCline as Cline, true) + expect(result).toContain("Desktop files not shown automatically") + expect(listFiles).not.toHaveBeenCalled() + }) + + it("should include recently modified files if any", async () => { + ;(mockCline.fileContextTracker!.getAndClearRecentlyModifiedFiles as jest.Mock).mockReturnValue([ + "modified1.ts", + "modified2.ts", + ]) + + const result = await getEnvironmentDetails(mockCline as Cline) + + expect(result).toContain("# Recently Modified Files") + expect(result).toContain("modified1.ts") + expect(result).toContain("modified2.ts") + }) + + it("should include active terminal information", async () => { + const mockActiveTerminal = { + id: "terminal-1", + getLastCommand: jest.fn().mockReturnValue("npm test"), + getProcessesWithOutput: jest.fn().mockReturnValue([]), + } as MockTerminal + + ;(TerminalRegistry.getTerminals as jest.Mock).mockReturnValue([mockActiveTerminal]) + ;(TerminalRegistry.getUnretrievedOutput as jest.Mock).mockReturnValue("Test output") + + const result = await getEnvironmentDetails(mockCline as Cline) + + expect(result).toContain("# Actively Running Terminals") + expect(result).toContain("Original command: `npm test`") + expect(result).toContain("Test output") + + mockCline.didEditFile = true + await getEnvironmentDetails(mockCline as Cline) + expect(delay).toHaveBeenCalledWith(300) + + expect(pWaitFor).toHaveBeenCalled() + }) + + it("should include inactive terminals with output", async () => { + const mockProcess = { + command: "npm build", + getUnretrievedOutput: jest.fn().mockReturnValue("Build output"), + } + + const mockInactiveTerminal = { + id: "terminal-2", + getProcessesWithOutput: jest.fn().mockReturnValue([mockProcess]), + cleanCompletedProcessQueue: jest.fn(), + } as MockTerminal + + ;(TerminalRegistry.getTerminals as jest.Mock).mockImplementation((active: boolean) => + active ? [] : [mockInactiveTerminal], + ) + + const result = await getEnvironmentDetails(mockCline as Cline) + + expect(result).toContain("# Inactive Terminals with Completed Process Output") + expect(result).toContain("Terminal terminal-2") + expect(result).toContain("Command: `npm build`") + expect(result).toContain("Build output") + + expect(mockInactiveTerminal.cleanCompletedProcessQueue).toHaveBeenCalled() + }) + + it("should include warning when file writing is not allowed", async () => { + ;(isToolAllowedForMode as jest.Mock).mockReturnValue(false) + ;(getModeBySlug as jest.Mock).mockImplementation((slug: string) => { + if (slug === "code") { + return { name: "💻 Code" } + } + + if (slug === defaultModeSlug) { + return { name: "Default Mode" } + } + + return null + }) + + const result = await getEnvironmentDetails(mockCline as Cline) + + expect(result).toContain("NOTE: You are currently in '💻 Code' mode, which does not allow write operations") + }) + + it("should include experiment-specific details when Power Steering is enabled", async () => { + mockState.experiments = { [EXPERIMENT_IDS.POWER_STEERING]: true } + ;(experiments.isEnabled as jest.Mock).mockReturnValue(true) + + const result = await getEnvironmentDetails(mockCline as Cline) + + expect(result).toContain("You are a code assistant") + expect(result).toContain("Custom instructions") + }) + + it("should handle missing provider or state", async () => { + // Mock provider to return null. + mockCline.providerRef!.deref = jest.fn().mockReturnValue(null) + + const result = await getEnvironmentDetails(mockCline as Cline) + + // Verify the function still returns a result. + expect(result).toContain("") + expect(result).toContain("") + + // Mock provider to return null state. + mockCline.providerRef!.deref = jest.fn().mockReturnValue({ + getState: jest.fn().mockResolvedValue(null), + }) + + const result2 = await getEnvironmentDetails(mockCline as Cline) + + // Verify the function still returns a result. + expect(result2).toContain("") + expect(result2).toContain("") + }) + + it("should handle errors gracefully", async () => { + ;(pWaitFor as unknown as jest.Mock).mockRejectedValue(new Error("Test error")) + + const mockErrorTerminal = { + id: "terminal-1", + getLastCommand: jest.fn().mockReturnValue("npm test"), + getProcessesWithOutput: jest.fn().mockReturnValue([]), + } as MockTerminal + + ;(TerminalRegistry.getTerminals as jest.Mock).mockReturnValue([mockErrorTerminal]) + ;(TerminalRegistry.getBackgroundTerminals as jest.Mock).mockReturnValue([]) + ;(mockCline.fileContextTracker!.getAndClearRecentlyModifiedFiles as jest.Mock).mockReturnValue([]) + + await expect(getEnvironmentDetails(mockCline as Cline)).resolves.not.toThrow() + }) +}) diff --git a/src/core/environment/getEnvironmentDetails.ts b/src/core/environment/getEnvironmentDetails.ts new file mode 100644 index 00000000000..bb662cf9e39 --- /dev/null +++ b/src/core/environment/getEnvironmentDetails.ts @@ -0,0 +1,270 @@ +import path from "path" +import os from "os" + +import * as vscode from "vscode" +import pWaitFor from "p-wait-for" +import delay from "delay" + +import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../../shared/experiments" +import { formatLanguage } from "../../shared/language" +import { defaultModeSlug, getFullModeDetails, getModeBySlug, isToolAllowedForMode } from "../../shared/modes" +import { getApiMetrics } from "../../shared/getApiMetrics" +import { listFiles } from "../../services/glob/list-files" +import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry" +import { Terminal } from "../../integrations/terminal/Terminal" +import { arePathsEqual } from "../../utils/path" +import { formatResponse } from "../prompts/responses" + +import { Cline } from "../Cline" + +export async function getEnvironmentDetails(cline: Cline, includeFileDetails: boolean = false) { + let details = "" + + const clineProvider = cline.providerRef.deref() + const state = await clineProvider?.getState() + const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } = state ?? {} + + // It could be useful for cline to know if the user went from one or no + // file to another between messages, so we always include this context. + details += "\n\n# VSCode Visible Files" + + const visibleFilePaths = vscode.window.visibleTextEditors + ?.map((editor) => editor.document?.uri?.fsPath) + .filter(Boolean) + .map((absolutePath) => path.relative(cline.cwd, absolutePath)) + .slice(0, maxWorkspaceFiles) + + // Filter paths through rooIgnoreController + const allowedVisibleFiles = cline.rooIgnoreController + ? cline.rooIgnoreController.filterPaths(visibleFilePaths) + : visibleFilePaths.map((p) => p.toPosix()).join("\n") + + if (allowedVisibleFiles) { + details += `\n${allowedVisibleFiles}` + } else { + details += "\n(No visible files)" + } + + details += "\n\n# VSCode Open Tabs" + const { maxOpenTabsContext } = state ?? {} + const maxTabs = maxOpenTabsContext ?? 20 + const openTabPaths = vscode.window.tabGroups.all + .flatMap((group) => group.tabs) + .map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath) + .filter(Boolean) + .map((absolutePath) => path.relative(cline.cwd, absolutePath).toPosix()) + .slice(0, maxTabs) + + // Filter paths through rooIgnoreController + const allowedOpenTabs = cline.rooIgnoreController + ? cline.rooIgnoreController.filterPaths(openTabPaths) + : openTabPaths.map((p) => p.toPosix()).join("\n") + + if (allowedOpenTabs) { + details += `\n${allowedOpenTabs}` + } else { + details += "\n(No open tabs)" + } + + // Get task-specific and background terminals. + const busyTerminals = [ + ...TerminalRegistry.getTerminals(true, cline.taskId), + ...TerminalRegistry.getBackgroundTerminals(true), + ] + + const inactiveTerminals = [ + ...TerminalRegistry.getTerminals(false, cline.taskId), + ...TerminalRegistry.getBackgroundTerminals(false), + ] + + if (busyTerminals.length > 0) { + if (cline.didEditFile) { + await delay(300) // Delay after saving file to let terminals catch up. + } + + // Wait for terminals to cool down. + await pWaitFor(() => busyTerminals.every((t) => !TerminalRegistry.isProcessHot(t.id)), { + interval: 100, + timeout: 5_000, + }).catch(() => {}) + } + + // Reset, this lets us know when to wait for saved files to update terminals. + cline.didEditFile = false + + // Waiting for updated diagnostics lets terminal output be the most + // up-to-date possible. + let terminalDetails = "" + + if (busyTerminals.length > 0) { + // Terminals are cool, let's retrieve their output. + terminalDetails += "\n\n# Actively Running Terminals" + + for (const busyTerminal of busyTerminals) { + terminalDetails += `\n## Original command: \`${busyTerminal.getLastCommand()}\`` + let newOutput = TerminalRegistry.getUnretrievedOutput(busyTerminal.id) + + if (newOutput) { + newOutput = Terminal.compressTerminalOutput(newOutput, terminalOutputLineLimit) + terminalDetails += `\n### New Output\n${newOutput}` + } + } + } + + // First check if any inactive terminals in this task have completed + // processes with output. + const terminalsWithOutput = inactiveTerminals.filter((terminal) => { + const completedProcesses = terminal.getProcessesWithOutput() + return completedProcesses.length > 0 + }) + + // Only add the header if there are terminals with output. + if (terminalsWithOutput.length > 0) { + terminalDetails += "\n\n# Inactive Terminals with Completed Process Output" + + // Process each terminal with output. + for (const inactiveTerminal of terminalsWithOutput) { + let terminalOutputs: string[] = [] + + // Get output from completed processes queue. + const completedProcesses = inactiveTerminal.getProcessesWithOutput() + + for (const process of completedProcesses) { + let output = process.getUnretrievedOutput() + + if (output) { + output = Terminal.compressTerminalOutput(output, terminalOutputLineLimit) + terminalOutputs.push(`Command: \`${process.command}\`\n${output}`) + } + } + + // Clean the queue after retrieving output. + inactiveTerminal.cleanCompletedProcessQueue() + + // Add this terminal's outputs to the details. + if (terminalOutputs.length > 0) { + terminalDetails += `\n## Terminal ${inactiveTerminal.id}` + terminalOutputs.forEach((output) => { + terminalDetails += `\n### New Output\n${output}` + }) + } + } + } + + // console.log(`[Cline#getEnvironmentDetails] terminalDetails: ${terminalDetails}`) + + // Add recently modified files section. + const recentlyModifiedFiles = cline.fileContextTracker.getAndClearRecentlyModifiedFiles() + + if (recentlyModifiedFiles.length > 0) { + details += + "\n\n# Recently Modified Files\nThese files have been modified since you last accessed them (file was just edited so you may need to re-read it before editing):" + for (const filePath of recentlyModifiedFiles) { + details += `\n${filePath}` + } + } + + if (terminalDetails) { + details += terminalDetails + } + + // 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 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})` + + // Add context tokens information. + const { contextTokens, totalCost } = getApiMetrics(cline.clineMessages) + const modelInfo = cline.api.getModel().info + const contextWindow = modelInfo.contextWindow + + const contextPercentage = + contextTokens && contextWindow ? Math.round((contextTokens / contextWindow) * 100) : undefined + + details += `\n\n# Current Context Size (Tokens)\n${contextTokens ? `${contextTokens.toLocaleString()} (${contextPercentage}%)` : "(Not available)"}` + details += `\n\n# Current Cost\n${totalCost !== null ? `$${totalCost.toFixed(2)}` : "(Not available)"}` + + // Add current mode and any mode-specific warnings. + const { + mode, + customModes, + apiModelId, + customModePrompts, + experiments = {} as Record, + customInstructions: globalCustomInstructions, + language, + } = state ?? {} + + const currentMode = mode ?? defaultModeSlug + + const modeDetails = await getFullModeDetails(currentMode, customModes, customModePrompts, { + cwd: cline.cwd, + globalCustomInstructions, + language: language ?? formatLanguage(vscode.env.language), + }) + + details += `\n\n# Current Mode\n` + details += `${currentMode}\n` + details += `${modeDetails.name}\n` + details += `${apiModelId}\n` + + if (Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.POWER_STEERING)) { + details += `${modeDetails.roleDefinition}\n` + + if (modeDetails.customInstructions) { + details += `${modeDetails.customInstructions}\n` + } + } + + // Add warning if not in code mode. + if ( + !isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], { apply_diff: cline.diffEnabled }) && + !isToolAllowedForMode("apply_diff", currentMode, customModes ?? [], { apply_diff: cline.diffEnabled }) + ) { + const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode + const defaultModeName = getModeBySlug(defaultModeSlug, customModes)?.name ?? defaultModeSlug + details += `\n\nNOTE: You are currently in '${currentModeName}' mode, which does not allow write operations. To write files, the user will need to switch to a mode that supports file writing, such as '${defaultModeName}' mode.` + } + + if (includeFileDetails) { + details += `\n\n# Current Workspace Directory (${cline.cwd.toPosix()}) Files\n` + const isDesktop = arePathsEqual(cline.cwd, path.join(os.homedir(), "Desktop")) + + if (isDesktop) { + // Don't want to immediately access desktop since it would show + // permission popup. + details += "(Desktop files not shown automatically. Use list_files to explore if needed.)" + } else { + const maxFiles = maxWorkspaceFiles ?? 200 + const [files, didHitLimit] = await listFiles(cline.cwd, true, maxFiles) + const { showRooIgnoredFiles = true } = state ?? {} + + const result = formatResponse.formatFilesList( + cline.cwd, + files, + didHitLimit, + cline.rooIgnoreController, + showRooIgnoredFiles, + ) + + details += result + } + } + + return `\n${details.trim()}\n` +} diff --git a/src/services/glob/list-files.ts b/src/services/glob/list-files.ts index 6d30930ecb5..e1809ba4e82 100644 --- a/src/services/glob/list-files.ts +++ b/src/services/glob/list-files.ts @@ -40,6 +40,7 @@ const DIRS_TO_IGNORE = [ export async function listFiles(dirPath: string, recursive: boolean, limit: number): Promise<[string[], boolean]> { // Handle special directories const specialResult = await handleSpecialDirectories(dirPath) + if (specialResult) { return specialResult } diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 12b66ae0d79..2b0d363221d 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -85,11 +85,13 @@ const ApiOptions = ({ return Object.entries(headers) }) - // Effect to synchronize internal customHeaders state with prop changes useEffect(() => { const propHeaders = apiConfiguration?.openAiHeaders || {} - if (JSON.stringify(customHeaders) !== JSON.stringify(Object.entries(propHeaders))) setCustomHeaders(Object.entries(propHeaders)) - }, [apiConfiguration?.openAiHeaders]) + + if (JSON.stringify(customHeaders) !== JSON.stringify(Object.entries(propHeaders))) { + setCustomHeaders(Object.entries(propHeaders)) + } + }, [apiConfiguration?.openAiHeaders, customHeaders]) const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) const [openAiNativeBaseUrlSelected, setOpenAiNativeBaseUrlSelected] = useState( From 805ac1689a6b750c24189b73de94f1f9c63b0565 Mon Sep 17 00:00:00 2001 From: Deon588 Date: Mon, 5 May 2025 21:18:07 +0200 Subject: [PATCH 015/228] Improve Accessibility of Auto-Approve Toggles (#3145) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: DEON NEL Co-authored-by: cte --- .../components/settings/AutoApproveToggle.tsx | 6 +- .../__tests__/AutoApproveToggle.test.tsx | 91 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 webview-ui/src/components/settings/__tests__/AutoApproveToggle.test.tsx diff --git a/webview-ui/src/components/settings/AutoApproveToggle.tsx b/webview-ui/src/components/settings/AutoApproveToggle.tsx index f9fa5889e5f..17307e83670 100644 --- a/webview-ui/src/components/settings/AutoApproveToggle.tsx +++ b/webview-ui/src/components/settings/AutoApproveToggle.tsx @@ -1,9 +1,9 @@ +import type { GlobalSettings } from "@roo/schemas" + import { useAppTranslation } from "@/i18n/TranslationContext" import { cn } from "@/lib/utils" import { Button } from "@/components/ui" -import { GlobalSettings } from "@roo/schemas" - type AutoApproveToggles = Pick< GlobalSettings, | "alwaysAllowReadOnly" @@ -105,6 +105,8 @@ export const AutoApproveToggle = ({ onToggle, ...props }: AutoApproveToggleProps variant={props[key] ? "default" : "outline"} onClick={() => onToggle(key, !props[key])} title={t(descriptionKey || "")} + aria-label={t(labelKey)} + aria-pressed={!!props[key]} data-testid={testId} className={cn(" aspect-square h-[80px]", !props[key] && "opacity-50")}> diff --git a/webview-ui/src/components/settings/__tests__/AutoApproveToggle.test.tsx b/webview-ui/src/components/settings/__tests__/AutoApproveToggle.test.tsx new file mode 100644 index 00000000000..ce6c978ff28 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/AutoApproveToggle.test.tsx @@ -0,0 +1,91 @@ +import { render, screen, fireEvent } from "@testing-library/react" +import "@testing-library/jest-dom" + +import { AutoApproveToggle, autoApproveSettingsConfig } from "../AutoApproveToggle" +import { TranslationProvider } from "@/i18n/__mocks__/TranslationContext" + +jest.mock("@/i18n/TranslationContext", () => { + const actual = jest.requireActual("@/i18n/TranslationContext") + return { + ...actual, + useAppTranslation: () => ({ + t: (key: string) => key, + }), + } +}) + +describe("AutoApproveToggle", () => { + const mockOnToggle = jest.fn() + const initialProps = { + alwaysAllowReadOnly: true, + alwaysAllowWrite: false, + alwaysAllowBrowser: false, + alwaysApproveResubmit: true, + alwaysAllowMcp: false, + alwaysAllowModeSwitch: true, + alwaysAllowSubtasks: false, + alwaysAllowExecute: true, + onToggle: mockOnToggle, + } + + beforeEach(() => { + mockOnToggle.mockClear() + }) + + test("renders all toggle buttons with correct initial ARIA attributes", () => { + render( + + + , + ) + + Object.values(autoApproveSettingsConfig).forEach((config) => { + const button = screen.getByTestId(config.testId) + expect(button).toBeInTheDocument() + expect(button).toHaveAttribute("aria-label", config.labelKey) + expect(button).toHaveAttribute("aria-pressed", String(initialProps[config.key])) + }) + }) + + test("calls onToggle with the correct key and value when a button is clicked", () => { + render( + + + , + ) + + const writeToggleButton = screen.getByTestId(autoApproveSettingsConfig.alwaysAllowWrite.testId) + fireEvent.click(writeToggleButton) + + expect(mockOnToggle).toHaveBeenCalledTimes(1) + expect(mockOnToggle).toHaveBeenCalledWith("alwaysAllowWrite", true) + + const readOnlyButton = screen.getByTestId(autoApproveSettingsConfig.alwaysAllowReadOnly.testId) + fireEvent.click(readOnlyButton) + expect(mockOnToggle).toHaveBeenCalledTimes(2) + expect(mockOnToggle).toHaveBeenCalledWith("alwaysAllowReadOnly", false) + }) + + test("updates aria-pressed attribute after toggle", () => { + const { rerender } = render( + + + , + ) + + const writeToggleButton = screen.getByTestId(autoApproveSettingsConfig.alwaysAllowWrite.testId) + expect(writeToggleButton).toHaveAttribute("aria-pressed", "false") + + const updatedProps = { ...initialProps, alwaysAllowWrite: true } + rerender( + + + , + ) + + expect(screen.getByTestId(autoApproveSettingsConfig.alwaysAllowWrite.testId)).toHaveAttribute( + "aria-pressed", + "true", + ) + }) +}) From 904f71cb19ad0ca91a4096baa083f16c0fd9363f Mon Sep 17 00:00:00 2001 From: KJ7LNW <93454819+KJ7LNW@users.noreply.github.com> Date: Mon, 5 May 2025 12:19:50 -0700 Subject: [PATCH 016/228] feat: add VSCode terminal environment inheritance setting (#2862) Co-authored-by: Eric Wheeler --- src/core/webview/webviewMessageHandler.ts | 31 ++ src/shared/ExtensionMessage.ts | 4 + src/shared/WebviewMessage.ts | 5 + .../components/settings/TerminalSettings.tsx | 351 +++++++++++------- webview-ui/src/i18n/locales/ca/settings.json | 12 + webview-ui/src/i18n/locales/de/settings.json | 12 + webview-ui/src/i18n/locales/en/settings.json | 12 + webview-ui/src/i18n/locales/es/settings.json | 12 + webview-ui/src/i18n/locales/fr/settings.json | 12 + webview-ui/src/i18n/locales/hi/settings.json | 12 + webview-ui/src/i18n/locales/it/settings.json | 12 + webview-ui/src/i18n/locales/ja/settings.json | 12 + webview-ui/src/i18n/locales/ko/settings.json | 12 + webview-ui/src/i18n/locales/pl/settings.json | 12 + .../src/i18n/locales/pt-BR/settings.json | 12 + webview-ui/src/i18n/locales/ru/settings.json | 12 + webview-ui/src/i18n/locales/tr/settings.json | 12 + webview-ui/src/i18n/locales/vi/settings.json | 12 + .../src/i18n/locales/zh-CN/settings.json | 12 + .../src/i18n/locales/zh-TW/settings.json | 12 + 20 files changed, 450 insertions(+), 133 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 8528870a637..0e84f660c7e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -592,6 +592,37 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await updateGlobalState("fuzzyMatchThreshold", message.value) await provider.postStateToWebview() break + case "updateVSCodeSetting": { + // Allowlist of VSCode settings that can be updated + // Add new settings here when needed for future expansion + const ALLOWED_VSCODE_SETTINGS = ["terminal.integrated.inheritEnv"] as const + + if (message.setting && message.value !== undefined) { + if (!ALLOWED_VSCODE_SETTINGS.includes(message.setting as (typeof ALLOWED_VSCODE_SETTINGS)[number])) { + provider.log(`Attempted to update restricted VSCode setting: ${message.setting}`) + vscode.window.showErrorMessage(`Cannot update restricted VSCode setting: ${message.setting}`) + break + } + await vscode.workspace.getConfiguration().update(message.setting, message.value, true) + } + break + } + case "getVSCodeSetting": + if (message.setting) { + try { + const value = vscode.workspace.getConfiguration().get(message.setting) + await provider.postMessageToWebview({ type: "vsCodeSetting", setting: message.setting, value }) + } catch (error) { + console.error(`Failed to get VSCode setting ${message.setting}:`, error) + await provider.postMessageToWebview({ + type: "vsCodeSetting", + setting: message.setting, + error: `Failed to get setting: ${error.message}`, + value: undefined, + }) + } + } + break case "alwaysApproveResubmit": await updateGlobalState("alwaysApproveResubmit", message.bool ?? false) await provider.postStateToWebview() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 0b61dcaa0a8..adbafcdb24c 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -68,6 +68,7 @@ export interface ExtensionMessage { | "acceptInput" | "setHistoryPreviewCollapsed" | "commandExecutionStatus" + | "vsCodeSetting" text?: string action?: | "chatButtonClicked" @@ -104,6 +105,9 @@ export interface ExtensionMessage { promptText?: string results?: { path: string; type: "file" | "folder"; label?: string }[] error?: string + setting?: string + value?: any + vscodeSettingValue?: unknown } export type ExtensionState = Pick< diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ad922f2c041..ca5115a5497 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -51,6 +51,9 @@ export interface WebviewMessage { | "openFile" | "openMention" | "cancelTask" + | "updateVSCodeSetting" + | "getVSCodeSetting" + | "vsCodeSetting" | "alwaysAllowBrowser" | "alwaysAllowMcp" | "alwaysAllowModeSwitch" @@ -134,6 +137,7 @@ export interface WebviewMessage { images?: string[] bool?: boolean value?: number + vscodeSettingValue?: unknown commands?: string[] audioType?: AudioType serverName?: string @@ -145,6 +149,7 @@ export interface WebviewMessage { dataUrls?: string[] values?: Record query?: string + setting?: string slug?: string modeConfig?: ModeConfig timeout?: number diff --git a/webview-ui/src/components/settings/TerminalSettings.tsx b/webview-ui/src/components/settings/TerminalSettings.tsx index 452314ac39e..d155a600c90 100644 --- a/webview-ui/src/components/settings/TerminalSettings.tsx +++ b/webview-ui/src/components/settings/TerminalSettings.tsx @@ -1,5 +1,6 @@ -import { HTMLAttributes } from "react" +import { HTMLAttributes, useState, useEffect } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" +import { vscode } from "@/utils/vscode" import { SquareTerminal } from "lucide-react" import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" @@ -50,10 +51,31 @@ export const TerminalSettings = ({ className, ...props }: TerminalSettingsProps) => { + const [inheritEnv, setInheritEnv] = useState(true) + + useEffect(() => { + // Get initial value from VSCode configuration + vscode.postMessage({ type: "getVSCodeSetting", setting: "terminal.integrated.inheritEnv" }) + }, []) + useEffect(() => { + const handler = (event: MessageEvent<{ type: string; setting?: string; value?: boolean; error?: string }>) => { + const message = event.data + if (message.type === "vsCodeSetting" && message.setting === "terminal.integrated.inheritEnv") { + if (message.error) { + console.error("Failed to get terminal setting:", message.error) + } else { + setInheritEnv(message.value ?? true) + } + } + } + window.addEventListener("message", handler) + return () => window.removeEventListener("message", handler) + }, []) + const { t } = useAppTranslation() return ( -
+
@@ -62,149 +84,212 @@ export const TerminalSettings = ({
-
- -
- setCachedStateField("terminalOutputLineLimit", value)} - data-testid="terminal-output-limit-slider" - /> - {terminalOutputLineLimit ?? 500} -
-
- {t("settings:terminal.outputLineLimit.description")} + {/* Basic Settings */} +
+
+ +
{t("settings:terminal.basic.label")}
-
- -
- setCachedStateField("terminalCompressProgressBar", e.target.checked)} - data-testid="terminal-compress-progress-bar-checkbox"> - {t("settings:terminal.compressProgressBar.label")} - -
- {t("settings:terminal.compressProgressBar.description")} +
+
+ +
+ setCachedStateField("terminalOutputLineLimit", value)} + data-testid="terminal-output-limit-slider" + /> + {terminalOutputLineLimit ?? 500} +
+
+ {t("settings:terminal.outputLineLimit.description")} +
+
+
+ + setCachedStateField("terminalCompressProgressBar", e.target.checked) + } + data-testid="terminal-compress-progress-bar-checkbox"> + {t("settings:terminal.compressProgressBar.label")} + +
+ {t("settings:terminal.compressProgressBar.description")} +
+
-
- -
- - setCachedStateField( - "terminalShellIntegrationTimeout", - Math.min(60_000, Math.max(5_000, value)), - ) - } - /> - {(terminalShellIntegrationTimeout ?? 5000) / 1000}s + {/* Advanced Settings */} +
+
+ +
{t("settings:terminal.advanced.label")}
-
- {t("settings:terminal.shellIntegrationTimeout.description")} -
-
+

+ {t("settings:terminal.advanced.description")} +

+
+
+ { + setInheritEnv(e.target.checked) + vscode.postMessage({ + type: "updateVSCodeSetting", + setting: "terminal.integrated.inheritEnv", + value: e.target.checked, + }) + }} + data-testid="terminal-inherit-env-checkbox"> + {t("settings:terminal.inheritEnv.label")} + +
+ {t("settings:terminal.inheritEnv.description")} +
+
-
- - setCachedStateField("terminalShellIntegrationDisabled", e.target.checked) - }> - {t("settings:terminal.shellIntegrationDisabled.label")} - -
- {t("settings:terminal.shellIntegrationDisabled.description")} -
-
+
+ + setCachedStateField("terminalShellIntegrationDisabled", e.target.checked) + }> + + {t("settings:terminal.shellIntegrationDisabled.label")} + + +
+ {t("settings:terminal.shellIntegrationDisabled.description")} +
+
+ {!terminalShellIntegrationDisabled && ( + <> +
+ +
+ + setCachedStateField( + "terminalShellIntegrationTimeout", + Math.min(60000, Math.max(1000, value)), + ) + } + /> + + {(terminalShellIntegrationTimeout ?? 5000) / 1000}s + +
+
+ {t("settings:terminal.shellIntegrationTimeout.description")} +
+
-
- -
- - setCachedStateField("terminalCommandDelay", Math.min(1000, Math.max(0, value))) - } - /> - {terminalCommandDelay ?? 50}ms -
-
- {t("settings:terminal.commandDelay.description")} -
-
+
+ +
+ + setCachedStateField( + "terminalCommandDelay", + Math.min(1000, Math.max(0, value)), + ) + } + /> + {terminalCommandDelay ?? 50}ms +
+
+ {t("settings:terminal.commandDelay.description")} +
+
-
- setCachedStateField("terminalPowershellCounter", e.target.checked)} - data-testid="terminal-powershell-counter-checkbox"> - {t("settings:terminal.powershellCounter.label")} - -
- {t("settings:terminal.powershellCounter.description")} -
-
+
+ + setCachedStateField("terminalPowershellCounter", e.target.checked) + } + data-testid="terminal-powershell-counter-checkbox"> + + {t("settings:terminal.powershellCounter.label")} + + +
+ {t("settings:terminal.powershellCounter.description")} +
+
-
- setCachedStateField("terminalZshClearEolMark", e.target.checked)} - data-testid="terminal-zsh-clear-eol-mark-checkbox"> - {t("settings:terminal.zshClearEolMark.label")} - -
- {t("settings:terminal.zshClearEolMark.description")} -
-
+
+ + setCachedStateField("terminalZshClearEolMark", e.target.checked) + } + data-testid="terminal-zsh-clear-eol-mark-checkbox"> + + {t("settings:terminal.zshClearEolMark.label")} + + +
+ {t("settings:terminal.zshClearEolMark.description")} +
+
-
- setCachedStateField("terminalZshOhMy", e.target.checked)} - data-testid="terminal-zsh-oh-my-checkbox"> - {t("settings:terminal.zshOhMy.label")} - -
- {t("settings:terminal.zshOhMy.description")} -
-
+
+ setCachedStateField("terminalZshOhMy", e.target.checked)} + data-testid="terminal-zsh-oh-my-checkbox"> + {t("settings:terminal.zshOhMy.label")} + +
+ {t("settings:terminal.zshOhMy.description")} +
+
-
- setCachedStateField("terminalZshP10k", e.target.checked)} - data-testid="terminal-zsh-p10k-checkbox"> - {t("settings:terminal.zshP10k.label")} - -
- {t("settings:terminal.zshP10k.description")} -
-
+
+ setCachedStateField("terminalZshP10k", e.target.checked)} + data-testid="terminal-zsh-p10k-checkbox"> + {t("settings:terminal.zshP10k.label")} + +
+ {t("settings:terminal.zshP10k.description")} +
+
-
- setCachedStateField("terminalZdotdir", e.target.checked)} - data-testid="terminal-zdotdir-checkbox"> - {t("settings:terminal.zdotdir.label")} - -
- {t("settings:terminal.zdotdir.description")} +
+ setCachedStateField("terminalZdotdir", e.target.checked)} + data-testid="terminal-zdotdir-checkbox"> + {t("settings:terminal.zdotdir.label")} + +
+ {t("settings:terminal.zdotdir.description")} +
+
+ + )}
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 91ed2326c1d..8aa87ec2558 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Configuració del terminal: Bàsica", + "description": "Configuració bàsica del terminal" + }, + "advanced": { + "label": "Configuració del terminal: Avançada", + "description": "Les següents opcions poden requerir reiniciar el terminal per aplicar la configuració" + }, "outputLineLimit": { "label": "Límit de sortida de terminal", "description": "Nombre màxim de línies a incloure a la sortida del terminal en executar comandes. Quan s'excedeix, s'eliminaran línies del mig, estalviant token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Habilita la integració Powerlevel10k", "description": "Quan està habilitat, estableix POWERLEVEL9K_TERM_SHELL_INTEGRATION=true per habilitar les característiques d'integració del shell Powerlevel10k. (experimental)" + }, + "inheritEnv": { + "label": "Hereta variables d'entorn", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 3db3f0b4d4b..407856cc036 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Terminal-Einstellungen: Grundlegend", + "description": "Grundlegende Terminal-Einstellungen" + }, + "advanced": { + "label": "Terminal-Einstellungen: Erweitert", + "description": "Die folgenden Optionen erfordern möglicherweise einen Terminal-Neustart, um die Einstellung zu übernehmen" + }, "outputLineLimit": { "label": "Terminal-Ausgabelimit", "description": "Maximale Anzahl von Zeilen, die in der Terminal-Ausgabe bei der Ausführung von Befehlen enthalten sein sollen. Bei Überschreitung werden Zeilen aus der Mitte entfernt, wodurch Token gespart werden." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Powerlevel10k-Integration aktivieren", "description": "Wenn aktiviert, wird POWERLEVEL9K_TERM_SHELL_INTEGRATION=true gesetzt, um die Shell-Integrationsfunktionen von Powerlevel10k zu aktivieren. (experimentell)" + }, + "inheritEnv": { + "label": "Umgebungsvariablen übernehmen", + "description": "Wenn aktiviert, übernimmt das Terminal Umgebungsvariablen vom übergeordneten VSCode-Prozess, wie z.B. in Benutzerprofilen definierte Shell-Integrationseinstellungen. Dies schaltet direkt die globale VSCode-Einstellung `terminal.integrated.inheritEnv` um" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index fe585334832..237664761e0 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Terminal Settings: Basic", + "description": "Basic terminal settings" + }, + "advanced": { + "label": "Terminal Settings: Advanced", + "description": "The following options may require a terminal restart to apply the setting" + }, "outputLineLimit": { "label": "Terminal output limit", "description": "Maximum number of lines to include in terminal output when executing commands. When exceeded lines will be removed from the middle, saving tokens." @@ -349,6 +357,10 @@ "zdotdir": { "label": "Enable ZDOTDIR handling", "description": "When enabled, creates a temporary directory for ZDOTDIR to handle zsh shell integration properly. This ensures VSCode shell integration works correctly with zsh while preserving your zsh configuration. (experimental)" + }, + "inheritEnv": { + "label": "Inherit environment variables", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 882f171abab..0d9d5cea718 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Configuración del terminal: Básica", + "description": "Configuración básica del terminal" + }, + "advanced": { + "label": "Configuración del terminal: Avanzada", + "description": "Las siguientes opciones pueden requerir reiniciar el terminal para aplicar la configuración" + }, "outputLineLimit": { "label": "Límite de salida de terminal", "description": "Número máximo de líneas a incluir en la salida del terminal al ejecutar comandos. Cuando se excede, se eliminarán líneas del medio, ahorrando token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Habilitar integración Powerlevel10k", "description": "Cuando está habilitado, establece POWERLEVEL9K_TERM_SHELL_INTEGRATION=true para habilitar las características de integración del shell Powerlevel10k. (experimental)" + }, + "inheritEnv": { + "label": "Heredar variables de entorno", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 00abf194d1d..1bf17af9934 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Paramètres du terminal : Base", + "description": "Paramètres de base du terminal" + }, + "advanced": { + "label": "Paramètres du terminal : Avancé", + "description": "Les options suivantes peuvent nécessiter un redémarrage du terminal pour appliquer le paramètre" + }, "outputLineLimit": { "label": "Limite de sortie du terminal", "description": "Nombre maximum de lignes à inclure dans la sortie du terminal lors de l'exécution de commandes. Lorsque ce nombre est dépassé, les lignes seront supprimées du milieu, économisant des token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Activer l'intégration Powerlevel10k", "description": "Lorsqu'activé, définit POWERLEVEL9K_TERM_SHELL_INTEGRATION=true pour activer les fonctionnalités d'intégration du shell Powerlevel10k. (expérimental)" + }, + "inheritEnv": { + "label": "Hériter des variables d'environnement", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2d4b527bb6e..597c4ddceaa 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "टर्मिनल सेटिंग्स: मूल", + "description": "मूल टर्मिनल सेटिंग्स" + }, + "advanced": { + "label": "टर्मिनल सेटिंग्स: उन्नत", + "description": "निम्नलिखित विकल्पों को लागू करने के लिए टर्मिनल को पुनरारंभ करने की आवश्यकता हो सकती है" + }, "outputLineLimit": { "label": "टर्मिनल आउटपुट सीमा", "description": "कमांड निष्पादित करते समय टर्मिनल आउटपुट में शामिल करने के लिए पंक्तियों की अधिकतम संख्या। पार होने पर पंक्तियाँ मध्य से हटा दी जाएंगी, token बचाते हुए।" @@ -349,6 +357,10 @@ "zshP10k": { "label": "Powerlevel10k एकीकरण सक्षम करें", "description": "सक्षम होने पर, Powerlevel10k शेल एकीकरण सुविधाओं को सक्षम करने के लिए POWERLEVEL9K_TERM_SHELL_INTEGRATION=true सेट करता है। (प्रयोगात्मक)" + }, + "inheritEnv": { + "label": "पर्यावरण चर विरासत में लें", + "description": "सक्षम होने पर, टर्मिनल VSCode के मूल प्रक्रिया से पर्यावरण चर विरासत में लेता है, जैसे उपयोगकर्ता प्रोफ़ाइल में परिभाषित शेल एकीकरण सेटिंग्स। यह VSCode की वैश्विक सेटिंग `terminal.integrated.inheritEnv` को सीधे टॉगल करता है" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 8b6f068b907..5927c92db23 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Impostazioni terminale: Base", + "description": "Impostazioni base del terminale" + }, + "advanced": { + "label": "Impostazioni terminale: Avanzate", + "description": "Le seguenti opzioni potrebbero richiedere il riavvio del terminale per applicare l'impostazione" + }, "outputLineLimit": { "label": "Limite output terminale", "description": "Numero massimo di righe da includere nell'output del terminale durante l'esecuzione dei comandi. Quando superato, le righe verranno rimosse dal centro, risparmiando token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Abilita integrazione Powerlevel10k", "description": "Quando abilitato, imposta POWERLEVEL9K_TERM_SHELL_INTEGRATION=true per abilitare le funzionalità di integrazione della shell Powerlevel10k. (sperimentale)" + }, + "inheritEnv": { + "label": "Eredita variabili d'ambiente", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 0eee0902a44..1d027d93a55 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "ターミナル設定:基本", + "description": "基本的なターミナル設定" + }, + "advanced": { + "label": "ターミナル設定:詳細", + "description": "以下のオプションは設定を適用するためにターミナルの再起動が必要な場合があります" + }, "outputLineLimit": { "label": "ターミナル出力制限", "description": "コマンド実行時にターミナル出力に含める最大行数。超過すると中央から行が削除され、tokenを節約します。" @@ -349,6 +357,10 @@ "zshP10k": { "label": "Powerlevel10k 統合を有効化", "description": "有効にすると、POWERLEVEL9K_TERM_SHELL_INTEGRATION=true を設定して Powerlevel10k シェル統合機能を有効にします。(実験的)" + }, + "inheritEnv": { + "label": "環境変数を継承", + "description": "有効にすると、ターミナルは VSCode の親プロセスから環境変数を継承します。ユーザープロファイルで定義されたシェル統合設定などが含まれます。これは VSCode のグローバル設定 `terminal.integrated.inheritEnv` を直接切り替えます" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index db1ed2d3c84..0d5b884cef8 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "터미널 설정: 기본", + "description": "기본 터미널 설정" + }, + "advanced": { + "label": "터미널 설정: 고급", + "description": "다음 옵션들은 설정을 적용하기 위해 터미널 재시작이 필요할 수 있습니다" + }, "outputLineLimit": { "label": "터미널 출력 제한", "description": "명령 실행 시 터미널 출력에 포함할 최대 라인 수. 초과 시 중간에서 라인이 제거되어 token이 절약됩니다." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Powerlevel10k 통합 활성화", "description": "활성화하면 POWERLEVEL9K_TERM_SHELL_INTEGRATION=true를 설정하여 Powerlevel10k 셸 통합 기능을 활성화합니다. (실험적)" + }, + "inheritEnv": { + "label": "환경 변수 상속", + "description": "활성화하면 터미널이 VSCode 부모 프로세스로부터 환경 변수를 상속받습니다. 사용자 프로필에 정의된 셸 통합 설정 등이 포함됩니다. 이는 VSCode 전역 설정 `terminal.integrated.inheritEnv`를 직접 전환합니다" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index fbc1b375e04..25397b11d8b 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Ustawienia terminala: Podstawowe", + "description": "Podstawowe ustawienia terminala" + }, + "advanced": { + "label": "Ustawienia terminala: Zaawansowane", + "description": "Poniższe opcje mogą wymagać ponownego uruchomienia terminala, aby zastosować ustawienie" + }, "outputLineLimit": { "label": "Limit wyjścia terminala", "description": "Maksymalna liczba linii do uwzględnienia w wyjściu terminala podczas wykonywania poleceń. Po przekroczeniu linie będą usuwane ze środka, oszczędzając token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Włącz integrację Powerlevel10k", "description": "Po włączeniu ustawia POWERLEVEL9K_TERM_SHELL_INTEGRATION=true, aby włączyć funkcje integracji powłoki Powerlevel10k. (eksperymentalne)" + }, + "inheritEnv": { + "label": "Dziedzicz zmienne środowiskowe", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index d8d683f8477..35360788c9c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Configurações do terminal: Básicas", + "description": "Configurações básicas do terminal" + }, + "advanced": { + "label": "Configurações do terminal: Avançadas", + "description": "As seguintes opções podem exigir reiniciar o terminal para aplicar a configuração" + }, "outputLineLimit": { "label": "Limite de saída do terminal", "description": "Número máximo de linhas a incluir na saída do terminal ao executar comandos. Quando excedido, as linhas serão removidas do meio, economizando token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Ativar integração Powerlevel10k", "description": "Quando ativado, define POWERLEVEL9K_TERM_SHELL_INTEGRATION=true para habilitar os recursos de integração do shell Powerlevel10k. (experimental)" + }, + "inheritEnv": { + "label": "Herdar variáveis de ambiente", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 85e0432e156..4b82f266ad3 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Настройки терминала: Основные", + "description": "Основные настройки терминала" + }, + "advanced": { + "label": "Настройки терминала: Расширенные", + "description": "Следующие параметры могут потребовать перезапуск терминала для применения настроек" + }, "outputLineLimit": { "label": "Лимит вывода терминала", "description": "Максимальное количество строк, включаемых в вывод терминала при выполнении команд. При превышении строки из середины будут удаляться для экономии токенов." @@ -349,6 +357,10 @@ "zdotdir": { "label": "Включить обработку ZDOTDIR", "description": "Если включено, создаёт временную директорию для ZDOTDIR для корректной интеграции zsh. Это обеспечивает корректную работу интеграции VSCode с zsh, сохраняя вашу конфигурацию. (экспериментально)" + }, + "inheritEnv": { + "label": "Наследовать переменные среды", + "description": "Если включено, терминал будет наследовать переменные среды от родительского процесса VSCode, такие как настройки интеграции оболочки, определённые в профиле пользователя. Напрямую переключает глобальную настройку VSCode `terminal.integrated.inheritEnv`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index ffd586bf023..e92f14329d9 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Terminal Ayarları: Temel", + "description": "Temel terminal ayarları" + }, + "advanced": { + "label": "Terminal Ayarları: Gelişmiş", + "description": "Aşağıdaki seçeneklerin uygulanması için terminalin yeniden başlatılması gerekebilir" + }, "outputLineLimit": { "label": "Terminal çıktısı sınırı", "description": "Komutları yürütürken terminal çıktısına dahil edilecek maksimum satır sayısı. Aşıldığında, token tasarrufu sağlayarak satırlar ortadan kaldırılacaktır." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Powerlevel10k entegrasyonunu etkinleştir", "description": "Etkinleştirildiğinde, Powerlevel10k kabuk entegrasyon özelliklerini etkinleştirmek için POWERLEVEL9K_TERM_SHELL_INTEGRATION=true ayarlar. (deneysel)" + }, + "inheritEnv": { + "label": "Ortam değişkenlerini devral", + "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" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 1ef56743355..b49c5fa0a2c 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "Cài đặt Terminal: Cơ bản", + "description": "Cài đặt cơ bản cho terminal" + }, + "advanced": { + "label": "Cài đặt Terminal: Nâng cao", + "description": "Các tùy chọn sau có thể yêu cầu khởi động lại terminal để áp dụng cài đặt" + }, "outputLineLimit": { "label": "Giới hạn đầu ra terminal", "description": "Số dòng tối đa để đưa vào đầu ra terminal khi thực hiện lệnh. Khi vượt quá, các dòng sẽ bị xóa khỏi phần giữa, tiết kiệm token." @@ -349,6 +357,10 @@ "zshP10k": { "label": "Bật tích hợp Powerlevel10k", "description": "Khi được bật, đặt POWERLEVEL9K_TERM_SHELL_INTEGRATION=true để kích hoạt các tính năng tích hợp shell của Powerlevel10k. (thử nghiệm)" + }, + "inheritEnv": { + "label": "Kế thừa biến môi trường", + "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`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 7ecfa8024c5..b9f32090e3e 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "终端设置:基础", + "description": "基础终端设置" + }, + "advanced": { + "label": "终端设置:高级", + "description": "以下选项可能需要重启终端才能应用设置" + }, "outputLineLimit": { "label": "终端输出限制", "description": "执行命令时在终端输出中包含的最大行数。超过时将从中间删除行,节省 token。" @@ -349,6 +357,10 @@ "zshP10k": { "label": "启用 Powerlevel10k 集成", "description": "启用后,设置 POWERLEVEL9K_TERM_SHELL_INTEGRATION=true 以启用 Powerlevel10k shell 集成功能。(实验性)" + }, + "inheritEnv": { + "label": "继承环境变量", + "description": "启用后,终端将从 VSCode 父进程继承环境变量,如用户配置文件中定义的 shell 集成设置。这直接切换 VSCode 全局设置 `terminal.integrated.inheritEnv`" } }, "advanced": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 32b9f709769..508df804ac1 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -310,6 +310,14 @@ } }, "terminal": { + "basic": { + "label": "終端機設定:基本", + "description": "基本終端機設定" + }, + "advanced": { + "label": "終端機設定:進階", + "description": "以下選項可能需要重新啟動終端機才能套用設定" + }, "outputLineLimit": { "label": "終端機輸出行數限制", "description": "執行命令時終端機輸出的最大行數。超過此限制時,會從中間移除多餘的行數,以節省 token 用量。" @@ -349,6 +357,10 @@ "zshP10k": { "label": "啟用 Powerlevel10k 整合", "description": "啟用後,設定 POWERLEVEL9K_TERM_SHELL_INTEGRATION=true 以啟用 Powerlevel10k shell 整合功能。(實驗性)" + }, + "inheritEnv": { + "label": "繼承環境變數", + "description": "啟用後,終端機將從 VSCode 父程序繼承環境變數,如使用者設定檔中定義的 shell 整合設定。這直接切換 VSCode 全域設定 `terminal.integrated.inheritEnv`" } }, "advanced": { From 973295835a3d0977b159e887cc5d26cf133b5648 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 5 May 2025 12:56:41 -0700 Subject: [PATCH 017/228] Webview message handler + terminal settings cleanup (#3189) --- src/core/webview/generateSystemPrompt.ts | 73 +++++++++ src/core/webview/webviewMessageHandler.ts | 144 ++++++------------ src/shared/ExtensionMessage.ts | 1 - src/shared/WebviewMessage.ts | 1 - .../components/settings/TerminalSettings.tsx | 71 +++++---- webview-ui/src/i18n/locales/ca/settings.json | 8 +- webview-ui/src/i18n/locales/de/settings.json | 2 +- webview-ui/src/i18n/locales/en/settings.json | 8 +- webview-ui/src/i18n/locales/es/settings.json | 8 +- webview-ui/src/i18n/locales/fr/settings.json | 2 +- webview-ui/src/i18n/locales/it/settings.json | 2 +- webview-ui/src/i18n/locales/pl/settings.json | 2 +- .../src/i18n/locales/pt-BR/settings.json | 8 +- webview-ui/src/i18n/locales/ru/settings.json | 2 +- webview-ui/src/i18n/locales/tr/settings.json | 2 +- webview-ui/src/i18n/locales/vi/settings.json | 2 +- 16 files changed, 182 insertions(+), 154 deletions(-) create mode 100644 src/core/webview/generateSystemPrompt.ts diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts new file mode 100644 index 00000000000..f676fa18f62 --- /dev/null +++ b/src/core/webview/generateSystemPrompt.ts @@ -0,0 +1,73 @@ +import { WebviewMessage } from "../../shared/WebviewMessage" +import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" +import { buildApiHandler } from "../../api" + +import { SYSTEM_PROMPT } from "../prompts/system" +import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" + +import { ClineProvider } from "./ClineProvider" + +export const generateSystemPrompt = async (provider: ClineProvider, message: WebviewMessage) => { + const { + apiConfiguration, + customModePrompts, + customInstructions, + browserViewportSize, + diffEnabled, + mcpEnabled, + fuzzyMatchThreshold, + experiments, + enableMcpServerCreation, + browserToolEnabled, + language, + } = await provider.getState() + + const diffStrategy = new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) + + const cwd = provider.cwd + + const mode = message.mode ?? defaultModeSlug + const customModes = await provider.customModesManager.getCustomModes() + + const rooIgnoreInstructions = provider.getCurrentCline()?.rooIgnoreController?.getInstructions() + + // Determine if browser tools can be used based on model support, mode, and user settings + let modelSupportsComputerUse = false + + // Create a temporary API handler to check if the model supports computer use + // This avoids relying on an active Cline instance which might not exist during preview + try { + const tempApiHandler = buildApiHandler(apiConfiguration) + modelSupportsComputerUse = tempApiHandler.getModel().info.supportsComputerUse ?? false + } catch (error) { + console.error("Error checking if model supports computer use:", error) + } + + // Check if the current mode includes the browser tool group + const modeConfig = getModeBySlug(mode, customModes) + const modeSupportsBrowser = modeConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false + + // Only enable browser tools if the model supports it, the mode includes browser tools, + // and browser tools are enabled in settings + const canUseBrowserTool = modelSupportsComputerUse && modeSupportsBrowser && (browserToolEnabled ?? true) + + const systemPrompt = await SYSTEM_PROMPT( + provider.context, + cwd, + canUseBrowserTool, + mcpEnabled ? provider.getMcpHub() : undefined, + diffStrategy, + browserViewportSize ?? "900x600", + mode, + customModePrompts, + customModes, + customInstructions, + diffEnabled, + experiments, + enableMcpServerCreation, + language, + rooIgnoreInstructions, + ) + + return systemPrompt +} diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 0e84f660c7e..c3ee6d8c3ac 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -32,12 +32,12 @@ import { openMention } from "../mentions" import { telemetryService } from "../../services/telemetry/TelemetryService" import { TelemetrySetting } from "../../shared/TelemetrySetting" import { getWorkspacePath } from "../../utils/path" -import { Mode, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" -import { SYSTEM_PROMPT } from "../prompts/system" -import { buildApiHandler } from "../../api" +import { Mode, defaultModeSlug } from "../../shared/modes" import { GlobalState } from "../../schemas" -import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" import { getModels } from "../../api/providers/fetchers/cache" +import { generateSystemPrompt } from "./generateSystemPrompt" + +const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"]) export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => { // Utility functions provided for concise get/update of global state via contextProxy API. @@ -128,14 +128,9 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We provider.isViewLaunched = true break case "newTask": - // Code that should run in response to the hello message command - //vscode.window.showInformationMessage(message.text!) - - // Send a message to our webview. - // You can send any JSON serializable data. - // Could also do this in extension .ts - //provider.postMessageToWebview({ type: "text", text: `Extension: ${Date.now()}` }) - // initializing new instance of Cline will make sure that any agentically running promises in old instance don't affect our new task. this essentially creates a fresh slate for the new task + // Initializing new instance of Cline will make sure that any + // agentically running promises in old instance don't affect our new + // task. This essentially creates a fresh slate for the new task. await provider.initClineWithTask(message.text, message.images) break case "apiConfiguration": @@ -375,16 +370,29 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We break case "allowedCommands": await provider.context.globalState.update("allowedCommands", message.commands) - // Also update workspace settings + + // Also update workspace settings. await vscode.workspace .getConfiguration("roo-cline") .update("allowedCommands", message.commands, vscode.ConfigurationTarget.Global) + + break + case "openCustomModesSettings": { + const customModesFilePath = await provider.customModesManager.getCustomModesFilePath() + + if (customModesFilePath) { + openFile(customModesFilePath) + } + break + } case "openMcpSettings": { const mcpSettingsFilePath = await provider.getMcpHub()?.getMcpSettingsFilePath() + if (mcpSettingsFilePath) { openFile(mcpSettingsFilePath) } + break } case "openProjectMcpSettings": { @@ -400,20 +408,16 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We try { await fs.mkdir(rooDir, { recursive: true }) const exists = await fileExistsAtPath(mcpPath) + if (!exists) { await fs.writeFile(mcpPath, JSON.stringify({ mcpServers: {} }, null, 2)) } + await openFile(mcpPath) } catch (error) { vscode.window.showErrorMessage(t("common:errors.create_mcp_json", { error: `${error}` })) } - break - } - case "openCustomModesSettings": { - const customModesFilePath = await provider.customModesManager.getCustomModesFilePath() - if (customModesFilePath) { - openFile(customModesFilePath) - } + break } case "deleteMcpServer": { @@ -559,6 +563,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We if (!message.text) { // Use testBrowserConnection for auto-discovery const chromeHostUrl = await discoverChromeHostUrl() + if (chromeHostUrl) { // Send the result back to the webview await provider.postMessageToWebview({ @@ -578,6 +583,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We // Test the provided URL const customHostUrl = message.text const hostIsValid = await tryChromeHostUrl(message.text) + // Send the result back to the webview await provider.postMessageToWebview({ type: "browserConnectionResult", @@ -593,35 +599,40 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await provider.postStateToWebview() break case "updateVSCodeSetting": { - // Allowlist of VSCode settings that can be updated - // Add new settings here when needed for future expansion - const ALLOWED_VSCODE_SETTINGS = ["terminal.integrated.inheritEnv"] as const - - if (message.setting && message.value !== undefined) { - if (!ALLOWED_VSCODE_SETTINGS.includes(message.setting as (typeof ALLOWED_VSCODE_SETTINGS)[number])) { - provider.log(`Attempted to update restricted VSCode setting: ${message.setting}`) - vscode.window.showErrorMessage(`Cannot update restricted VSCode setting: ${message.setting}`) - break + const { setting, value } = message + + if (setting !== undefined && value !== undefined) { + if (ALLOWED_VSCODE_SETTINGS.has(setting)) { + await vscode.workspace.getConfiguration().update(setting, value, true) + } else { + vscode.window.showErrorMessage(`Cannot update restricted VSCode setting: ${setting}`) } - await vscode.workspace.getConfiguration().update(message.setting, message.value, true) } + break } case "getVSCodeSetting": - if (message.setting) { + const { setting } = message + + if (setting) { try { - const value = vscode.workspace.getConfiguration().get(message.setting) - await provider.postMessageToWebview({ type: "vsCodeSetting", setting: message.setting, value }) + await provider.postMessageToWebview({ + type: "vsCodeSetting", + setting, + value: vscode.workspace.getConfiguration().get(setting), + }) } catch (error) { console.error(`Failed to get VSCode setting ${message.setting}:`, error) + await provider.postMessageToWebview({ type: "vsCodeSetting", - setting: message.setting, + setting, error: `Failed to get setting: ${error.message}`, value: undefined, }) } } + break case "alwaysApproveResubmit": await updateGlobalState("alwaysApproveResubmit", message.bool ?? false) @@ -1290,68 +1301,3 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We } } } - -const generateSystemPrompt = async (provider: ClineProvider, message: WebviewMessage) => { - const { - apiConfiguration, - customModePrompts, - customInstructions, - browserViewportSize, - diffEnabled, - mcpEnabled, - fuzzyMatchThreshold, - experiments, - enableMcpServerCreation, - browserToolEnabled, - language, - } = await provider.getState() - - const diffStrategy = new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) - - const cwd = provider.cwd - - const mode = message.mode ?? defaultModeSlug - const customModes = await provider.customModesManager.getCustomModes() - - const rooIgnoreInstructions = provider.getCurrentCline()?.rooIgnoreController?.getInstructions() - - // Determine if browser tools can be used based on model support, mode, and user settings - let modelSupportsComputerUse = false - - // Create a temporary API handler to check if the model supports computer use - // This avoids relying on an active Cline instance which might not exist during preview - try { - const tempApiHandler = buildApiHandler(apiConfiguration) - modelSupportsComputerUse = tempApiHandler.getModel().info.supportsComputerUse ?? false - } catch (error) { - console.error("Error checking if model supports computer use:", error) - } - - // Check if the current mode includes the browser tool group - const modeConfig = getModeBySlug(mode, customModes) - const modeSupportsBrowser = modeConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false - - // Only enable browser tools if the model supports it, the mode includes browser tools, - // and browser tools are enabled in settings - const canUseBrowserTool = modelSupportsComputerUse && modeSupportsBrowser && (browserToolEnabled ?? true) - - const systemPrompt = await SYSTEM_PROMPT( - provider.context, - cwd, - canUseBrowserTool, - mcpEnabled ? provider.getMcpHub() : undefined, - diffStrategy, - browserViewportSize ?? "900x600", - mode, - customModePrompts, - customModes, - customInstructions, - diffEnabled, - experiments, - enableMcpServerCreation, - language, - rooIgnoreInstructions, - ) - - return systemPrompt -} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index adbafcdb24c..d118dd3989a 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -107,7 +107,6 @@ export interface ExtensionMessage { error?: string setting?: string value?: any - vscodeSettingValue?: unknown } export type ExtensionState = Pick< diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index ca5115a5497..34d996b7cf6 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -137,7 +137,6 @@ export interface WebviewMessage { images?: string[] bool?: boolean value?: number - vscodeSettingValue?: unknown commands?: string[] audioType?: AudioType serverName?: string diff --git a/webview-ui/src/components/settings/TerminalSettings.tsx b/webview-ui/src/components/settings/TerminalSettings.tsx index d155a600c90..3d1eeea0d6b 100644 --- a/webview-ui/src/components/settings/TerminalSettings.tsx +++ b/webview-ui/src/components/settings/TerminalSettings.tsx @@ -1,8 +1,11 @@ -import { HTMLAttributes, useState, useEffect } from "react" +import { HTMLAttributes, useState, useCallback } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" import { vscode } from "@/utils/vscode" import { SquareTerminal } from "lucide-react" import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" +import { useEvent, useMount } from "react-use" + +import { ExtensionMessage } from "@roo/shared/ExtensionMessage" import { cn } from "@/lib/utils" import { Slider } from "@/components/ui" @@ -51,31 +54,34 @@ export const TerminalSettings = ({ className, ...props }: TerminalSettingsProps) => { + const { t } = useAppTranslation() + const [inheritEnv, setInheritEnv] = useState(true) - useEffect(() => { - // Get initial value from VSCode configuration - vscode.postMessage({ type: "getVSCodeSetting", setting: "terminal.integrated.inheritEnv" }) - }, []) - useEffect(() => { - const handler = (event: MessageEvent<{ type: string; setting?: string; value?: boolean; error?: string }>) => { - const message = event.data - if (message.type === "vsCodeSetting" && message.setting === "terminal.integrated.inheritEnv") { - if (message.error) { - console.error("Failed to get terminal setting:", message.error) - } else { - setInheritEnv(message.value ?? true) + useMount(() => vscode.postMessage({ type: "getVSCodeSetting", setting: "terminal.integrated.inheritEnv" })) + + const onMessage = useCallback((event: MessageEvent) => { + const message: ExtensionMessage = event.data + + switch (message.type) { + case "vsCodeSetting": + switch (message.setting) { + case "terminal.integrated.inheritEnv": + setInheritEnv(message.value ?? true) + break + default: + break } - } + break + default: + break } - window.addEventListener("message", handler) - return () => window.removeEventListener("message", handler) }, []) - const { t } = useAppTranslation() + useEvent("message", onMessage) return ( -
+
@@ -86,11 +92,13 @@ export const TerminalSettings = ({
{/* Basic Settings */}
-
- -
{t("settings:terminal.basic.label")}
+
+
+ +
{t("settings:terminal.basic.label")}
+
-
+
{/* Advanced Settings */} -
-
- -
{t("settings:terminal.advanced.label")}
+
+
+
+ +
{t("settings:terminal.advanced.label")}
+
+
+ {t("settings:terminal.advanced.description")} +
-

- {t("settings:terminal.advanced.description")} -

-
+
+ {!terminalShellIntegrationDisabled && ( <>
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 8aa87ec2558..37ee6faa2b9 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Configuració del terminal: Avançada", - "description": "Les següents opcions poden requerir reiniciar el terminal per aplicar la configuració" + "description": "Les següents opcions poden requerir reiniciar el terminal per aplicar la configuració." }, "outputLineLimit": { "label": "Límit de sortida de terminal", @@ -336,7 +336,7 @@ }, "zdotdir": { "label": "Habilitar gestió de ZDOTDIR", - "description": "Quan està habilitat, crea un directori temporal per a ZDOTDIR per gestionar correctament la integració del shell zsh. Això assegura que la integració del shell de VSCode funcioni correctament amb zsh mentre es preserva la teva configuració de zsh. (experimental)" + "description": "Quan està habilitat, crea un directori temporal per a ZDOTDIR per gestionar correctament la integració del shell zsh. Això assegura que la integració del shell de VSCode funcioni correctament amb zsh mentre es preserva la teva configuració de zsh." }, "commandDelay": { "label": "Retard de comanda del terminal", @@ -352,11 +352,11 @@ }, "zshOhMy": { "label": "Habilita la integració Oh My Zsh", - "description": "Quan està habilitat, estableix ITERM_SHELL_INTEGRATION_INSTALLED=Yes per habilitar les característiques d'integració del shell Oh My Zsh. Aplicar aquesta configuració pot requerir reiniciar l'IDE. (experimental)" + "description": "Quan està habilitat, estableix ITERM_SHELL_INTEGRATION_INSTALLED=Yes per habilitar les característiques d'integració del shell Oh My Zsh. Aplicar aquesta configuració pot requerir reiniciar l'IDE." }, "zshP10k": { "label": "Habilita la integració Powerlevel10k", - "description": "Quan està habilitat, estableix POWERLEVEL9K_TERM_SHELL_INTEGRATION=true per habilitar les característiques d'integració del shell Powerlevel10k. (experimental)" + "description": "Quan està habilitat, estableix POWERLEVEL9K_TERM_SHELL_INTEGRATION=true per habilitar les característiques d'integració del shell Powerlevel10k." }, "inheritEnv": { "label": "Hereta variables d'entorn", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 407856cc036..8a8c006dfc1 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Terminal-Einstellungen: Erweitert", - "description": "Die folgenden Optionen erfordern möglicherweise einen Terminal-Neustart, um die Einstellung zu übernehmen" + "description": "Die folgenden Optionen erfordern möglicherweise einen Terminal-Neustart, um die Einstellung zu übernehmen." }, "outputLineLimit": { "label": "Terminal-Ausgabelimit", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 237664761e0..62f243b888c 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Terminal Settings: Advanced", - "description": "The following options may require a terminal restart to apply the setting" + "description": "The following options may require a terminal restart to apply the setting." }, "outputLineLimit": { "label": "Terminal output limit", @@ -348,15 +348,15 @@ }, "zshOhMy": { "label": "Enable Oh My Zsh integration", - "description": "When enabled, sets ITERM_SHELL_INTEGRATION_INSTALLED=Yes to enable Oh My Zsh shell integration features. Applying this setting might require restarting the IDE. (experimental)" + "description": "When enabled, sets ITERM_SHELL_INTEGRATION_INSTALLED=Yes to enable Oh My Zsh shell integration features. Applying this setting might require restarting the IDE." }, "zshP10k": { "label": "Enable Powerlevel10k integration", - "description": "When enabled, sets POWERLEVEL9K_TERM_SHELL_INTEGRATION=true to enable Powerlevel10k shell integration features. (experimental)" + "description": "When enabled, sets POWERLEVEL9K_TERM_SHELL_INTEGRATION=true to enable Powerlevel10k shell integration features." }, "zdotdir": { "label": "Enable ZDOTDIR handling", - "description": "When enabled, creates a temporary directory for ZDOTDIR to handle zsh shell integration properly. This ensures VSCode shell integration works correctly with zsh while preserving your zsh configuration. (experimental)" + "description": "When enabled, creates a temporary directory for ZDOTDIR to handle zsh shell integration properly. This ensures VSCode shell integration works correctly with zsh while preserving your zsh configuration." }, "inheritEnv": { "label": "Inherit environment variables", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 0d9d5cea718..406aa002d86 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Configuración del terminal: Avanzada", - "description": "Las siguientes opciones pueden requerir reiniciar el terminal para aplicar la configuración" + "description": "Las siguientes opciones pueden requerir reiniciar el terminal para aplicar la configuración." }, "outputLineLimit": { "label": "Límite de salida de terminal", @@ -336,7 +336,7 @@ }, "zdotdir": { "label": "Habilitar gestión de ZDOTDIR", - "description": "Cuando está habilitado, crea un directorio temporal para ZDOTDIR para manejar correctamente la integración del shell zsh. Esto asegura que la integración del shell de VSCode funcione correctamente con zsh mientras preserva tu configuración de zsh. (experimental)" + "description": "Cuando está habilitado, crea un directorio temporal para ZDOTDIR para manejar correctamente la integración del shell zsh. Esto asegura que la integración del shell de VSCode funcione correctamente con zsh mientras preserva tu configuración de zsh." }, "commandDelay": { "label": "Retraso de comando del terminal", @@ -352,11 +352,11 @@ }, "zshOhMy": { "label": "Habilitar integración Oh My Zsh", - "description": "Cuando está habilitado, establece ITERM_SHELL_INTEGRATION_INSTALLED=Yes para habilitar las características de integración del shell Oh My Zsh. Aplicar esta configuración puede requerir reiniciar el IDE. (experimental)" + "description": "Cuando está habilitado, establece ITERM_SHELL_INTEGRATION_INSTALLED=Yes para habilitar las características de integración del shell Oh My Zsh. Aplicar esta configuración puede requerir reiniciar el IDE." }, "zshP10k": { "label": "Habilitar integración Powerlevel10k", - "description": "Cuando está habilitado, establece POWERLEVEL9K_TERM_SHELL_INTEGRATION=true para habilitar las características de integración del shell Powerlevel10k. (experimental)" + "description": "Cuando está habilitado, establece POWERLEVEL9K_TERM_SHELL_INTEGRATION=true para habilitar las características de integración del shell Powerlevel10k." }, "inheritEnv": { "label": "Heredar variables de entorno", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 1bf17af9934..4ec79cce9c2 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Paramètres du terminal : Avancé", - "description": "Les options suivantes peuvent nécessiter un redémarrage du terminal pour appliquer le paramètre" + "description": "Les options suivantes peuvent nécessiter un redémarrage du terminal pour appliquer le paramètre." }, "outputLineLimit": { "label": "Limite de sortie du terminal", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 5927c92db23..a151ae5dab1 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Impostazioni terminale: Avanzate", - "description": "Le seguenti opzioni potrebbero richiedere il riavvio del terminale per applicare l'impostazione" + "description": "Le seguenti opzioni potrebbero richiedere il riavvio del terminale per applicare l'impostazione." }, "outputLineLimit": { "label": "Limite output terminale", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 25397b11d8b..1a1ac06e647 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Ustawienia terminala: Zaawansowane", - "description": "Poniższe opcje mogą wymagać ponownego uruchomienia terminala, aby zastosować ustawienie" + "description": "Poniższe opcje mogą wymagać ponownego uruchomienia terminala, aby zastosować ustawienie." }, "outputLineLimit": { "label": "Limit wyjścia terminala", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 35360788c9c..cf4cd9dada6 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Configurações do terminal: Avançadas", - "description": "As seguintes opções podem exigir reiniciar o terminal para aplicar a configuração" + "description": "As seguintes opções podem exigir reiniciar o terminal para aplicar a configuração." }, "outputLineLimit": { "label": "Limite de saída do terminal", @@ -336,7 +336,7 @@ }, "zdotdir": { "label": "Ativar gerenciamento do ZDOTDIR", - "description": "Quando ativado, cria um diretório temporário para o ZDOTDIR para lidar corretamente com a integração do shell zsh. Isso garante que a integração do shell do VSCode funcione corretamente com o zsh enquanto preserva sua configuração do zsh. (experimental)" + "description": "Quando ativado, cria um diretório temporário para o ZDOTDIR para lidar corretamente com a integração do shell zsh. Isso garante que a integração do shell do VSCode funcione corretamente com o zsh enquanto preserva sua configuração do zsh." }, "commandDelay": { "label": "Atraso de comando do terminal", @@ -352,11 +352,11 @@ }, "zshOhMy": { "label": "Ativar integração Oh My Zsh", - "description": "Quando ativado, define ITERM_SHELL_INTEGRATION_INSTALLED=Yes para habilitar os recursos de integração do shell Oh My Zsh. A aplicação desta configuração pode exigir a reinicialização do IDE. (experimental)" + "description": "Quando ativado, define ITERM_SHELL_INTEGRATION_INSTALLED=Yes para habilitar os recursos de integração do shell Oh My Zsh. A aplicação desta configuração pode exigir a reinicialização do IDE." }, "zshP10k": { "label": "Ativar integração Powerlevel10k", - "description": "Quando ativado, define POWERLEVEL9K_TERM_SHELL_INTEGRATION=true para habilitar os recursos de integração do shell Powerlevel10k. (experimental)" + "description": "Quando ativado, define POWERLEVEL9K_TERM_SHELL_INTEGRATION=true para habilitar os recursos de integração do shell Powerlevel10k." }, "inheritEnv": { "label": "Herdar variáveis de ambiente", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 4b82f266ad3..80aae23aac5 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Настройки терминала: Расширенные", - "description": "Следующие параметры могут потребовать перезапуск терминала для применения настроек" + "description": "Следующие параметры могут потребовать перезапуск терминала для применения настроек." }, "outputLineLimit": { "label": "Лимит вывода терминала", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index e92f14329d9..e5a1fc0375f 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Terminal Ayarları: Gelişmiş", - "description": "Aşağıdaki seçeneklerin uygulanması için terminalin yeniden başlatılması gerekebilir" + "description": "Aşağıdaki seçeneklerin uygulanması için terminalin yeniden başlatılması gerekebilir." }, "outputLineLimit": { "label": "Terminal çıktısı sınırı", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index b49c5fa0a2c..2c6c7f34bf3 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -316,7 +316,7 @@ }, "advanced": { "label": "Cài đặt Terminal: Nâng cao", - "description": "Các tùy chọn sau có thể yêu cầu khởi động lại terminal để áp dụng cài đặt" + "description": "Các tùy chọn sau có thể yêu cầu khởi động lại terminal để áp dụng cài đặt." }, "outputLineLimit": { "label": "Giới hạn đầu ra terminal", From 6666d43c7d58eb9c7ea1ff9cc1660283469b2cf2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 17:40:24 -0400 Subject: [PATCH 018/228] chore: Configure Renovate (#1771) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000000..5db72dd6a94 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} From 883be32b1a37f62f76117d62c2eb6f61f4999671 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 6 May 2025 02:16:27 +0400 Subject: [PATCH 019/228] feat: Add Groq and Chutes API providers (#3034) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: Chris Estreich --- src/api/index.ts | 6 + src/api/providers/__tests__/chutes.test.ts | 142 ++++++++++ src/api/providers/__tests__/groq.test.ts | 142 ++++++++++ .../base-openai-compatible-provider.ts | 129 +++++++++ src/api/providers/chutes.ts | 17 ++ src/api/providers/groq.ts | 17 ++ src/exports/roo-code.d.ts | 6 + src/exports/types.ts | 6 + src/i18n/locales/ca/common.json | 6 + src/i18n/locales/de/common.json | 6 + src/i18n/locales/es/common.json | 6 + src/i18n/locales/fr/common.json | 6 + src/i18n/locales/hi/common.json | 6 + src/i18n/locales/it/common.json | 6 + src/i18n/locales/ja/common.json | 6 + src/i18n/locales/ko/common.json | 6 + src/i18n/locales/pl/common.json | 6 + src/i18n/locales/pt-BR/common.json | 6 + src/i18n/locales/ru/common.json | 6 + src/i18n/locales/tr/common.json | 6 + src/i18n/locales/vi/common.json | 6 + src/i18n/locales/zh-CN/common.json | 6 + src/i18n/locales/zh-TW/common.json | 6 + src/schemas/index.ts | 14 + src/shared/api.ts | 245 ++++++++++++++++++ .../src/components/settings/ApiOptions.tsx | 43 +++ .../src/components/settings/constants.ts | 6 + .../components/ui/hooks/useSelectedModel.ts | 8 + webview-ui/src/i18n/locales/ca/settings.json | 4 + webview-ui/src/i18n/locales/de/settings.json | 4 + webview-ui/src/i18n/locales/en/settings.json | 4 + webview-ui/src/i18n/locales/es/settings.json | 4 + webview-ui/src/i18n/locales/fr/settings.json | 4 + webview-ui/src/i18n/locales/hi/settings.json | 4 + webview-ui/src/i18n/locales/it/settings.json | 4 + webview-ui/src/i18n/locales/ja/settings.json | 4 + webview-ui/src/i18n/locales/ko/settings.json | 4 + webview-ui/src/i18n/locales/pl/settings.json | 4 + .../src/i18n/locales/pt-BR/settings.json | 4 + webview-ui/src/i18n/locales/ru/settings.json | 4 + webview-ui/src/i18n/locales/tr/settings.json | 4 + webview-ui/src/i18n/locales/vi/settings.json | 4 + .../src/i18n/locales/zh-CN/settings.json | 4 + .../src/i18n/locales/zh-TW/settings.json | 4 + 44 files changed, 935 insertions(+) create mode 100644 src/api/providers/__tests__/chutes.test.ts create mode 100644 src/api/providers/__tests__/groq.test.ts create mode 100644 src/api/providers/base-openai-compatible-provider.ts create mode 100644 src/api/providers/chutes.ts create mode 100644 src/api/providers/groq.ts diff --git a/src/api/index.ts b/src/api/index.ts index 861ba59b99c..24007c66827 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -23,6 +23,8 @@ import { RequestyHandler } from "./providers/requesty" import { HumanRelayHandler } from "./providers/human-relay" import { FakeAIHandler } from "./providers/fake-ai" import { XAIHandler } from "./providers/xai" +import { GroqHandler } from "./providers/groq" +import { ChutesHandler } from "./providers/chutes" export interface SingleCompletionHandler { completePrompt(prompt: string): Promise @@ -88,6 +90,10 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { return new FakeAIHandler(options) case "xai": return new XAIHandler(options) + case "groq": + return new GroqHandler(options) + case "chutes": + return new ChutesHandler(options) default: return new AnthropicHandler(options) } diff --git a/src/api/providers/__tests__/chutes.test.ts b/src/api/providers/__tests__/chutes.test.ts new file mode 100644 index 00000000000..946aff96c88 --- /dev/null +++ b/src/api/providers/__tests__/chutes.test.ts @@ -0,0 +1,142 @@ +// npx jest src/api/providers/__tests__/chutes.test.ts + +import OpenAI from "openai" +import { Anthropic } from "@anthropic-ai/sdk" + +import { ChutesModelId, chutesDefaultModelId, chutesModels } from "../../../shared/api" + +import { ChutesHandler } from "../chutes" + +jest.mock("openai", () => { + const createMock = jest.fn() + return jest.fn(() => ({ chat: { completions: { create: createMock } } })) +}) + +describe("ChutesHandler", () => { + let handler: ChutesHandler + let mockCreate: jest.Mock + + beforeEach(() => { + jest.clearAllMocks() + mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create + handler = new ChutesHandler({}) + }) + + test("should use the correct Chutes base URL", () => { + new ChutesHandler({}) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://llm.chutes.ai/v1" })) + }) + + test("should use the provided API key", () => { + const chutesApiKey = "test-chutes-api-key" + new ChutesHandler({ chutesApiKey }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: chutesApiKey })) + }) + + test("should return default model when no model is specified", () => { + const model = handler.getModel() + expect(model.id).toBe(chutesDefaultModelId) + expect(model.info).toEqual(chutesModels[chutesDefaultModelId]) + }) + + test("should return specified model when valid model is provided", () => { + const testModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" + const handlerWithModel = new ChutesHandler({ apiModelId: testModelId }) + const model = handlerWithModel.getModel() + + expect(model.id).toBe(testModelId) + expect(model.info).toEqual(chutesModels[testModelId]) + }) + + test("completePrompt method should return text from Chutes API", async () => { + const expectedResponse = "This is a test response from Chutes" + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) + const result = await handler.completePrompt("test prompt") + expect(result).toBe(expectedResponse) + }) + + test("should handle errors in completePrompt", async () => { + const errorMessage = "Chutes API error" + mockCreate.mockRejectedValueOnce(new Error(errorMessage)) + await expect(handler.completePrompt("test prompt")).rejects.toThrow(`Chutes completion error: ${errorMessage}`) + }) + + test("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from Chutes stream" + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: testContent } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) + }) + + test("createMessage should yield usage data from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) + }) + + test("createMessage should pass correct parameters to Chutes client", async () => { + const modelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" + const modelInfo = chutesModels[modelId] + const handlerWithModel = new ChutesHandler({ apiModelId: modelId }) + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + async next() { + return { done: true } + }, + }), + } + }) + + const systemPrompt = "Test system prompt for Chutes" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Chutes" }] + + const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) + await messageGenerator.next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + max_tokens: modelInfo.maxTokens, + temperature: 0.5, + messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), + stream: true, + stream_options: { include_usage: true }, + }), + ) + }) +}) diff --git a/src/api/providers/__tests__/groq.test.ts b/src/api/providers/__tests__/groq.test.ts new file mode 100644 index 00000000000..6b38dcc9274 --- /dev/null +++ b/src/api/providers/__tests__/groq.test.ts @@ -0,0 +1,142 @@ +// npx jest src/api/providers/__tests__/groq.test.ts + +import OpenAI from "openai" +import { Anthropic } from "@anthropic-ai/sdk" + +import { GroqModelId, groqDefaultModelId, groqModels } from "../../../shared/api" + +import { GroqHandler } from "../groq" + +jest.mock("openai", () => { + const createMock = jest.fn() + return jest.fn(() => ({ chat: { completions: { create: createMock } } })) +}) + +describe("GroqHandler", () => { + let handler: GroqHandler + let mockCreate: jest.Mock + + beforeEach(() => { + jest.clearAllMocks() + mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create + handler = new GroqHandler({}) + }) + + test("should use the correct Groq base URL", () => { + new GroqHandler({}) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.groq.com/openai/v1" })) + }) + + test("should use the provided API key", () => { + const groqApiKey = "test-groq-api-key" + new GroqHandler({ groqApiKey }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: groqApiKey })) + }) + + test("should return default model when no model is specified", () => { + const model = handler.getModel() + expect(model.id).toBe(groqDefaultModelId) // Use groqDefaultModelId + expect(model.info).toEqual(groqModels[groqDefaultModelId]) // Use groqModels + }) + + test("should return specified model when valid model is provided", () => { + const testModelId: GroqModelId = "llama-3.3-70b-versatile" // Use a valid Groq model ID and type + const handlerWithModel = new GroqHandler({ apiModelId: testModelId }) // Instantiate GroqHandler + const model = handlerWithModel.getModel() + + expect(model.id).toBe(testModelId) + expect(model.info).toEqual(groqModels[testModelId]) // Use groqModels + }) + + test("completePrompt method should return text from Groq API", async () => { + const expectedResponse = "This is a test response from Groq" + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) + const result = await handler.completePrompt("test prompt") + expect(result).toBe(expectedResponse) + }) + + test("should handle errors in completePrompt", async () => { + const errorMessage = "Groq API error" + mockCreate.mockRejectedValueOnce(new Error(errorMessage)) + await expect(handler.completePrompt("test prompt")).rejects.toThrow(`Groq completion error: ${errorMessage}`) + }) + + test("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from Groq stream" + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: testContent } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) + }) + + test("createMessage should yield usage data from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) + }) + + test("createMessage should pass correct parameters to Groq client", async () => { + const modelId: GroqModelId = "llama-3.1-8b-instant" + const modelInfo = groqModels[modelId] + const handlerWithModel = new GroqHandler({ apiModelId: modelId }) + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + async next() { + return { done: true } + }, + }), + } + }) + + const systemPrompt = "Test system prompt for Groq" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Groq" }] + + const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) + await messageGenerator.next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + max_tokens: modelInfo.maxTokens, + temperature: 0.5, + messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), + stream: true, + stream_options: { include_usage: true }, + }), + ) + }) +}) diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts new file mode 100644 index 00000000000..82eeb830331 --- /dev/null +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -0,0 +1,129 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + +import { ApiHandlerOptions, ModelInfo } from "../../shared/api" +import { ApiStream } from "../transform/stream" +import { convertToOpenAiMessages } from "../transform/openai-format" + +import { SingleCompletionHandler } from "../index" +import { DEFAULT_HEADERS } from "./constants" +import { BaseProvider } from "./base-provider" + +type BaseOpenAiCompatibleProviderOptions = ApiHandlerOptions & { + providerName: string + baseURL: string + defaultProviderModelId: ModelName + providerModels: Record + defaultTemperature?: number +} + +export abstract class BaseOpenAiCompatibleProvider + extends BaseProvider + implements SingleCompletionHandler +{ + protected readonly providerName: string + protected readonly baseURL: string + protected readonly defaultTemperature: number + protected readonly defaultProviderModelId: ModelName + protected readonly providerModels: Record + + protected readonly options: ApiHandlerOptions + + private client: OpenAI + + constructor({ + providerName, + baseURL, + defaultProviderModelId, + providerModels, + defaultTemperature, + ...options + }: BaseOpenAiCompatibleProviderOptions) { + super() + + this.providerName = providerName + this.baseURL = baseURL + this.defaultProviderModelId = defaultProviderModelId + this.providerModels = providerModels + this.defaultTemperature = defaultTemperature ?? 0 + + this.options = options + + if (!this.options.apiKey) { + throw new Error("API key is required") + } + + this.client = new OpenAI({ + baseURL, + apiKey: this.options.apiKey, + defaultHeaders: DEFAULT_HEADERS, + }) + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const { + id: model, + info: { maxTokens: max_tokens }, + } = this.getModel() + + const temperature = this.options.modelTemperature ?? this.defaultTemperature + + const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { + model, + max_tokens, + temperature, + messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], + stream: true, + stream_options: { include_usage: true }, + } + + const stream = await this.client.chat.completions.create(params) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + + if (delta?.content) { + yield { + type: "text", + text: delta.content, + } + } + + if (chunk.usage) { + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + } + } + } + } + + async completePrompt(prompt: string): Promise { + const { id: modelId } = this.getModel() + + try { + const response = await this.client.chat.completions.create({ + model: modelId, + messages: [{ role: "user", content: prompt }], + }) + + return response.choices[0]?.message.content || "" + } catch (error) { + if (error instanceof Error) { + throw new Error(`${this.providerName} completion error: ${error.message}`) + } + + throw error + } + } + + override getModel() { + const id = + this.options.apiModelId && this.options.apiModelId in this.providerModels + ? (this.options.apiModelId as ModelName) + : this.defaultProviderModelId + + return { id, info: this.providerModels[id] } + } +} diff --git a/src/api/providers/chutes.ts b/src/api/providers/chutes.ts new file mode 100644 index 00000000000..6f7481f1809 --- /dev/null +++ b/src/api/providers/chutes.ts @@ -0,0 +1,17 @@ +import { ApiHandlerOptions, ChutesModelId, chutesDefaultModelId, chutesModels } from "../../shared/api" + +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + +export class ChutesHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + super({ + ...options, + providerName: "Chutes", + baseURL: "https://llm.chutes.ai/v1", + apiKey: options.chutesApiKey, + defaultProviderModelId: chutesDefaultModelId, + providerModels: chutesModels, + defaultTemperature: 0.5, + }) + } +} diff --git a/src/api/providers/groq.ts b/src/api/providers/groq.ts new file mode 100644 index 00000000000..2f4e763b8e5 --- /dev/null +++ b/src/api/providers/groq.ts @@ -0,0 +1,17 @@ +import { ApiHandlerOptions, GroqModelId, groqDefaultModelId, groqModels } from "../../shared/api" // Updated imports for Groq + +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + +export class GroqHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + super({ + ...options, + providerName: "Groq", + baseURL: "https://api.groq.com/openai/v1", + apiKey: options.groqApiKey, + defaultProviderModelId: groqDefaultModelId, + providerModels: groqModels, + defaultTemperature: 0.5, + }) + } +} diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 386c983cc88..8a7452dfb8d 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -21,6 +21,8 @@ type ProviderSettings = { | "human-relay" | "fake-ai" | "xai" + | "groq" + | "chutes" ) | undefined apiModelId?: string | undefined @@ -120,6 +122,8 @@ type ProviderSettings = { requestyApiKey?: string | undefined requestyModelId?: string | undefined xaiApiKey?: string | undefined + groqApiKey?: string | undefined + chutesApiKey?: string | undefined modelMaxTokens?: number | undefined modelMaxThinkingTokens?: number | undefined includeMaxTokens?: boolean | undefined @@ -158,6 +162,8 @@ type GlobalSettings = { | "human-relay" | "fake-ai" | "xai" + | "groq" + | "chutes" ) | undefined }[] diff --git a/src/exports/types.ts b/src/exports/types.ts index 330f9c8c21e..b3d441e9ab6 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -22,6 +22,8 @@ type ProviderSettings = { | "human-relay" | "fake-ai" | "xai" + | "groq" + | "chutes" ) | undefined apiModelId?: string | undefined @@ -121,6 +123,8 @@ type ProviderSettings = { requestyApiKey?: string | undefined requestyModelId?: string | undefined xaiApiKey?: string | undefined + groqApiKey?: string | undefined + chutesApiKey?: string | undefined modelMaxTokens?: number | undefined modelMaxThinkingTokens?: number | undefined includeMaxTokens?: boolean | undefined @@ -161,6 +165,8 @@ type GlobalSettings = { | "human-relay" | "fake-ai" | "xai" + | "groq" + | "chutes" ) | undefined }[] diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 633b90ec3d0..2bd61dfd930 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -89,5 +89,11 @@ "path_placeholder": "D:\\RooCodeStorage", "enter_absolute_path": "Introdueix una ruta completa (p. ex. D:\\RooCodeStorage o /home/user/storage)", "enter_valid_path": "Introdueix una ruta vàlida" + }, + "settings": { + "providers": { + "groqApiKey": "Clau API de Groq", + "getGroqApiKey": "Obté la clau API de Groq" + } } } diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index ceda64bad71..5eb6b05e8ff 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Was soll Roo tun?", "task_placeholder": "Gib deine Aufgabe hier ein" + }, + "settings": { + "providers": { + "groqApiKey": "Groq API-Schlüssel", + "getGroqApiKey": "Groq API-Schlüssel erhalten" + } } } diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 2bfb43055a8..beea1ba3a9d 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "¿Qué debe hacer Roo?", "task_placeholder": "Escribe tu tarea aquí" + }, + "settings": { + "providers": { + "groqApiKey": "Clave API de Groq", + "getGroqApiKey": "Obtener clave API de Groq" + } } } diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 7399432c6a8..c34d0105a44 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Que doit faire Roo ?", "task_placeholder": "Écris ta tâche ici" + }, + "settings": { + "providers": { + "groqApiKey": "Clé API Groq", + "getGroqApiKey": "Obtenir la clé API Groq" + } } } diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index bf5421eff8f..07438c873b1 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Roo को क्या करना है?", "task_placeholder": "अपना कार्य यहाँ लिखें" + }, + "settings": { + "providers": { + "groqApiKey": "ग्रोक एपीआई कुंजी", + "getGroqApiKey": "ग्रोक एपीआई कुंजी प्राप्त करें" + } } } diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 69e2c2123f1..476285169f5 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Cosa deve fare Roo?", "task_placeholder": "Scrivi il tuo compito qui" + }, + "settings": { + "providers": { + "groqApiKey": "Chiave API Groq", + "getGroqApiKey": "Ottieni chiave API Groq" + } } } diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 6f40c8e03d5..f44469d0c90 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Rooにどんなことをさせますか?", "task_placeholder": "タスクをここに入力してください" + }, + "settings": { + "providers": { + "groqApiKey": "Groq APIキー", + "getGroqApiKey": "Groq APIキーを取得" + } } } diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 9026315da2e..944d9ba19b8 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Roo에게 무엇을 시킬까요?", "task_placeholder": "여기에 작업을 입력하세요" + }, + "settings": { + "providers": { + "groqApiKey": "Groq API 키", + "getGroqApiKey": "Groq API 키 받기" + } } } diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 49f51cefff1..46ed243b496 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Co ma zrobić Roo?", "task_placeholder": "Wpisz swoje zadanie tutaj" + }, + "settings": { + "providers": { + "groqApiKey": "Klucz API Groq", + "getGroqApiKey": "Uzyskaj klucz API Groq" + } } } diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 80112a91ab8..a6588b2fdad 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -89,5 +89,11 @@ "path_placeholder": "D:\\RooCodeStorage", "enter_absolute_path": "Por favor, digite um caminho absoluto (ex: D:\\RooCodeStorage ou /home/user/storage)", "enter_valid_path": "Por favor, digite um caminho válido" + }, + "settings": { + "providers": { + "groqApiKey": "Chave de API Groq", + "getGroqApiKey": "Obter chave de API Groq" + } } } diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 80829e138ce..baef76ea78c 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Что должен сделать Roo?", "task_placeholder": "Введите вашу задачу здесь" + }, + "settings": { + "providers": { + "groqApiKey": "Ключ API Groq", + "getGroqApiKey": "Получить ключ API Groq" + } } } diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 61b8e12fb5a..2c4ec8b3548 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Roo ne yapsın?", "task_placeholder": "Görevini buraya yaz" + }, + "settings": { + "providers": { + "groqApiKey": "Groq API Anahtarı", + "getGroqApiKey": "Groq API Anahtarı Al" + } } } diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 8945e9e098e..499309df750 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "Bạn muốn Roo làm gì?", "task_placeholder": "Nhập nhiệm vụ của bạn ở đây" + }, + "settings": { + "providers": { + "groqApiKey": "Khóa API Groq", + "getGroqApiKey": "Lấy khóa API Groq" + } } } diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 2fc49c9b378..ac3754ccd64 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "让Roo做什么?", "task_placeholder": "在这里输入任务" + }, + "settings": { + "providers": { + "groqApiKey": "Groq API 密钥", + "getGroqApiKey": "获取 Groq API 密钥" + } } } diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index a51cfa0e9a4..93c59acbbbb 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -89,5 +89,11 @@ "input": { "task_prompt": "讓 Roo 做什麼?", "task_placeholder": "在這裡輸入工作" + }, + "settings": { + "providers": { + "groqApiKey": "Groq API 金鑰", + "getGroqApiKey": "取得 Groq API 金鑰" + } } } diff --git a/src/schemas/index.ts b/src/schemas/index.ts index c6e34fe3955..e69fff6dcc8 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -29,6 +29,8 @@ export const providerNames = [ "human-relay", "fake-ai", "xai", + "groq", + "chutes", ] as const export const providerNamesSchema = z.enum(providerNames) @@ -423,6 +425,10 @@ export const providerSettingsSchema = z.object({ requestyModelId: z.string().optional(), // X.AI (Grok) xaiApiKey: z.string().optional(), + // Groq + groqApiKey: z.string().optional(), + // Chutes AI + chutesApiKey: z.string().optional(), // Claude 3.7 Sonnet Thinking modelMaxTokens: z.number().optional(), modelMaxThinkingTokens: z.number().optional(), @@ -529,6 +535,10 @@ const providerSettingsRecord: ProviderSettingsRecord = { fakeAi: undefined, // X.AI (Grok) xaiApiKey: undefined, + // Groq + groqApiKey: undefined, + // Chutes AI + chutesApiKey: undefined, } export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Keys[] @@ -721,6 +731,8 @@ export type SecretState = Pick< | "unboundApiKey" | "requestyApiKey" | "xaiApiKey" + | "groqApiKey" + | "chutesApiKey" > type SecretStateRecord = Record, undefined> @@ -740,6 +752,8 @@ const secretStateRecord: SecretStateRecord = { unboundApiKey: undefined, requestyApiKey: undefined, xaiApiKey: undefined, + groqApiKey: undefined, + chutesApiKey: undefined, } export const SECRET_STATE_KEYS = Object.keys(secretStateRecord) as Keys[] diff --git a/src/shared/api.ts b/src/shared/api.ts index 17bff9db47e..0acb82d45e4 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1400,6 +1400,251 @@ export const vscodeLlmModels = { } > +// Groq +// https://console.groq.com/docs/models +export type GroqModelId = + | "llama-3.1-8b-instant" + | "llama-3.3-70b-versatile" + | "meta-llama/llama-4-scout-17b-16e-instruct" + | "meta-llama/llama-4-maverick-17b-128e-instruct" + | "mistral-saba-24b" + | "qwen-qwq-32b" + | "deepseek-r1-distill-llama-70b" +export const groqDefaultModelId: GroqModelId = "llama-3.3-70b-versatile" // Defaulting to Llama3 70B Versatile +export const groqModels = { + // Models based on API response: https://api.groq.com/openai/v1/models + "llama-3.1-8b-instant": { + maxTokens: 131072, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Meta Llama 3.1 8B Instant model, 128K context.", + }, + "llama-3.3-70b-versatile": { + maxTokens: 32768, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Meta Llama 3.3 70B Versatile model, 128K context.", + }, + "meta-llama/llama-4-scout-17b-16e-instruct": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Meta Llama 4 Scout 17B Instruct model, 128K context.", + }, + "meta-llama/llama-4-maverick-17b-128e-instruct": { + maxTokens: 8192, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Meta Llama 4 Maverick 17B Instruct model, 128K context.", + }, + "mistral-saba-24b": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Mistral Saba 24B model, 32K context.", + }, + "qwen-qwq-32b": { + maxTokens: 131072, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Alibaba Qwen QwQ 32B model, 128K context.", + }, + "deepseek-r1-distill-llama-70b": { + maxTokens: 131072, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 Distill Llama 70B model, 128K context.", + }, +} as const satisfies Record + +// Chutes AI +// https://llm.chutes.ai/v1 (OpenAI compatible) +export type ChutesModelId = + | "deepseek-ai/DeepSeek-R1" + | "deepseek-ai/DeepSeek-V3" + | "unsloth/Llama-3.3-70B-Instruct" + | "chutesai/Llama-4-Scout-17B-16E-Instruct" + | "unsloth/Mistral-Nemo-Instruct-2407" + | "unsloth/gemma-3-12b-it" + | "NousResearch/DeepHermes-3-Llama-3-8B-Preview" + | "unsloth/gemma-3-4b-it" + | "nvidia/Llama-3_3-Nemotron-Super-49B-v1" + | "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1" + | "chutesai/Llama-4-Maverick-17B-128E-Instruct-FP8" + | "deepseek-ai/DeepSeek-V3-Base" + | "deepseek-ai/DeepSeek-R1-Zero" + | "deepseek-ai/DeepSeek-V3-0324" + | "microsoft/MAI-DS-R1-FP8" + | "tngtech/DeepSeek-R1T-Chimera" +export const chutesDefaultModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" +export const chutesModels = { + "deepseek-ai/DeepSeek-R1": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 model.", + }, + "deepseek-ai/DeepSeek-V3": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek V3 model.", + }, + "unsloth/Llama-3.3-70B-Instruct": { + maxTokens: 32768, // From Groq + contextWindow: 131072, // From Groq + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Llama 3.3 70B Instruct model.", + }, + "chutesai/Llama-4-Scout-17B-16E-Instruct": { + maxTokens: 32768, + contextWindow: 512000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "ChutesAI Llama 4 Scout 17B Instruct model, 512K context.", + }, + "unsloth/Mistral-Nemo-Instruct-2407": { + maxTokens: 32768, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Mistral Nemo Instruct model.", + }, + "unsloth/gemma-3-12b-it": { + maxTokens: 32768, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 3 12B IT model.", + }, + "NousResearch/DeepHermes-3-Llama-3-8B-Preview": { + maxTokens: 32768, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Nous DeepHermes 3 Llama 3 8B Preview model.", + }, + "unsloth/gemma-3-4b-it": { + maxTokens: 32768, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 3 4B IT model.", + }, + "nvidia/Llama-3_3-Nemotron-Super-49B-v1": { + maxTokens: 32768, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Nvidia Llama 3.3 Nemotron Super 49B model.", + }, + "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1": { + maxTokens: 32768, + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Nvidia Llama 3.1 Nemotron Ultra 253B model.", + }, + "chutesai/Llama-4-Maverick-17B-128E-Instruct-FP8": { + maxTokens: 32768, + contextWindow: 256000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "ChutesAI Llama 4 Maverick 17B Instruct FP8 model.", + }, + "deepseek-ai/DeepSeek-V3-Base": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek V3 Base model.", + }, + "deepseek-ai/DeepSeek-R1-Zero": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 Zero model.", + }, + "deepseek-ai/DeepSeek-V3-0324": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek V3 (0324) model.", + }, + "microsoft/MAI-DS-R1-FP8": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Microsoft MAI-DS-R1 FP8 model.", + }, + "tngtech/DeepSeek-R1T-Chimera": { + maxTokens: 32768, + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "TNGTech DeepSeek R1T Chimera model.", + }, +} as const satisfies Record + /** * Constants */ diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 2b0d363221d..5f2d50b94c9 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1613,6 +1613,49 @@ const ApiOptions = ({ )} + {selectedProvider === "groq" && ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.groqApiKey && ( + + {t("settings:providers.getGroqApiKey")} + + )} + + )} + + {selectedProvider === "chutes" && ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {/* Add a link to get Chutes API key if available */} + {/* {!apiConfiguration?.chutesApiKey && ( + + {t("settings:providers.getChutesApiKey")} + + )} */} + + )} + {selectedProvider === "unbound" && ( <> a.label.localeCompare(b.label)) export const VERTEX_REGIONS = [ diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 6b5b01b9186..b02a4d024b0 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -19,6 +19,10 @@ import { vertexModels, xaiDefaultModelId, xaiModels, + groqModels, + groqDefaultModelId, + chutesModels, + chutesDefaultModelId, vscodeLlmModels, vscodeLlmDefaultModelId, openRouterDefaultModelId, @@ -84,6 +88,10 @@ function getSelectedModelInfo({ return routerModels.unbound[id] ?? routerModels.unbound[unboundDefaultModelId] case "xai": return xaiModels[id as keyof typeof xaiModels] ?? xaiModels[xaiDefaultModelId] + case "groq": + return groqModels[id as keyof typeof groqModels] ?? groqModels[groqDefaultModelId] + case "chutes": + return chutesModels[id as keyof typeof chutesModels] ?? chutesModels[chutesDefaultModelId] case "bedrock": // Special case for custom ARN. if (id === "custom-arn") { diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 37ee6faa2b9..ca7546b2a5f 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Clau API d'Anthropic", "getAnthropicApiKey": "Obtenir clau API d'Anthropic", "anthropicUseAuthToken": "Passar la clau API d'Anthropic com a capçalera d'autorització en lloc de X-Api-Key", + "chutesApiKey": "Clau API de Chutes", + "getChutesApiKey": "Obtenir clau API de Chutes", "deepSeekApiKey": "Clau API de DeepSeek", "getDeepSeekApiKey": "Obtenir clau API de DeepSeek", "geminiApiKey": "Clau API de Gemini", + "getGroqApiKey": "Obtenir clau API de Groq", + "groqApiKey": "Clau API de Groq", "getGeminiApiKey": "Obtenir clau API de Gemini", "openAiApiKey": "Clau API d'OpenAI", "openAiBaseUrl": "URL base", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 8a8c006dfc1..db4398402fa 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -123,10 +123,14 @@ "anthropicApiKey": "Anthropic API-Schlüssel", "getAnthropicApiKey": "Anthropic API-Schlüssel erhalten", "anthropicUseAuthToken": "Anthropic API-Schlüssel als Authorization-Header anstelle von X-Api-Key übergeben", + "chutesApiKey": "Chutes API-Schlüssel", + "getChutesApiKey": "Chutes API-Schlüssel erhalten", "deepSeekApiKey": "DeepSeek API-Schlüssel", "getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten", "geminiApiKey": "Gemini API-Schlüssel", "getGeminiApiKey": "Gemini API-Schlüssel erhalten", + "getGroqApiKey": "Groq API-Schlüssel erhalten", + "groqApiKey": "Groq API-Schlüssel", "openAiApiKey": "OpenAI API-Schlüssel", "openAiBaseUrl": "Basis-URL", "getOpenAiApiKey": "OpenAI API-Schlüssel erhalten", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 62f243b888c..b817318df65 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API Key", "getAnthropicApiKey": "Get Anthropic API Key", "anthropicUseAuthToken": "Pass Anthropic API Key as Authorization header instead of X-Api-Key", + "chutesApiKey": "Chutes API Key", + "getChutesApiKey": "Get Chutes API Key", "deepSeekApiKey": "DeepSeek API Key", "getDeepSeekApiKey": "Get DeepSeek API Key", "geminiApiKey": "Gemini API Key", + "getGroqApiKey": "Get Groq API Key", + "groqApiKey": "Groq API Key", "getGeminiApiKey": "Get Gemini API Key", "openAiApiKey": "OpenAI API Key", "openAiBaseUrl": "Base URL", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 406aa002d86..4a7191ffeb4 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Clave API de Anthropic", "getAnthropicApiKey": "Obtener clave API de Anthropic", "anthropicUseAuthToken": "Pasar la clave API de Anthropic como encabezado de autorización en lugar de X-Api-Key", + "chutesApiKey": "Clave API de Chutes", + "getChutesApiKey": "Obtener clave API de Chutes", "deepSeekApiKey": "Clave API de DeepSeek", "getDeepSeekApiKey": "Obtener clave API de DeepSeek", "geminiApiKey": "Clave API de Gemini", + "getGroqApiKey": "Obtener clave API de Groq", + "groqApiKey": "Clave API de Groq", "getGeminiApiKey": "Obtener clave API de Gemini", "openAiApiKey": "Clave API de OpenAI", "openAiBaseUrl": "URL base", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 4ec79cce9c2..b2a1ef33e45 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Clé API Anthropic", "getAnthropicApiKey": "Obtenir la clé API Anthropic", "anthropicUseAuthToken": "Passer la clé API Anthropic comme en-tête d'autorisation au lieu de X-Api-Key", + "chutesApiKey": "Clé API Chutes", + "getChutesApiKey": "Obtenir la clé API Chutes", "deepSeekApiKey": "Clé API DeepSeek", "getDeepSeekApiKey": "Obtenir la clé API DeepSeek", "geminiApiKey": "Clé API Gemini", + "getGroqApiKey": "Obtenir la clé API Groq", + "groqApiKey": "Clé API Groq", "getGeminiApiKey": "Obtenir la clé API Gemini", "openAiApiKey": "Clé API OpenAI", "openAiBaseUrl": "URL de base", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 597c4ddceaa..f62d6373564 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API कुंजी", "getAnthropicApiKey": "Anthropic API कुंजी प्राप्त करें", "anthropicUseAuthToken": "X-Api-Key के बजाय Anthropic API कुंजी को Authorization हेडर के रूप में पास करें", + "chutesApiKey": "Chutes API कुंजी", + "getChutesApiKey": "Chutes API कुंजी प्राप्त करें", "deepSeekApiKey": "DeepSeek API कुंजी", "getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें", "geminiApiKey": "Gemini API कुंजी", + "getGroqApiKey": "Groq API कुंजी प्राप्त करें", + "groqApiKey": "Groq API कुंजी", "getGeminiApiKey": "Gemini API कुंजी प्राप्त करें", "openAiApiKey": "OpenAI API कुंजी", "openAiBaseUrl": "बेस URL", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index a151ae5dab1..1c20c2d5957 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Chiave API Anthropic", "getAnthropicApiKey": "Ottieni chiave API Anthropic", "anthropicUseAuthToken": "Passa la chiave API Anthropic come header di autorizzazione invece di X-Api-Key", + "chutesApiKey": "Chiave API Chutes", + "getChutesApiKey": "Ottieni chiave API Chutes", "deepSeekApiKey": "Chiave API DeepSeek", "getDeepSeekApiKey": "Ottieni chiave API DeepSeek", "geminiApiKey": "Chiave API Gemini", + "getGroqApiKey": "Ottieni chiave API Groq", + "groqApiKey": "Chiave API Groq", "getGeminiApiKey": "Ottieni chiave API Gemini", "openAiApiKey": "Chiave API OpenAI", "openAiBaseUrl": "URL base", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 1d027d93a55..1ed0ab7c269 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic APIキー", "getAnthropicApiKey": "Anthropic APIキーを取得", "anthropicUseAuthToken": "Anthropic APIキーをX-Api-Keyの代わりにAuthorizationヘッダーとして渡す", + "chutesApiKey": "Chutes APIキー", + "getChutesApiKey": "Chutes APIキーを取得", "deepSeekApiKey": "DeepSeek APIキー", "getDeepSeekApiKey": "DeepSeek APIキーを取得", "geminiApiKey": "Gemini APIキー", + "getGroqApiKey": "Groq APIキーを取得", + "groqApiKey": "Groq APIキー", "getGeminiApiKey": "Gemini APIキーを取得", "openAiApiKey": "OpenAI APIキー", "openAiBaseUrl": "ベースURL", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 0d5b884cef8..b0ceaee5304 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API 키", "getAnthropicApiKey": "Anthropic API 키 받기", "anthropicUseAuthToken": "X-Api-Key 대신 Authorization 헤더로 Anthropic API 키 전달", + "chutesApiKey": "Chutes API 키", + "getChutesApiKey": "Chutes API 키 받기", "deepSeekApiKey": "DeepSeek API 키", "getDeepSeekApiKey": "DeepSeek API 키 받기", "geminiApiKey": "Gemini API 키", + "getGroqApiKey": "Groq API 키 받기", + "groqApiKey": "Groq API 키", "getGeminiApiKey": "Gemini API 키 받기", "openAiApiKey": "OpenAI API 키", "openAiBaseUrl": "기본 URL", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 1a1ac06e647..c4f28f19f61 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Klucz API Anthropic", "getAnthropicApiKey": "Uzyskaj klucz API Anthropic", "anthropicUseAuthToken": "Przekaż klucz API Anthropic jako nagłówek Authorization zamiast X-Api-Key", + "chutesApiKey": "Klucz API Chutes", + "getChutesApiKey": "Uzyskaj klucz API Chutes", "deepSeekApiKey": "Klucz API DeepSeek", "getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek", "geminiApiKey": "Klucz API Gemini", + "getGroqApiKey": "Uzyskaj klucz API Groq", + "groqApiKey": "Klucz API Groq", "getGeminiApiKey": "Uzyskaj klucz API Gemini", "openAiApiKey": "Klucz API OpenAI", "openAiBaseUrl": "URL bazowy", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index cf4cd9dada6..76b3f597d29 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Chave de API Anthropic", "getAnthropicApiKey": "Obter chave de API Anthropic", "anthropicUseAuthToken": "Passar a chave de API Anthropic como cabeçalho Authorization em vez de X-Api-Key", + "chutesApiKey": "Chave de API Chutes", + "getChutesApiKey": "Obter chave de API Chutes", "deepSeekApiKey": "Chave de API DeepSeek", "getDeepSeekApiKey": "Obter chave de API DeepSeek", "geminiApiKey": "Chave de API Gemini", + "getGroqApiKey": "Obter chave de API Groq", + "groqApiKey": "Chave de API Groq", "getGeminiApiKey": "Obter chave de API Gemini", "openAiApiKey": "Chave de API OpenAI", "openAiBaseUrl": "URL Base", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 80aae23aac5..f46d1d98374 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API-ключ", "getAnthropicApiKey": "Получить Anthropic API-ключ", "anthropicUseAuthToken": "Передавать Anthropic API-ключ как Authorization-заголовок вместо X-Api-Key", + "chutesApiKey": "Chutes API-ключ", + "getChutesApiKey": "Получить Chutes API-ключ", "deepSeekApiKey": "DeepSeek API-ключ", "getDeepSeekApiKey": "Получить DeepSeek API-ключ", "geminiApiKey": "Gemini API-ключ", + "getGroqApiKey": "Получить Groq API-ключ", + "groqApiKey": "Groq API-ключ", "getGeminiApiKey": "Получить Gemini API-ключ", "openAiApiKey": "OpenAI API-ключ", "openAiBaseUrl": "Базовый URL", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index e5a1fc0375f..580e35ee043 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API Anahtarı", "getAnthropicApiKey": "Anthropic API Anahtarı Al", "anthropicUseAuthToken": "Anthropic API Anahtarını X-Api-Key yerine Authorization başlığı olarak geçir", + "chutesApiKey": "Chutes API Anahtarı", + "getChutesApiKey": "Chutes API Anahtarı Al", "deepSeekApiKey": "DeepSeek API Anahtarı", "getDeepSeekApiKey": "DeepSeek API Anahtarı Al", "geminiApiKey": "Gemini API Anahtarı", + "getGroqApiKey": "Groq API Anahtarı Al", + "groqApiKey": "Groq API Anahtarı", "getGeminiApiKey": "Gemini API Anahtarı Al", "openAiApiKey": "OpenAI API Anahtarı", "openAiBaseUrl": "Temel URL", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 2c6c7f34bf3..51174c4d4f4 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -122,9 +122,13 @@ "anthropicApiKey": "Khóa API Anthropic", "getAnthropicApiKey": "Lấy khóa API Anthropic", "anthropicUseAuthToken": "Truyền khóa API Anthropic dưới dạng tiêu đề Authorization thay vì X-Api-Key", + "chutesApiKey": "Khóa API Chutes", + "getChutesApiKey": "Lấy khóa API Chutes", "deepSeekApiKey": "Khóa API DeepSeek", "getDeepSeekApiKey": "Lấy khóa API DeepSeek", "geminiApiKey": "Khóa API Gemini", + "getGroqApiKey": "Lấy khóa API Groq", + "groqApiKey": "Khóa API Groq", "getGeminiApiKey": "Lấy khóa API Gemini", "openAiApiKey": "Khóa API OpenAI", "openAiBaseUrl": "URL cơ sở", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index b9f32090e3e..6a706361e2f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API 密钥", "getAnthropicApiKey": "获取 Anthropic API 密钥", "anthropicUseAuthToken": "将 Anthropic API 密钥作为 Authorization 标头传递,而不是 X-Api-Key", + "chutesApiKey": "Chutes API 密钥", + "getChutesApiKey": "获取 Chutes API 密钥", "deepSeekApiKey": "DeepSeek API 密钥", "getDeepSeekApiKey": "获取 DeepSeek API 密钥", "geminiApiKey": "Gemini API 密钥", + "getGroqApiKey": "获取 Groq API 密钥", + "groqApiKey": "Groq API 密钥", "getGeminiApiKey": "获取 Gemini API 密钥", "openAiApiKey": "OpenAI API 密钥", "openAiBaseUrl": "OpenAI 基础 URL", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 508df804ac1..75afc650dd6 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -123,9 +123,13 @@ "anthropicApiKey": "Anthropic API 金鑰", "getAnthropicApiKey": "取得 Anthropic API 金鑰", "anthropicUseAuthToken": "將 Anthropic API 金鑰作為 Authorization 標頭傳遞,而非使用 X-Api-Key", + "chutesApiKey": "Chutes API 金鑰", + "getChutesApiKey": "取得 Chutes API 金鑰", "deepSeekApiKey": "DeepSeek API 金鑰", "getDeepSeekApiKey": "取得 DeepSeek API 金鑰", "geminiApiKey": "Gemini API 金鑰", + "getGroqApiKey": "取得 Groq API 金鑰", + "groqApiKey": "Groq API 金鑰", "getGeminiApiKey": "取得 Gemini API 金鑰", "openAiApiKey": "OpenAI API 金鑰", "openAiBaseUrl": "基礎 URL", From da6c50bf104ce4954a1a235c620e5f673f9a90c3 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 5 May 2025 15:53:01 -0700 Subject: [PATCH 020/228] Organize provider settings into separate components (#3196) --- .changeset/five-pigs-talk.md | 5 + src/api/providers/__tests__/chutes.test.ts | 9 +- src/api/providers/__tests__/groq.test.ts | 17 +- .../src/components/settings/ApiOptions.tsx | 1231 +---------------- .../settings/providers/Anthropic.tsx | 84 ++ .../components/settings/providers/Bedrock.tsx | 126 ++ .../components/settings/providers/Gemini.tsx | 77 ++ .../settings/providers/LMStudio.tsx | 152 ++ .../components/settings/providers/Ollama.tsx | 86 ++ .../components/settings/providers/OpenAI.tsx | 77 ++ .../settings/providers/OpenAICompatible.tsx | 578 ++++++++ .../settings/providers/OpenRouter.tsx | 109 ++ .../OpenRouterBalanceDisplay.tsx | 0 .../settings/providers/VSCodeLM.tsx | 86 ++ .../components/settings/providers/Vertex.tsx | 97 ++ .../src/components/settings/transforms.ts | 3 + 16 files changed, 1552 insertions(+), 1185 deletions(-) create mode 100644 .changeset/five-pigs-talk.md create mode 100644 webview-ui/src/components/settings/providers/Anthropic.tsx create mode 100644 webview-ui/src/components/settings/providers/Bedrock.tsx create mode 100644 webview-ui/src/components/settings/providers/Gemini.tsx create mode 100644 webview-ui/src/components/settings/providers/LMStudio.tsx create mode 100644 webview-ui/src/components/settings/providers/Ollama.tsx create mode 100644 webview-ui/src/components/settings/providers/OpenAI.tsx create mode 100644 webview-ui/src/components/settings/providers/OpenAICompatible.tsx create mode 100644 webview-ui/src/components/settings/providers/OpenRouter.tsx rename webview-ui/src/components/settings/{ => providers}/OpenRouterBalanceDisplay.tsx (100%) create mode 100644 webview-ui/src/components/settings/providers/VSCodeLM.tsx create mode 100644 webview-ui/src/components/settings/providers/Vertex.tsx create mode 100644 webview-ui/src/components/settings/transforms.ts diff --git a/.changeset/five-pigs-talk.md b/.changeset/five-pigs-talk.md new file mode 100644 index 00000000000..8ab089ce408 --- /dev/null +++ b/.changeset/five-pigs-talk.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Organize provider settings into separate components diff --git a/src/api/providers/__tests__/chutes.test.ts b/src/api/providers/__tests__/chutes.test.ts index 946aff96c88..63af600be79 100644 --- a/src/api/providers/__tests__/chutes.test.ts +++ b/src/api/providers/__tests__/chutes.test.ts @@ -19,11 +19,11 @@ describe("ChutesHandler", () => { beforeEach(() => { jest.clearAllMocks() mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create - handler = new ChutesHandler({}) + handler = new ChutesHandler({ chutesApiKey: "test-chutes-api-key" }) }) test("should use the correct Chutes base URL", () => { - new ChutesHandler({}) + new ChutesHandler({ chutesApiKey: "test-chutes-api-key" }) expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://llm.chutes.ai/v1" })) }) @@ -41,9 +41,8 @@ describe("ChutesHandler", () => { test("should return specified model when valid model is provided", () => { const testModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" - const handlerWithModel = new ChutesHandler({ apiModelId: testModelId }) + const handlerWithModel = new ChutesHandler({ apiModelId: testModelId, chutesApiKey: "test-chutes-api-key" }) const model = handlerWithModel.getModel() - expect(model.id).toBe(testModelId) expect(model.info).toEqual(chutesModels[testModelId]) }) @@ -110,7 +109,7 @@ describe("ChutesHandler", () => { test("createMessage should pass correct parameters to Chutes client", async () => { const modelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" const modelInfo = chutesModels[modelId] - const handlerWithModel = new ChutesHandler({ apiModelId: modelId }) + const handlerWithModel = new ChutesHandler({ apiModelId: modelId, chutesApiKey: "test-chutes-api-key" }) mockCreate.mockImplementationOnce(() => { return { diff --git a/src/api/providers/__tests__/groq.test.ts b/src/api/providers/__tests__/groq.test.ts index 6b38dcc9274..068f7248fdd 100644 --- a/src/api/providers/__tests__/groq.test.ts +++ b/src/api/providers/__tests__/groq.test.ts @@ -19,11 +19,11 @@ describe("GroqHandler", () => { beforeEach(() => { jest.clearAllMocks() mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create - handler = new GroqHandler({}) + handler = new GroqHandler({ groqApiKey: "test-groq-api-key" }) }) test("should use the correct Groq base URL", () => { - new GroqHandler({}) + new GroqHandler({ groqApiKey: "test-groq-api-key" }) expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.groq.com/openai/v1" })) }) @@ -35,17 +35,16 @@ describe("GroqHandler", () => { test("should return default model when no model is specified", () => { const model = handler.getModel() - expect(model.id).toBe(groqDefaultModelId) // Use groqDefaultModelId - expect(model.info).toEqual(groqModels[groqDefaultModelId]) // Use groqModels + expect(model.id).toBe(groqDefaultModelId) + expect(model.info).toEqual(groqModels[groqDefaultModelId]) }) test("should return specified model when valid model is provided", () => { - const testModelId: GroqModelId = "llama-3.3-70b-versatile" // Use a valid Groq model ID and type - const handlerWithModel = new GroqHandler({ apiModelId: testModelId }) // Instantiate GroqHandler + const testModelId: GroqModelId = "llama-3.3-70b-versatile" + const handlerWithModel = new GroqHandler({ apiModelId: testModelId, groqApiKey: "test-groq-api-key" }) const model = handlerWithModel.getModel() - expect(model.id).toBe(testModelId) - expect(model.info).toEqual(groqModels[testModelId]) // Use groqModels + expect(model.info).toEqual(groqModels[testModelId]) }) test("completePrompt method should return text from Groq API", async () => { @@ -110,7 +109,7 @@ describe("GroqHandler", () => { test("createMessage should pass correct parameters to Groq client", async () => { const modelId: GroqModelId = "llama-3.1-8b-instant" const modelInfo = groqModels[modelId] - const handlerWithModel = new GroqHandler({ apiModelId: modelId }) + const handlerWithModel = new GroqHandler({ apiModelId: modelId, groqApiKey: "test-groq-api-key" }) mockCreate.mockImplementationOnce(() => { return { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 5f2d50b94c9..e662c1a3ade 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,31 +1,17 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react" -import { useDebounce, useEvent } from "react-use" -import { Trans } from "react-i18next" -import { LanguageModelChatSelector } from "vscode" -import { Checkbox } from "vscrui" -import { - VSCodeButton, - VSCodeLink, - VSCodeRadio, - VSCodeRadioGroup, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react" +import { useDebounce } from "react-use" +import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { ExternalLinkIcon } from "@radix-ui/react-icons" -import { ReasoningEffort as ReasoningEffortType } from "@roo/schemas" import { ApiConfiguration, - ModelInfo, - azureOpenAiDefaultApiVersion, glamaDefaultModelId, mistralDefaultModelId, - openAiModelInfoSaneDefaults, openRouterDefaultModelId, unboundDefaultModelId, requestyDefaultModelId, ApiProvider, } from "@roo/shared/api" -import { ExtensionMessage } from "@roo/shared/ExtensionMessage" import { vscode } from "@src/utils/vscode" import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@src/utils/validate" @@ -36,18 +22,28 @@ import { useOpenRouterModelProviders, OPENROUTER_DEFAULT_PROVIDER_NAME, } from "@src/components/ui/hooks/useOpenRouterModelProviders" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button } from "@src/components/ui" -import { getRequestyAuthUrl, getOpenRouterAuthUrl, getGlamaAuthUrl } from "@src/oauth/urls" - -import { VSCodeButtonLink } from "../common/VSCodeButtonLink" - -import { MODELS_BY_PROVIDER, PROVIDERS, VERTEX_REGIONS, REASONING_MODELS, AWS_REGIONS } from "./constants" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" +import { getRequestyAuthUrl, getGlamaAuthUrl } from "@src/oauth/urls" + +// Providers +import { Anthropic } from "./providers/Anthropic" +import { Bedrock } from "./providers/Bedrock" +import { Gemini } from "./providers/Gemini" +import { LMStudio } from "./providers/LMStudio" +import { Ollama } from "./providers/Ollama" +import { OpenAI } from "./providers/OpenAI" +import { OpenAICompatible } from "./providers/OpenAICompatible" +import { OpenRouter } from "./providers/OpenRouter" +import { Vertex } from "./providers/Vertex" +import { VSCodeLM } from "./providers/VSCodeLM" + +import { MODELS_BY_PROVIDER, PROVIDERS, REASONING_MODELS } from "./constants" +import { inputEventTransform, noTransform } from "./transforms" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" -import { R1FormatSetting } from "./R1FormatSetting" -import { OpenRouterBalanceDisplay } from "./OpenRouterBalanceDisplay" import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay" import { ReasoningEffort } from "./ReasoningEffort" import { PromptCachingControl } from "./PromptCachingControl" @@ -74,12 +70,6 @@ const ApiOptions = ({ }: ApiOptionsProps) => { const { t } = useAppTranslation() - const [ollamaModels, setOllamaModels] = useState([]) - const [lmStudioModels, setLmStudioModels] = useState([]) - const [vsCodeLmModels, setVsCodeLmModels] = useState([]) - - const [openAiModels, setOpenAiModels] = useState | null>(null) - const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => { const headers = apiConfiguration?.openAiHeaders || {} return Object.entries(headers) @@ -93,67 +83,28 @@ const ApiOptions = ({ } }, [apiConfiguration?.openAiHeaders, customHeaders]) - const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) - const [openAiNativeBaseUrlSelected, setOpenAiNativeBaseUrlSelected] = useState( - !!apiConfiguration?.openAiNativeBaseUrl, - ) - const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) - const [openRouterBaseUrlSelected, setOpenRouterBaseUrlSelected] = useState(!!apiConfiguration?.openRouterBaseUrl) - const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat) - const [googleGeminiBaseUrlSelected, setGoogleGeminiBaseUrlSelected] = useState( - !!apiConfiguration?.googleGeminiBaseUrl, - ) - - const handleAddCustomHeader = useCallback(() => { - // Only update the local state to show the new row in the UI - setCustomHeaders((prev) => [...prev, ["", ""]]) - // Do not update the main configuration yet, wait for user input - }, []) - - const handleUpdateHeaderKey = useCallback((index: number, newKey: string) => { - setCustomHeaders((prev) => { - const updated = [...prev] - if (updated[index]) { - updated[index] = [newKey, updated[index][1]] - } - return updated - }) - }, []) - - const handleUpdateHeaderValue = useCallback((index: number, newValue: string) => { - setCustomHeaders((prev) => { - const updated = [...prev] - if (updated[index]) { - updated[index] = [updated[index][0], newValue] - } - return updated - }) - }, []) - - const handleRemoveCustomHeader = useCallback((index: number) => { - setCustomHeaders((prev) => prev.filter((_, i) => i !== index)) - }, []) - - // Helper to convert array of tuples to object (filtering out empty keys) + // Helper to convert array of tuples to object (filtering out empty keys). const convertHeadersToObject = (headers: [string, string][]): Record => { const result: Record = {} - // Process each header tuple + // Process each header tuple. for (const [key, value] of headers) { const trimmedKey = key.trim() - // Skip empty keys - if (!trimmedKey) continue + // Skip empty keys. + if (!trimmedKey) { + continue + } - // For duplicates, the last one in the array wins - // This matches how HTTP headers work in general + // For duplicates, the last one in the array wins. + // This matches how HTTP headers work in general. result[trimmedKey] = value.trim() } return result } - // Debounced effect to update the main configuration when local customHeaders state stabilizes + // Debounced effect to update the main configuration when local customHeaders state stabilizes. useDebounce( () => { const currentConfigHeaders = apiConfiguration?.openAiHeaders || {} @@ -169,9 +120,6 @@ const ApiOptions = ({ ) const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false) - const noTransform = (value: T) => value - - const inputEventTransform = (event: E) => (event as { target: HTMLInputElement })?.target?.value as any const handleInputChange = useCallback( ( @@ -204,8 +152,9 @@ const ApiOptions = ({ useDebounce( () => { if (selectedProvider === "openai") { - // Use our custom headers state to build the headers object + // Use our custom headers state to build the headers object. const headerObject = convertHeadersToObject(customHeaders) + vscode.postMessage({ type: "requestOpenAiModels", values: { @@ -238,6 +187,7 @@ const ApiOptions = ({ useEffect(() => { const apiValidationResult = validateApiConfiguration(apiConfiguration) || validateModelId(apiConfiguration, routerModels) + setErrorMessage(apiValidationResult) }, [apiConfiguration, routerModels, setErrorMessage]) @@ -250,38 +200,6 @@ const ApiOptions = ({ apiConfiguration.openRouterModelId in routerModels.openrouter, }) - const onMessage = useCallback((event: MessageEvent) => { - const message: ExtensionMessage = event.data - - switch (message.type) { - case "openAiModels": { - const updatedModels = message.openAiModels ?? [] - setOpenAiModels(Object.fromEntries(updatedModels.map((item) => [item, openAiModelInfoSaneDefaults]))) - break - } - case "ollamaModels": - { - const newModels = message.ollamaModels ?? [] - setOllamaModels(newModels) - } - break - case "lmStudioModels": - { - const newModels = message.lmStudioModels ?? [] - setLmStudioModels(newModels) - } - break - case "vsCodeLmModels": - { - const newModels = message.vsCodeLmModels ?? [] - setVsCodeLmModels(newModels) - } - break - } - }, []) - - useEvent("message", onMessage) - const selectedProviderModelOptions = useMemo( () => MODELS_BY_PROVIDER[selectedProvider] @@ -293,16 +211,13 @@ const ApiOptions = ({ [selectedProvider], ) - // Base URL for provider documentation - const DOC_BASE_URL = "https://docs.roocode.com/providers" - - // Custom URL path mappings for providers with different slugs + // Custom URL path mappings for providers with different slugs. const providerUrlSlugs: Record = { "openai-native": "openai", openai: "openai-compatible", } - // Helper function to get provider display name from PROVIDERS constant + // Helper function to get provider display name from PROVIDERS constant. const getProviderDisplayName = (providerKey: string): string | undefined => { const provider = PROVIDERS.find((p) => p.value === providerKey) return provider?.label @@ -320,7 +235,7 @@ const ApiOptions = ({ const urlSlug = providerUrlSlugs[selectedProvider] || selectedProvider return { - url: `${DOC_BASE_URL}/${urlSlug}`, + url: `https://docs.roocode.com/providers/${urlSlug}`, name: displayName, } } @@ -403,126 +318,16 @@ const ApiOptions = ({ {errorMessage && } {selectedProvider === "openrouter" && ( - <> - -
- - {apiConfiguration?.openRouterApiKey && ( - - )} -
-
-
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.openRouterApiKey && ( - - {t("settings:providers.getOpenRouterApiKey")} - - )} - {!fromWelcomeView && ( - <> -
- { - setOpenRouterBaseUrlSelected(checked) - - if (!checked) { - setApiConfigurationField("openRouterBaseUrl", "") - } - }}> - {t("settings:providers.useCustomBaseUrl")} - - {openRouterBaseUrlSelected && ( - - )} -
- - , - }} - /> - - - )} - + )} {selectedProvider === "anthropic" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.apiKey && ( - - {t("settings:providers.getAnthropicApiKey")} - - )} -
- { - setAnthropicBaseUrlSelected(checked) - - if (!checked) { - setApiConfigurationField("anthropicBaseUrl", "") - setApiConfigurationField("anthropicUseAuthToken", false) // added - } - }}> - {t("settings:providers.useCustomBaseUrl")} - - {anthropicBaseUrlSelected && ( - <> - - - {/* added */} - - {t("settings:providers.anthropicUseAuthToken")} - - - )} -
- + )} {selectedProvider === "glama" && ( @@ -579,46 +384,7 @@ const ApiOptions = ({ )} {selectedProvider === "openai-native" && ( - <> - { - setOpenAiNativeBaseUrlSelected(checked) - - if (!checked) { - setApiConfigurationField("openAiNativeBaseUrl", "") - } - }}> - {t("settings:providers.useCustomBaseUrl")} - - {openAiNativeBaseUrlSelected && ( - <> - - - )} - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.openAiNativeApiKey && ( - - {t("settings:providers.getOpenAiApiKey")} - - )} - + )} {selectedProvider === "mistral" && ( @@ -661,832 +427,30 @@ const ApiOptions = ({ )} {selectedProvider === "bedrock" && ( - <> - (e.target as HTMLInputElement).value === "profile", - )}> - {t("settings:providers.awsCredentials")} - {t("settings:providers.awsProfile")} - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {apiConfiguration?.awsUseProfile ? ( - - - - ) : ( - <> - - - - - - - - - - - )} -
- - -
- - {t("settings:providers.awsCrossRegion")} - - {selectedModelInfo?.supportsPromptCache && ( - -
- {t("settings:providers.enablePromptCaching")} - -
-
- )} -
-
- {t("settings:providers.cacheUsageNote")} -
-
- + )} {selectedProvider === "vertex" && ( - <> -
-
{t("settings:providers.googleCloudSetup.title")}
-
- - {t("settings:providers.googleCloudSetup.step1")} - -
-
- - {t("settings:providers.googleCloudSetup.step2")} - -
-
- - {t("settings:providers.googleCloudSetup.step3")} - -
-
- - - - - - - - - -
- - -
- + )} {selectedProvider === "gemini" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.geminiApiKey && ( - - {t("settings:providers.getGeminiApiKey")} - - )} -
- { - setGoogleGeminiBaseUrlSelected(checked) - - if (!checked) { - setApiConfigurationField("googleGeminiBaseUrl", "") - } - }}> - {t("settings:providers.useCustomBaseUrl")} - - {googleGeminiBaseUrlSelected && ( - - )} -
- + )} {selectedProvider === "openai" && ( - <> - - - - - - - - -
- { - setOpenAiLegacyFormatSelected(checked) - setApiConfigurationField("openAiLegacyFormat", checked) - }}> - {t("settings:providers.useLegacyFormat")} - -
- - {t("settings:modelInfo.enableStreaming")} - - - {t("settings:modelInfo.useAzure")} - -
- { - setAzureApiVersionSelected(checked) - - if (!checked) { - setApiConfigurationField("azureApiVersion", "") - } - }}> - {t("settings:modelInfo.azureApiVersion")} - - {azureApiVersionSelected && ( - - )} -
- - {/* Custom Headers UI */} -
-
- - - - -
- {!customHeaders.length ? ( -
- {t("settings:providers.noCustomHeaders")} -
- ) : ( - customHeaders.map(([key, value], index) => ( -
- handleUpdateHeaderKey(index, e.target.value)} - /> - handleUpdateHeaderValue(index, e.target.value)} - /> - handleRemoveCustomHeader(index)}> - - -
- )) - )} -
- -
- { - setApiConfigurationField("enableReasoningEffort", checked) - - if (!checked) { - const { reasoningEffort: _, ...openAiCustomModelInfo } = - apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults - - setApiConfigurationField("openAiCustomModelInfo", openAiCustomModelInfo) - } - }}> - {t("settings:providers.setReasoningLevel")} - - {!!apiConfiguration.enableReasoningEffort && ( - { - if (field === "reasoningEffort") { - const openAiCustomModelInfo = - apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults - - setApiConfigurationField("openAiCustomModelInfo", { - ...openAiCustomModelInfo, - reasoningEffort: value as ReasoningEffortType, - }) - } - }} - /> - )} -
-
-
- {t("settings:providers.customModel.capabilities")} -
- -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.maxTokens - - if (!value) { - return "var(--vscode-input-border)" - } - - return value > 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - title={t("settings:providers.customModel.maxTokens.description")} - onInput={handleInputChange("openAiCustomModelInfo", (e) => { - const value = parseInt((e.target as HTMLInputElement).value) - - return { - ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), - maxTokens: isNaN(value) ? undefined : value, - } - })} - placeholder={t("settings:placeholders.numbers.maxTokens")} - className="w-full"> - - -
- {t("settings:providers.customModel.maxTokens.description")} -
-
- -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow - - if (!value) { - return "var(--vscode-input-border)" - } - - return value > 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - title={t("settings:providers.customModel.contextWindow.description")} - onInput={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseInt(value) - - return { - ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), - contextWindow: isNaN(parsed) - ? openAiModelInfoSaneDefaults.contextWindow - : parsed, - } - })} - placeholder={t("settings:placeholders.numbers.contextWindow")} - className="w-full"> - - -
- {t("settings:providers.customModel.contextWindow.description")} -
-
- -
-
- { - return { - ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), - supportsImages: checked, - } - })}> - - {t("settings:providers.customModel.imageSupport.label")} - - - -
-
- {t("settings:providers.customModel.imageSupport.description")} -
-
- -
-
- { - return { - ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), - supportsComputerUse: checked, - } - })}> - - {t("settings:providers.customModel.computerUse.label")} - - - -
-
- {t("settings:providers.customModel.computerUse.description")} -
-
- -
-
- { - return { - ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), - supportsPromptCache: checked, - } - })}> - - {t("settings:providers.customModel.promptCache.label")} - - - -
-
- {t("settings:providers.customModel.promptCache.description")} -
-
- -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice - - if (!value && value !== 0) { - return "var(--vscode-input-border)" - } - - return value >= 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - onChange={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseFloat(value) - - return { - ...(apiConfiguration?.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), - inputPrice: isNaN(parsed) ? openAiModelInfoSaneDefaults.inputPrice : parsed, - } - })} - placeholder={t("settings:placeholders.numbers.inputPrice")} - className="w-full"> -
- - -
-
-
- -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice - - if (!value && value !== 0) { - return "var(--vscode-input-border)" - } - - return value >= 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - onChange={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseFloat(value) - - return { - ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), - outputPrice: isNaN(parsed) ? openAiModelInfoSaneDefaults.outputPrice : parsed, - } - })} - placeholder={t("settings:placeholders.numbers.outputPrice")} - className="w-full"> -
- - -
-
-
- - {apiConfiguration?.openAiCustomModelInfo?.supportsPromptCache && ( - <> -
- { - const value = apiConfiguration?.openAiCustomModelInfo?.cacheReadsPrice - - if (!value && value !== 0) { - return "var(--vscode-input-border)" - } - - return value >= 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - onChange={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseFloat(value) - - return { - ...(apiConfiguration?.openAiCustomModelInfo ?? - openAiModelInfoSaneDefaults), - cacheReadsPrice: isNaN(parsed) ? 0 : parsed, - } - })} - placeholder={t("settings:placeholders.numbers.inputPrice")} - className="w-full"> -
- - {t("settings:providers.customModel.pricing.cacheReads.label")} - - -
-
-
-
- { - const value = apiConfiguration?.openAiCustomModelInfo?.cacheWritesPrice - - if (!value && value !== 0) { - return "var(--vscode-input-border)" - } - - return value >= 0 - ? "var(--vscode-charts-green)" - : "var(--vscode-errorForeground)" - })(), - }} - onChange={handleInputChange("openAiCustomModelInfo", (e) => { - const value = (e.target as HTMLInputElement).value - const parsed = parseFloat(value) - - return { - ...(apiConfiguration?.openAiCustomModelInfo ?? - openAiModelInfoSaneDefaults), - cacheWritesPrice: isNaN(parsed) ? 0 : parsed, - } - })} - placeholder={t("settings:placeholders.numbers.cacheWritePrice")} - className="w-full"> -
- - -
-
-
- - )} - - -
- + )} {selectedProvider === "lmstudio" && ( - <> - - - - - - - {lmStudioModels.length > 0 && ( - - {lmStudioModels.map((model) => ( - - {model} - - ))} - - )} - { - setApiConfigurationField("lmStudioSpeculativeDecodingEnabled", checked) - }}> - {t("settings:providers.lmStudio.speculativeDecoding")} - - {apiConfiguration?.lmStudioSpeculativeDecodingEnabled && ( - <> -
- - - -
- {t("settings:providers.lmStudio.draftModelDesc")} -
-
- {lmStudioModels.length > 0 && ( - <> -
- {t("settings:providers.lmStudio.selectDraftModel")} -
- - {lmStudioModels.map((model) => ( - - {model} - - ))} - - {lmStudioModels.length === 0 && ( -
- {t("settings:providers.lmStudio.noModelsFound")} -
- )} - - )} - - )} -
- , - b: , - span: ( - - Note: - - ), - }} - /> -
- + )} {selectedProvider === "deepseek" && ( @@ -1511,85 +475,11 @@ const ApiOptions = ({ )} {selectedProvider === "vscode-lm" && ( - <> -
- - {vsCodeLmModels.length > 0 ? ( - - ) : ( -
- {t("settings:providers.vscodeLmDescription")} -
- )} -
-
{t("settings:providers.vscodeLmWarning")}
- + )} {selectedProvider === "ollama" && ( - <> - - - - - - - {ollamaModels.length > 0 && ( - - {ollamaModels.map((model) => ( - - {model} - - ))} - - )} -
- {t("settings:providers.ollama.description")} - - {t("settings:providers.ollama.warning")} - -
- + )} {selectedProvider === "xai" && ( @@ -1647,12 +537,11 @@ const ApiOptions = ({
{t("settings:providers.apiKeyStorageNotice")}
- {/* Add a link to get Chutes API key if available */} - {/* {!apiConfiguration?.chutesApiKey && ( - + {!apiConfiguration?.chutesApiKey && ( + {t("settings:providers.getChutesApiKey")} - )} */} + )} )} diff --git a/webview-ui/src/components/settings/providers/Anthropic.tsx b/webview-ui/src/components/settings/providers/Anthropic.tsx new file mode 100644 index 00000000000..bf310d1cb7e --- /dev/null +++ b/webview-ui/src/components/settings/providers/Anthropic.tsx @@ -0,0 +1,84 @@ +import { useCallback, useState } from "react" +import { Checkbox } from "vscrui" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform, noTransform } from "../transforms" + +type AnthropicProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const Anthropic = ({ apiConfiguration, setApiConfigurationField }: AnthropicProps) => { + const { t } = useAppTranslation() + + const [anthropicBaseUrlSelected, setAnthropicBaseUrlSelected] = useState(!!apiConfiguration?.anthropicBaseUrl) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.apiKey && ( + + {t("settings:providers.getAnthropicApiKey")} + + )} +
+ { + setAnthropicBaseUrlSelected(checked) + + if (!checked) { + setApiConfigurationField("anthropicBaseUrl", "") + setApiConfigurationField("anthropicUseAuthToken", false) + } + }}> + {t("settings:providers.useCustomBaseUrl")} + + {anthropicBaseUrlSelected && ( + <> + + + {t("settings:providers.anthropicUseAuthToken")} + + + )} +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/Bedrock.tsx b/webview-ui/src/components/settings/providers/Bedrock.tsx new file mode 100644 index 00000000000..fc69e74ddef --- /dev/null +++ b/webview-ui/src/components/settings/providers/Bedrock.tsx @@ -0,0 +1,126 @@ +import { useCallback } from "react" +import { Checkbox } from "vscrui" +import { VSCodeTextField, VSCodeRadio, VSCodeRadioGroup } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration, ModelInfo } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" + +import { AWS_REGIONS } from "../constants" +import { inputEventTransform, noTransform } from "../transforms" + +type BedrockProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void + selectedModelInfo?: ModelInfo +} + +export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedModelInfo }: BedrockProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + (e.target as HTMLInputElement).value === "profile", + )}> + {t("settings:providers.awsCredentials")} + {t("settings:providers.awsProfile")} + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {apiConfiguration?.awsUseProfile ? ( + + + + ) : ( + <> + + + + + + + + + + + )} +
+ + +
+ + {t("settings:providers.awsCrossRegion")} + + {selectedModelInfo?.supportsPromptCache && ( + +
+ {t("settings:providers.enablePromptCaching")} + +
+
+ )} +
+
+ {t("settings:providers.cacheUsageNote")} +
+
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/Gemini.tsx b/webview-ui/src/components/settings/providers/Gemini.tsx new file mode 100644 index 00000000000..490ab73a51a --- /dev/null +++ b/webview-ui/src/components/settings/providers/Gemini.tsx @@ -0,0 +1,77 @@ +import { useCallback, useState } from "react" +import { Checkbox } from "vscrui" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform } from "../transforms" + +type GeminiProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const Gemini = ({ apiConfiguration, setApiConfigurationField }: GeminiProps) => { + const { t } = useAppTranslation() + + const [googleGeminiBaseUrlSelected, setGoogleGeminiBaseUrlSelected] = useState( + !!apiConfiguration?.googleGeminiBaseUrl, + ) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.geminiApiKey && ( + + {t("settings:providers.getGeminiApiKey")} + + )} +
+ { + setGoogleGeminiBaseUrlSelected(checked) + + if (!checked) { + setApiConfigurationField("googleGeminiBaseUrl", "") + } + }}> + {t("settings:providers.useCustomBaseUrl")} + + {googleGeminiBaseUrlSelected && ( + + )} +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/LMStudio.tsx b/webview-ui/src/components/settings/providers/LMStudio.tsx new file mode 100644 index 00000000000..0979ec9242e --- /dev/null +++ b/webview-ui/src/components/settings/providers/LMStudio.tsx @@ -0,0 +1,152 @@ +import { useCallback, useState } from "react" +import { useEvent } from "react-use" +import { Trans } from "react-i18next" +import { Checkbox } from "vscrui" +import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { ExtensionMessage } from "@roo/shared/ExtensionMessage" + +import { inputEventTransform } from "../transforms" + +type LMStudioProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudioProps) => { + const { t } = useAppTranslation() + + const [lmStudioModels, setLmStudioModels] = useState([]) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + const onMessage = useCallback((event: MessageEvent) => { + const message: ExtensionMessage = event.data + + switch (message.type) { + case "lmStudioModels": + { + const newModels = message.lmStudioModels ?? [] + setLmStudioModels(newModels) + } + break + } + }, []) + + useEvent("message", onMessage) + + return ( + <> + + + + + + + {lmStudioModels.length > 0 && ( + + {lmStudioModels.map((model) => ( + + {model} + + ))} + + )} + { + setApiConfigurationField("lmStudioSpeculativeDecodingEnabled", checked) + }}> + {t("settings:providers.lmStudio.speculativeDecoding")} + + {apiConfiguration?.lmStudioSpeculativeDecodingEnabled && ( + <> +
+ + + +
+ {t("settings:providers.lmStudio.draftModelDesc")} +
+
+ {lmStudioModels.length > 0 && ( + <> +
{t("settings:providers.lmStudio.selectDraftModel")}
+ + {lmStudioModels.map((model) => ( + + {model} + + ))} + + {lmStudioModels.length === 0 && ( +
+ {t("settings:providers.lmStudio.noModelsFound")} +
+ )} + + )} + + )} +
+ , + b: , + span: ( + + Note: + + ), + }} + /> +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/Ollama.tsx b/webview-ui/src/components/settings/providers/Ollama.tsx new file mode 100644 index 00000000000..5646fae18cf --- /dev/null +++ b/webview-ui/src/components/settings/providers/Ollama.tsx @@ -0,0 +1,86 @@ +import { useState, useCallback } from "react" +import { useEvent } from "react-use" +import { VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" +import { ExtensionMessage } from "@roo/shared/ExtensionMessage" + +import { useAppTranslation } from "@src/i18n/TranslationContext" + +import { inputEventTransform } from "../transforms" + +type OllamaProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaProps) => { + const { t } = useAppTranslation() + + const [ollamaModels, setOllamaModels] = useState([]) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + const onMessage = useCallback((event: MessageEvent) => { + const message: ExtensionMessage = event.data + + switch (message.type) { + case "ollamaModels": + { + const newModels = message.ollamaModels ?? [] + setOllamaModels(newModels) + } + break + } + }, []) + + useEvent("message", onMessage) + + return ( + <> + + + + + + + {ollamaModels.length > 0 && ( + + {ollamaModels.map((model) => ( + + {model} + + ))} + + )} +
+ {t("settings:providers.ollama.description")} + {t("settings:providers.ollama.warning")} +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/OpenAI.tsx b/webview-ui/src/components/settings/providers/OpenAI.tsx new file mode 100644 index 00000000000..501dc9df55e --- /dev/null +++ b/webview-ui/src/components/settings/providers/OpenAI.tsx @@ -0,0 +1,77 @@ +import { useCallback, useState } from "react" +import { Checkbox } from "vscrui" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform } from "../transforms" + +type OpenAIProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const OpenAI = ({ apiConfiguration, setApiConfigurationField }: OpenAIProps) => { + const { t } = useAppTranslation() + + const [openAiNativeBaseUrlSelected, setOpenAiNativeBaseUrlSelected] = useState( + !!apiConfiguration?.openAiNativeBaseUrl, + ) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + { + setOpenAiNativeBaseUrlSelected(checked) + + if (!checked) { + setApiConfigurationField("openAiNativeBaseUrl", "") + } + }}> + {t("settings:providers.useCustomBaseUrl")} + + {openAiNativeBaseUrlSelected && ( + <> + + + )} + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.openAiNativeApiKey && ( + + {t("settings:providers.getOpenAiApiKey")} + + )} + + ) +} diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx new file mode 100644 index 00000000000..9d55d57f8c6 --- /dev/null +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -0,0 +1,578 @@ +import { useState, useCallback } from "react" +import { useEvent } from "react-use" +import { Checkbox } from "vscrui" +import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ModelInfo, ReasoningEffort as ReasoningEffortType } from "@roo/schemas" +import { ApiConfiguration, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults } from "@roo/shared/api" +import { ExtensionMessage } from "@roo/shared/ExtensionMessage" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Button } from "@src/components/ui" + +import { inputEventTransform, noTransform } from "../transforms" +import { ModelPicker } from "../ModelPicker" +import { R1FormatSetting } from "../R1FormatSetting" +import { ReasoningEffort } from "../ReasoningEffort" + +type OpenAICompatibleProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const OpenAICompatible = ({ apiConfiguration, setApiConfigurationField }: OpenAICompatibleProps) => { + const { t } = useAppTranslation() + + const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) + const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat) + + const [openAiModels, setOpenAiModels] = useState | null>(null) + + const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => { + const headers = apiConfiguration?.openAiHeaders || {} + return Object.entries(headers) + }) + + const handleAddCustomHeader = useCallback(() => { + // Only update the local state to show the new row in the UI. + setCustomHeaders((prev) => [...prev, ["", ""]]) + // Do not update the main configuration yet, wait for user input. + }, []) + + const handleUpdateHeaderKey = useCallback((index: number, newKey: string) => { + setCustomHeaders((prev) => { + const updated = [...prev] + + if (updated[index]) { + updated[index] = [newKey, updated[index][1]] + } + + return updated + }) + }, []) + + const handleUpdateHeaderValue = useCallback((index: number, newValue: string) => { + setCustomHeaders((prev) => { + const updated = [...prev] + + if (updated[index]) { + updated[index] = [updated[index][0], newValue] + } + + return updated + }) + }, []) + + const handleRemoveCustomHeader = useCallback((index: number) => { + setCustomHeaders((prev) => prev.filter((_, i) => i !== index)) + }, []) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + const onMessage = useCallback((event: MessageEvent) => { + const message: ExtensionMessage = event.data + + switch (message.type) { + case "openAiModels": { + const updatedModels = message.openAiModels ?? [] + setOpenAiModels(Object.fromEntries(updatedModels.map((item) => [item, openAiModelInfoSaneDefaults]))) + break + } + } + }, []) + + useEvent("message", onMessage) + + return ( + <> + + + + + + + + +
+ { + setOpenAiLegacyFormatSelected(checked) + setApiConfigurationField("openAiLegacyFormat", checked) + }}> + {t("settings:providers.useLegacyFormat")} + +
+ + {t("settings:modelInfo.enableStreaming")} + + + {t("settings:modelInfo.useAzure")} + +
+ { + setAzureApiVersionSelected(checked) + + if (!checked) { + setApiConfigurationField("azureApiVersion", "") + } + }}> + {t("settings:modelInfo.azureApiVersion")} + + {azureApiVersionSelected && ( + + )} +
+ + {/* Custom Headers UI */} +
+
+ + + + +
+ {!customHeaders.length ? ( +
+ {t("settings:providers.noCustomHeaders")} +
+ ) : ( + customHeaders.map(([key, value], index) => ( +
+ handleUpdateHeaderKey(index, e.target.value)} + /> + handleUpdateHeaderValue(index, e.target.value)} + /> + handleRemoveCustomHeader(index)}> + + +
+ )) + )} +
+ +
+ { + setApiConfigurationField("enableReasoningEffort", checked) + + if (!checked) { + const { reasoningEffort: _, ...openAiCustomModelInfo } = + apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults + + setApiConfigurationField("openAiCustomModelInfo", openAiCustomModelInfo) + } + }}> + {t("settings:providers.setReasoningLevel")} + + {!!apiConfiguration.enableReasoningEffort && ( + { + if (field === "reasoningEffort") { + const openAiCustomModelInfo = + apiConfiguration.openAiCustomModelInfo || openAiModelInfoSaneDefaults + + setApiConfigurationField("openAiCustomModelInfo", { + ...openAiCustomModelInfo, + reasoningEffort: value as ReasoningEffortType, + }) + } + }} + /> + )} +
+
+
+ {t("settings:providers.customModel.capabilities")} +
+ +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.maxTokens + + if (!value) { + return "var(--vscode-input-border)" + } + + return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + title={t("settings:providers.customModel.maxTokens.description")} + onInput={handleInputChange("openAiCustomModelInfo", (e) => { + const value = parseInt((e.target as HTMLInputElement).value) + + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + maxTokens: isNaN(value) ? undefined : value, + } + })} + placeholder={t("settings:placeholders.numbers.maxTokens")} + className="w-full"> + + +
+ {t("settings:providers.customModel.maxTokens.description")} +
+
+ +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.contextWindow + + if (!value) { + return "var(--vscode-input-border)" + } + + return value > 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + title={t("settings:providers.customModel.contextWindow.description")} + onInput={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseInt(value) + + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + contextWindow: isNaN(parsed) ? openAiModelInfoSaneDefaults.contextWindow : parsed, + } + })} + placeholder={t("settings:placeholders.numbers.contextWindow")} + className="w-full"> + + +
+ {t("settings:providers.customModel.contextWindow.description")} +
+
+ +
+
+ { + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + supportsImages: checked, + } + })}> + + {t("settings:providers.customModel.imageSupport.label")} + + + +
+
+ {t("settings:providers.customModel.imageSupport.description")} +
+
+ +
+
+ { + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + supportsComputerUse: checked, + } + })}> + {t("settings:providers.customModel.computerUse.label")} + + +
+
+ {t("settings:providers.customModel.computerUse.description")} +
+
+ +
+
+ { + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + supportsPromptCache: checked, + } + })}> + {t("settings:providers.customModel.promptCache.label")} + + +
+
+ {t("settings:providers.customModel.promptCache.description")} +
+
+ +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.inputPrice + + if (!value && value !== 0) { + return "var(--vscode-input-border)" + } + + return value >= 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + onChange={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseFloat(value) + + return { + ...(apiConfiguration?.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), + inputPrice: isNaN(parsed) ? openAiModelInfoSaneDefaults.inputPrice : parsed, + } + })} + placeholder={t("settings:placeholders.numbers.inputPrice")} + className="w-full"> +
+ + +
+
+
+ +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.outputPrice + + if (!value && value !== 0) { + return "var(--vscode-input-border)" + } + + return value >= 0 ? "var(--vscode-charts-green)" : "var(--vscode-errorForeground)" + })(), + }} + onChange={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseFloat(value) + + return { + ...(apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults), + outputPrice: isNaN(parsed) ? openAiModelInfoSaneDefaults.outputPrice : parsed, + } + })} + placeholder={t("settings:placeholders.numbers.outputPrice")} + className="w-full"> +
+ + +
+
+
+ + {apiConfiguration?.openAiCustomModelInfo?.supportsPromptCache && ( + <> +
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.cacheReadsPrice + + if (!value && value !== 0) { + return "var(--vscode-input-border)" + } + + return value >= 0 + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} + onChange={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseFloat(value) + + return { + ...(apiConfiguration?.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), + cacheReadsPrice: isNaN(parsed) ? 0 : parsed, + } + })} + placeholder={t("settings:placeholders.numbers.inputPrice")} + className="w-full"> +
+ + {t("settings:providers.customModel.pricing.cacheReads.label")} + + +
+
+
+
+ { + const value = apiConfiguration?.openAiCustomModelInfo?.cacheWritesPrice + + if (!value && value !== 0) { + return "var(--vscode-input-border)" + } + + return value >= 0 + ? "var(--vscode-charts-green)" + : "var(--vscode-errorForeground)" + })(), + }} + onChange={handleInputChange("openAiCustomModelInfo", (e) => { + const value = (e.target as HTMLInputElement).value + const parsed = parseFloat(value) + + return { + ...(apiConfiguration?.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), + cacheWritesPrice: isNaN(parsed) ? 0 : parsed, + } + })} + placeholder={t("settings:placeholders.numbers.cacheWritePrice")} + className="w-full"> +
+ + +
+
+
+ + )} + + +
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/OpenRouter.tsx b/webview-ui/src/components/settings/providers/OpenRouter.tsx new file mode 100644 index 00000000000..e9fd3397928 --- /dev/null +++ b/webview-ui/src/components/settings/providers/OpenRouter.tsx @@ -0,0 +1,109 @@ +import { useCallback, useState } from "react" +import { Trans } from "react-i18next" +import { Checkbox } from "vscrui" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { getOpenRouterAuthUrl } from "@src/oauth/urls" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform, noTransform } from "../transforms" + +import { OpenRouterBalanceDisplay } from "./OpenRouterBalanceDisplay" + +type OpenRouterProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void + uriScheme: string | undefined + fromWelcomeView?: boolean +} + +export const OpenRouter = ({ + apiConfiguration, + setApiConfigurationField, + uriScheme, + fromWelcomeView, +}: OpenRouterProps) => { + const { t } = useAppTranslation() + + const [openRouterBaseUrlSelected, setOpenRouterBaseUrlSelected] = useState(!!apiConfiguration?.openRouterBaseUrl) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + +
+ + {apiConfiguration?.openRouterApiKey && ( + + )} +
+
+
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.openRouterApiKey && ( + + {t("settings:providers.getOpenRouterApiKey")} + + )} + {!fromWelcomeView && ( + <> +
+ { + setOpenRouterBaseUrlSelected(checked) + + if (!checked) { + setApiConfigurationField("openRouterBaseUrl", "") + } + }}> + {t("settings:providers.useCustomBaseUrl")} + + {openRouterBaseUrlSelected && ( + + )} +
+ + , + }} + /> + + + )} + + ) +} diff --git a/webview-ui/src/components/settings/OpenRouterBalanceDisplay.tsx b/webview-ui/src/components/settings/providers/OpenRouterBalanceDisplay.tsx similarity index 100% rename from webview-ui/src/components/settings/OpenRouterBalanceDisplay.tsx rename to webview-ui/src/components/settings/providers/OpenRouterBalanceDisplay.tsx diff --git a/webview-ui/src/components/settings/providers/VSCodeLM.tsx b/webview-ui/src/components/settings/providers/VSCodeLM.tsx new file mode 100644 index 00000000000..ca701ce024a --- /dev/null +++ b/webview-ui/src/components/settings/providers/VSCodeLM.tsx @@ -0,0 +1,86 @@ +import { useState, useCallback } from "react" +import { useEvent } from "react-use" +import { LanguageModelChatSelector } from "vscode" + +import { ApiConfiguration } from "@roo/shared/api" +import { ExtensionMessage } from "@roo/shared/ExtensionMessage" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" + +import { inputEventTransform } from "../transforms" + +type VSCodeLMProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const VSCodeLM = ({ apiConfiguration, setApiConfigurationField }: VSCodeLMProps) => { + const { t } = useAppTranslation() + + const [vsCodeLmModels, setVsCodeLmModels] = useState([]) + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + const onMessage = useCallback((event: MessageEvent) => { + const message: ExtensionMessage = event.data + + switch (message.type) { + case "vsCodeLmModels": + { + const newModels = message.vsCodeLmModels ?? [] + setVsCodeLmModels(newModels) + } + break + } + }, []) + + useEvent("message", onMessage) + + return ( + <> +
+ + {vsCodeLmModels.length > 0 ? ( + + ) : ( +
+ {t("settings:providers.vscodeLmDescription")} +
+ )} +
+
{t("settings:providers.vscodeLmWarning")}
+ + ) +} diff --git a/webview-ui/src/components/settings/providers/Vertex.tsx b/webview-ui/src/components/settings/providers/Vertex.tsx new file mode 100644 index 00000000000..f0723f9d313 --- /dev/null +++ b/webview-ui/src/components/settings/providers/Vertex.tsx @@ -0,0 +1,97 @@ +import { useCallback } from "react" +import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" + +import { inputEventTransform } from "../transforms" +import { VERTEX_REGIONS } from "../constants" + +type VertexProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const Vertex = ({ apiConfiguration, setApiConfigurationField }: VertexProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> +
+
{t("settings:providers.googleCloudSetup.title")}
+
+ + {t("settings:providers.googleCloudSetup.step1")} + +
+
+ + {t("settings:providers.googleCloudSetup.step2")} + +
+
+ + {t("settings:providers.googleCloudSetup.step3")} + +
+
+ + + + + + + + + +
+ + +
+ + ) +} diff --git a/webview-ui/src/components/settings/transforms.ts b/webview-ui/src/components/settings/transforms.ts new file mode 100644 index 00000000000..22befdaf4ed --- /dev/null +++ b/webview-ui/src/components/settings/transforms.ts @@ -0,0 +1,3 @@ +export const noTransform = (value: T) => value + +export const inputEventTransform = (event: E) => (event as { target: HTMLInputElement })?.target?.value as any From 8ab0de3d02ad72cc23f59e1f0df40891833634c8 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 5 May 2025 23:19:24 -0400 Subject: [PATCH 021/228] Use Lucide icons and translations in the code block (#3203) --- .../src/components/common/CodeBlock.tsx | 31 ++++++++--------- .../common/__tests__/CodeBlock.test.tsx | 33 +++++++++++++++++++ webview-ui/src/i18n/locales/ca/chat.json | 9 +++++ webview-ui/src/i18n/locales/de/chat.json | 9 +++++ webview-ui/src/i18n/locales/en/chat.json | 9 +++++ webview-ui/src/i18n/locales/es/chat.json | 9 +++++ webview-ui/src/i18n/locales/fr/chat.json | 9 +++++ webview-ui/src/i18n/locales/hi/chat.json | 9 +++++ webview-ui/src/i18n/locales/it/chat.json | 9 +++++ webview-ui/src/i18n/locales/ja/chat.json | 9 +++++ webview-ui/src/i18n/locales/ko/chat.json | 9 +++++ webview-ui/src/i18n/locales/pl/chat.json | 9 +++++ webview-ui/src/i18n/locales/pt-BR/chat.json | 9 +++++ webview-ui/src/i18n/locales/ru/chat.json | 9 +++++ webview-ui/src/i18n/locales/tr/chat.json | 9 +++++ webview-ui/src/i18n/locales/vi/chat.json | 9 +++++ webview-ui/src/i18n/locales/zh-CN/chat.json | 9 +++++ webview-ui/src/i18n/locales/zh-TW/chat.json | 9 +++++ 18 files changed, 193 insertions(+), 15 deletions(-) diff --git a/webview-ui/src/components/common/CodeBlock.tsx b/webview-ui/src/components/common/CodeBlock.tsx index 1b295e7d1a3..2bc4527c032 100644 --- a/webview-ui/src/components/common/CodeBlock.tsx +++ b/webview-ui/src/components/common/CodeBlock.tsx @@ -4,6 +4,8 @@ import { useCopyToClipboard } from "@src/utils/clipboard" import { getHighlighter, isLanguageLoaded, normalizeLanguage, ExtendedLanguage } from "@src/utils/highlighter" import { bundledLanguages } from "shiki" import type { ShikiTransformer } from "shiki" +import { ChevronDown, ChevronUp, WrapText, AlignJustify, Copy, Check } from "lucide-react" +import { useAppTranslation } from "@src/i18n/TranslationContext" export const CODE_BLOCK_BG_COLOR = "var(--vscode-editor-background, --vscode-sideBar-background, rgb(30 30 30))" export const WRAPPER_ALPHA = "cc" // 80% opacity // Configuration constants @@ -34,13 +36,6 @@ interface CodeBlockProps { onLanguageChange?: (language: string) => void } -const ButtonIcon = styled.span` - display: inline-block; - width: 1.2em; - text-align: center; - vertical-align: middle; -` - const CodeBlockButton = styled.button` background: transparent; border: none; @@ -50,16 +45,23 @@ const CodeBlockButton = styled.button` margin: 0 0px; display: flex; align-items: center; + justify-content: center; opacity: 0.4; border-radius: 3px; pointer-events: var(--copy-button-events, none); margin-left: 4px; height: 24px; + width: 24px; &:hover { background: var(--vscode-toolbar-hoverBackground); opacity: 1; } + + /* Style for Lucide icons to ensure consistent sizing and positioning */ + svg { + display: block; + } ` const CodeBlockButtonWrapper = styled.div` @@ -229,6 +231,7 @@ const CodeBlock = memo( const preRef = useRef(null) const copyButtonWrapperRef = useRef(null) const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard() + const { t } = useAppTranslation() // Update current language when prop changes, but only if user hasn't made a selection useEffect(() => { @@ -696,19 +699,17 @@ const CodeBlock = memo( WINDOW_SHADE_SETTINGS.transitionDelayS * 1000 + 50, ) }} - title={`${windowShade ? "Expand" : "Collapse"} code block`}> - {windowShade ? "⌄" : "⌃"} + title={t(`chat:codeblock.tooltips.${windowShade ? "expand" : "collapse"}`)}> + {windowShade ? : } )} setWordWrap(!wordWrap)} - title={`${wordWrap ? "Disable" : "Enable"} word wrap`}> - - {wordWrap ? "⟼" : "⤸"} - + title={t(`chat:codeblock.tooltips.${wordWrap ? "disable_wrap" : "enable_wrap"}`)}> + {wordWrap ? : } - - + + {showCopyFeedback ? : } )} diff --git a/webview-ui/src/components/common/__tests__/CodeBlock.test.tsx b/webview-ui/src/components/common/__tests__/CodeBlock.test.tsx index e7347067c29..28576c3c7d6 100644 --- a/webview-ui/src/components/common/__tests__/CodeBlock.test.tsx +++ b/webview-ui/src/components/common/__tests__/CodeBlock.test.tsx @@ -2,6 +2,23 @@ import { render, screen, fireEvent, act } from "@testing-library/react" import "@testing-library/jest-dom" import CodeBlock from "../CodeBlock" +// Mock the translation context +jest.mock("../../../i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => { + // Return fixed English strings for tests + const translations: { [key: string]: string } = { + "chat:codeblock.tooltips.copy_code": "Copy code", + "chat:codeblock.tooltips.expand": "Expand code block", + "chat:codeblock.tooltips.collapse": "Collapse code block", + "chat:codeblock.tooltips.enable_wrap": "Enable word wrap", + "chat:codeblock.tooltips.disable_wrap": "Disable word wrap", + } + return translations[key] || key + }, + }), +})) + // Mock shiki module jest.mock("shiki", () => ({ bundledLanguages: { @@ -11,6 +28,22 @@ jest.mock("shiki", () => ({ }, })) +// Mock all lucide-react icons with a proxy to handle any icon requested +jest.mock("lucide-react", () => { + return new Proxy( + {}, + { + get: function (obj, prop) { + // Return a component factory for any icon that's requested + if (prop === "__esModule") { + return true + } + return () =>
{String(prop)}
+ }, + }, + ) +}) + // Mock the highlighter utility jest.mock("../../../utils/highlighter", () => { const mockHighlighter = { diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 282cac6a64c..2dbf818edcf 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -233,6 +233,15 @@ "close": "Tancar navegador" } }, + "codeblock": { + "tooltips": { + "expand": "Expandir bloc de codi", + "collapse": "Contraure bloc de codi", + "enable_wrap": "Activar ajustament de línia", + "disable_wrap": "Desactivar ajustament de línia", + "copy_code": "Copiar codi" + } + }, "systemPromptWarning": "ADVERTÈNCIA: S'ha activat una substitució personalitzada d'instruccions del sistema. Això pot trencar greument la funcionalitat i causar un comportament impredictible.", "shellIntegration": { "title": "Advertència d'execució d'ordres", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 7d4a7990ebd..058c4436fc5 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -233,6 +233,15 @@ "close": "Browser schließen" } }, + "codeblock": { + "tooltips": { + "expand": "Code-Block erweitern", + "collapse": "Code-Block reduzieren", + "enable_wrap": "Zeilenumbruch aktivieren", + "disable_wrap": "Zeilenumbruch deaktivieren", + "copy_code": "Code kopieren" + } + }, "systemPromptWarning": "WARNUNG: Benutzerdefinierte Systemaufforderung aktiv. Dies kann die Funktionalität erheblich beeinträchtigen und zu unvorhersehbarem Verhalten führen.", "shellIntegration": { "title": "Befehlsausführungswarnung", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index f810143084f..05efd561a5e 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -233,6 +233,15 @@ "close": "Close browser" } }, + "codeblock": { + "tooltips": { + "expand": "Expand code block", + "collapse": "Collapse code block", + "enable_wrap": "Enable word wrap", + "disable_wrap": "Disable word wrap", + "copy_code": "Copy code" + } + }, "systemPromptWarning": "WARNING: Custom system prompt override active. This can severely break functionality and cause unpredictable behavior.", "shellIntegration": { "title": "Command Execution Warning", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index afabbb6f64d..84b40091d11 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -233,6 +233,15 @@ "close": "Cerrar navegador" } }, + "codeblock": { + "tooltips": { + "expand": "Expandir bloque de código", + "collapse": "Contraer bloque de código", + "enable_wrap": "Activar ajuste de línea", + "disable_wrap": "Desactivar ajuste de línea", + "copy_code": "Copiar código" + } + }, "systemPromptWarning": "ADVERTENCIA: Anulación de instrucciones del sistema personalizada activa. Esto puede romper gravemente la funcionalidad y causar un comportamiento impredecible.", "shellIntegration": { "title": "Advertencia de ejecución de comandos", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 0627715e619..4af00e9fc67 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -233,6 +233,15 @@ "close": "Fermer le navigateur" } }, + "codeblock": { + "tooltips": { + "expand": "Développer le bloc de code", + "collapse": "Réduire le bloc de code", + "enable_wrap": "Activer le retour à la ligne", + "disable_wrap": "Désactiver le retour à la ligne", + "copy_code": "Copier le code" + } + }, "systemPromptWarning": "AVERTISSEMENT : Remplacement d'instructions système personnalisées actif. Cela peut gravement perturber la fonctionnalité et provoquer un comportement imprévisible.", "shellIntegration": { "title": "Avertissement d'exécution de commande", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 62aedebafda..7845ca97ad2 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -233,6 +233,15 @@ "close": "ब्राउज़र बंद करें" } }, + "codeblock": { + "tooltips": { + "expand": "कोड ब्लॉक का विस्तार करें", + "collapse": "कोड ब्लॉक को संकुचित करें", + "enable_wrap": "वर्ड रैप सक्षम करें", + "disable_wrap": "वर्ड रैप अक्षम करें", + "copy_code": "कोड कॉपी करें" + } + }, "systemPromptWarning": "चेतावनी: कस्टम सिस्टम प्रॉम्प्ट ओवरराइड सक्रिय है। यह कार्यक्षमता को गंभीर रूप से बाधित कर सकता है और अनियमित व्यवहार का कारण बन सकता है.", "shellIntegration": { "title": "कमांड निष्पादन चेतावनी", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index ac5163c69a7..55e2d272627 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -233,6 +233,15 @@ "close": "Chiudi browser" } }, + "codeblock": { + "tooltips": { + "expand": "Espandi blocco di codice", + "collapse": "Comprimi blocco di codice", + "enable_wrap": "Attiva a capo automatico", + "disable_wrap": "Disattiva a capo automatico", + "copy_code": "Copia codice" + } + }, "systemPromptWarning": "ATTENZIONE: Sovrascrittura personalizzata delle istruzioni di sistema attiva. Questo può compromettere gravemente le funzionalità e causare comportamenti imprevedibili.", "shellIntegration": { "title": "Avviso di esecuzione comando", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index f31b8575a9a..6530e3f3c47 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -233,6 +233,15 @@ "close": "ブラウザを閉じる" } }, + "codeblock": { + "tooltips": { + "expand": "コードブロックを展開", + "collapse": "コードブロックを折りたたむ", + "enable_wrap": "折り返しを有効化", + "disable_wrap": "折り返しを無効化", + "copy_code": "コードをコピー" + } + }, "systemPromptWarning": "警告:カスタムシステムプロンプトの上書きが有効です。これにより機能が深刻に損なわれ、予測不可能な動作が発生する可能性があります。", "shellIntegration": { "title": "コマンド実行警告", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 1354a7cce82..827ec0cfea5 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -233,6 +233,15 @@ "close": "브라우저 닫기" } }, + "codeblock": { + "tooltips": { + "expand": "코드 블록 확장", + "collapse": "코드 블록 축소", + "enable_wrap": "자동 줄바꿈 활성화", + "disable_wrap": "자동 줄바꿈 비활성화", + "copy_code": "코드 복사" + } + }, "systemPromptWarning": "경고: 사용자 정의 시스템 프롬프트 재정의가 활성화되었습니다. 이로 인해 기능이 심각하게 손상되고 예측할 수 없는 동작이 발생할 수 있습니다.", "shellIntegration": { "title": "명령 실행 경고", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 0a7adbe7317..2597f2ba13d 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -233,6 +233,15 @@ "close": "Zamknij przeglądarkę" } }, + "codeblock": { + "tooltips": { + "expand": "Rozwiń blok kodu", + "collapse": "Zwiń blok kodu", + "enable_wrap": "Włącz zawijanie wierszy", + "disable_wrap": "Wyłącz zawijanie wierszy", + "copy_code": "Kopiuj kod" + } + }, "systemPromptWarning": "OSTRZEŻENIE: Aktywne niestandardowe zastąpienie instrukcji systemowych. Może to poważnie zakłócić funkcjonalność i powodować nieprzewidywalne zachowanie.", "shellIntegration": { "title": "Ostrzeżenie wykonania polecenia", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 0600ea3a85d..37820facbd2 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -233,6 +233,15 @@ "close": "Fechar navegador" } }, + "codeblock": { + "tooltips": { + "expand": "Expandir bloco de código", + "collapse": "Recolher bloco de código", + "enable_wrap": "Ativar quebra de linha", + "disable_wrap": "Desativar quebra de linha", + "copy_code": "Copiar código" + } + }, "systemPromptWarning": "AVISO: Substituição personalizada de instrução do sistema ativa. Isso pode comprometer gravemente a funcionalidade e causar comportamento imprevisível.", "shellIntegration": { "title": "Aviso de execução de comando", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 77ddc3cb227..5bd13934f1b 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -233,6 +233,15 @@ "close": "Закрыть браузер" } }, + "codeblock": { + "tooltips": { + "expand": "Развернуть блок кода", + "collapse": "Свернуть блок кода", + "enable_wrap": "Включить перенос строк", + "disable_wrap": "Отключить перенос строк", + "copy_code": "Копировать код" + } + }, "systemPromptWarning": "ПРЕДУПРЕЖДЕНИЕ: Активна пользовательская системная подсказка. Это может серьезно нарушить работу и вызвать непредсказуемое поведение.", "shellIntegration": { "title": "Предупреждение о выполнении команды", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 8003cdb0451..2ea0eaaf059 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -233,6 +233,15 @@ "close": "Tarayıcıyı kapat" } }, + "codeblock": { + "tooltips": { + "expand": "Kod bloğunu genişlet", + "collapse": "Kod bloğunu daralt", + "enable_wrap": "Satır kaydırmayı etkinleştir", + "disable_wrap": "Satır kaydırmayı devre dışı bırak", + "copy_code": "Kodu kopyala" + } + }, "systemPromptWarning": "UYARI: Özel sistem komut geçersiz kılma aktif. Bu işlevselliği ciddi şekilde bozabilir ve öngörülemeyen davranışlara neden olabilir.", "shellIntegration": { "title": "Komut Çalıştırma Uyarısı", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index ed913a6d6b4..7b9091dc08c 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -233,6 +233,15 @@ "close": "Đóng trình duyệt" } }, + "codeblock": { + "tooltips": { + "expand": "Mở rộng khối mã", + "collapse": "Thu gọn khối mã", + "enable_wrap": "Bật tự động xuống dòng", + "disable_wrap": "Tắt tự động xuống dòng", + "copy_code": "Sao chép mã" + } + }, "systemPromptWarning": "CẢNH BÁO: Đã kích hoạt ghi đè lệnh nhắc hệ thống tùy chỉnh. Điều này có thể phá vỡ nghiêm trọng chức năng và gây ra hành vi không thể dự đoán.", "shellIntegration": { "title": "Cảnh báo thực thi lệnh", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 961801edb10..0caff90987a 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -233,6 +233,15 @@ "close": "关闭浏览器" } }, + "codeblock": { + "tooltips": { + "expand": "展开代码块", + "collapse": "收起代码块", + "enable_wrap": "启用自动换行", + "disable_wrap": "禁用自动换行", + "copy_code": "复制代码" + } + }, "systemPromptWarning": "警告:自定义系统提示词覆盖已激活。这可能严重破坏功能并导致不可预测的行为。", "shellIntegration": { "title": "命令执行警告", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 03c64cc1807..884d86ddd7f 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -233,6 +233,15 @@ "close": "關閉瀏覽器" } }, + "codeblock": { + "tooltips": { + "expand": "展開程式碼區塊", + "collapse": "摺疊程式碼區塊", + "enable_wrap": "啟用自動換行", + "disable_wrap": "停用自動換行", + "copy_code": "複製程式碼" + } + }, "systemPromptWarning": "警告:自訂系統提示詞覆蓋已啟用。這可能嚴重破壞功能並導致不可預測的行為。", "shellIntegration": { "title": "命令執行警告", From ce8fbbdafa40f76fb8eba71fb22f857907f7f56d Mon Sep 17 00:00:00 2001 From: Daniel Trugman Date: Tue, 6 May 2025 04:49:11 +0100 Subject: [PATCH 022/228] Requesty provider fixes (#3193) Co-authored-by: Chris Estreich --- src/api/providers/__tests__/requesty.test.ts | 441 +++++++----------- src/api/providers/fetchers/cache.ts | 14 +- src/api/providers/requesty.ts | 119 ++++- src/core/webview/webviewMessageHandler.ts | 18 +- src/shared/WebviewMessage.ts | 1 + src/shared/api.ts | 11 +- .../src/components/settings/ApiOptions.tsx | 31 +- webview-ui/src/i18n/locales/ca/settings.json | 2 + webview-ui/src/i18n/locales/de/settings.json | 2 + webview-ui/src/i18n/locales/en/settings.json | 2 + webview-ui/src/i18n/locales/es/settings.json | 2 + webview-ui/src/i18n/locales/fr/settings.json | 2 + webview-ui/src/i18n/locales/hi/settings.json | 2 + webview-ui/src/i18n/locales/it/settings.json | 2 + webview-ui/src/i18n/locales/ja/settings.json | 2 + webview-ui/src/i18n/locales/ko/settings.json | 2 + webview-ui/src/i18n/locales/pl/settings.json | 2 + .../src/i18n/locales/pt-BR/settings.json | 2 + webview-ui/src/i18n/locales/ru/settings.json | 2 + webview-ui/src/i18n/locales/tr/settings.json | 2 + webview-ui/src/i18n/locales/vi/settings.json | 2 + .../src/i18n/locales/zh-CN/settings.json | 2 + .../src/i18n/locales/zh-TW/settings.json | 2 + webview-ui/src/oauth/urls.ts | 4 + 24 files changed, 356 insertions(+), 315 deletions(-) diff --git a/src/api/providers/__tests__/requesty.test.ts b/src/api/providers/__tests__/requesty.test.ts index 4cf583a89fc..3e6803c4aa3 100644 --- a/src/api/providers/__tests__/requesty.test.ts +++ b/src/api/providers/__tests__/requesty.test.ts @@ -2,338 +2,227 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { ApiHandlerOptions, ModelInfo } from "../../../shared/api" + import { RequestyHandler } from "../requesty" -import { convertToOpenAiMessages } from "../../transform/openai-format" -import { convertToR1Format } from "../../transform/r1-format" +import { ApiHandlerOptions } from "../../../shared/api" -// Mock OpenAI and transform functions jest.mock("openai") -jest.mock("../../transform/openai-format") -jest.mock("../../transform/r1-format") +jest.mock("delay", () => jest.fn(() => Promise.resolve())) jest.mock("../fetchers/cache", () => ({ - getModels: jest.fn().mockResolvedValue({ - "test-model": { - maxTokens: 8192, - contextWindow: 200_000, - supportsImages: true, - supportsComputerUse: true, - supportsPromptCache: true, - inputPrice: 3.0, - outputPrice: 15.0, - cacheWritesPrice: 3.75, - cacheReadsPrice: 0.3, - description: "Test model description", - }, + getModels: jest.fn().mockImplementation(() => { + return Promise.resolve({ + "coding/claude-3-7-sonnet": { + maxTokens: 8192, + contextWindow: 200000, + supportsImages: true, + supportsPromptCache: true, + supportsComputerUse: true, + inputPrice: 3, + outputPrice: 15, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + description: "Claude 3.7 Sonnet", + }, + }) }), })) describe("RequestyHandler", () => { - let handler: RequestyHandler - let mockCreate: jest.Mock - - const modelInfo: ModelInfo = { - maxTokens: 8192, - contextWindow: 200_000, - supportsImages: true, - supportsComputerUse: true, - supportsPromptCache: true, - inputPrice: 3.0, - outputPrice: 15.0, - cacheWritesPrice: 3.75, - cacheReadsPrice: 0.3, - description: - "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)", - } - - const defaultOptions: ApiHandlerOptions = { + const mockOptions: ApiHandlerOptions = { requestyApiKey: "test-key", - requestyModelId: "test-model", - openAiStreamingEnabled: true, - includeMaxTokens: true, // Add this to match the implementation + requestyModelId: "coding/claude-3-7-sonnet", } - beforeEach(() => { - // Clear mocks - jest.clearAllMocks() + beforeEach(() => jest.clearAllMocks()) + + it("initializes with correct options", () => { + const handler = new RequestyHandler(mockOptions) + expect(handler).toBeInstanceOf(RequestyHandler) + + expect(OpenAI).toHaveBeenCalledWith({ + baseURL: "https://router.requesty.ai/v1", + apiKey: mockOptions.requestyApiKey, + defaultHeaders: { + "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", + "X-Title": "Roo Code", + }, + }) + }) + + describe("fetchModel", () => { + it("returns correct model info when options are provided", async () => { + const handler = new RequestyHandler(mockOptions) + const result = await handler.fetchModel() + + expect(result).toMatchObject({ + id: mockOptions.requestyModelId, + info: { + maxTokens: 8192, + contextWindow: 200000, + supportsImages: true, + supportsPromptCache: true, + supportsComputerUse: true, + inputPrice: 3, + outputPrice: 15, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + description: "Claude 3.7 Sonnet", + }, + }) + }) + + it("returns default model info when options are not provided", async () => { + const handler = new RequestyHandler({}) + const result = await handler.fetchModel() + + expect(result).toMatchObject({ + id: mockOptions.requestyModelId, + info: { + maxTokens: 8192, + contextWindow: 200000, + supportsImages: true, + supportsPromptCache: true, + supportsComputerUse: true, + inputPrice: 3, + outputPrice: 15, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + description: "Claude 3.7 Sonnet", + }, + }) + }) + }) - // Setup mock create function that preserves params - mockCreate = jest.fn().mockImplementation((_params) => { - return { - [Symbol.asyncIterator]: async function* () { + describe("createMessage", () => { + it("generates correct stream chunks", async () => { + const handler = new RequestyHandler(mockOptions) + + const mockStream = { + async *[Symbol.asyncIterator]() { yield { - choices: [{ delta: { content: "Hello" } }], + id: mockOptions.requestyModelId, + choices: [{ delta: { content: "test response" } }], } yield { - choices: [{ delta: { content: " world" } }], + id: "test-id", + choices: [{ delta: {} }], usage: { - prompt_tokens: 30, - completion_tokens: 10, + prompt_tokens: 10, + completion_tokens: 20, prompt_tokens_details: { - cached_tokens: 15, caching_tokens: 5, + cached_tokens: 2, }, }, } }, } - }) - // Mock OpenAI constructor - ;(OpenAI as jest.MockedClass).mockImplementation( - () => - ({ - chat: { - completions: { - create: (params: any) => { - // Store params for verification - const result = mockCreate(params) - // Make params available for test assertions - ;(result as any).params = params - return result - }, - }, - }, - }) as unknown as OpenAI, - ) + // Mock OpenAI chat.completions.create + const mockCreate = jest.fn().mockResolvedValue(mockStream) - // Mock transform functions - ;(convertToOpenAiMessages as jest.Mock).mockImplementation((messages) => messages) - ;(convertToR1Format as jest.Mock).mockImplementation((messages) => messages) + ;(OpenAI as jest.MockedClass).prototype.chat = { + completions: { create: mockCreate }, + } as any - // Create handler instance - handler = new RequestyHandler(defaultOptions) - }) + const systemPrompt = "test system prompt" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "test message" }] - describe("constructor", () => { - it("should initialize with correct options", () => { - expect(OpenAI).toHaveBeenCalledWith({ - baseURL: "https://router.requesty.ai/v1", - apiKey: defaultOptions.requestyApiKey, - defaultHeaders: { - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - }, - }) - }) - }) + const generator = handler.createMessage(systemPrompt, messages) + const chunks = [] - describe("createMessage", () => { - const systemPrompt = "You are a helpful assistant" - const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }] + for await (const chunk of generator) { + chunks.push(chunk) + } - describe("with streaming enabled", () => { - beforeEach(() => { - const stream = { - [Symbol.asyncIterator]: async function* () { - yield { - choices: [{ delta: { content: "Hello" } }], - } - yield { - choices: [{ delta: { content: " world" } }], - usage: { - prompt_tokens: 30, - completion_tokens: 10, - prompt_tokens_details: { - cached_tokens: 15, - caching_tokens: 5, - }, - }, - } - }, - } - mockCreate.mockResolvedValue(stream) + // Verify stream chunks + expect(chunks).toHaveLength(2) // One text chunk and one usage chunk + expect(chunks[0]).toEqual({ type: "text", text: "test response" }) + expect(chunks[1]).toEqual({ + type: "usage", + inputTokens: 10, + outputTokens: 20, + cacheWriteTokens: 5, + cacheReadTokens: 2, + totalCost: expect.any(Number), }) - it("should handle streaming response correctly", async () => { - const stream = handler.createMessage(systemPrompt, messages) - const results = [] - - for await (const chunk of stream) { - results.push(chunk) - } - - expect(results).toEqual([ - { type: "text", text: "Hello" }, - { type: "text", text: " world" }, - { - type: "usage", - inputTokens: 30, - outputTokens: 10, - cacheWriteTokens: 5, - cacheReadTokens: 15, - totalCost: 0.00020325000000000003, // (10 * 3 / 1,000,000) + (5 * 3.75 / 1,000,000) + (15 * 0.3 / 1,000,000) + (10 * 15 / 1,000,000) (the ...0 is a fp skew) - }, - ]) - - // Get the actual params that were passed - const calls = mockCreate.mock.calls - expect(calls.length).toBe(1) - const actualParams = calls[0][0] - - expect(actualParams).toEqual({ - model: defaultOptions.requestyModelId, - temperature: 0, + // Verify OpenAI client was called with correct parameters + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + max_tokens: undefined, messages: [ { role: "system", - content: [ - { - cache_control: { - type: "ephemeral", - }, - text: systemPrompt, - type: "text", - }, - ], + content: "test system prompt", }, { role: "user", - content: [ - { - cache_control: { - type: "ephemeral", - }, - text: "Hello", - type: "text", - }, - ], + content: "test message", }, ], + model: "coding/claude-3-7-sonnet", stream: true, stream_options: { include_usage: true }, - max_tokens: modelInfo.maxTokens, - }) - }) - - it("should not include max_tokens when includeMaxTokens is false", async () => { - handler = new RequestyHandler({ - ...defaultOptions, - includeMaxTokens: false, - }) - - await handler.createMessage(systemPrompt, messages).next() - - expect(mockCreate).toHaveBeenCalledWith( - expect.not.objectContaining({ - max_tokens: expect.any(Number), - }), - ) - }) - - it("should handle deepseek-reasoner model format", async () => { - handler = new RequestyHandler({ - ...defaultOptions, - requestyModelId: "deepseek-reasoner", - }) - - await handler.createMessage(systemPrompt, messages).next() - - expect(convertToR1Format).toHaveBeenCalledWith([{ role: "user", content: systemPrompt }, ...messages]) - }) + temperature: undefined, + }), + ) }) - describe("with streaming disabled", () => { - beforeEach(() => { - handler = new RequestyHandler({ - ...defaultOptions, - openAiStreamingEnabled: false, - }) - - mockCreate.mockResolvedValue({ - choices: [{ message: { content: "Hello world" } }], - usage: { - prompt_tokens: 10, - completion_tokens: 5, - }, - }) - }) - - it("should handle non-streaming response correctly", async () => { - const stream = handler.createMessage(systemPrompt, messages) - const results = [] + it("handles API errors", async () => { + const handler = new RequestyHandler(mockOptions) + const mockError = new Error("API Error") + const mockCreate = jest.fn().mockRejectedValue(mockError) + ;(OpenAI as jest.MockedClass).prototype.chat = { + completions: { create: mockCreate }, + } as any - for await (const chunk of stream) { - results.push(chunk) - } - - expect(results).toEqual([ - { type: "text", text: "Hello world" }, - { - type: "usage", - inputTokens: 10, - outputTokens: 5, - cacheWriteTokens: 0, - cacheReadTokens: 0, - totalCost: 0.000105, // (10 * 3 / 1,000,000) + (5 * 15 / 1,000,000) - }, - ]) - - expect(mockCreate).toHaveBeenCalledWith({ - model: defaultOptions.requestyModelId, - messages: [ - { role: "user", content: systemPrompt }, - { - role: "user", - content: [ - { - cache_control: { - type: "ephemeral", - }, - text: "Hello", - type: "text", - }, - ], - }, - ], - }) - }) + const generator = handler.createMessage("test", []) + await expect(generator.next()).rejects.toThrow("API Error") }) }) - describe("getModel", () => { - it("should return correct model information", () => { - const result = handler.getModel() - expect(result).toEqual({ - id: defaultOptions.requestyModelId, - info: modelInfo, - }) - }) + describe("completePrompt", () => { + it("returns correct response", async () => { + const handler = new RequestyHandler(mockOptions) + const mockResponse = { choices: [{ message: { content: "test completion" } }] } - it("should use sane defaults when no model info provided", () => { - handler = new RequestyHandler(defaultOptions) - const result = handler.getModel() + const mockCreate = jest.fn().mockResolvedValue(mockResponse) + ;(OpenAI as jest.MockedClass).prototype.chat = { + completions: { create: mockCreate }, + } as any - expect(result).toEqual({ - id: defaultOptions.requestyModelId, - info: modelInfo, - }) - }) - }) + const result = await handler.completePrompt("test prompt") - describe("completePrompt", () => { - beforeEach(() => { - mockCreate.mockResolvedValue({ - choices: [{ message: { content: "Completed response" } }], - }) - }) + expect(result).toBe("test completion") - it("should complete prompt successfully", async () => { - const result = await handler.completePrompt("Test prompt") - expect(result).toBe("Completed response") expect(mockCreate).toHaveBeenCalledWith({ - model: defaultOptions.requestyModelId, - messages: [{ role: "user", content: "Test prompt" }], + model: mockOptions.requestyModelId, + max_tokens: undefined, + messages: [{ role: "system", content: "test prompt" }], + temperature: undefined, }) }) - it("should handle errors correctly", async () => { - const errorMessage = "API error" - mockCreate.mockRejectedValue(new Error(errorMessage)) + it("handles API errors", async () => { + const handler = new RequestyHandler(mockOptions) + const mockError = new Error("API Error") + const mockCreate = jest.fn().mockRejectedValue(mockError) + ;(OpenAI as jest.MockedClass).prototype.chat = { + completions: { create: mockCreate }, + } as any - await expect(handler.completePrompt("Test prompt")).rejects.toThrow( - `OpenAI completion error: ${errorMessage}`, - ) + await expect(handler.completePrompt("test prompt")).rejects.toThrow("API Error") + }) + + it("handles unexpected errors", async () => { + const handler = new RequestyHandler(mockOptions) + const mockCreate = jest.fn().mockRejectedValue(new Error("Unexpected error")) + ;(OpenAI as jest.MockedClass).prototype.chat = { + completions: { create: mockCreate }, + } as any + + await expect(handler.completePrompt("test prompt")).rejects.toThrow("Unexpected error") }) }) }) diff --git a/src/api/providers/fetchers/cache.ts b/src/api/providers/fetchers/cache.ts index ab6dcce0210..a1c2e9986d0 100644 --- a/src/api/providers/fetchers/cache.ts +++ b/src/api/providers/fetchers/cache.ts @@ -38,9 +38,8 @@ async function readModels(router: RouterName): Promise * @param router - The router to fetch models from. * @returns The models from the cache or the fetched models. */ -export const getModels = async (router: RouterName): Promise => { +export const getModels = async (router: RouterName, apiKey: string | undefined = undefined): Promise => { let models = memoryCache.get(router) - if (models) { // console.log(`[getModels] NodeCache hit for ${router} -> ${Object.keys(models).length}`) return models @@ -51,7 +50,8 @@ export const getModels = async (router: RouterName): Promise => { models = await getOpenRouterModels() break case "requesty": - models = await getRequestyModels() + // Requesty models endpoint requires an API key for per-user custom policies + models = await getRequestyModels(apiKey) break case "glama": models = await getGlamaModels() @@ -80,3 +80,11 @@ export const getModels = async (router: RouterName): Promise => { return models ?? {} } + +/** + * Flush models memory cache for a specific router + * @param router - The router to flush models for. + */ +export const flushModels = async (router: RouterName) => { + memoryCache.del(router) +} diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts index 9fe976bb51c..a8a18bd4ddd 100644 --- a/src/api/providers/requesty.ts +++ b/src/api/providers/requesty.ts @@ -1,11 +1,19 @@ import { Anthropic } from "@anthropic-ai/sdk" -import OpenAI from "openai" - -import { ModelInfo, ModelRecord, requestyDefaultModelId, requestyDefaultModelInfo } from "../../shared/api" +import { + ApiHandlerOptions, + ModelInfo, + ModelRecord, + requestyDefaultModelId, + requestyDefaultModelInfo, +} from "../../shared/api" +import { convertToOpenAiMessages } from "../transform/openai-format" import { calculateApiCostOpenAI } from "../../utils/cost" import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" -import { OpenAiHandler, OpenAiHandlerOptions } from "./openai" +import { SingleCompletionHandler } from "../" +import { BaseProvider } from "./base-provider" +import { DEFAULT_HEADERS } from "./constants" import { getModels } from "./fetchers/cache" +import OpenAI from "openai" // Requesty usage includes an extra field for Anthropic use cases. // Safely cast the prompt token details section to the appropriate structure. @@ -17,25 +25,28 @@ interface RequestyUsage extends OpenAI.CompletionUsage { total_cost?: number } -export class RequestyHandler extends OpenAiHandler { +type RequestyChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {} + +export class RequestyHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions protected models: ModelRecord = {} + private client: OpenAI - constructor(options: OpenAiHandlerOptions) { - if (!options.requestyApiKey) { - throw new Error("Requesty API key is required. Please provide it in the settings.") - } + constructor(options: ApiHandlerOptions) { + super() + this.options = options + + const apiKey = this.options.requestyApiKey ?? "not-provided" + const baseURL = "https://router.requesty.ai/v1" - super({ - ...options, - openAiApiKey: options.requestyApiKey, - openAiModelId: options.requestyModelId ?? requestyDefaultModelId, - openAiBaseUrl: "https://router.requesty.ai/v1", - }) + const defaultHeaders = DEFAULT_HEADERS + + this.client = new OpenAI({ baseURL, apiKey, defaultHeaders }) } - override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + public async fetchModel() { this.models = await getModels("requesty") - yield* super.createMessage(systemPrompt, messages) + return this.getModel() } override getModel(): { id: string; info: ModelInfo } { @@ -44,7 +55,7 @@ export class RequestyHandler extends OpenAiHandler { return { id, info } } - protected override processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { + protected processUsageMetrics(usage: any, modelInfo?: ModelInfo): ApiStreamUsageChunk { const requestyUsage = usage as RequestyUsage const inputTokens = requestyUsage?.prompt_tokens || 0 const outputTokens = requestyUsage?.completion_tokens || 0 @@ -64,8 +75,74 @@ export class RequestyHandler extends OpenAiHandler { } } - override async completePrompt(prompt: string): Promise { - this.models = await getModels("requesty") - return super.completePrompt(prompt) + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = await this.fetchModel() + + let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [ + { role: "system", content: systemPrompt }, + ...convertToOpenAiMessages(messages), + ] + + let maxTokens = undefined + if (this.options.includeMaxTokens) { + maxTokens = model.info.maxTokens + } + + const temperature = this.options.modelTemperature + + const completionParams: RequestyChatCompletionParams = { + model: model.id, + max_tokens: maxTokens, + messages: openAiMessages, + temperature: temperature, + stream: true, + stream_options: { include_usage: true }, + } + + const stream = await this.client.chat.completions.create(completionParams) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + if (delta?.content) { + yield { + type: "text", + text: delta.content, + } + } + + if (delta && "reasoning_content" in delta && delta.reasoning_content) { + yield { + type: "reasoning", + text: (delta.reasoning_content as string | undefined) || "", + } + } + + if (chunk.usage) { + yield this.processUsageMetrics(chunk.usage, model.info) + } + } + } + + async completePrompt(prompt: string): Promise { + const model = await this.fetchModel() + + let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [{ role: "system", content: prompt }] + + let maxTokens = undefined + if (this.options.includeMaxTokens) { + maxTokens = model.info.maxTokens + } + + const temperature = this.options.modelTemperature + + const completionParams: RequestyChatCompletionParams = { + model: model.id, + max_tokens: maxTokens, + messages: openAiMessages, + temperature: temperature, + } + + const response: OpenAI.Chat.ChatCompletion = await this.client.chat.completions.create(completionParams) + return response.choices[0]?.message.content || "" } } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c3ee6d8c3ac..0a2c636ee4e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -6,7 +6,7 @@ import * as vscode from "vscode" import { ClineProvider } from "./ClineProvider" import { Language, ApiConfigMeta } from "../../schemas" import { changeLanguage, t } from "../../i18n" -import { ApiConfiguration } from "../../shared/api" +import { ApiConfiguration, RouterName, toRouterName } from "../../shared/api" import { supportPrompt } from "../../shared/support-prompt" import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage" @@ -34,7 +34,7 @@ import { TelemetrySetting } from "../../shared/TelemetrySetting" import { getWorkspacePath } from "../../utils/path" import { Mode, defaultModeSlug } from "../../shared/modes" import { GlobalState } from "../../schemas" -import { getModels } from "../../api/providers/fetchers/cache" +import { getModels, flushModels } from "../../api/providers/fetchers/cache" import { generateSystemPrompt } from "./generateSystemPrompt" const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"]) @@ -282,12 +282,18 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We case "resetState": await provider.resetState() break + case "flushRouterModels": + const routerName: RouterName = toRouterName(message.text) + await flushModels(routerName) + break case "requestRouterModels": + const { apiConfiguration } = await provider.getState() + const [openRouterModels, requestyModels, glamaModels, unboundModels] = await Promise.all([ - getModels("openrouter"), - getModels("requesty"), - getModels("glama"), - getModels("unbound"), + getModels("openrouter", apiConfiguration.openRouterApiKey), + getModels("requesty", apiConfiguration.requestyApiKey), + getModels("glama", apiConfiguration.glamaApiKey), + getModels("unbound", apiConfiguration.unboundApiKey), ]) provider.postMessageToWebview({ diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 34d996b7cf6..fe8dfff40ce 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -42,6 +42,7 @@ export interface WebviewMessage { | "importSettings" | "exportSettings" | "resetState" + | "flushRouterModels" | "requestRouterModels" | "requestOpenAiModels" | "requestOllamaModels" diff --git a/src/shared/api.ts b/src/shared/api.ts index 0acb82d45e4..43b7ad76434 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -437,7 +437,7 @@ export const glamaDefaultModelInfo: ModelInfo = { // Requesty // https://requesty.ai/router-2 -export const requestyDefaultModelId = "anthropic/claude-3-7-sonnet-latest" +export const requestyDefaultModelId = "coding/claude-3-7-sonnet" export const requestyDefaultModelInfo: ModelInfo = { maxTokens: 8192, contextWindow: 200_000, @@ -449,7 +449,7 @@ export const requestyDefaultModelInfo: ModelInfo = { cacheWritesPrice: 3.75, cacheReadsPrice: 0.3, description: - "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)", + "The best coding model, optimized by Requesty, and automatically routed to the fastest provider. Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)", } // OpenRouter @@ -1701,6 +1701,13 @@ export type RouterName = (typeof routerNames)[number] export const isRouterName = (value: string): value is RouterName => routerNames.includes(value as RouterName) +export function toRouterName(value?: string): RouterName { + if (value && isRouterName(value)) { + return value + } + throw new Error(`Invalid router name: ${value}`) +} + export type ModelRecord = Record export type RouterModels = Record diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index e662c1a3ade..f75724ba269 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -22,9 +22,9 @@ import { useOpenRouterModelProviders, OPENROUTER_DEFAULT_PROVIDER_NAME, } from "@src/components/ui/hooks/useOpenRouterModelProviders" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button } from "@src/components/ui" import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" -import { getRequestyAuthUrl, getGlamaAuthUrl } from "@src/oauth/urls" +import { getRequestyApiKeyUrl, getGlamaAuthUrl } from "@src/oauth/urls" // Providers import { Anthropic } from "./providers/Anthropic" @@ -75,6 +75,8 @@ const ApiOptions = ({ return Object.entries(headers) }) + const [requestyShowRefreshHint, setRequestyShowRefreshHint] = useState() + useEffect(() => { const propHeaders = apiConfiguration?.openAiHeaders || {} @@ -138,7 +140,7 @@ const ApiOptions = ({ info: selectedModelInfo, } = useSelectedModel(apiConfiguration) - const { data: routerModels } = useRouterModels() + const { data: routerModels, refetch: refetchRouterModels } = useRouterModels() // Update apiConfiguration.aiModelId whenever selectedModelId changes. useEffect(() => { @@ -373,13 +375,28 @@ const ApiOptions = ({ {t("settings:providers.apiKeyStorageNotice")}
{!apiConfiguration?.requestyApiKey && ( - + {t("settings:providers.getRequestyApiKey")} )} + + {requestyShowRefreshHint && ( +
+ {t("settings:providers.flushedModelsCache")} +
+ )} )} diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index ca7546b2a5f..38dc4c67bd3 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -119,6 +119,8 @@ "glamaApiKey": "Clau API de Glama", "getGlamaApiKey": "Obtenir clau API de Glama", "requestyApiKey": "Clau API de Requesty", + "flushModelsCache": "Netejar memòria cau de models", + "flushedModelsCache": "Memòria cau netejada, si us plau torna a obrir la vista de configuració", "getRequestyApiKey": "Obtenir clau API de Requesty", "anthropicApiKey": "Clau API d'Anthropic", "getAnthropicApiKey": "Obtenir clau API d'Anthropic", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index db4398402fa..e12ad8fa461 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -106,6 +106,8 @@ "awsCustomArnUse": "Geben Sie eine gültige Amazon Bedrock ARN für das Modell ein, das Sie verwenden möchten. Formatbeispiele:", "awsCustomArnDesc": "Stellen Sie sicher, dass die Region in der ARN mit Ihrer oben ausgewählten AWS-Region übereinstimmt.", "openRouterApiKey": "OpenRouter API-Schlüssel", + "flushModelsCache": "Modell-Cache leeren", + "flushedModelsCache": "Cache geleert, bitte öffnen Sie die Einstellungsansicht erneut", "getOpenRouterApiKey": "OpenRouter API-Schlüssel erhalten", "apiKeyStorageNotice": "API-Schlüssel werden sicher im VSCode Secret Storage gespeichert", "glamaApiKey": "Glama API-Schlüssel", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index b817318df65..6c042baf216 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -118,6 +118,8 @@ "headerValue": "Header value", "noCustomHeaders": "No custom headers defined. Click the + button to add one.", "requestyApiKey": "Requesty API Key", + "flushModelsCache": "Flush cached models", + "flushedModelsCache": "Flushed cache, please reopen the settings view", "getRequestyApiKey": "Get Requesty API Key", "openRouterTransformsText": "Compress prompts and message chains to the context size (OpenRouter Transforms)", "anthropicApiKey": "Anthropic API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 4a7191ffeb4..cedc110bf55 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -106,6 +106,8 @@ "awsCustomArnUse": "Ingrese un ARN de Amazon Bedrock válido para el modelo que desea utilizar. Ejemplos de formato:", "awsCustomArnDesc": "Asegúrese de que la región en el ARN coincida con la región de AWS seleccionada anteriormente.", "openRouterApiKey": "Clave API de OpenRouter", + "flushModelsCache": "Limpiar modelos en caché", + "flushedModelsCache": "Caché limpiada, por favor vuelva a abrir la vista de configuración", "getOpenRouterApiKey": "Obtener clave API de OpenRouter", "apiKeyStorageNotice": "Las claves API se almacenan de forma segura en el Almacenamiento Secreto de VSCode", "glamaApiKey": "Clave API de Glama", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index b2a1ef33e45..615b252d7f3 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -106,6 +106,8 @@ "awsCustomArnUse": "Entrez un ARN Amazon Bedrock valide pour le modèle que vous souhaitez utiliser. Exemples de format :", "awsCustomArnDesc": "Assurez-vous que la région dans l'ARN correspond à la région AWS sélectionnée ci-dessus.", "openRouterApiKey": "Clé API OpenRouter", + "flushModelsCache": "Vider le cache des modèles", + "flushedModelsCache": "Cache vidé, veuillez rouvrir la vue des paramètres", "getOpenRouterApiKey": "Obtenir la clé API OpenRouter", "apiKeyStorageNotice": "Les clés API sont stockées en toute sécurité dans le stockage sécurisé de VSCode", "glamaApiKey": "Clé API Glama", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index f62d6373564..d5c948b30d9 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -118,6 +118,8 @@ "headerValue": "हेडर मूल्य", "noCustomHeaders": "कोई कस्टम हेडर परिभाषित नहीं है। एक जोड़ने के लिए + बटन पर क्लिक करें।", "requestyApiKey": "Requesty API कुंजी", + "flushModelsCache": "मॉडल कैश साफ़ करें", + "flushedModelsCache": "कैश साफ़ किया गया, कृपया सेटिंग्स व्यू को फिर से खोलें", "getRequestyApiKey": "Requesty API कुंजी प्राप्त करें", "openRouterTransformsText": "संदर्भ आकार के लिए प्रॉम्प्ट और संदेश श्रृंखलाओं को संपीड़ित करें (OpenRouter ट्रांसफॉर्म)", "anthropicApiKey": "Anthropic API कुंजी", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 1c20c2d5957..d7dfefda360 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -118,6 +118,8 @@ "headerValue": "Valore intestazione", "noCustomHeaders": "Nessuna intestazione personalizzata definita. Fai clic sul pulsante + per aggiungerne una.", "requestyApiKey": "Chiave API Requesty", + "flushModelsCache": "Svuota cache dei modelli", + "flushedModelsCache": "Cache svuotata, riapri la vista delle impostazioni", "getRequestyApiKey": "Ottieni chiave API Requesty", "openRouterTransformsText": "Comprimi prompt e catene di messaggi alla dimensione del contesto (Trasformazioni OpenRouter)", "anthropicApiKey": "Chiave API Anthropic", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 1ed0ab7c269..98e30541212 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -118,6 +118,8 @@ "headerValue": "ヘッダー値", "noCustomHeaders": "カスタムヘッダーが定義されていません。+ ボタンをクリックして追加してください。", "requestyApiKey": "Requesty APIキー", + "flushModelsCache": "モデルキャッシュをクリア", + "flushedModelsCache": "キャッシュをクリアしました。設定ビューを再開してください", "getRequestyApiKey": "Requesty APIキーを取得", "openRouterTransformsText": "プロンプトとメッセージチェーンをコンテキストサイズに圧縮 (OpenRouter Transforms)", "anthropicApiKey": "Anthropic APIキー", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index b0ceaee5304..9431a9bc5b2 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -118,6 +118,8 @@ "headerValue": "헤더 값", "noCustomHeaders": "정의된 사용자 정의 헤더가 없습니다. + 버튼을 클릭하여 추가하세요.", "requestyApiKey": "Requesty API 키", + "flushModelsCache": "모델 캐시 지우기", + "flushedModelsCache": "캐시가 지워졌습니다. 설정 보기를 다시 열어주세요", "getRequestyApiKey": "Requesty API 키 받기", "openRouterTransformsText": "프롬프트와 메시지 체인을 컨텍스트 크기로 압축 (OpenRouter Transforms)", "anthropicApiKey": "Anthropic API 키", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index c4f28f19f61..ad3da8d9a85 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -118,6 +118,8 @@ "headerValue": "Wartość nagłówka", "noCustomHeaders": "Brak zdefiniowanych niestandardowych nagłówków. Kliknij przycisk +, aby dodać.", "requestyApiKey": "Klucz API Requesty", + "flushModelsCache": "Wyczyść pamięć podręczną modeli", + "flushedModelsCache": "Pamięć podręczna wyczyszczona, proszę ponownie otworzyć widok ustawień", "getRequestyApiKey": "Uzyskaj klucz API Requesty", "openRouterTransformsText": "Kompresuj podpowiedzi i łańcuchy wiadomości do rozmiaru kontekstu (Transformacje OpenRouter)", "anthropicApiKey": "Klucz API Anthropic", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 76b3f597d29..b6f1f752539 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -106,6 +106,8 @@ "awsCustomArnUse": "Insira um ARN Amazon Bedrock válido para o modelo que deseja usar. Exemplos de formato:", "awsCustomArnDesc": "Certifique-se de que a região no ARN corresponde à região AWS selecionada acima.", "openRouterApiKey": "Chave de API OpenRouter", + "flushModelsCache": "Limpar cache de modelos", + "flushedModelsCache": "Cache limpo, por favor reabra a visualização de configurações", "getOpenRouterApiKey": "Obter chave de API OpenRouter", "apiKeyStorageNotice": "As chaves de API são armazenadas com segurança no Armazenamento Secreto do VSCode", "glamaApiKey": "Chave de API Glama", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index f46d1d98374..6fa1cc03262 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -118,6 +118,8 @@ "headerValue": "Значение заголовка", "noCustomHeaders": "Пользовательские заголовки не определены. Нажмите кнопку +, чтобы добавить.", "requestyApiKey": "Requesty API-ключ", + "flushModelsCache": "Очистить кэш моделей", + "flushedModelsCache": "Кэш очищен, пожалуйста, переоткройте представление настроек", "getRequestyApiKey": "Получить Requesty API-ключ", "openRouterTransformsText": "Сжимать подсказки и цепочки сообщений до размера контекста (OpenRouter Transforms)", "anthropicApiKey": "Anthropic API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 580e35ee043..af7b4a217b0 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -106,6 +106,8 @@ "awsCustomArnUse": "Kullanmak istediğiniz model için geçerli bir Amazon Bedrock ARN'si girin. Format örnekleri:", "awsCustomArnDesc": "ARN içindeki bölgenin yukarıda seçilen AWS Bölgesiyle eşleştiğinden emin olun.", "openRouterApiKey": "OpenRouter API Anahtarı", + "flushModelsCache": "Model önbelleğini temizle", + "flushedModelsCache": "Önbellek temizlendi, lütfen ayarlar görünümünü yeniden açın", "getOpenRouterApiKey": "OpenRouter API Anahtarı Al", "apiKeyStorageNotice": "API anahtarları VSCode'un Gizli Depolamasında güvenli bir şekilde saklanır", "glamaApiKey": "Glama API Anahtarı", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 51174c4d4f4..fe5a0bfd755 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -118,6 +118,8 @@ "headerValue": "Giá trị tiêu đề", "noCustomHeaders": "Chưa có tiêu đề tùy chỉnh nào được định nghĩa. Nhấp vào nút + để thêm.", "requestyApiKey": "Khóa API Requesty", + "flushModelsCache": "Xóa bộ nhớ đệm mô hình", + "flushedModelsCache": "Đã xóa bộ nhớ đệm, vui lòng mở lại chế độ xem cài đặt", "getRequestyApiKey": "Lấy khóa API Requesty", "anthropicApiKey": "Khóa API Anthropic", "getAnthropicApiKey": "Lấy khóa API Anthropic", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 6a706361e2f..0e0cf7f0feb 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -118,6 +118,8 @@ "glamaApiKey": "Glama API 密钥", "getGlamaApiKey": "获取 Glama API 密钥", "requestyApiKey": "Requesty API 密钥", + "flushModelsCache": "清除模型缓存", + "flushedModelsCache": "缓存已清除,请重新打开设置视图", "getRequestyApiKey": "获取 Requesty API 密钥", "openRouterTransformsText": "自动压缩提示词和消息链到上下文长度限制内 (OpenRouter转换)", "anthropicApiKey": "Anthropic API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 75afc650dd6..6a58cc8a6f7 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -118,6 +118,8 @@ "headerValue": "標頭值", "noCustomHeaders": "尚未定義自訂標頭。點擊 + 按鈕以新增。", "requestyApiKey": "Requesty API 金鑰", + "flushModelsCache": "清除模型快取", + "flushedModelsCache": "快取已清除,請重新開啟設定視圖", "getRequestyApiKey": "取得 Requesty API 金鑰", "openRouterTransformsText": "將提示和訊息鏈壓縮到上下文大小 (OpenRouter 轉換)", "anthropicApiKey": "Anthropic API 金鑰", diff --git a/webview-ui/src/oauth/urls.ts b/webview-ui/src/oauth/urls.ts index 46a1815377c..e75b65b9c7b 100644 --- a/webview-ui/src/oauth/urls.ts +++ b/webview-ui/src/oauth/urls.ts @@ -14,3 +14,7 @@ export function getOpenRouterAuthUrl(uriScheme?: string) { export function getRequestyAuthUrl(uriScheme?: string) { return `https://app.requesty.ai/oauth/authorize?callback_url=${getCallbackUrl("requesty", uriScheme)}` } + +export function getRequestyApiKeyUrl() { + return "https://app.requesty.ai/api-keys" +} From a649a53ec6c3a8beea5831226b7dc2a991af4429 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Mon, 5 May 2025 23:09:24 -0700 Subject: [PATCH 023/228] Move remaining provider settings into separate components (#3208) --- evals/packages/types/src/roo-code.ts | 2 - src/core/webview/ClineProvider.ts | 4 +- src/exports/roo-code.d.ts | 1 - src/exports/types.ts | 1 - src/schemas/index.ts | 2 - src/shared/api.ts | 2 +- .../src/components/settings/ApiOptions.tsx | 515 ++++-------------- .../src/components/settings/constants.ts | 18 +- .../settings/providers/BedrockCustomArn.tsx | 53 ++ .../components/settings/providers/Chutes.tsx | 50 ++ .../settings/providers/DeepSeek.tsx | 50 ++ .../components/settings/providers/Glama.tsx | 63 +++ .../components/settings/providers/Groq.tsx | 50 ++ .../components/settings/providers/Mistral.tsx | 67 +++ .../settings/providers/OpenRouter.tsx | 65 ++- .../settings/providers/Requesty.tsx | 97 ++++ .../RequestyBalanceDisplay.tsx | 0 .../components/settings/providers/Unbound.tsx | 61 +++ .../src/components/settings/providers/XAI.tsx | 50 ++ .../components/settings/providers/index.ts | 18 + .../components/ui/hooks/useSelectedModel.ts | 203 ++++--- webview-ui/src/i18n/locales/ca/settings.json | 6 +- webview-ui/src/i18n/locales/de/settings.json | 6 +- webview-ui/src/i18n/locales/en/settings.json | 6 +- webview-ui/src/i18n/locales/es/settings.json | 6 +- webview-ui/src/i18n/locales/fr/settings.json | 6 +- webview-ui/src/i18n/locales/hi/settings.json | 6 +- webview-ui/src/i18n/locales/it/settings.json | 6 +- webview-ui/src/i18n/locales/ja/settings.json | 6 +- webview-ui/src/i18n/locales/ko/settings.json | 6 +- webview-ui/src/i18n/locales/pl/settings.json | 6 +- .../src/i18n/locales/pt-BR/settings.json | 6 +- webview-ui/src/i18n/locales/ru/settings.json | 6 +- webview-ui/src/i18n/locales/tr/settings.json | 6 +- webview-ui/src/i18n/locales/vi/settings.json | 6 +- .../src/i18n/locales/zh-CN/settings.json | 6 +- .../src/i18n/locales/zh-TW/settings.json | 6 +- webview-ui/src/oauth/urls.ts | 7 +- 38 files changed, 917 insertions(+), 558 deletions(-) create mode 100644 webview-ui/src/components/settings/providers/BedrockCustomArn.tsx create mode 100644 webview-ui/src/components/settings/providers/Chutes.tsx create mode 100644 webview-ui/src/components/settings/providers/DeepSeek.tsx create mode 100644 webview-ui/src/components/settings/providers/Glama.tsx create mode 100644 webview-ui/src/components/settings/providers/Groq.tsx create mode 100644 webview-ui/src/components/settings/providers/Mistral.tsx create mode 100644 webview-ui/src/components/settings/providers/Requesty.tsx rename webview-ui/src/components/settings/{ => providers}/RequestyBalanceDisplay.tsx (100%) create mode 100644 webview-ui/src/components/settings/providers/Unbound.tsx create mode 100644 webview-ui/src/components/settings/providers/XAI.tsx create mode 100644 webview-ui/src/components/settings/providers/index.ts diff --git a/evals/packages/types/src/roo-code.ts b/evals/packages/types/src/roo-code.ts index f530dc3ea1e..693192f9152 100644 --- a/evals/packages/types/src/roo-code.ts +++ b/evals/packages/types/src/roo-code.ts @@ -355,7 +355,6 @@ export const providerSettingsSchema = z.object({ awsRegion: z.string().optional(), awsUseCrossRegionInference: z.boolean().optional(), awsUsePromptCache: z.boolean().optional(), - awspromptCacheId: z.string().optional(), awsProfile: z.string().optional(), awsUseProfile: z.boolean().optional(), awsCustomArn: z.string().optional(), @@ -455,7 +454,6 @@ const providerSettingsRecord: ProviderSettingsRecord = { awsRegion: undefined, awsUseCrossRegionInference: undefined, awsUsePromptCache: undefined, - awspromptCacheId: undefined, awsProfile: undefined, awsUseProfile: undefined, awsCustomArn: undefined, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index ca9b63d4ae2..899d16aae2c 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -13,8 +13,8 @@ import { GlobalState, ProviderSettings, RooCodeSettings } from "../../schemas" import { t } from "../../i18n" import { setPanel } from "../../activate/registerCommands" import { + ProviderName, ApiConfiguration, - ApiProvider, requestyDefaultModelId, openRouterDefaultModelId, glamaDefaultModelId, @@ -1297,7 +1297,7 @@ export class ClineProvider extends EventEmitter implements const customModes = await this.customModesManager.getCustomModes() // Determine apiProvider with the same logic as before. - const apiProvider: ApiProvider = stateValues.apiProvider ? stateValues.apiProvider : "anthropic" + const apiProvider: ProviderName = stateValues.apiProvider ? stateValues.apiProvider : "anthropic" // Build the apiConfiguration object combining state values and secrets. const providerSettings = this.contextProxy.getProviderSettings() diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 8a7452dfb8d..0f2250f6c2e 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -42,7 +42,6 @@ type ProviderSettings = { awsRegion?: string | undefined awsUseCrossRegionInference?: boolean | undefined awsUsePromptCache?: boolean | undefined - awspromptCacheId?: string | undefined awsProfile?: string | undefined awsUseProfile?: boolean | undefined awsCustomArn?: string | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index b3d441e9ab6..1cb395156e7 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -43,7 +43,6 @@ type ProviderSettings = { awsRegion?: string | undefined awsUseCrossRegionInference?: boolean | undefined awsUsePromptCache?: boolean | undefined - awspromptCacheId?: string | undefined awsProfile?: string | undefined awsUseProfile?: boolean | undefined awsCustomArn?: string | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index e69fff6dcc8..0a59e5f13cd 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -366,7 +366,6 @@ export const providerSettingsSchema = z.object({ awsRegion: z.string().optional(), awsUseCrossRegionInference: z.boolean().optional(), awsUsePromptCache: z.boolean().optional(), - awspromptCacheId: z.string().optional(), awsProfile: z.string().optional(), awsUseProfile: z.boolean().optional(), awsCustomArn: z.string().optional(), @@ -471,7 +470,6 @@ const providerSettingsRecord: ProviderSettingsRecord = { awsRegion: undefined, awsUseCrossRegionInference: undefined, awsUsePromptCache: undefined, - awspromptCacheId: undefined, awsProfile: undefined, awsUseProfile: undefined, awsCustomArn: undefined, diff --git a/src/shared/api.ts b/src/shared/api.ts index 43b7ad76434..7b635a81748 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1,6 +1,6 @@ import { ModelInfo, ProviderName, ProviderSettings } from "../schemas" -export type { ModelInfo, ProviderName as ApiProvider } +export type { ModelInfo, ProviderName } export type ApiHandlerOptions = Omit diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index f75724ba269..70aa53aee63 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,55 +1,55 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react" import { useDebounce } from "react-use" -import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { ExternalLinkIcon } from "@radix-ui/react-icons" +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { - ApiConfiguration, - glamaDefaultModelId, - mistralDefaultModelId, + type ProviderName, + type ApiConfiguration, openRouterDefaultModelId, - unboundDefaultModelId, requestyDefaultModelId, - ApiProvider, + glamaDefaultModelId, + unboundDefaultModelId, } from "@roo/shared/api" import { vscode } from "@src/utils/vscode" -import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@src/utils/validate" +import { validateApiConfiguration, validateModelId } from "@src/utils/validate" import { useAppTranslation } from "@src/i18n/TranslationContext" import { useRouterModels } from "@src/components/ui/hooks/useRouterModels" import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui" + import { - useOpenRouterModelProviders, - OPENROUTER_DEFAULT_PROVIDER_NAME, -} from "@src/components/ui/hooks/useOpenRouterModelProviders" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button } from "@src/components/ui" -import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" -import { getRequestyApiKeyUrl, getGlamaAuthUrl } from "@src/oauth/urls" - -// Providers -import { Anthropic } from "./providers/Anthropic" -import { Bedrock } from "./providers/Bedrock" -import { Gemini } from "./providers/Gemini" -import { LMStudio } from "./providers/LMStudio" -import { Ollama } from "./providers/Ollama" -import { OpenAI } from "./providers/OpenAI" -import { OpenAICompatible } from "./providers/OpenAICompatible" -import { OpenRouter } from "./providers/OpenRouter" -import { Vertex } from "./providers/Vertex" -import { VSCodeLM } from "./providers/VSCodeLM" + Anthropic, + Bedrock, + Chutes, + DeepSeek, + Gemini, + Glama, + Groq, + LMStudio, + Mistral, + Ollama, + OpenAI, + OpenAICompatible, + OpenRouter, + Requesty, + Unbound, + Vertex, + VSCodeLM, + XAI, +} from "./providers" import { MODELS_BY_PROVIDER, PROVIDERS, REASONING_MODELS } from "./constants" import { inputEventTransform, noTransform } from "./transforms" import { ModelInfoView } from "./ModelInfoView" -import { ModelPicker } from "./ModelPicker" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" -import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay" import { ReasoningEffort } from "./ReasoningEffort" import { PromptCachingControl } from "./PromptCachingControl" import { DiffSettingsControl } from "./DiffSettingsControl" import { TemperatureControl } from "./TemperatureControl" import { RateLimitSecondsControl } from "./RateLimitSecondsControl" +import { BedrockCustomArn } from "./providers/BedrockCustomArn" export interface ApiOptionsProps { uriScheme: string | undefined @@ -75,8 +75,6 @@ const ApiOptions = ({ return Object.entries(headers) }) - const [requestyShowRefreshHint, setRequestyShowRefreshHint] = useState() - useEffect(() => { const propHeaders = apiConfiguration?.openAiHeaders || {} @@ -106,13 +104,14 @@ const ApiOptions = ({ return result } - // Debounced effect to update the main configuration when local customHeaders state stabilizes. + // Debounced effect to update the main configuration when local + // customHeaders state stabilizes. useDebounce( () => { const currentConfigHeaders = apiConfiguration?.openAiHeaders || {} const newHeadersObject = convertHeadersToObject(customHeaders) - // Only update if the processed object is different from the current config + // Only update if the processed object is different from the current config. if (JSON.stringify(currentConfigHeaders) !== JSON.stringify(newHeadersObject)) { setApiConfigurationField("openAiHeaders", newHeadersObject) } @@ -142,7 +141,7 @@ const ApiOptions = ({ const { data: routerModels, refetch: refetchRouterModels } = useRouterModels() - // Update apiConfiguration.aiModelId whenever selectedModelId changes. + // Update `apiModelId` whenever `selectedModelId` changes. useEffect(() => { if (selectedModelId) { setApiConfigurationField("apiModelId", selectedModelId) @@ -193,16 +192,7 @@ const ApiOptions = ({ setErrorMessage(apiValidationResult) }, [apiConfiguration, routerModels, setErrorMessage]) - const { data: openRouterModelProviders } = useOpenRouterModelProviders(apiConfiguration?.openRouterModelId, { - enabled: - selectedProvider === "openrouter" && - !!apiConfiguration?.openRouterModelId && - routerModels?.openrouter && - Object.keys(routerModels.openrouter).length > 1 && - apiConfiguration.openRouterModelId in routerModels.openrouter, - }) - - const selectedProviderModelOptions = useMemo( + const selectedProviderModels = useMemo( () => MODELS_BY_PROVIDER[selectedProvider] ? Object.keys(MODELS_BY_PROVIDER[selectedProvider]).map((modelId) => ({ @@ -213,37 +203,8 @@ const ApiOptions = ({ [selectedProvider], ) - // Custom URL path mappings for providers with different slugs. - const providerUrlSlugs: Record = { - "openai-native": "openai", - openai: "openai-compatible", - } - - // Helper function to get provider display name from PROVIDERS constant. - const getProviderDisplayName = (providerKey: string): string | undefined => { - const provider = PROVIDERS.find((p) => p.value === providerKey) - return provider?.label - } - - // Helper function to get the documentation URL and name for the currently selected provider - const getSelectedProviderDocUrl = (): { url: string; name: string } | undefined => { - const displayName = getProviderDisplayName(selectedProvider) - - if (!displayName) { - return undefined - } - - // Get the URL slug - use custom mapping if available, otherwise use the provider key - const urlSlug = providerUrlSlugs[selectedProvider] || selectedProvider - - return { - url: `https://docs.roocode.com/providers/${urlSlug}`, - name: displayName, - } - } - - const onApiProviderChange = useCallback( - (value: ApiProvider) => { + const onProviderChange = useCallback( + (value: ProviderName) => { // It would be much easier to have a single attribute that stores // the modelId, but we have a separate attribute for each of // OpenRouter, Glama, Unbound, and Requesty. @@ -285,25 +246,40 @@ const ApiOptions = ({ ], ) + const docs = useMemo(() => { + const provider = PROVIDERS.find(({ value }) => value === selectedProvider) + const name = provider?.label + + if (!name) { + return undefined + } + + // Get the URL slug - use custom mapping if available, otherwise use the provider key. + const slugs: Record = { + "openai-native": "openai", + openai: "openai-compatible", + } + + return { + url: `https://docs.roocode.com/providers/${slugs[selectedProvider] || selectedProvider}`, + name, + } + }, [selectedProvider]) + return (
- {getSelectedProviderDocUrl() && ( + {docs && (
- - {t("settings:providers.providerDocumentation", { - provider: getSelectedProviderDocUrl()!.name, - })} + + {t("settings:providers.providerDocumentation", { provider: docs.name })}
)}
- onProviderChange(value as ProviderName)}> @@ -323,81 +299,41 @@ const ApiOptions = ({ )} - {selectedProvider === "anthropic" && ( - + {selectedProvider === "requesty" && ( + )} {selectedProvider === "glama" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.glamaApiKey && ( - - {t("settings:providers.getGlamaApiKey")} - - )} - + )} - {selectedProvider === "requesty" && ( - <> - -
- - {apiConfiguration?.requestyApiKey && ( - - )} -
-
-
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.requestyApiKey && ( - - {t("settings:providers.getRequestyApiKey")} - - )} - - {requestyShowRefreshHint && ( -
- {t("settings:providers.flushedModelsCache")} -
- )} - + {selectedProvider === "unbound" && ( + + )} + + {selectedProvider === "anthropic" && ( + )} {selectedProvider === "openai-native" && ( @@ -405,42 +341,7 @@ const ApiOptions = ({ )} {selectedProvider === "mistral" && ( - <> - - {t("settings:providers.mistralApiKey")} - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.mistralApiKey && ( - - {t("settings:providers.getMistralApiKey")} - - )} - {(apiConfiguration?.apiModelId?.startsWith("codestral-") || - (!apiConfiguration?.apiModelId && mistralDefaultModelId.startsWith("codestral-"))) && ( - <> - - - -
- {t("settings:providers.codestralBaseUrlDesc")} -
- - )} - + )} {selectedProvider === "bedrock" && ( @@ -471,24 +372,7 @@ const ApiOptions = ({ )} {selectedProvider === "deepseek" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.deepSeekApiKey && ( - - {t("settings:providers.getDeepSeekApiKey")} - - )} - + )} {selectedProvider === "vscode-lm" && ( @@ -500,87 +384,15 @@ const ApiOptions = ({ )} {selectedProvider === "xai" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.xaiApiKey && ( - - {t("settings:providers.getXaiApiKey")} - - )} - + )} {selectedProvider === "groq" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.groqApiKey && ( - - {t("settings:providers.getGroqApiKey")} - - )} - + )} {selectedProvider === "chutes" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.chutesApiKey && ( - - {t("settings:providers.getChutesApiKey")} - - )} - - )} - - {selectedProvider === "unbound" && ( - <> - - - -
- {t("settings:providers.apiKeyStorageNotice")} -
- {!apiConfiguration?.unboundApiKey && ( - - {t("settings:providers.getUnboundApiKey")} - - )} - + )} {selectedProvider === "human-relay" && ( @@ -594,99 +406,10 @@ const ApiOptions = ({ )} - {/* Model Pickers */} - - {selectedProvider === "openrouter" && ( - - )} - - {selectedProvider === "openrouter" && - openRouterModelProviders && - Object.keys(openRouterModelProviders).length > 0 && ( -
-
- - - - -
- -
- {t("settings:providers.openRouter.providerRouting.description")}{" "} - - {t("settings:providers.openRouter.providerRouting.learnMore")}. - -
-
- )} - - {selectedProvider === "glama" && ( - - )} - - {selectedProvider === "unbound" && ( - - )} - - {selectedProvider === "requesty" && ( - - )} - - {selectedProviderModelOptions.length > 0 && ( + {selectedProviderModels.length > 0 && ( <>
- setApiConfigurationField("openRouterSpecificProvider", value)}> + + + + + + {OPENROUTER_DEFAULT_PROVIDER_NAME} + + {Object.entries(openRouterModelProviders).map(([value, { label }]) => ( + + {label} + + ))} + + +
+ {t("settings:providers.openRouter.providerRouting.description")}{" "} + + {t("settings:providers.openRouter.providerRouting.learnMore")}. + +
+
+ )} ) } diff --git a/webview-ui/src/components/settings/providers/Requesty.tsx b/webview-ui/src/components/settings/providers/Requesty.tsx new file mode 100644 index 00000000000..a8c8fc80996 --- /dev/null +++ b/webview-ui/src/components/settings/providers/Requesty.tsx @@ -0,0 +1,97 @@ +import { useCallback, useState } from "react" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration, RouterModels, requestyDefaultModelId } from "@roo/shared/api" + +import { vscode } from "@src/utils/vscode" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" +import { Button } from "@src/components/ui" + +import { inputEventTransform } from "../transforms" +import { ModelPicker } from "../ModelPicker" +import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay" + +type RequestyProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void + routerModels?: RouterModels + refetchRouterModels: () => void +} + +export const Requesty = ({ + apiConfiguration, + setApiConfigurationField, + routerModels, + refetchRouterModels, +}: RequestyProps) => { + const { t } = useAppTranslation() + + const [didRefetch, setDidRefetch] = useState() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + +
+ + {apiConfiguration?.requestyApiKey && ( + + )} +
+
+
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.requestyApiKey && ( + + {t("settings:providers.getRequestyApiKey")} + + )} + + {didRefetch && ( +
+ {t("settings:providers.refreshModels.hint")} +
+ )} + + + ) +} diff --git a/webview-ui/src/components/settings/RequestyBalanceDisplay.tsx b/webview-ui/src/components/settings/providers/RequestyBalanceDisplay.tsx similarity index 100% rename from webview-ui/src/components/settings/RequestyBalanceDisplay.tsx rename to webview-ui/src/components/settings/providers/RequestyBalanceDisplay.tsx diff --git a/webview-ui/src/components/settings/providers/Unbound.tsx b/webview-ui/src/components/settings/providers/Unbound.tsx new file mode 100644 index 00000000000..7467d2b7c6e --- /dev/null +++ b/webview-ui/src/components/settings/providers/Unbound.tsx @@ -0,0 +1,61 @@ +import { useCallback } from "react" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration, RouterModels, unboundDefaultModelId } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform } from "../transforms" +import { ModelPicker } from "../ModelPicker" + +type UnboundProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void + routerModels?: RouterModels +} + +export const Unbound = ({ apiConfiguration, setApiConfigurationField, routerModels }: UnboundProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.unboundApiKey && ( + + {t("settings:providers.getUnboundApiKey")} + + )} + + + ) +} diff --git a/webview-ui/src/components/settings/providers/XAI.tsx b/webview-ui/src/components/settings/providers/XAI.tsx new file mode 100644 index 00000000000..39b0e53d34e --- /dev/null +++ b/webview-ui/src/components/settings/providers/XAI.tsx @@ -0,0 +1,50 @@ +import { useCallback } from "react" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import { ApiConfiguration } from "@roo/shared/api" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform } from "../transforms" + +type XAIProps = { + apiConfiguration: ApiConfiguration + setApiConfigurationField: (field: keyof ApiConfiguration, value: ApiConfiguration[keyof ApiConfiguration]) => void +} + +export const XAI = ({ apiConfiguration, setApiConfigurationField }: XAIProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ApiConfiguration[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.xaiApiKey && ( + + {t("settings:providers.getXaiApiKey")} + + )} + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts new file mode 100644 index 00000000000..e83c8586281 --- /dev/null +++ b/webview-ui/src/components/settings/providers/index.ts @@ -0,0 +1,18 @@ +export { Anthropic } from "./Anthropic" +export { Bedrock } from "./Bedrock" +export { Chutes } from "./Chutes" +export { DeepSeek } from "./DeepSeek" +export { Gemini } from "./Gemini" +export { Glama } from "./Glama" +export { Groq } from "./Groq" +export { LMStudio } from "./LMStudio" +export { Mistral } from "./Mistral" +export { Ollama } from "./Ollama" +export { OpenAI } from "./OpenAI" +export { OpenAICompatible } from "./OpenAICompatible" +export { OpenRouter } from "./OpenRouter" +export { Requesty } from "./Requesty" +export { Unbound } from "./Unbound" +export { Vertex } from "./Vertex" +export { VSCodeLM } from "./VSCodeLM" +export { XAI } from "./XAI" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index b02a4d024b0..e82e6670a9b 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -1,7 +1,8 @@ import { - ApiConfiguration, - RouterModels, - ModelInfo, + type ProviderName, + type ApiConfiguration, + type RouterModels, + type ModelInfo, anthropicDefaultModelId, anthropicModels, bedrockDefaultModelId, @@ -36,97 +37,137 @@ import { useRouterModels } from "./useRouterModels" export const useSelectedModel = (apiConfiguration?: ApiConfiguration) => { const { data: routerModels, isLoading, isError } = useRouterModels() const provider = apiConfiguration?.apiProvider || "anthropic" - const id = apiConfiguration ? getSelectedModelId({ provider, apiConfiguration }) : anthropicDefaultModelId - const info = routerModels ? getSelectedModelInfo({ provider, id, apiConfiguration, routerModels }) : undefined - return { provider, id, info, isLoading, isError } -} -function getSelectedModelId({ provider, apiConfiguration }: { provider: string; apiConfiguration: ApiConfiguration }) { - switch (provider) { - case "openrouter": - return apiConfiguration.openRouterModelId ?? openRouterDefaultModelId - case "requesty": - return apiConfiguration.requestyModelId ?? requestyDefaultModelId - case "glama": - return apiConfiguration.glamaModelId ?? glamaDefaultModelId - case "unbound": - return apiConfiguration.unboundModelId ?? unboundDefaultModelId - case "openai": - return apiConfiguration.openAiModelId || "" - case "ollama": - return apiConfiguration.ollamaModelId || "" - case "lmstudio": - return apiConfiguration.lmStudioModelId || "" - case "vscode-lm": - return apiConfiguration?.vsCodeLmModelSelector - ? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}` - : "" - default: - return apiConfiguration.apiModelId ?? anthropicDefaultModelId - } + const { id, info } = + apiConfiguration && routerModels + ? getSelectedModel({ provider, apiConfiguration, routerModels }) + : { id: anthropicDefaultModelId, info: undefined } + + return { provider, id, info, isLoading, isError } } -function getSelectedModelInfo({ +function getSelectedModel({ provider, - id, apiConfiguration, routerModels, }: { - provider: string - id: string - apiConfiguration?: ApiConfiguration + provider: ProviderName + apiConfiguration: ApiConfiguration routerModels: RouterModels -}): ModelInfo { +}): { id: string; info: ModelInfo } { switch (provider) { - case "openrouter": - return routerModels.openrouter[id] ?? routerModels.openrouter[openRouterDefaultModelId] - case "requesty": - return routerModels.requesty[id] ?? routerModels.requesty[requestyDefaultModelId] - case "glama": - return routerModels.glama[id] ?? routerModels.glama[glamaDefaultModelId] - case "unbound": - return routerModels.unbound[id] ?? routerModels.unbound[unboundDefaultModelId] - case "xai": - return xaiModels[id as keyof typeof xaiModels] ?? xaiModels[xaiDefaultModelId] - case "groq": - return groqModels[id as keyof typeof groqModels] ?? groqModels[groqDefaultModelId] - case "chutes": - return chutesModels[id as keyof typeof chutesModels] ?? chutesModels[chutesDefaultModelId] - case "bedrock": + case "openrouter": { + const id = apiConfiguration.openRouterModelId ?? openRouterDefaultModelId + const info = routerModels.openrouter[id] + return info + ? { id, info } + : { id: openRouterDefaultModelId, info: routerModels.openrouter[openRouterDefaultModelId] } + } + case "requesty": { + const id = apiConfiguration.requestyModelId ?? requestyDefaultModelId + const info = routerModels.requesty[id] + return info + ? { id, info } + : { id: requestyDefaultModelId, info: routerModels.requesty[requestyDefaultModelId] } + } + case "glama": { + const id = apiConfiguration.glamaModelId ?? glamaDefaultModelId + const info = routerModels.glama[id] + return info ? { id, info } : { id: glamaDefaultModelId, info: routerModels.glama[glamaDefaultModelId] } + } + case "unbound": { + const id = apiConfiguration.unboundModelId ?? unboundDefaultModelId + const info = routerModels.unbound[id] + return info + ? { id, info } + : { id: unboundDefaultModelId, info: routerModels.unbound[unboundDefaultModelId] } + } + case "xai": { + const id = apiConfiguration.apiModelId ?? xaiDefaultModelId + const info = xaiModels[id as keyof typeof xaiModels] + return info ? { id, info } : { id: xaiDefaultModelId, info: xaiModels[xaiDefaultModelId] } + } + case "groq": { + const id = apiConfiguration.apiModelId ?? groqDefaultModelId + const info = groqModels[id as keyof typeof groqModels] + return info ? { id, info } : { id: groqDefaultModelId, info: groqModels[groqDefaultModelId] } + } + case "chutes": { + const id = apiConfiguration.apiModelId ?? chutesDefaultModelId + const info = chutesModels[id as keyof typeof chutesModels] + return info ? { id, info } : { id: chutesDefaultModelId, info: chutesModels[chutesDefaultModelId] } + } + case "bedrock": { + const id = apiConfiguration.apiModelId ?? bedrockDefaultModelId + const info = bedrockModels[id as keyof typeof bedrockModels] + // Special case for custom ARN. if (id === "custom-arn") { - return { maxTokens: 5000, contextWindow: 128_000, supportsPromptCache: false, supportsImages: true } + return { + id, + info: { maxTokens: 5000, contextWindow: 128_000, supportsPromptCache: false, supportsImages: true }, + } } - return bedrockModels[id as keyof typeof bedrockModels] ?? bedrockModels[bedrockDefaultModelId] - case "vertex": - return vertexModels[id as keyof typeof vertexModels] ?? vertexModels[vertexDefaultModelId] - case "gemini": - return geminiModels[id as keyof typeof geminiModels] ?? geminiModels[geminiDefaultModelId] - case "deepseek": - return deepSeekModels[id as keyof typeof deepSeekModels] ?? deepSeekModels[deepSeekDefaultModelId] - case "openai-native": - return ( - openAiNativeModels[id as keyof typeof openAiNativeModels] ?? - openAiNativeModels[openAiNativeDefaultModelId] - ) - case "mistral": - return mistralModels[id as keyof typeof mistralModels] ?? mistralModels[mistralDefaultModelId] - case "openai": - return apiConfiguration?.openAiCustomModelInfo || openAiModelInfoSaneDefaults - case "ollama": - return openAiModelInfoSaneDefaults - case "lmstudio": - return openAiModelInfoSaneDefaults - case "vscode-lm": + return info ? { id, info } : { id: bedrockDefaultModelId, info: bedrockModels[bedrockDefaultModelId] } + } + case "vertex": { + const id = apiConfiguration.apiModelId ?? vertexDefaultModelId + const info = vertexModels[id as keyof typeof vertexModels] + return info ? { id, info } : { id: vertexDefaultModelId, info: vertexModels[vertexDefaultModelId] } + } + case "gemini": { + const id = apiConfiguration.apiModelId ?? geminiDefaultModelId + const info = geminiModels[id as keyof typeof geminiModels] + return info ? { id, info } : { id: geminiDefaultModelId, info: geminiModels[geminiDefaultModelId] } + } + case "deepseek": { + const id = apiConfiguration.apiModelId ?? deepSeekDefaultModelId + const info = deepSeekModels[id as keyof typeof deepSeekModels] + return info ? { id, info } : { id: deepSeekDefaultModelId, info: deepSeekModels[deepSeekDefaultModelId] } + } + case "openai-native": { + const id = apiConfiguration.apiModelId ?? openAiNativeDefaultModelId + const info = openAiNativeModels[id as keyof typeof openAiNativeModels] + return info + ? { id, info } + : { id: openAiNativeDefaultModelId, info: openAiNativeModels[openAiNativeDefaultModelId] } + } + case "mistral": { + const id = apiConfiguration.apiModelId ?? mistralDefaultModelId + const info = mistralModels[id as keyof typeof mistralModels] + return info ? { id, info } : { id: mistralDefaultModelId, info: mistralModels[mistralDefaultModelId] } + } + case "openai": { + const id = apiConfiguration.openAiModelId ?? "" + const info = apiConfiguration?.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults + return { id, info } + } + case "ollama": { + const id = apiConfiguration.ollamaModelId ?? "" + const info = openAiModelInfoSaneDefaults + return { id, info } + } + case "lmstudio": { + const id = apiConfiguration.lmStudioModelId ?? "" + const info = openAiModelInfoSaneDefaults + return { id, info } + } + case "vscode-lm": { + const id = apiConfiguration?.vsCodeLmModelSelector + ? `${apiConfiguration.vsCodeLmModelSelector.vendor}/${apiConfiguration.vsCodeLmModelSelector.family}` + : vscodeLlmDefaultModelId const modelFamily = apiConfiguration?.vsCodeLmModelSelector?.family ?? vscodeLlmDefaultModelId - - return { - ...openAiModelInfoSaneDefaults, - ...vscodeLlmModels[modelFamily as keyof typeof vscodeLlmModels], - supportsImages: false, // VSCode LM API currently doesn't support images. - } - default: - return anthropicModels[id as keyof typeof anthropicModels] ?? anthropicModels[anthropicDefaultModelId] + const info = vscodeLlmModels[modelFamily as keyof typeof vscodeLlmModels] + return { id, info: { ...openAiModelInfoSaneDefaults, ...info, supportsImages: false } } // VSCode LM API currently doesn't support images. + } + // case "anthropic": + // case "human-relay": + // case "fake-ai": + default: { + const id = apiConfiguration.apiModelId ?? anthropicDefaultModelId + const info = anthropicModels[id as keyof typeof anthropicModels] + return info ? { id, info } : { id: anthropicDefaultModelId, info: anthropicModels[anthropicDefaultModelId] } + } } } diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 38dc4c67bd3..c375463fa01 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -119,9 +119,11 @@ "glamaApiKey": "Clau API de Glama", "getGlamaApiKey": "Obtenir clau API de Glama", "requestyApiKey": "Clau API de Requesty", - "flushModelsCache": "Netejar memòria cau de models", - "flushedModelsCache": "Memòria cau netejada, si us plau torna a obrir la vista de configuració", "getRequestyApiKey": "Obtenir clau API de Requesty", + "refreshModels": { + "label": "Actualitzar models", + "hint": "Si us plau, torneu a obrir la configuració per veure els models més recents." + }, "anthropicApiKey": "Clau API d'Anthropic", "getAnthropicApiKey": "Obtenir clau API d'Anthropic", "anthropicUseAuthToken": "Passar la clau API d'Anthropic com a capçalera d'autorització en lloc de X-Api-Key", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index e12ad8fa461..e3afa643b5c 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -106,8 +106,6 @@ "awsCustomArnUse": "Geben Sie eine gültige Amazon Bedrock ARN für das Modell ein, das Sie verwenden möchten. Formatbeispiele:", "awsCustomArnDesc": "Stellen Sie sicher, dass die Region in der ARN mit Ihrer oben ausgewählten AWS-Region übereinstimmt.", "openRouterApiKey": "OpenRouter API-Schlüssel", - "flushModelsCache": "Modell-Cache leeren", - "flushedModelsCache": "Cache geleert, bitte öffnen Sie die Einstellungsansicht erneut", "getOpenRouterApiKey": "OpenRouter API-Schlüssel erhalten", "apiKeyStorageNotice": "API-Schlüssel werden sicher im VSCode Secret Storage gespeichert", "glamaApiKey": "Glama API-Schlüssel", @@ -121,6 +119,10 @@ "noCustomHeaders": "Keine benutzerdefinierten Headers definiert. Klicke auf die + Schaltfläche, um einen hinzuzufügen.", "requestyApiKey": "Requesty API-Schlüssel", "getRequestyApiKey": "Requesty API-Schlüssel erhalten", + "refreshModels": { + "label": "Modelle aktualisieren", + "hint": "Bitte öffne die Einstellungen erneut, um die neuesten Modelle zu sehen." + }, "openRouterTransformsText": "Prompts und Nachrichtenketten auf Kontextgröße komprimieren (OpenRouter Transformationen)", "anthropicApiKey": "Anthropic API-Schlüssel", "getAnthropicApiKey": "Anthropic API-Schlüssel erhalten", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 6c042baf216..c3b67d8e33c 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -118,8 +118,10 @@ "headerValue": "Header value", "noCustomHeaders": "No custom headers defined. Click the + button to add one.", "requestyApiKey": "Requesty API Key", - "flushModelsCache": "Flush cached models", - "flushedModelsCache": "Flushed cache, please reopen the settings view", + "refreshModels": { + "label": "Refresh Models", + "hint": "Please reopen the settings to see the latest models." + }, "getRequestyApiKey": "Get Requesty API Key", "openRouterTransformsText": "Compress prompts and message chains to the context size (OpenRouter Transforms)", "anthropicApiKey": "Anthropic API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index cedc110bf55..eb732605ff7 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -106,8 +106,6 @@ "awsCustomArnUse": "Ingrese un ARN de Amazon Bedrock válido para el modelo que desea utilizar. Ejemplos de formato:", "awsCustomArnDesc": "Asegúrese de que la región en el ARN coincida con la región de AWS seleccionada anteriormente.", "openRouterApiKey": "Clave API de OpenRouter", - "flushModelsCache": "Limpiar modelos en caché", - "flushedModelsCache": "Caché limpiada, por favor vuelva a abrir la vista de configuración", "getOpenRouterApiKey": "Obtener clave API de OpenRouter", "apiKeyStorageNotice": "Las claves API se almacenan de forma segura en el Almacenamiento Secreto de VSCode", "glamaApiKey": "Clave API de Glama", @@ -121,6 +119,10 @@ "noCustomHeaders": "No hay encabezados personalizados definidos. Haga clic en el botón + para añadir uno.", "requestyApiKey": "Clave API de Requesty", "getRequestyApiKey": "Obtener clave API de Requesty", + "refreshModels": { + "label": "Actualizar modelos", + "hint": "Por favor, vuelve a abrir la configuración para ver los modelos más recientes." + }, "openRouterTransformsText": "Comprimir prompts y cadenas de mensajes al tamaño del contexto (Transformaciones de OpenRouter)", "anthropicApiKey": "Clave API de Anthropic", "getAnthropicApiKey": "Obtener clave API de Anthropic", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 615b252d7f3..7e55b4a43f1 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -106,8 +106,6 @@ "awsCustomArnUse": "Entrez un ARN Amazon Bedrock valide pour le modèle que vous souhaitez utiliser. Exemples de format :", "awsCustomArnDesc": "Assurez-vous que la région dans l'ARN correspond à la région AWS sélectionnée ci-dessus.", "openRouterApiKey": "Clé API OpenRouter", - "flushModelsCache": "Vider le cache des modèles", - "flushedModelsCache": "Cache vidé, veuillez rouvrir la vue des paramètres", "getOpenRouterApiKey": "Obtenir la clé API OpenRouter", "apiKeyStorageNotice": "Les clés API sont stockées en toute sécurité dans le stockage sécurisé de VSCode", "glamaApiKey": "Clé API Glama", @@ -121,6 +119,10 @@ "noCustomHeaders": "Aucun en-tête personnalisé défini. Cliquez sur le bouton + pour en ajouter un.", "requestyApiKey": "Clé API Requesty", "getRequestyApiKey": "Obtenir la clé API Requesty", + "refreshModels": { + "label": "Actualiser les modèles", + "hint": "Veuillez rouvrir les paramètres pour voir les modèles les plus récents." + }, "openRouterTransformsText": "Compresser les prompts et chaînes de messages à la taille du contexte (Transformations OpenRouter)", "anthropicApiKey": "Clé API Anthropic", "getAnthropicApiKey": "Obtenir la clé API Anthropic", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index d5c948b30d9..6dc671782ff 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -118,9 +118,11 @@ "headerValue": "हेडर मूल्य", "noCustomHeaders": "कोई कस्टम हेडर परिभाषित नहीं है। एक जोड़ने के लिए + बटन पर क्लिक करें।", "requestyApiKey": "Requesty API कुंजी", - "flushModelsCache": "मॉडल कैश साफ़ करें", - "flushedModelsCache": "कैश साफ़ किया गया, कृपया सेटिंग्स व्यू को फिर से खोलें", "getRequestyApiKey": "Requesty API कुंजी प्राप्त करें", + "refreshModels": { + "label": "मॉडल रिफ्रेश करें", + "hint": "नवीनतम मॉडल देखने के लिए कृपया सेटिंग्स को फिर से खोलें।" + }, "openRouterTransformsText": "संदर्भ आकार के लिए प्रॉम्प्ट और संदेश श्रृंखलाओं को संपीड़ित करें (OpenRouter ट्रांसफॉर्म)", "anthropicApiKey": "Anthropic API कुंजी", "getAnthropicApiKey": "Anthropic API कुंजी प्राप्त करें", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index d7dfefda360..b47f31aa25a 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -118,9 +118,11 @@ "headerValue": "Valore intestazione", "noCustomHeaders": "Nessuna intestazione personalizzata definita. Fai clic sul pulsante + per aggiungerne una.", "requestyApiKey": "Chiave API Requesty", - "flushModelsCache": "Svuota cache dei modelli", - "flushedModelsCache": "Cache svuotata, riapri la vista delle impostazioni", "getRequestyApiKey": "Ottieni chiave API Requesty", + "refreshModels": { + "label": "Aggiorna modelli", + "hint": "Riapri le impostazioni per vedere i modelli più recenti." + }, "openRouterTransformsText": "Comprimi prompt e catene di messaggi alla dimensione del contesto (Trasformazioni OpenRouter)", "anthropicApiKey": "Chiave API Anthropic", "getAnthropicApiKey": "Ottieni chiave API Anthropic", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 98e30541212..3430fb835a6 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -118,9 +118,11 @@ "headerValue": "ヘッダー値", "noCustomHeaders": "カスタムヘッダーが定義されていません。+ ボタンをクリックして追加してください。", "requestyApiKey": "Requesty APIキー", - "flushModelsCache": "モデルキャッシュをクリア", - "flushedModelsCache": "キャッシュをクリアしました。設定ビューを再開してください", "getRequestyApiKey": "Requesty APIキーを取得", + "refreshModels": { + "label": "モデルを更新", + "hint": "最新のモデルを表示するには設定を再度開いてください。" + }, "openRouterTransformsText": "プロンプトとメッセージチェーンをコンテキストサイズに圧縮 (OpenRouter Transforms)", "anthropicApiKey": "Anthropic APIキー", "getAnthropicApiKey": "Anthropic APIキーを取得", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 9431a9bc5b2..1816c1ab108 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -118,9 +118,11 @@ "headerValue": "헤더 값", "noCustomHeaders": "정의된 사용자 정의 헤더가 없습니다. + 버튼을 클릭하여 추가하세요.", "requestyApiKey": "Requesty API 키", - "flushModelsCache": "모델 캐시 지우기", - "flushedModelsCache": "캐시가 지워졌습니다. 설정 보기를 다시 열어주세요", "getRequestyApiKey": "Requesty API 키 받기", + "refreshModels": { + "label": "모델 새로고침", + "hint": "최신 모델을 보려면 설정을 다시 열어주세요." + }, "openRouterTransformsText": "프롬프트와 메시지 체인을 컨텍스트 크기로 압축 (OpenRouter Transforms)", "anthropicApiKey": "Anthropic API 키", "getAnthropicApiKey": "Anthropic API 키 받기", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index ad3da8d9a85..564a4fac506 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -118,9 +118,11 @@ "headerValue": "Wartość nagłówka", "noCustomHeaders": "Brak zdefiniowanych niestandardowych nagłówków. Kliknij przycisk +, aby dodać.", "requestyApiKey": "Klucz API Requesty", - "flushModelsCache": "Wyczyść pamięć podręczną modeli", - "flushedModelsCache": "Pamięć podręczna wyczyszczona, proszę ponownie otworzyć widok ustawień", "getRequestyApiKey": "Uzyskaj klucz API Requesty", + "refreshModels": { + "label": "Odśwież modele", + "hint": "Proszę ponownie otworzyć ustawienia, aby zobaczyć najnowsze modele." + }, "openRouterTransformsText": "Kompresuj podpowiedzi i łańcuchy wiadomości do rozmiaru kontekstu (Transformacje OpenRouter)", "anthropicApiKey": "Klucz API Anthropic", "getAnthropicApiKey": "Uzyskaj klucz API Anthropic", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index b6f1f752539..628378a7547 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -106,8 +106,6 @@ "awsCustomArnUse": "Insira um ARN Amazon Bedrock válido para o modelo que deseja usar. Exemplos de formato:", "awsCustomArnDesc": "Certifique-se de que a região no ARN corresponde à região AWS selecionada acima.", "openRouterApiKey": "Chave de API OpenRouter", - "flushModelsCache": "Limpar cache de modelos", - "flushedModelsCache": "Cache limpo, por favor reabra a visualização de configurações", "getOpenRouterApiKey": "Obter chave de API OpenRouter", "apiKeyStorageNotice": "As chaves de API são armazenadas com segurança no Armazenamento Secreto do VSCode", "glamaApiKey": "Chave de API Glama", @@ -121,6 +119,10 @@ "noCustomHeaders": "Nenhum cabeçalho personalizado definido. Clique no botão + para adicionar um.", "requestyApiKey": "Chave de API Requesty", "getRequestyApiKey": "Obter chave de API Requesty", + "refreshModels": { + "label": "Atualizar modelos", + "hint": "Por favor, reabra as configurações para ver os modelos mais recentes." + }, "openRouterTransformsText": "Comprimir prompts e cadeias de mensagens para o tamanho do contexto (Transformações OpenRouter)", "anthropicApiKey": "Chave de API Anthropic", "getAnthropicApiKey": "Obter chave de API Anthropic", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 6fa1cc03262..3d03429ea76 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -118,9 +118,11 @@ "headerValue": "Значение заголовка", "noCustomHeaders": "Пользовательские заголовки не определены. Нажмите кнопку +, чтобы добавить.", "requestyApiKey": "Requesty API-ключ", - "flushModelsCache": "Очистить кэш моделей", - "flushedModelsCache": "Кэш очищен, пожалуйста, переоткройте представление настроек", "getRequestyApiKey": "Получить Requesty API-ключ", + "refreshModels": { + "label": "Обновить модели", + "hint": "Пожалуйста, откройте настройки заново, чтобы увидеть последние модели." + }, "openRouterTransformsText": "Сжимать подсказки и цепочки сообщений до размера контекста (OpenRouter Transforms)", "anthropicApiKey": "Anthropic API-ключ", "getAnthropicApiKey": "Получить Anthropic API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index af7b4a217b0..f6c0db88564 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -106,8 +106,6 @@ "awsCustomArnUse": "Kullanmak istediğiniz model için geçerli bir Amazon Bedrock ARN'si girin. Format örnekleri:", "awsCustomArnDesc": "ARN içindeki bölgenin yukarıda seçilen AWS Bölgesiyle eşleştiğinden emin olun.", "openRouterApiKey": "OpenRouter API Anahtarı", - "flushModelsCache": "Model önbelleğini temizle", - "flushedModelsCache": "Önbellek temizlendi, lütfen ayarlar görünümünü yeniden açın", "getOpenRouterApiKey": "OpenRouter API Anahtarı Al", "apiKeyStorageNotice": "API anahtarları VSCode'un Gizli Depolamasında güvenli bir şekilde saklanır", "glamaApiKey": "Glama API Anahtarı", @@ -121,6 +119,10 @@ "noCustomHeaders": "Tanımlanmış özel başlık yok. Eklemek için + düğmesine tıklayın.", "requestyApiKey": "Requesty API Anahtarı", "getRequestyApiKey": "Requesty API Anahtarı Al", + "refreshModels": { + "label": "Modelleri Yenile", + "hint": "En son modelleri görmek için lütfen ayarları yeniden açın." + }, "openRouterTransformsText": "İstem ve mesaj zincirlerini bağlam boyutuna sıkıştır (OpenRouter Dönüşümleri)", "anthropicApiKey": "Anthropic API Anahtarı", "getAnthropicApiKey": "Anthropic API Anahtarı Al", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index fe5a0bfd755..284ea759bec 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -118,8 +118,6 @@ "headerValue": "Giá trị tiêu đề", "noCustomHeaders": "Chưa có tiêu đề tùy chỉnh nào được định nghĩa. Nhấp vào nút + để thêm.", "requestyApiKey": "Khóa API Requesty", - "flushModelsCache": "Xóa bộ nhớ đệm mô hình", - "flushedModelsCache": "Đã xóa bộ nhớ đệm, vui lòng mở lại chế độ xem cài đặt", "getRequestyApiKey": "Lấy khóa API Requesty", "anthropicApiKey": "Khóa API Anthropic", "getAnthropicApiKey": "Lấy khóa API Anthropic", @@ -181,6 +179,10 @@ "warning": "Lưu ý: Roo Code sử dụng các lời nhắc phức tạp và hoạt động tốt nhất với các mô hình Claude. Các mô hình kém mạnh hơn có thể không hoạt động như mong đợi." }, "openRouterTransformsText": "Nén lời nhắc và chuỗi tin nhắn theo kích thước ngữ cảnh (OpenRouter Transforms)", + "refreshModels": { + "label": "Làm mới mô hình", + "hint": "Vui lòng mở lại cài đặt để xem các mô hình mới nhất." + }, "unboundApiKey": "Khóa API Unbound", "getUnboundApiKey": "Lấy khóa API Unbound", "humanRelay": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 0e0cf7f0feb..28fc887170f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -118,9 +118,11 @@ "glamaApiKey": "Glama API 密钥", "getGlamaApiKey": "获取 Glama API 密钥", "requestyApiKey": "Requesty API 密钥", - "flushModelsCache": "清除模型缓存", - "flushedModelsCache": "缓存已清除,请重新打开设置视图", "getRequestyApiKey": "获取 Requesty API 密钥", + "refreshModels": { + "label": "刷新模型", + "hint": "请重新打开设置以查看最新模型。" + }, "openRouterTransformsText": "自动压缩提示词和消息链到上下文长度限制内 (OpenRouter转换)", "anthropicApiKey": "Anthropic API 密钥", "getAnthropicApiKey": "获取 Anthropic API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 6a58cc8a6f7..cbbf1575dee 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -118,9 +118,11 @@ "headerValue": "標頭值", "noCustomHeaders": "尚未定義自訂標頭。點擊 + 按鈕以新增。", "requestyApiKey": "Requesty API 金鑰", - "flushModelsCache": "清除模型快取", - "flushedModelsCache": "快取已清除,請重新開啟設定視圖", "getRequestyApiKey": "取得 Requesty API 金鑰", + "refreshModels": { + "label": "重新整理模型", + "hint": "請重新開啟設定以查看最新模型。" + }, "openRouterTransformsText": "將提示和訊息鏈壓縮到上下文大小 (OpenRouter 轉換)", "anthropicApiKey": "Anthropic API 金鑰", "getAnthropicApiKey": "取得 Anthropic API 金鑰", diff --git a/webview-ui/src/oauth/urls.ts b/webview-ui/src/oauth/urls.ts index e75b65b9c7b..205c19cf53e 100644 --- a/webview-ui/src/oauth/urls.ts +++ b/webview-ui/src/oauth/urls.ts @@ -1,6 +1,5 @@ export function getCallbackUrl(provider: string, uriScheme?: string) { - const callbackUrl = `${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/${provider}` - return encodeURIComponent(callbackUrl) + return encodeURIComponent(`${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/${provider}`) } export function getGlamaAuthUrl(uriScheme?: string) { @@ -14,7 +13,3 @@ export function getOpenRouterAuthUrl(uriScheme?: string) { export function getRequestyAuthUrl(uriScheme?: string) { return `https://app.requesty.ai/oauth/authorize?callback_url=${getCallbackUrl("requesty", uriScheme)}` } - -export function getRequestyApiKeyUrl() { - return "https://app.requesty.ai/api-keys" -} From 82dd3d5c4b0da22fbbc4717d30d0360c6d89f590 Mon Sep 17 00:00:00 2001 From: Kal Sze <381556+ksze@users.noreply.github.com> Date: Tue, 6 May 2025 00:32:08 -0600 Subject: [PATCH 024/228] #1287 - ignore stderr of MCP servers unless it really fails to connect (#1441) Co-authored-by: cte --- renovate.json | 6 +- webview-ui/src/components/mcp/McpView.tsx | 91 +++++++++++++---------- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/renovate.json b/renovate.json index 5db72dd6a94..ac084260c3c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,4 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"] } diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index b21a479730a..b8fddd5be7c 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -1,6 +1,7 @@ -import { useState } from "react" -import { Button } from "@/components/ui/button" +import React, { useState } from "react" +import { Trans } from "react-i18next" import { + VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodePanels, @@ -10,13 +11,21 @@ import { import { McpServer } from "@roo/shared/mcp" -import { vscode } from "@/utils/vscode" -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui" - +import { vscode } from "@src/utils/vscode" import { useExtensionState } from "@src/context/ExtensionStateContext" import { useAppTranslation } from "@src/i18n/TranslationContext" -import { Trans } from "react-i18next" +import { + Button, + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@src/components/ui" + import { Tab, TabContent, TabHeader } from "../common/Tab" + import McpToolRow from "./McpToolRow" import McpResourceRow from "./McpResourceRow" import McpEnabledToggle from "./McpEnabledToggle" @@ -158,7 +167,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM } const handleRowClick = () => { - if (!server.error) { + if (server.status === "connected") { setIsExpanded(!isExpanded) } } @@ -199,12 +208,12 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM alignItems: "center", padding: "8px", background: "var(--vscode-textCodeBlock-background)", - cursor: server.error ? "default" : "pointer", - borderRadius: isExpanded || server.error ? "4px 4px 0 0" : "4px", + cursor: server.status === "connected" ? "pointer" : "default", + borderRadius: isExpanded || server.status === "connected" ? "4px" : "4px 4px 0 0", opacity: server.disabled ? 0.6 : 1, }} onClick={handleRowClick}> - {!server.error && ( + {server.status === "connected" && (
- {server.error ? ( -
-
- {server.error} -
- -
- ) : ( + {server.status === "connected" ? ( isExpanded && (
) + ) : ( +
+
+ {server.error && + server.error.split("\n").map((item, index) => ( + + {index > 0 &&
} + {item} +
+ ))} +
+ + {server.status === "connecting" ? "Retrying..." : "Retry Connection"} + +
)} {/* Delete Confirmation Dialog */} From 78b208346713b77d7fd1d05a33604e335686e21a Mon Sep 17 00:00:00 2001 From: robertheadley Date: Tue, 6 May 2025 02:17:20 -0500 Subject: [PATCH 025/228] feat: Add error console to MCP servers - Edited with Roo Code and Anthropic Claude 3.5 (#2722) Co-authored-by: cte --- package-lock.json | 20 +++++++++--- package.json | 2 +- src/services/mcp/McpHub.ts | 31 ++++++++++++++---- src/shared/mcp.ts | 12 +++++++ webview-ui/package-lock.json | 11 +++++++ webview-ui/package.json | 1 + webview-ui/src/components/mcp/McpErrorRow.tsx | 32 +++++++++++++++++++ webview-ui/src/components/mcp/McpView.tsx | 22 +++++++++++++ webview-ui/src/i18n/locales/ca/mcp.json | 7 ++-- webview-ui/src/i18n/locales/de/mcp.json | 7 ++-- webview-ui/src/i18n/locales/en/mcp.json | 7 ++-- webview-ui/src/i18n/locales/es/mcp.json | 7 ++-- webview-ui/src/i18n/locales/fr/mcp.json | 7 ++-- webview-ui/src/i18n/locales/hi/mcp.json | 7 ++-- webview-ui/src/i18n/locales/it/mcp.json | 7 ++-- webview-ui/src/i18n/locales/ja/mcp.json | 7 ++-- webview-ui/src/i18n/locales/ko/mcp.json | 7 ++-- webview-ui/src/i18n/locales/pl/mcp.json | 7 ++-- webview-ui/src/i18n/locales/pt-BR/mcp.json | 7 ++-- webview-ui/src/i18n/locales/ru/mcp.json | 7 ++-- webview-ui/src/i18n/locales/tr/mcp.json | 7 ++-- webview-ui/src/i18n/locales/vi/mcp.json | 7 ++-- webview-ui/src/i18n/locales/zh-CN/mcp.json | 7 ++-- webview-ui/src/i18n/locales/zh-TW/mcp.json | 7 ++-- 24 files changed, 199 insertions(+), 44 deletions(-) create mode 100644 webview-ui/src/components/mcp/McpErrorRow.tsx diff --git a/package-lock.json b/package-lock.json index 9b6691d7840..53bdd4ee386 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.779.0", "@google/genai": "^0.12.0", "@mistralai/mistralai": "^1.3.6", - "@modelcontextprotocol/sdk": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.9.0", "@types/clone-deep": "^4.0.4", "@types/pdf-parse": "^1.1.4", "@types/tmp": "^0.2.6", @@ -6629,17 +6629,18 @@ "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz", - "integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", + "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", + "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", - "pkce-challenge": "^4.1.0", + "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" @@ -6648,6 +6649,15 @@ "node": ">=18" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { "version": "3.24.2", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", diff --git a/package.json b/package.json index 0f7aed1416d..13b0a4a4a70 100644 --- a/package.json +++ b/package.json @@ -370,7 +370,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.779.0", "@google/genai": "^0.12.0", "@mistralai/mistralai": "^1.3.6", - "@modelcontextprotocol/sdk": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.9.0", "@types/clone-deep": "^4.0.4", "@types/pdf-parse": "^1.1.4", "@types/tmp": "^0.2.6", diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 46f59858f9b..e83f7afb9e1 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -541,6 +541,7 @@ export class McpHub { disabled: config.disabled, source, projectPath: source === "project" ? vscode.workspace.workspaceFolders?.[0]?.uri.fsPath : undefined, + errorHistory: [], }, client, transport, @@ -567,13 +568,31 @@ export class McpHub { } } - private appendErrorMessage(connection: McpConnection, error: string) { + private appendErrorMessage(connection: McpConnection, error: string, level: "error" | "warn" | "info" = "error") { const MAX_ERROR_LENGTH = 1000 - const newError = connection.server.error ? `${connection.server.error}\n${error}` : error - connection.server.error = - newError.length > MAX_ERROR_LENGTH - ? `${newError.substring(0, MAX_ERROR_LENGTH)}...(error message truncated)` - : newError + const truncatedError = + error.length > MAX_ERROR_LENGTH + ? `${error.substring(0, MAX_ERROR_LENGTH)}...(error message truncated)` + : error + + // Add to error history + if (!connection.server.errorHistory) { + connection.server.errorHistory = [] + } + + connection.server.errorHistory.push({ + message: truncatedError, + timestamp: Date.now(), + level, + }) + + // Keep only the last 100 errors + if (connection.server.errorHistory.length > 100) { + connection.server.errorHistory = connection.server.errorHistory.slice(-100) + } + + // Update current error display + connection.server.error = truncatedError } /** diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index 7a490851bcf..547c0658ac1 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -1,8 +1,15 @@ +export type McpErrorEntry = { + message: string + timestamp: number + level: "error" | "warn" | "info" +} + export type McpServer = { name: string config: string status: "connected" | "connecting" | "disconnected" error?: string + errorHistory?: McpErrorEntry[] tools?: McpTool[] resources?: McpResource[] resourceTemplates?: McpResourceTemplate[] @@ -55,6 +62,11 @@ export type McpToolCallResponse = { data: string mimeType: string } + | { + type: "audio" + data: string + mimeType: string + } | { type: "resource" resource: { diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 28ee6f2797b..1723a99680f 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -27,6 +27,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^4.1.0", "debounce": "^2.1.1", "fast-deep-equal": "^3.1.3", "fzf": "^0.5.2", @@ -10190,6 +10191,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index e33988c4971..a57470cead1 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -36,6 +36,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^4.1.0", "debounce": "^2.1.1", "fast-deep-equal": "^3.1.3", "fzf": "^0.5.2", diff --git a/webview-ui/src/components/mcp/McpErrorRow.tsx b/webview-ui/src/components/mcp/McpErrorRow.tsx new file mode 100644 index 00000000000..3b1ea8f7ce7 --- /dev/null +++ b/webview-ui/src/components/mcp/McpErrorRow.tsx @@ -0,0 +1,32 @@ +import { useMemo } from "react" +import { formatRelative } from "date-fns" + +import type { McpErrorEntry } from "@roo/shared/mcp" + +type McpErrorRowProps = { + error: McpErrorEntry +} + +export const McpErrorRow = ({ error }: McpErrorRowProps) => { + const color = useMemo(() => { + switch (error.level) { + case "error": + return "var(--vscode-testing-iconFailed)" + case "warn": + return "var(--vscode-charts-yellow)" + case "info": + return "var(--vscode-testing-iconPassed)" + } + }, [error.level]) + + return ( +
+
+ {error.message} +
+
+ {formatRelative(error.timestamp, new Date())} +
+
+ ) +} diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index b8fddd5be7c..b03be6500bb 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -29,6 +29,7 @@ import { Tab, TabContent, TabHeader } from "../common/Tab" import McpToolRow from "./McpToolRow" import McpResourceRow from "./McpResourceRow" import McpEnabledToggle from "./McpEnabledToggle" +import { McpErrorRow } from "./McpErrorRow" type McpViewProps = { onDone: () => void @@ -42,6 +43,7 @@ const McpView = ({ onDone }: McpViewProps) => { enableMcpServerCreation, setEnableMcpServerCreation, } = useExtensionState() + const { t } = useAppTranslation() return ( @@ -330,6 +332,9 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM {t("mcp:tabs.resources")} ( {[...(server.resourceTemplates || []), ...(server.resources || [])].length || 0}) + + {t("mcp:tabs.errors")} ({server.errorHistory?.length || 0}) + {server.tools && server.tools.length > 0 ? ( @@ -372,6 +377,23 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
)} + + + {server.errorHistory && server.errorHistory.length > 0 ? ( +
+ {[...server.errorHistory] + .sort((a, b) => b.timestamp - a.timestamp) + .map((error, index) => ( + + ))} +
+ ) : ( +
+ {t("mcp:emptyState.noErrors")} +
+ )} +
{/* Network Timeout */} diff --git a/webview-ui/src/i18n/locales/ca/mcp.json b/webview-ui/src/i18n/locales/ca/mcp.json index 8da2cbb953c..36962cbc531 100644 --- a/webview-ui/src/i18n/locales/ca/mcp.json +++ b/webview-ui/src/i18n/locales/ca/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Eines", - "resources": "Recursos" + "resources": "Recursos", + "errors": "Errors" }, "emptyState": { "noTools": "No s'han trobat eines", - "noResources": "No s'han trobat recursos" + "noResources": "No s'han trobat recursos", + "noLogs": "No s'han trobat registres", + "noErrors": "No s'han trobat errors" }, "networkTimeout": { "label": "Temps d'espera de xarxa", diff --git a/webview-ui/src/i18n/locales/de/mcp.json b/webview-ui/src/i18n/locales/de/mcp.json index 60332c27d6f..0c4ed99247a 100644 --- a/webview-ui/src/i18n/locales/de/mcp.json +++ b/webview-ui/src/i18n/locales/de/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Tools", - "resources": "Ressourcen" + "resources": "Ressourcen", + "errors": "Fehler" }, "emptyState": { "noTools": "Keine Tools gefunden", - "noResources": "Keine Ressourcen gefunden" + "noResources": "Keine Ressourcen gefunden", + "noLogs": "Keine Logs gefunden", + "noErrors": "Keine Fehler gefunden" }, "networkTimeout": { "label": "Netzwerk-Timeout", diff --git a/webview-ui/src/i18n/locales/en/mcp.json b/webview-ui/src/i18n/locales/en/mcp.json index 95ec55bd0c5..26d637165be 100644 --- a/webview-ui/src/i18n/locales/en/mcp.json +++ b/webview-ui/src/i18n/locales/en/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Tools", - "resources": "Resources" + "resources": "Resources", + "errors": "Errors" }, "emptyState": { "noTools": "No tools found", - "noResources": "No resources found" + "noResources": "No resources found", + "noLogs": "No logs found", + "noErrors": "No errors found" }, "networkTimeout": { "label": "Network Timeout", diff --git a/webview-ui/src/i18n/locales/es/mcp.json b/webview-ui/src/i18n/locales/es/mcp.json index 6b6c7eb1988..c2d2b79b5af 100644 --- a/webview-ui/src/i18n/locales/es/mcp.json +++ b/webview-ui/src/i18n/locales/es/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Herramientas", - "resources": "Recursos" + "resources": "Recursos", + "errors": "Errores" }, "emptyState": { "noTools": "No se encontraron herramientas", - "noResources": "No se encontraron recursos" + "noResources": "No se encontraron recursos", + "noLogs": "No se encontraron registros", + "noErrors": "No se encontraron errores" }, "networkTimeout": { "label": "Tiempo de espera de red", diff --git a/webview-ui/src/i18n/locales/fr/mcp.json b/webview-ui/src/i18n/locales/fr/mcp.json index 2f3195067ca..6827eadd6e4 100644 --- a/webview-ui/src/i18n/locales/fr/mcp.json +++ b/webview-ui/src/i18n/locales/fr/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Outils", - "resources": "Ressources" + "resources": "Ressources", + "errors": "Erreurs" }, "emptyState": { "noTools": "Aucun outil trouvé", - "noResources": "Aucune ressource trouvée" + "noResources": "Aucune ressource trouvée", + "noLogs": "Aucun journal trouvé", + "noErrors": "Aucune erreur trouvée" }, "networkTimeout": { "label": "Délai d'attente réseau", diff --git a/webview-ui/src/i18n/locales/hi/mcp.json b/webview-ui/src/i18n/locales/hi/mcp.json index 32aee49dc49..c7cc5217b83 100644 --- a/webview-ui/src/i18n/locales/hi/mcp.json +++ b/webview-ui/src/i18n/locales/hi/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "उपकरण", - "resources": "संसाधन" + "resources": "संसाधन", + "errors": "त्रुटियाँ" }, "emptyState": { "noTools": "कोई उपकरण नहीं मिला", - "noResources": "कोई संसाधन नहीं मिला" + "noResources": "कोई संसाधन नहीं मिला", + "noLogs": "कोई लॉग नहीं मिला", + "noErrors": "कोई त्रुटि नहीं मिली" }, "networkTimeout": { "label": "नेटवर्क टाइमआउट", diff --git a/webview-ui/src/i18n/locales/it/mcp.json b/webview-ui/src/i18n/locales/it/mcp.json index 18bc9b487ec..43fe2d894ff 100644 --- a/webview-ui/src/i18n/locales/it/mcp.json +++ b/webview-ui/src/i18n/locales/it/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Strumenti", - "resources": "Risorse" + "resources": "Risorse", + "errors": "Errori" }, "emptyState": { "noTools": "Nessuno strumento trovato", - "noResources": "Nessuna risorsa trovata" + "noResources": "Nessuna risorsa trovata", + "noLogs": "Nessun log trovato", + "noErrors": "Nessun errore trovato" }, "networkTimeout": { "label": "Timeout di rete", diff --git a/webview-ui/src/i18n/locales/ja/mcp.json b/webview-ui/src/i18n/locales/ja/mcp.json index d5920c1c46f..c3d402df1bd 100644 --- a/webview-ui/src/i18n/locales/ja/mcp.json +++ b/webview-ui/src/i18n/locales/ja/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "ツール", - "resources": "リソース" + "resources": "リソース", + "errors": "エラー" }, "emptyState": { "noTools": "ツールが見つかりません", - "noResources": "リソースが見つかりません" + "noResources": "リソースが見つかりません", + "noLogs": "ログが見つかりません", + "noErrors": "エラーが見つかりません" }, "networkTimeout": { "label": "ネットワークタイムアウト", diff --git a/webview-ui/src/i18n/locales/ko/mcp.json b/webview-ui/src/i18n/locales/ko/mcp.json index 5d45eb0fca2..804e7bf28ea 100644 --- a/webview-ui/src/i18n/locales/ko/mcp.json +++ b/webview-ui/src/i18n/locales/ko/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "도구", - "resources": "리소스" + "resources": "리소스", + "errors": "오류" }, "emptyState": { "noTools": "도구를 찾을 수 없음", - "noResources": "리소스를 찾을 수 없음" + "noResources": "리소스를 찾을 수 없음", + "noLogs": "로그를 찾을 수 없음", + "noErrors": "오류를 찾을 수 없음" }, "networkTimeout": { "label": "네트워크 타임아웃", diff --git a/webview-ui/src/i18n/locales/pl/mcp.json b/webview-ui/src/i18n/locales/pl/mcp.json index 4f1f2a9411b..ecc13e82b32 100644 --- a/webview-ui/src/i18n/locales/pl/mcp.json +++ b/webview-ui/src/i18n/locales/pl/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Narzędzia", - "resources": "Zasoby" + "resources": "Zasoby", + "errors": "Błędy" }, "emptyState": { "noTools": "Nie znaleziono narzędzi", - "noResources": "Nie znaleziono zasobów" + "noResources": "Nie znaleziono zasobów", + "noLogs": "Nie znaleziono logów", + "noErrors": "Nie znaleziono błędów" }, "networkTimeout": { "label": "Limit czasu sieci", diff --git a/webview-ui/src/i18n/locales/pt-BR/mcp.json b/webview-ui/src/i18n/locales/pt-BR/mcp.json index 2c8c282e0d4..a218b07e855 100644 --- a/webview-ui/src/i18n/locales/pt-BR/mcp.json +++ b/webview-ui/src/i18n/locales/pt-BR/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Ferramentas", - "resources": "Recursos" + "resources": "Recursos", + "errors": "Erros" }, "emptyState": { "noTools": "Nenhuma ferramenta encontrada", - "noResources": "Nenhum recurso encontrado" + "noResources": "Nenhum recurso encontrado", + "noLogs": "Nenhum log encontrado", + "noErrors": "Nenhum erro encontrado" }, "networkTimeout": { "label": "Tempo limite de rede", diff --git a/webview-ui/src/i18n/locales/ru/mcp.json b/webview-ui/src/i18n/locales/ru/mcp.json index e99bedc2c36..62e667afffb 100644 --- a/webview-ui/src/i18n/locales/ru/mcp.json +++ b/webview-ui/src/i18n/locales/ru/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Инструменты", - "resources": "Ресурсы" + "resources": "Ресурсы", + "errors": "Ошибки" }, "emptyState": { "noTools": "Инструменты не найдены", - "noResources": "Ресурсы не найдены" + "noResources": "Ресурсы не найдены", + "noLogs": "Логи не найдены", + "noErrors": "Ошибки не найдены" }, "networkTimeout": { "label": "Тайм-аут сети", diff --git a/webview-ui/src/i18n/locales/tr/mcp.json b/webview-ui/src/i18n/locales/tr/mcp.json index c21b009773c..1f339af7cc1 100644 --- a/webview-ui/src/i18n/locales/tr/mcp.json +++ b/webview-ui/src/i18n/locales/tr/mcp.json @@ -19,11 +19,14 @@ }, "tabs": { "tools": "Araçlar", - "resources": "Kaynaklar" + "resources": "Kaynaklar", + "errors": "Hatalar" }, "emptyState": { "noTools": "Araç bulunamadı", - "noResources": "Kaynak bulunamadı" + "noResources": "Kaynak bulunamadı", + "noLogs": "Günlük bulunamadı", + "noErrors": "Hata bulunamadı" }, "networkTimeout": { "label": "Ağ Zaman Aşımı", diff --git a/webview-ui/src/i18n/locales/vi/mcp.json b/webview-ui/src/i18n/locales/vi/mcp.json index 1252094da4e..c7c10be655b 100644 --- a/webview-ui/src/i18n/locales/vi/mcp.json +++ b/webview-ui/src/i18n/locales/vi/mcp.json @@ -20,11 +20,14 @@ }, "tabs": { "tools": "Công cụ", - "resources": "Tài nguyên" + "resources": "Tài nguyên", + "errors": "Lỗi" }, "emptyState": { "noTools": "Không tìm thấy công cụ nào", - "noResources": "Không tìm thấy tài nguyên nào" + "noResources": "Không tìm thấy tài nguyên nào", + "noLogs": "Không tìm thấy nhật ký", + "noErrors": "Không tìm thấy lỗi" }, "networkTimeout": { "label": "Thời gian chờ mạng", diff --git a/webview-ui/src/i18n/locales/zh-CN/mcp.json b/webview-ui/src/i18n/locales/zh-CN/mcp.json index 858b2eeb0a7..8e1d1dbe8a0 100644 --- a/webview-ui/src/i18n/locales/zh-CN/mcp.json +++ b/webview-ui/src/i18n/locales/zh-CN/mcp.json @@ -20,11 +20,14 @@ }, "tabs": { "tools": "工具", - "resources": "资源" + "resources": "资源", + "errors": "错误" }, "emptyState": { "noTools": "未找到工具", - "noResources": "未找到资源" + "noResources": "未找到资源", + "noLogs": "未找到日志", + "noErrors": "未找到错误" }, "networkTimeout": { "label": "请求超时", diff --git a/webview-ui/src/i18n/locales/zh-TW/mcp.json b/webview-ui/src/i18n/locales/zh-TW/mcp.json index 5ac0afd0458..67cc0bde4d0 100644 --- a/webview-ui/src/i18n/locales/zh-TW/mcp.json +++ b/webview-ui/src/i18n/locales/zh-TW/mcp.json @@ -20,11 +20,14 @@ }, "tabs": { "tools": "工具", - "resources": "資源" + "resources": "資源", + "errors": "錯誤" }, "emptyState": { "noTools": "找不到工具", - "noResources": "找不到資源" + "noResources": "找不到資源", + "noLogs": "找不到日誌", + "noErrors": "找不到錯誤" }, "networkTimeout": { "label": "網路逾時", From c7016000816546e33bc333637daef6710bca04eb Mon Sep 17 00:00:00 2001 From: dlab-anton Date: Tue, 6 May 2025 19:48:42 +0700 Subject: [PATCH 026/228] Feat: Vertical settings tabs (#2914) Co-authored-by: Matt Rubens --- src/activate/registerCommands.ts | 24 +- webview-ui/src/components/common/Tab.tsx | 48 +- .../src/components/settings/SettingsView.tsx | 526 +++++++++++------- .../settings/__tests__/SettingsView.test.tsx | 210 ++++++- webview-ui/src/components/settings/styles.ts | 33 +- webview-ui/src/i18n/locales/ca/settings.json | 12 +- webview-ui/src/i18n/locales/de/settings.json | 12 +- webview-ui/src/i18n/locales/en/settings.json | 10 +- webview-ui/src/i18n/locales/es/settings.json | 12 +- webview-ui/src/i18n/locales/fr/settings.json | 12 +- webview-ui/src/i18n/locales/hi/settings.json | 12 +- webview-ui/src/i18n/locales/it/settings.json | 12 +- webview-ui/src/i18n/locales/ja/settings.json | 10 +- webview-ui/src/i18n/locales/ko/settings.json | 10 +- webview-ui/src/i18n/locales/pl/settings.json | 12 +- .../src/i18n/locales/pt-BR/settings.json | 14 +- webview-ui/src/i18n/locales/ru/settings.json | 10 +- webview-ui/src/i18n/locales/tr/settings.json | 12 +- webview-ui/src/i18n/locales/vi/settings.json | 12 +- .../src/i18n/locales/zh-CN/settings.json | 8 +- .../src/i18n/locales/zh-TW/settings.json | 10 +- 21 files changed, 656 insertions(+), 365 deletions(-) diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index c1712a80419..f90fc1e1d62 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -116,6 +116,8 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt telemetryService.captureTitleButtonClicked("settings") visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) + // Also explicitly post the visibility message to trigger scroll reliably + visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" }) }, "roo-cline.historyButtonClicked": () => { const visibleProvider = getVisibleProviderOrLog(outputChannel) @@ -212,10 +214,26 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit { + const panel = e.webviewPanel + if (panel.visible) { + panel.webview.postMessage({ type: "action", action: "didBecomeVisible" }) // Use the same message type as in SettingsView.tsx + } + }, + null, // First null is for `thisArgs` + context.subscriptions, // Register listener for disposal + ) + // Handle panel closing events. - newPanel.onDidDispose(() => { - setPanel(undefined, "tab") - }) + newPanel.onDidDispose( + () => { + setPanel(undefined, "tab") + }, + null, + context.subscriptions, // Also register dispose listener + ) // Lock the editor group so clicking on files doesn't open them over the panel. await delay(100) diff --git a/webview-ui/src/components/common/Tab.tsx b/webview-ui/src/components/common/Tab.tsx index 48794320fec..99c2b7b4b0b 100644 --- a/webview-ui/src/components/common/Tab.tsx +++ b/webview-ui/src/components/common/Tab.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes, useCallback } from "react" +import React, { HTMLAttributes, useCallback, forwardRef } from "react" import { useExtensionState } from "@/context/ExtensionStateContext" import { cn } from "@/lib/utils" @@ -6,7 +6,7 @@ import { cn } from "@/lib/utils" type TabProps = HTMLAttributes export const Tab = ({ className, children, ...props }: TabProps) => ( -
+
{children}
) @@ -45,3 +45,47 @@ export const TabContent = ({ className, children, ...props }: TabProps) => {
) } + +export const TabList = forwardRef< + HTMLDivElement, + HTMLAttributes & { + value: string + onValueChange: (value: string) => void + } +>(({ children, className, value, onValueChange, ...props }, ref) => { + return ( +
+ {React.Children.map(children, (child) => { + if (React.isValidElement(child)) { + return React.cloneElement(child as React.ReactElement, { + isSelected: child.props.value === value, + onSelect: () => onValueChange(child.props.value), + }) + } + return child + })} +
+ ) +}) + +export const TabTrigger = forwardRef< + HTMLButtonElement, + React.ButtonHTMLAttributes & { + value: string + isSelected?: boolean + onSelect?: () => void + } +>(({ children, className, value: _value, isSelected, onSelect, ...props }, ref) => { + return ( + + ) +}) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index fc36ac5a908..eeb11ea6ebf 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -1,4 +1,14 @@ -import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react" +import React, { + forwardRef, + memo, + useCallback, + useEffect, + useImperativeHandle, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react" import { useAppTranslation } from "@/i18n/TranslationContext" import { CheckCheck, @@ -14,7 +24,6 @@ import { Info, LucideIcon, } from "lucide-react" -import { CaretSortIcon } from "@radix-ui/react-icons" import { ExperimentId } from "@roo/shared/experiments" import { TelemetrySetting } from "@roo/shared/TelemetrySetting" @@ -32,13 +41,13 @@ import { AlertDialogHeader, AlertDialogFooter, Button, - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, } from "@/components/ui" -import { Tab, TabContent, TabHeader } from "../common/Tab" +import { Tab, TabContent, TabHeader, TabList, TabTrigger } from "../common/Tab" import { SetCachedStateField, SetExperimentEnabled } from "./types" import { SectionHeader } from "./SectionHeader" import ApiConfigManager from "./ApiConfigManager" @@ -53,6 +62,14 @@ import { ExperimentalSettings } from "./ExperimentalSettings" import { LanguageSettings } from "./LanguageSettings" import { About } from "./About" import { Section } from "./Section" +import { cn } from "@/lib/utils" + +export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden" +export const settingsTabList = + "w-48 data-[compact=true]:w-12 flex-shrink-0 flex flex-col overflow-y-auto overflow-x-hidden border-r border-vscode-sideBar-background" +export const settingsTabTrigger = + "whitespace-nowrap overflow-hidden min-w-0 h-12 px-4 py-3 box-border flex items-center border-l-2 border-transparent text-vscode-foreground opacity-70 hover:bg-vscode-list-hoverBackground data-[compact=true]:w-12 data-[compact=true]:p-4" +export const settingsTabTriggerActive = "opacity-100 border-vscode-focusBorder bg-vscode-list-activeSelectionBackground" export interface SettingsViewRef { checkUnsaveChanges: (then: () => void) => void @@ -87,6 +104,11 @@ const SettingsView = forwardRef(({ onDone, t const [isDiscardDialogShow, setDiscardDialogShow] = useState(false) const [isChangeDetected, setChangeDetected] = useState(false) const [errorMessage, setErrorMessage] = useState(undefined) + const [activeTab, setActiveTab] = useState( + targetSection && sectionNames.includes(targetSection as SectionName) + ? (targetSection as SectionName) + : "providers", + ) const prevApiConfigName = useRef(currentApiConfigName) const confirmDialogHandler = useRef<() => void>() @@ -125,7 +147,7 @@ const SettingsView = forwardRef(({ onDone, t telemetrySetting, terminalOutputLineLimit, terminalShellIntegrationTimeout, - terminalShellIntegrationDisabled, + terminalShellIntegrationDisabled, // Added from upstream terminalCommandDelay, terminalPowershellCounter, terminalZshClearEolMark, @@ -139,7 +161,6 @@ const SettingsView = forwardRef(({ onDone, t terminalCompressProgressBar, } = cachedState - // Make sure apiConfiguration is initialized and managed by SettingsView. const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) useEffect(() => { @@ -279,81 +300,116 @@ const SettingsView = forwardRef(({ onDone, t useImperativeHandle(ref, () => ({ checkUnsaveChanges }), [checkUnsaveChanges]) - const onConfirmDialogResult = useCallback((confirm: boolean) => { - if (confirm) { - confirmDialogHandler.current?.() + const onConfirmDialogResult = useCallback( + (confirm: boolean) => { + if (confirm) { + // Discard changes: Reset state and flag + setCachedState(extensionState) // Revert to original state + setChangeDetected(false) // Reset change flag + confirmDialogHandler.current?.() // Execute the pending action (e.g., tab switch) + } + // If confirm is false (Cancel), do nothing, dialog closes automatically + }, + [extensionState], // Depend on extensionState to get the latest original state + ) + + // Handle tab changes with unsaved changes check + const handleTabChange = useCallback( + (newTab: SectionName) => { + // Directly switch tab without checking for unsaved changes + setActiveTab(newTab) + }, + [], // No dependency on isChangeDetected needed anymore + ) + + // Store direct DOM element refs for each tab + const tabRefs = useRef>( + Object.fromEntries(sectionNames.map((name) => [name, null])) as Record, + ) + + // Track whether we're in compact mode + const [isCompactMode, setIsCompactMode] = useState(false) + const containerRef = useRef(null) + + // Setup resize observer to detect when we should switch to compact mode + useEffect(() => { + if (!containerRef.current) return + + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + // If container width is less than 500px, switch to compact mode + setIsCompactMode(entry.contentRect.width < 500) + } + }) + + observer.observe(containerRef.current) + + return () => { + observer?.disconnect() } }, []) - const providersRef = useRef(null) - const autoApproveRef = useRef(null) - const browserRef = useRef(null) - const checkpointsRef = useRef(null) - const notificationsRef = useRef(null) - const contextManagementRef = useRef(null) - const terminalRef = useRef(null) - const experimentalRef = useRef(null) - const languageRef = useRef(null) - const aboutRef = useRef(null) - - const sections: { id: SectionName; icon: LucideIcon; ref: React.RefObject }[] = useMemo( + const sections: { id: SectionName; icon: LucideIcon }[] = useMemo( () => [ - { id: "providers", icon: Webhook, ref: providersRef }, - { id: "autoApprove", icon: CheckCheck, ref: autoApproveRef }, - { id: "browser", icon: SquareMousePointer, ref: browserRef }, - { id: "checkpoints", icon: GitBranch, ref: checkpointsRef }, - { id: "notifications", icon: Bell, ref: notificationsRef }, - { id: "contextManagement", icon: Database, ref: contextManagementRef }, - { id: "terminal", icon: SquareTerminal, ref: terminalRef }, - { id: "experimental", icon: FlaskConical, ref: experimentalRef }, - { id: "language", icon: Globe, ref: languageRef }, - { id: "about", icon: Info, ref: aboutRef }, - ], - [ - providersRef, - autoApproveRef, - browserRef, - checkpointsRef, - notificationsRef, - contextManagementRef, - terminalRef, - experimentalRef, + { id: "providers", icon: Webhook }, + { id: "autoApprove", icon: CheckCheck }, + { id: "browser", icon: SquareMousePointer }, + { id: "checkpoints", icon: GitBranch }, + { id: "notifications", icon: Bell }, + { id: "contextManagement", icon: Database }, + { id: "terminal", icon: SquareTerminal }, + { id: "experimental", icon: FlaskConical }, + { id: "language", icon: Globe }, + { id: "about", icon: Info }, ], + [], // No dependencies needed now ) - const scrollToSection = (ref: React.RefObject) => ref.current?.scrollIntoView() + // Update target section logic to set active tab + useEffect(() => { + if (targetSection && sectionNames.includes(targetSection as SectionName)) { + setActiveTab(targetSection as SectionName) + } + }, [targetSection]) + + // Function to scroll the active tab into view for vertical layout + const scrollToActiveTab = useCallback(() => { + const activeTabElement = tabRefs.current[activeTab] + + if (activeTabElement) { + activeTabElement.scrollIntoView({ + behavior: "auto", + block: "nearest", + }) + } + }, [activeTab]) - // Scroll to target section when specified + // Effect to scroll when the active tab changes useEffect(() => { - if (targetSection) { - const sectionObj = sections.find((section) => section.id === targetSection) - if (sectionObj && sectionObj.ref.current) { - // Use setTimeout to ensure the scroll happens after render - setTimeout(() => scrollToSection(sectionObj.ref), 500) + scrollToActiveTab() + }, [activeTab, scrollToActiveTab]) + + // Effect to scroll when the webview becomes visible + useLayoutEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message.type === "action" && message.action === "didBecomeVisible") { + scrollToActiveTab() } } - }, [targetSection, sections]) + + window.addEventListener("message", handleMessage) + + return () => { + window.removeEventListener("message", handleMessage) + } + }, [scrollToActiveTab]) return (

{t("settings:header.title")}

- - - - - - {sections.map(({ id, icon: Icon, ref }) => ( - scrollToSection(ref)}> - - {t(`settings:sections.${id}`)} - - ))} - -
- -
- -
- -
{t("settings:sections.providers")}
+ {/* Vertical tabs layout */} +
+ {/* Tab sidebar */} + handleTabChange(value as SectionName)} + className={cn(settingsTabList)} + data-compact={isCompactMode} + data-testid="settings-tab-list"> + {sections.map(({ id, icon: Icon }) => { + const isSelected = id === activeTab + const onSelect = () => handleTabChange(id) + + // Base TabTrigger component definition + // We pass isSelected manually for styling, but onSelect is handled conditionally + const triggerComponent = ( + (tabRefs.current[id] = element)} + value={id} + isSelected={isSelected} // Pass manually for styling state + className={cn( + isSelected // Use manual isSelected for styling + ? `${settingsTabTrigger} ${settingsTabTriggerActive}` + : settingsTabTrigger, + "focus:ring-0", // Remove the focus ring styling + )} + data-testid={`tab-${id}`} + data-compact={isCompactMode}> +
+ + {t(`settings:sections.${id}`)} +
+
+ ) + + if (isCompactMode) { + // Wrap in Tooltip and manually add onClick to the trigger + return ( + + + + {/* Clone to avoid ref issues if triggerComponent itself had a key */} + {React.cloneElement(triggerComponent)} + + +

{t(`settings:sections.${id}`)}

+
+
+
+ ) + } else { + // Render trigger directly; TabList will inject onSelect via cloning + // Ensure the element passed to TabList has the key + return React.cloneElement(triggerComponent, { key: id }) + } + })} +
+ + {/* Content area */} + + {/* Providers Section */} + {activeTab === "providers" && ( +
+ +
+ +
{t("settings:sections.providers")}
+
+
+ +
+ + checkUnsaveChanges(() => + vscode.postMessage({ type: "loadApiConfiguration", text: configName }), + ) + } + onDeleteConfig={(configName: string) => + vscode.postMessage({ type: "deleteApiConfiguration", text: configName }) + } + onRenameConfig={(oldName: string, newName: string) => { + vscode.postMessage({ + type: "renameApiConfiguration", + values: { oldName, newName }, + apiConfiguration, + }) + prevApiConfigName.current = newName + }} + onUpsertConfig={(configName: string) => + vscode.postMessage({ + type: "upsertApiConfiguration", + text: configName, + apiConfiguration, + }) + } + /> + +
- - -
- - checkUnsaveChanges(() => - vscode.postMessage({ type: "loadApiConfiguration", text: configName }), - ) - } - onDeleteConfig={(configName: string) => - vscode.postMessage({ type: "deleteApiConfiguration", text: configName }) - } - onRenameConfig={(oldName: string, newName: string) => { - vscode.postMessage({ - type: "renameApiConfiguration", - values: { oldName, newName }, - apiConfiguration, - }) - prevApiConfigName.current = newName - }} - onUpsertConfig={(configName: string) => - vscode.postMessage({ - type: "upsertApiConfiguration", - text: configName, - apiConfiguration, - }) - } + )} + + {/* Auto-Approve Section */} + {activeTab === "autoApprove" && ( + - -
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
+ )} -
- -
- + {/* Checkpoints Section */} + {activeTab === "checkpoints" && ( + + )} + + {/* Notifications Section */} + {activeTab === "notifications" && ( + + )} + + {/* Context Management Section */} + {activeTab === "contextManagement" && ( + + )} + + {/* Terminal Section */} + {activeTab === "terminal" && ( + + )} + + {/* Experimental Section */} + {activeTab === "experimental" && ( + + )} + + {/* Language Section */} + {activeTab === "language" && ( + + )} + + {/* About Section */} + {activeTab === "about" && ( + + )} + +
diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx index 81f3dea1fd6..072296a7542 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx @@ -1,5 +1,4 @@ -// npx jest src/components/settings/__tests__/SettingsView.test.ts - +import React from "react" import { render, screen, fireEvent } from "@testing-library/react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" @@ -81,6 +80,47 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({ VSCodeRadioGroup: ({ children, onChange }: any) =>
{children}
, })) +// Mock Tab components +jest.mock("../../../components/common/Tab", () => ({ + ...jest.requireActual("../../../components/common/Tab"), + Tab: ({ children }: any) =>
{children}
, + TabHeader: ({ children }: any) =>
{children}
, + TabContent: ({ children }: any) =>
{children}
, + TabList: ({ children, value, onValueChange, "data-testid": dataTestId }: any) => { + // Store onValueChange in a global variable so TabTrigger can access it + ;(window as any).__onValueChange = onValueChange + return ( +
+ {children} +
+ ) + }, + TabTrigger: ({ children, value, "data-testid": dataTestId, onClick, isSelected }: any) => { + // This function simulates clicking on a tab and making its content visible + const handleClick = () => { + if (onClick) onClick() + // Access onValueChange from the global variable + const onValueChange = (window as any).__onValueChange + if (onValueChange) onValueChange(value) + // Make all tab contents invisible + document.querySelectorAll("[data-tab-content]").forEach((el) => { + ;(el as HTMLElement).style.display = "none" + }) + // Make this tab's content visible + const tabContent = document.querySelector(`[data-tab-content="${value}"]`) + if (tabContent) { + ;(tabContent as HTMLElement).style.display = "block" + } + } + + return ( + + ) + }, +})) + // Mock Slider component jest.mock("@/components/ui", () => ({ ...jest.requireActual("@/components/ui"), @@ -129,7 +169,7 @@ const renderSettingsView = () => { const onDone = jest.fn() const queryClient = new QueryClient() - render( + const result = render( @@ -140,7 +180,20 @@ const renderSettingsView = () => { // Hydrate initial state. mockPostMessage({}) - return { onDone } + // Helper function to activate a tab and ensure its content is visible + const activateTab = (tabId: string) => { + // Skip trying to find and click the tab, just directly render with the target section + // This bypasses the actual tab clicking mechanism but ensures the content is shown + result.rerender( + + + + + , + ) + } + + return { onDone, activateTab } } describe("SettingsView - Sound Settings", () => { @@ -149,7 +202,11 @@ describe("SettingsView - Sound Settings", () => { }) it("initializes with tts disabled by default", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") const ttsCheckbox = screen.getByTestId("tts-enabled-checkbox") expect(ttsCheckbox).not.toBeChecked() @@ -159,7 +216,11 @@ describe("SettingsView - Sound Settings", () => { }) it("initializes with sound disabled by default", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") const soundCheckbox = screen.getByTestId("sound-enabled-checkbox") expect(soundCheckbox).not.toBeChecked() @@ -169,7 +230,11 @@ describe("SettingsView - Sound Settings", () => { }) it("toggles tts setting and sends message to VSCode", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") const ttsCheckbox = screen.getByTestId("tts-enabled-checkbox") @@ -190,7 +255,11 @@ describe("SettingsView - Sound Settings", () => { }) it("toggles sound setting and sends message to VSCode", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") const soundCheckbox = screen.getByTestId("sound-enabled-checkbox") @@ -211,7 +280,11 @@ describe("SettingsView - Sound Settings", () => { }) it("shows tts slider when sound is enabled", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") // Enable tts const ttsCheckbox = screen.getByTestId("tts-enabled-checkbox") @@ -224,7 +297,11 @@ describe("SettingsView - Sound Settings", () => { }) it("shows volume slider when sound is enabled", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") // Enable sound const soundCheckbox = screen.getByTestId("sound-enabled-checkbox") @@ -237,7 +314,11 @@ describe("SettingsView - Sound Settings", () => { }) it("updates speed and sends message to VSCode when slider changes", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") // Enable tts const ttsCheckbox = screen.getByTestId("tts-enabled-checkbox") @@ -259,7 +340,11 @@ describe("SettingsView - Sound Settings", () => { }) it("updates volume and sends message to VSCode when slider changes", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") // Enable sound const soundCheckbox = screen.getByTestId("sound-enabled-checkbox") @@ -269,9 +354,9 @@ describe("SettingsView - Sound Settings", () => { const volumeSlider = screen.getByTestId("sound-volume-slider") fireEvent.change(volumeSlider, { target: { value: "0.75" } }) - // Click Save to save settings - const saveButton = screen.getByTestId("save-button") - fireEvent.click(saveButton) + // Click Save to save settings - use getAllByTestId to handle multiple elements + const saveButtons = screen.getAllByTestId("save-button") + fireEvent.click(saveButtons[0]) // Verify message sent to VSCode expect(vscode.postMessage).toHaveBeenCalledWith({ @@ -299,7 +384,11 @@ describe("SettingsView - Allowed Commands", () => { }) it("shows allowed commands section when alwaysAllowExecute is enabled", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the autoApprove tab + activateTab("autoApprove") // Enable always allow execute const executeCheckbox = screen.getByTestId("always-allow-execute-toggle") @@ -310,7 +399,11 @@ describe("SettingsView - Allowed Commands", () => { }) it("adds new command to the list", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the autoApprove tab + activateTab("autoApprove") // Enable always allow execute const executeCheckbox = screen.getByTestId("always-allow-execute-toggle") @@ -334,7 +427,11 @@ describe("SettingsView - Allowed Commands", () => { }) it("removes command from the list", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the autoApprove tab + activateTab("autoApprove") // Enable always allow execute const executeCheckbox = screen.getByTestId("always-allow-execute-toggle") @@ -360,8 +457,71 @@ describe("SettingsView - Allowed Commands", () => { }) }) + describe("SettingsView - Tab Navigation", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("renders with providers tab active by default", () => { + renderSettingsView() + + // Check that the tab list is rendered + const tabList = screen.getByTestId("settings-tab-list") + expect(tabList).toBeInTheDocument() + + // Check that providers content is visible + expect(screen.getByTestId("api-config-management")).toBeInTheDocument() + }) + + it("shows unsaved changes dialog when clicking Done with unsaved changes", () => { + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the notifications tab + activateTab("notifications") + + // Make a change to create unsaved changes + const soundCheckbox = screen.getByTestId("sound-enabled-checkbox") + fireEvent.click(soundCheckbox) + + // Click the Done button + const doneButton = screen.getByText("settings:common.done") + fireEvent.click(doneButton) + + // Check that unsaved changes dialog is shown + expect(screen.getByText("settings:unsavedChangesDialog.title")).toBeInTheDocument() + }) + + it("renders with targetSection prop", () => { + // Render with a specific target section + render( + + + + + , + ) + + // Hydrate initial state + mockPostMessage({}) + + // Verify browser-related content is visible and API config is not + expect(screen.queryByTestId("api-config-management")).not.toBeInTheDocument() + }) + }) +}) + +describe("SettingsView - Duplicate Commands", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + it("prevents duplicate commands", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the autoApprove tab + activateTab("autoApprove") // Enable always allow execute const executeCheckbox = screen.getByTestId("always-allow-execute-toggle") @@ -385,7 +545,11 @@ describe("SettingsView - Allowed Commands", () => { }) it("saves allowed commands when clicking Save", () => { - renderSettingsView() + // Render once and get the activateTab helper + const { activateTab } = renderSettingsView() + + // Activate the autoApprove tab + activateTab("autoApprove") // Enable always allow execute const executeCheckbox = screen.getByTestId("always-allow-execute-toggle") @@ -397,9 +561,9 @@ describe("SettingsView - Allowed Commands", () => { const addButton = screen.getByTestId("add-command-button") fireEvent.click(addButton) - // Click Save - const saveButton = screen.getByTestId("save-button") - fireEvent.click(saveButton) + // Click Save - use getAllByTestId to handle multiple elements + const saveButtons = screen.getAllByTestId("save-button") + fireEvent.click(saveButtons[0]) // Verify VSCode messages were sent expect(vscode.postMessage).toHaveBeenCalledWith( diff --git a/webview-ui/src/components/settings/styles.ts b/webview-ui/src/components/settings/styles.ts index ab403ab1b7c..7eac289bb0f 100644 --- a/webview-ui/src/components/settings/styles.ts +++ b/webview-ui/src/components/settings/styles.ts @@ -1,37 +1,6 @@ import styled from "styled-components" -export const DropdownWrapper = styled.div` - position: relative; - width: 100%; -` - -export const DropdownList = styled.div<{ $zIndex: number }>` - position: absolute; - top: calc(100% - 3px); - left: 0; - width: calc(100% - 2px); - max-height: 200px; - overflow-y: auto; - background-color: var(--vscode-dropdown-background); - border: 1px solid var(--vscode-list-activeSelectionBackground); - z-index: ${({ $zIndex }) => $zIndex}; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; -` - -export const DropdownItem = styled.div<{ $selected: boolean }>` - padding: 5px 10px; - cursor: pointer; - word-break: break-all; - white-space: normal; - - background-color: ${({ $selected }) => ($selected ? "var(--vscode-list-activeSelectionBackground)" : "inherit")}; - - &:hover { - background-color: var(--vscode-list-activeSelectionBackground); - } -` - +// Keep StyledMarkdown as it's used by ModelDescriptionMarkdown.tsx export const StyledMarkdown = styled.div` font-family: var(--vscode-font-family), diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index c375463fa01..23ecd846124 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Proveïdors", - "autoApprove": "Aprovació automàtica", - "browser": "Navegador / Ús de l'ordinador", + "autoApprove": "Auto-aprovació", + "browser": "Accés a l'ordinador", "checkpoints": "Punts de control", "notifications": "Notificacions", - "contextManagement": "Gestió de context", + "contextManagement": "Context", "terminal": "Terminal", - "advanced": "Avançat", - "experimental": "Funcions experimentals", + "experimental": "Experimental", "language": "Idioma", - "about": "Sobre Roo Code", - "interface": "Interfície" + "about": "Sobre Roo Code" }, "autoApprove": { "description": "Permet que Roo realitzi operacions automàticament sense requerir aprovació. Activeu aquesta configuració només si confieu plenament en la IA i enteneu els riscos de seguretat associats.", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index e3afa643b5c..7d421651f5c 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Anbieter", - "autoApprove": "Automatische Genehmigung", - "browser": "Browser / Computer-Nutzung", + "autoApprove": "Auto-Genehmigung", + "browser": "Computerzugriff", "checkpoints": "Kontrollpunkte", "notifications": "Benachrichtigungen", - "contextManagement": "Kontext-Management", + "contextManagement": "Kontext", "terminal": "Terminal", - "advanced": "Erweitert", - "experimental": "Experimentelle Funktionen", + "experimental": "Experimentell", "language": "Sprache", - "about": "Über Roo Code", - "interface": "Oberfläche" + "about": "Über Roo Code" }, "autoApprove": { "description": "Erlaubt Roo, Operationen automatisch ohne Genehmigung durchzuführen. Aktiviere diese Einstellungen nur, wenn du der KI vollständig vertraust und die damit verbundenen Sicherheitsrisiken verstehst.", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index c3b67d8e33c..00e7b022be4 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -23,16 +23,14 @@ "sections": { "providers": "Providers", "autoApprove": "Auto-Approve", - "browser": "Browser / Computer Use", + "browser": "Browser", "checkpoints": "Checkpoints", "notifications": "Notifications", - "contextManagement": "Context Management", + "contextManagement": "Context", "terminal": "Terminal", - "advanced": "Advanced", - "experimental": "Experimental Features", + "experimental": "Experimental", "language": "Language", - "about": "About Roo Code", - "interface": "Interface" + "about": "About Roo Code" }, "autoApprove": { "description": "Allow Roo to automatically perform operations without requiring approval. Enable these settings only if you fully trust the AI and understand the associated security risks.", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index eb732605ff7..a0e791b6ea3 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Proveedores", - "autoApprove": "Aprobación automática", - "browser": "Navegador / Uso del ordenador", + "autoApprove": "Auto-aprobación", + "browser": "Acceso al ordenador", "checkpoints": "Puntos de control", "notifications": "Notificaciones", - "contextManagement": "Gestión de contexto", + "contextManagement": "Contexto", "terminal": "Terminal", - "advanced": "Avanzado", - "experimental": "Funciones experimentales", + "experimental": "Experimental", "language": "Idioma", - "about": "Acerca de Roo Code", - "interface": "Interfaz" + "about": "Acerca de Roo Code" }, "autoApprove": { "description": "Permitir que Roo realice operaciones automáticamente sin requerir aprobación. Habilite esta configuración solo si confía plenamente en la IA y comprende los riesgos de seguridad asociados.", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 7e55b4a43f1..7e996034386 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Fournisseurs", - "autoApprove": "Approbation automatique", - "browser": "Navigateur / Utilisation de l'ordinateur", + "autoApprove": "Auto-approbation", + "browser": "Accès ordinateur", "checkpoints": "Points de contrôle", "notifications": "Notifications", - "contextManagement": "Gestion du contexte", + "contextManagement": "Contexte", "terminal": "Terminal", - "advanced": "Avancé", - "experimental": "Fonctionnalités expérimentales", + "experimental": "Expérimental", "language": "Langue", - "about": "À propos de Roo Code", - "interface": "Interface" + "about": "À propos de Roo Code" }, "autoApprove": { "description": "Permettre à Roo d'effectuer automatiquement des opérations sans requérir d'approbation. Activez ces paramètres uniquement si vous faites entièrement confiance à l'IA et que vous comprenez les risques de sécurité associés.", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 6dc671782ff..066645d4c59 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "प्रदाता", - "autoApprove": "स्वतः अनुमोदन", - "browser": "ब्राउज़र / कंप्यूटर उपयोग", + "autoApprove": "अनुमोदन", + "browser": "ब्राउज़र", "checkpoints": "चेकपॉइंट", "notifications": "सूचनाएँ", - "contextManagement": "संदर्भ प्रबंधन", + "contextManagement": "संदर्भ", "terminal": "टर्मिनल", - "advanced": "उन्नत", - "experimental": "प्रायोगिक सुविधाएँ", + "experimental": "प्रायोगिक", "language": "भाषा", - "about": "Roo Code के बारे में", - "interface": "इंटरफ़ेस" + "about": "परिचय" }, "autoApprove": { "description": "Roo को अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ऑपरेशन करने की अनुमति दें। इन सेटिंग्स को केवल तभी सक्षम करें जब आप AI पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index b47f31aa25a..d1cbfdb5ab4 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Fornitori", - "autoApprove": "Approvazione automatica", - "browser": "Browser / Uso del computer", + "autoApprove": "Auto-approvazione", + "browser": "Accesso computer", "checkpoints": "Punti di controllo", "notifications": "Notifiche", - "contextManagement": "Gestione del contesto", + "contextManagement": "Contesto", "terminal": "Terminal", - "advanced": "Avanzate", - "experimental": "Funzionalità sperimentali", + "experimental": "Sperimentale", "language": "Lingua", - "about": "Informazioni su Roo Code", - "interface": "Interfaccia" + "about": "Informazioni su Roo Code" }, "autoApprove": { "description": "Permetti a Roo di eseguire automaticamente operazioni senza richiedere approvazione. Abilita queste impostazioni solo se ti fidi completamente dell'IA e comprendi i rischi di sicurezza associati.", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 3430fb835a6..f82b9413c8d 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -23,16 +23,14 @@ "sections": { "providers": "プロバイダー", "autoApprove": "自動承認", - "browser": "ブラウザ / コンピューター使用", + "browser": "コンピューターアクセス", "checkpoints": "チェックポイント", "notifications": "通知", - "contextManagement": "コンテキスト管理", + "contextManagement": "コンテキスト", "terminal": "ターミナル", - "advanced": "詳細設定", - "experimental": "実験的機能", + "experimental": "実験的", "language": "言語", - "about": "Roo Codeについて", - "interface": "インターフェース" + "about": "Roo Codeについて" }, "autoApprove": { "description": "Rooが承認なしで自動的に操作を実行できるようにします。AIを完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 1816c1ab108..1cb7aef9eda 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -23,16 +23,14 @@ "sections": { "providers": "공급자", "autoApprove": "자동 승인", - "browser": "브라우저 / 컴퓨터 사용", + "browser": "컴퓨터 접근", "checkpoints": "체크포인트", "notifications": "알림", - "contextManagement": "컨텍스트 관리", + "contextManagement": "컨텍스트", "terminal": "터미널", - "advanced": "고급", - "experimental": "실험적 기능", + "experimental": "실험적", "language": "언어", - "about": "Roo Code 정보", - "interface": "인터페이스" + "about": "Roo Code 정보" }, "autoApprove": { "description": "Roo가 승인 없이 자동으로 작업을 수행할 수 있도록 허용합니다. AI를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 564a4fac506..8dad5cb1406 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Dostawcy", - "autoApprove": "Automatyczne zatwierdzanie", - "browser": "Przeglądarka / Użycie komputera", + "autoApprove": "Auto-zatwierdzanie", + "browser": "Dostęp komputera", "checkpoints": "Punkty kontrolne", "notifications": "Powiadomienia", - "contextManagement": "Zarządzanie kontekstem", + "contextManagement": "Kontekst", "terminal": "Terminal", - "advanced": "Zaawansowane", - "experimental": "Funkcje eksperymentalne", + "experimental": "Eksperymentalne", "language": "Język", - "about": "O Roo Code", - "interface": "Interfejs" + "about": "O Roo Code" }, "autoApprove": { "description": "Pozwól Roo na automatyczne wykonywanie operacji bez wymagania zatwierdzenia. Włącz te ustawienia tylko jeśli w pełni ufasz AI i rozumiesz związane z tym zagrożenia bezpieczeństwa.", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 628378a7547..e245ab30279 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Provedores", - "autoApprove": "Aprovação automática", - "browser": "Navegador / Uso do computador", - "checkpoints": "Pontos de verificação", + "autoApprove": "Aprovação", + "browser": "Navegador", + "checkpoints": "Checkpoints", "notifications": "Notificações", - "contextManagement": "Gestão de contexto", + "contextManagement": "Contexto", "terminal": "Terminal", - "advanced": "Avançado", - "experimental": "Recursos experimentais", + "experimental": "Experimental", "language": "Idioma", - "about": "Sobre o Roo Code", - "interface": "Interface" + "about": "Sobre" }, "autoApprove": { "description": "Permitir que o Roo realize operações automaticamente sem exigir aprovação. Ative essas configurações apenas se confiar totalmente na IA e compreender os riscos de segurança associados.", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 3d03429ea76..0c283b198a6 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -23,16 +23,14 @@ "sections": { "providers": "Провайдеры", "autoApprove": "Автоодобрение", - "browser": "Браузер / Использование компьютера", + "browser": "Доступ к компьютеру", "checkpoints": "Контрольные точки", "notifications": "Уведомления", - "contextManagement": "Управление контекстом", + "contextManagement": "Контекст", "terminal": "Терминал", - "advanced": "Дополнительно", - "experimental": "Экспериментальные функции", + "experimental": "Экспериментальное", "language": "Язык", - "about": "О Roo Code", - "interface": "Интерфейс" + "about": "О Roo Code" }, "autoApprove": { "description": "Разрешить Roo автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index f6c0db88564..5ee3eb89d4c 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Sağlayıcılar", - "autoApprove": "Otomatik Onay", - "browser": "Tarayıcı / Bilgisayar Kullanımı", + "autoApprove": "Oto-Onay", + "browser": "Bilgisayar Erişimi", "checkpoints": "Kontrol Noktaları", "notifications": "Bildirimler", - "contextManagement": "Bağlam Yönetimi", + "contextManagement": "Bağlam", "terminal": "Terminal", - "advanced": "Gelişmiş", - "experimental": "Deneysel Özellikler", + "experimental": "Deneysel", "language": "Dil", - "about": "Roo Code Hakkında", - "interface": "Arayüz" + "about": "Roo Code Hakkında" }, "autoApprove": { "description": "Roo'nun onay gerektirmeden otomatik olarak işlemler gerçekleştirmesine izin verin. Bu ayarları yalnızca yapay zekaya tamamen güveniyorsanız ve ilgili güvenlik risklerini anlıyorsanız etkinleştirin.", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 284ea759bec..41226b81bfd 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -22,17 +22,15 @@ }, "sections": { "providers": "Nhà cung cấp", - "autoApprove": "Tự động phê duyệt", - "browser": "Trình duyệt / Sử dụng máy tính", + "autoApprove": "Phê duyệt", + "browser": "Trình duyệt", "checkpoints": "Điểm kiểm tra", "notifications": "Thông báo", - "contextManagement": "Quản lý ngữ cảnh", + "contextManagement": "Ngữ cảnh", "terminal": "Terminal", - "advanced": "Nâng cao", - "experimental": "Tính năng thử nghiệm", + "experimental": "Thử nghiệm", "language": "Ngôn ngữ", - "about": "Về Roo Code", - "interface": "Giao diện" + "about": "Giới thiệu" }, "autoApprove": { "description": "Cho phép Roo tự động thực hiện các hoạt động mà không cần phê duyệt. Chỉ bật những cài đặt này nếu bạn hoàn toàn tin tưởng AI và hiểu rõ các rủi ro bảo mật liên quan.", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 28fc887170f..aa84e6b96bf 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -23,14 +23,12 @@ "sections": { "providers": "提供商", "autoApprove": "自动批准", - "browser": "浏览器交互", + "browser": "计算机交互", "checkpoints": "检查点", - "interface": "界面内容", "notifications": "通知", - "contextManagement": "上下文管理", + "contextManagement": "上下文", "terminal": "终端", - "advanced": "高级", - "experimental": "实验性功能", + "experimental": "实验性", "language": "语言", "about": "关于 Roo Code" }, diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index cbbf1575dee..1877135fcbe 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -23,16 +23,14 @@ "sections": { "providers": "供應商", "autoApprove": "自動核准", - "browser": "瀏覽器/電腦使用", + "browser": "電腦存取", "checkpoints": "檢查點", "notifications": "通知", - "contextManagement": "上下文管理", + "contextManagement": "上下文", "terminal": "終端機", - "advanced": "進階", - "experimental": "實驗性功能", + "experimental": "實驗性", "language": "語言", - "about": "關於 Roo Code", - "interface": "介面" + "about": "關於 Roo Code" }, "autoApprove": { "description": "允許 Roo 無需核准即執行操作。僅在您完全信任 AI 並了解相關安全風險時啟用這些設定。", From 6d0f08f23821c1b91c03d37dc83709e3592ed539 Mon Sep 17 00:00:00 2001 From: Tony Zhang <157202938+zhangtony239@users.noreply.github.com> Date: Tue, 6 May 2025 20:49:47 +0800 Subject: [PATCH 027/228] Fix language select width calculation (#3201) --- webview-ui/src/components/common/CodeBlock.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/webview-ui/src/components/common/CodeBlock.tsx b/webview-ui/src/components/common/CodeBlock.tsx index 2bc4527c032..70f1840dd5e 100644 --- a/webview-ui/src/components/common/CodeBlock.tsx +++ b/webview-ui/src/components/common/CodeBlock.tsx @@ -625,10 +625,7 @@ const CodeBlock = memo( { e.currentTarget.focus() From 8b6f5fd60b221399f30166a1c1a306bddc8bd7be Mon Sep 17 00:00:00 2001 From: Sam Hoang Van Date: Tue, 6 May 2025 22:19:47 +0700 Subject: [PATCH 028/228] Fix/remove path lib webview (#2529) --- webview-ui/src/utils/context-mentions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index e783f4c6253..8257be7ecbd 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -1,7 +1,7 @@ import { mentionRegex } from "@roo/shared/context-mentions" import { Fzf } from "fzf" import { ModeConfig } from "@roo/shared/modes" -import * as path from "path" + import { escapeSpaces } from "./path-mentions" export interface SearchResult { @@ -9,6 +9,11 @@ export interface SearchResult { type: "file" | "folder" label?: string } + +function getBasename(filepath: string): string { + return filepath.split("/").pop() || filepath +} + export function insertMention( text: string, position: number, @@ -254,7 +259,7 @@ export function getContextMenuOptions( // For display purposes, we don't escape spaces in the label or description const displayPath = formattedPath - const displayName = result.label || path.basename(result.path) + const displayName = result.label || getBasename(result.path) // We don't need to escape spaces here because the insertMention function // will handle that when the user selects a suggestion From 2e2c0a8f0272610fd7248a31bbd1f43c02cd5336 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Tue, 6 May 2025 08:34:29 -0700 Subject: [PATCH 029/228] chore: prepare for v3.16.0 release (#3214) --- .changeset/v3.16.0.md | 27 ++ README.md | 11 +- locales/ca/README.md | 65 +++-- locales/de/README.md | 65 +++-- locales/es/README.md | 65 +++-- locales/fr/README.md | 65 +++-- locales/hi/README.md | 65 +++-- locales/it/README.md | 65 +++-- locales/ja/README.md | 65 +++-- locales/ko/README.md | 65 +++-- locales/pl/README.md | 65 +++-- locales/pt-BR/README.md | 65 +++-- locales/ru/README.md | 65 +++-- locales/tr/README.md | 65 +++-- locales/vi/README.md | 65 +++-- locales/zh-CN/README.md | 65 +++-- locales/zh-TW/README.md | 65 +++-- package-lock.json | 302 ++++++++++---------- package.json | 2 +- src/core/webview/ClineProvider.ts | 2 +- webview-ui/src/i18n/locales/ca/chat.json | 10 +- webview-ui/src/i18n/locales/de/chat.json | 10 +- webview-ui/src/i18n/locales/en/chat.json | 10 +- webview-ui/src/i18n/locales/es/chat.json | 10 +- webview-ui/src/i18n/locales/fr/chat.json | 10 +- webview-ui/src/i18n/locales/hi/chat.json | 10 +- webview-ui/src/i18n/locales/it/chat.json | 10 +- webview-ui/src/i18n/locales/ja/chat.json | 10 +- webview-ui/src/i18n/locales/ko/chat.json | 10 +- webview-ui/src/i18n/locales/pl/chat.json | 10 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 10 +- webview-ui/src/i18n/locales/ru/chat.json | 10 +- webview-ui/src/i18n/locales/tr/chat.json | 10 +- webview-ui/src/i18n/locales/vi/chat.json | 10 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 10 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 10 +- 36 files changed, 776 insertions(+), 703 deletions(-) create mode 100644 .changeset/v3.16.0.md diff --git a/.changeset/v3.16.0.md b/.changeset/v3.16.0.md new file mode 100644 index 00000000000..63331e75bf8 --- /dev/null +++ b/.changeset/v3.16.0.md @@ -0,0 +1,27 @@ +--- +"roo-cline": minor +--- + +## New Features + +- Add Groq and Chutes API providers (thanks @shariqriazz!) +- Clickable code references in model responses navigate to source lines (thanks @KJ7LNW!) + +## Improvements + +- Organize provider settings into separate components (thanks @cte!) +- Move remaining provider settings into separate components (thanks @cte!) +- Use Lucide icons and translations in the code block (thanks @mrubens!) +- Improve accessibility of Auto-Approve Toggles (thanks @Deon588!) +- Add support for tests that use ESM libraries (thanks @cte!) + +## Fixes + +- Requesty provider fixes (thanks @dtrugman!) +- Fix migration and persistence of modeApiConfigs for per-mode API profiles (thanks @alasano!) + +## Code Quality + +- Webview message handler + terminal settings cleanup (thanks @cte!) +- Tidy up the Cline class (thanks @cte!) +- Move environment details to a separate module, add tests (thanks @cte!) diff --git a/README.md b/README.md index fd8da612724..ff7cfd5663c 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,14 @@ Check out the [CHANGELOG](CHANGELOG.md) for detailed updates and fixes. --- -## 🎉 Roo Code 3.15 Released +## 🎉 Roo Code 3.16 Released -Roo Code 3.15 brings new features and improvements based on your feedback! +Roo Code 3.16 brings new features and improvements based on your feedback! -- **Prompt Caching for Vertex** - Vertex AI now supports prompt caching, improving response times and reducing API costs. -- **Terminal Fallback** - Implemented a fallback mechanism when VSCode terminal shell integration fails, ensuring more reliable terminal operations. -- **Improved Code Snippets** - Enhanced code snippet rendering and interaction in the chat interface for better readability and usability. +- **Groq and Chutes API Providers** - Added support for Groq and Chutes API providers, expanding your model options. +- **Clickable Code References** - Code references in model responses now navigate directly to source lines. +- **MCP Stability Improvements** - Fixed several bugs to enhance the stability of MCP integrations. +- **Accessibility Improvements** - Enhanced accessibility of Auto-Approve toggles and other UI elements. --- diff --git a/locales/ca/README.md b/locales/ca/README.md index 4bbcfb1e36e..6738d90d954 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -47,13 +47,14 @@ Consulteu el [CHANGELOG](../CHANGELOG.md) per a actualitzacions i correccions de --- -## 🎉 Roo Code 3.15 Llançat +## 🎉 Roo Code 3.16 Llançat -Roo Code 3.15 aporta noves funcionalitats i millores basades en els vostres comentaris! +Roo Code 3.16 aporta noves funcionalitats i millores basades en els vostres comentaris! -- **Memòria cau per a prompts a Vertex** - Vertex AI ara suporta memòria cau de prompts, millorant els temps de resposta i reduint els costos d'API. -- **Mecanisme alternatiu per al Terminal** - S'ha implementat un mecanisme alternatiu quan la integració de shell del terminal de VSCode falla, assegurant operacions de terminal més fiables. -- **Fragments de codi millorats** - S'ha millorat la renderització i interacció amb fragments de codi a la interfície de xat per a una millor llegibilitat i usabilitat. +- **Proveïdors d'API Groq i Chutes** - S'ha afegit suport per als proveïdors d'API Groq i Chutes, ampliant les vostres opcions de models. +- **Referències de codi clicables** - Les referències de codi a les respostes del model ara naveguen directament a les línies de codi font. +- **Millores d'estabilitat MCP** - S'han corregit diversos errors per millorar l'estabilitat de les integracions MCP. +- **Millores d'accessibilitat** - S'ha millorat l'accessibilitat dels interruptors d'aprovació automàtica i altres elements de la interfície d'usuari. --- @@ -178,32 +179,34 @@ Ens encanten les contribucions de la comunitat! Comenceu llegint el nostre [CONT Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Llicència diff --git a/locales/de/README.md b/locales/de/README.md index 19ad5ebb6e1..eef52d27272 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -47,13 +47,14 @@ Sehen Sie sich das [CHANGELOG](../CHANGELOG.md) für detaillierte Updates und Fe --- -## 🎉 Roo Code 3.15 veröffentlicht +## 🎉 Roo Code 3.16 veröffentlicht -Roo Code 3.15 bringt neue Funktionen und Verbesserungen basierend auf deinem Feedback! +Roo Code 3.16 bringt neue Funktionen und Verbesserungen basierend auf deinem Feedback! -- **Prompt-Caching für Vertex** - Vertex AI unterstützt jetzt Prompt-Caching, was die Antwortzeiten verbessert und API-Kosten reduziert. -- **Terminal-Fallback** - Ein Fallback-Mechanismus wurde implementiert, der greift, wenn die VSCode-Terminal-Shell-Integration fehlschlägt, um zuverlässigere Terminal-Operationen zu gewährleisten. -- **Verbesserte Code-Snippets** - Verbesserte Darstellung und Interaktion mit Code-Snippets in der Chat-Oberfläche für bessere Lesbarkeit und Benutzerfreundlichkeit. +- **Groq und Chutes API-Provider** - Unterstützung für Groq und Chutes API-Provider hinzugefügt, was deine Modelloptionen erweitert. +- **Klickbare Code-Referenzen** - Code-Referenzen in Modellantworten navigieren jetzt direkt zu Quellzeilen. +- **MCP-Stabilitätsverbesserungen** - Mehrere Fehler behoben, um die Stabilität von MCP-Integrationen zu verbessern. +- **Barrierefreiheitsverbesserungen** - Verbesserte Zugänglichkeit von Auto-Approve-Schaltern und anderen UI-Elementen. --- @@ -178,32 +179,34 @@ Wir lieben Community-Beiträge! Beginnen Sie mit dem Lesen unserer [CONTRIBUTING Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Lizenz diff --git a/locales/es/README.md b/locales/es/README.md index a5d8df2f8e2..a6cbb5b9720 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -47,13 +47,14 @@ Consulta el [CHANGELOG](../CHANGELOG.md) para ver actualizaciones detalladas y c --- -## 🎉 Roo Code 3.15 Lanzado +## 🎉 Roo Code 3.16 Lanzado -¡Roo Code 3.15 trae nuevas funcionalidades y mejoras basadas en tus comentarios! +¡Roo Code 3.16 trae nuevas funcionalidades y mejoras basadas en tus comentarios! -- **Caché para prompts en Vertex** - Vertex AI ahora admite caché de prompts, mejorando los tiempos de respuesta y reduciendo los costos de API. -- **Mecanismo de respaldo para terminal** - Se implementó un mecanismo de respaldo cuando la integración de shell de terminal de VSCode falla, asegurando operaciones de terminal más confiables. -- **Fragmentos de código mejorados** - Renderizado e interacción mejorados de fragmentos de código en la interfaz de chat para mejor legibilidad y usabilidad. +- **Proveedores de API Groq y Chutes** - Se ha añadido soporte para proveedores de API Groq y Chutes, ampliando tus opciones de modelos. +- **Referencias de código clicables** - Las referencias de código en las respuestas del modelo ahora navegan directamente a las líneas de origen. +- **Mejoras de estabilidad MCP** - Se han corregido varios errores para mejorar la estabilidad de las integraciones MCP. +- **Mejoras de accesibilidad** - Accesibilidad mejorada de los interruptores de Auto-Aprobación y otros elementos de la interfaz. --- @@ -178,32 +179,34 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p ¡Gracias a todos nuestros colaboradores que han ayudado a mejorar Roo Code! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Licencia diff --git a/locales/fr/README.md b/locales/fr/README.md index bb5f3862a76..d0fa384b494 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -47,13 +47,14 @@ Consultez le [CHANGELOG](../CHANGELOG.md) pour des mises à jour détaillées et --- -## 🎉 Roo Code 3.15 est sorti +## 🎉 Roo Code 3.16 est sorti -Roo Code 3.15 apporte de nouvelles fonctionnalités et améliorations basées sur vos commentaires ! +Roo Code 3.16 apporte de nouvelles fonctionnalités et améliorations basées sur vos commentaires ! -- **Cache pour les prompts dans Vertex** - Vertex AI prend maintenant en charge le cache des prompts, améliorant les temps de réponse et réduisant les coûts d'API. -- **Mécanisme de secours pour le terminal** - Implémentation d'un mécanisme de secours lorsque l'intégration du shell du terminal VSCode échoue, garantissant des opérations de terminal plus fiables. -- **Fragments de code améliorés** - Rendu et interaction améliorés des fragments de code dans l'interface de chat pour une meilleure lisibilité et facilité d'utilisation. +- **Fournisseurs d'API Groq et Chutes** - Ajout du support pour les fournisseurs d'API Groq et Chutes, élargissant vos options de modèles. +- **Références de code cliquables** - Les références de code dans les réponses du modèle permettent maintenant de naviguer directement vers les lignes source. +- **Améliorations de stabilité MCP** - Correction de plusieurs bugs pour améliorer la stabilité des intégrations MCP. +- **Améliorations d'accessibilité** - Accessibilité améliorée des interrupteurs d'auto-approbation et d'autres éléments de l'interface utilisateur. --- @@ -178,32 +179,34 @@ Nous adorons les contributions de la communauté ! Commencez par lire notre [CON Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Licence diff --git a/locales/hi/README.md b/locales/hi/README.md index 2d14f74ef90..8f5e5df11df 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -47,13 +47,14 @@ --- -## 🎉 Roo Code 3.15 जारी +## 🎉 Roo Code 3.16 जारी -Roo Code 3.15 आपकी प्रतिक्रियाओं के आधार पर नई सुविधाएँ और सुधार लाता है! +Roo Code 3.16 आपकी प्रतिक्रियाओं के आधार पर नई सुविधाएँ और सुधार लाता है! -- **Vertex के लिए प्रॉम्प्ट कैशिंग** - Vertex AI अब प्रॉम्प्ट कैशिंग का समर्थन करता है, जिससे प्रतिक्रिया समय में सुधार और API लागत में कमी आती है। -- **टर्मिनल फॉलबैक** - VSCode टर्मिनल शेल एकीकरण विफल होने पर एक फॉलबैक तंत्र लागू किया गया है, जिससे अधिक विश्वसनीय टर्मिनल संचालन सुनिश्चित होता है। -- **बेहतर कोड स्निपेट्स** - चैट इंटरफेस में कोड स्निपेट्स की रेंडरिंग और इंटरैक्शन को बेहतर पठनीयता और उपयोगिता के लिए बढ़ाया गया है। +- **Groq और Chutes API प्रदाता** - Groq और Chutes API प्रदाताओं के लिए समर्थन जोड़ा गया है, जिससे आपके मॉडल विकल्प विस्तारित होते हैं। +- **क्लिक करने योग्य कोड संदर्भ** - मॉडल प्रतिक्रियाओं में कोड संदर्भ अब सीधे स्रोत लाइनों पर नेविगेट करते हैं। +- **MCP स्थिरता सुधार** - MCP एकीकरण की स्थिरता बढ़ाने के लिए कई बग्स को ठीक किया गया है। +- **पहुँच योग्यता सुधार** - स्वत:-अनुमोदन टॉगल और अन्य UI तत्वों की पहुँच योग्यता बढ़ाई गई है। --- @@ -178,32 +179,34 @@ code --install-extension bin/roo-cline-.vsix Roo Code को बेहतर बनाने में मदद करने वाले हमारे सभी योगदानकर्ताओं को धन्यवाद! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## लाइसेंस diff --git a/locales/it/README.md b/locales/it/README.md index dbf6fb5e886..652d4adb75b 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -47,13 +47,14 @@ Consulta il [CHANGELOG](../CHANGELOG.md) per aggiornamenti dettagliati e correzi --- -## 🎉 Roo Code 3.15 Rilasciato +## 🎉 Roo Code 3.16 Rilasciato -Roo Code 3.15 porta nuove funzionalità e miglioramenti basati sui tuoi feedback! +Roo Code 3.16 porta nuove funzionalità e miglioramenti basati sui tuoi feedback! -- **Cache per i prompt in Vertex** - Vertex AI ora supporta la cache dei prompt, migliorando i tempi di risposta e riducendo i costi API. -- **Fallback del Terminale** - Implementato un meccanismo di fallback quando l'integrazione della shell del terminale VSCode fallisce, garantendo operazioni del terminale più affidabili. -- **Snippet di Codice Migliorati** - Rendering e interazione migliorati degli snippet di codice nell'interfaccia di chat per una migliore leggibilità e usabilità. +- **Provider API Groq e Chutes** - Aggiunto supporto per i provider API Groq e Chutes, ampliando le tue opzioni di modelli. +- **Riferimenti di Codice Cliccabili** - I riferimenti di codice nelle risposte del modello ora navigano direttamente alle righe di origine. +- **Miglioramenti di Stabilità MCP** - Risolti diversi bug per migliorare la stabilità delle integrazioni MCP. +- **Miglioramenti di Accessibilità** - Migliorata l'accessibilità degli interruttori di Auto-Approvazione e altri elementi dell'interfaccia utente. --- @@ -178,32 +179,34 @@ Amiamo i contributi della community! Inizia leggendo il nostro [CONTRIBUTING.md] Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Licenza diff --git a/locales/ja/README.md b/locales/ja/README.md index 3700bc271a5..64a49001197 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -47,13 +47,14 @@ --- -## 🎉 Roo Code 3.15リリース +## 🎉 Roo Code 3.16リリース -Roo Code 3.15はユーザーのフィードバックに基づく新機能と改善を提供します! +Roo Code 3.16はユーザーのフィードバックに基づく新機能と改善を提供します! -- **Vertex向けプロンプトキャッシング** - Vertex AIがプロンプトキャッシングをサポートするようになり、応答時間の改善とAPIコストの削減を実現しました -- **ターミナルフォールバック** - VSCodeターミナルシェル統合が失敗した場合のフォールバックメカニズムを実装し、より信頼性の高いターミナル操作を確保しました -- **コードスニペットの改善** - チャットインターフェースでのコードスニペットのレンダリングと操作性を向上させ、読みやすさと使いやすさを改善しました +- **GroqとChutes APIプロバイダー** - GroqとChutes APIプロバイダーのサポートを追加し、モデルの選択肢を拡大しました +- **クリック可能なコード参照** - モデル応答内のコード参照がソースコードの行に直接ナビゲートできるようになりました +- **MCP安定性の向上** - MCP統合の安定性を高めるために複数のバグを修正しました +- **アクセシビリティの向上** - 自動承認トグルやその他のUI要素のアクセシビリティを向上させました --- @@ -178,32 +179,34 @@ code --install-extension bin/roo-cline-.vsix Roo Codeの改善に貢献してくれたすべての貢献者に感謝します! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## ライセンス diff --git a/locales/ko/README.md b/locales/ko/README.md index 29e2358178c..1313a12cb96 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -47,13 +47,14 @@ --- -## 🎉 Roo Code 3.15 출시 +## 🎉 Roo Code 3.16 출시 -Roo Code 3.15가 사용자 피드백을 바탕으로 새로운 기능과 개선 사항을 제공합니다! +Roo Code 3.16이 사용자 피드백을 바탕으로 새로운 기능과 개선 사항을 제공합니다! -- **Vertex용 프롬프트 캐싱** - Vertex AI에서 이제 프롬프트 캐싱을 지원하여 응답 시간을 개선하고 API 비용을 절감합니다. -- **터미널 폴백 메커니즘** - VSCode 터미널 쉘 통합이 실패할 때 작동하는 폴백 메커니즘을 구현하여 더 안정적인 터미널 작업을 보장합니다. -- **개선된 코드 스니펫** - 채팅 인터페이스에서 코드 스니펫의 렌더링과 상호작용을 개선하여 가독성과 사용성을 향상시켰습니다. +- **Groq 및 Chutes API 제공자** - Groq 및 Chutes API 제공자 지원을 추가하여 모델 옵션을 확장했습니다. +- **클릭 가능한 코드 참조** - 모델 응답의 코드 참조가 이제 소스 코드 라인으로 직접 이동할 수 있습니다. +- **MCP 안정성 개선** - MCP 통합의 안정성을 향상시키기 위해 여러 버그를 수정했습니다. +- **접근성 개선** - 자동 승인 토글 및 기타 UI 요소의 접근성을 향상시켰습니다. --- @@ -178,32 +179,34 @@ code --install-extension bin/roo-cline-.vsix Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사드립니다! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## 라이선스 diff --git a/locales/pl/README.md b/locales/pl/README.md index 08ad175d278..91d248d3905 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -47,13 +47,14 @@ Sprawdź [CHANGELOG](../CHANGELOG.md), aby uzyskać szczegółowe informacje o a --- -## 🎉 Roo Code 3.15 został wydany +## 🎉 Roo Code 3.16 został wydany -Roo Code 3.15 wprowadza nowe funkcje i usprawnienia na podstawie opinii użytkowników! +Roo Code 3.16 wprowadza nowe funkcje i usprawnienia na podstawie opinii użytkowników! -- **Pamięć podręczna dla promptów w Vertex** - Vertex AI teraz obsługuje pamięć podręczną promptów, poprawiając czas odpowiedzi i zmniejszając koszty API. -- **Awaryjny tryb terminala** - Zaimplementowano mechanizm awaryjny na wypadek niepowodzenia integracji powłoki terminala VSCode, zapewniając bardziej niezawodne działanie terminala. -- **Ulepszone fragmenty kodu** - Udoskonalono renderowanie i interakcję z fragmentami kodu w interfejsie czatu dla lepszej czytelności i użyteczności. +- **Dostawcy API Groq i Chutes** - Dodano obsługę dostawców API Groq i Chutes, rozszerzając dostępne opcje modeli. +- **Klikalne odniesienia do kodu** - Odniesienia do kodu w odpowiedziach modelu teraz nawigują bezpośrednio do linii źródłowych. +- **Ulepszenia stabilności MCP** - Naprawiono kilka błędów, aby zwiększyć stabilność integracji MCP. +- **Ulepszenia dostępności** - Zwiększono dostępność przełączników automatycznego zatwierdzania i innych elementów interfejsu użytkownika. --- @@ -178,32 +179,34 @@ Kochamy wkład społeczności! Zacznij od przeczytania naszego [CONTRIBUTING.md] Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Licencja diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index 153580a893f..def76394c2c 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -47,13 +47,14 @@ Confira o [CHANGELOG](../CHANGELOG.md) para atualizações e correções detalha --- -## 🎉 Roo Code 3.15 Lançado +## 🎉 Roo Code 3.16 Lançado -O Roo Code 3.15 traz novas funcionalidades e melhorias baseadas no seu feedback! +O Roo Code 3.16 traz novas funcionalidades e melhorias baseadas no seu feedback! -- **Cache para prompts no Vertex** - O Vertex AI agora suporta cache de prompts, melhorando os tempos de resposta e reduzindo custos de API. -- **Fallback para Terminal** - Implementado um mecanismo de fallback quando a integração do shell do terminal do VSCode falha, garantindo operações de terminal mais confiáveis. -- **Snippets de Código Aprimorados** - Renderização e interação aprimoradas com snippets de código na interface de chat para melhor legibilidade e usabilidade. +- **Provedores de API Groq e Chutes** - Adicionado suporte para provedores de API Groq e Chutes, expandindo suas opções de modelos. +- **Referências de código clicáveis** - Referências de código nas respostas do modelo agora navegam diretamente para as linhas de origem. +- **Melhorias de estabilidade MCP** - Corrigidos vários bugs para melhorar a estabilidade das integrações MCP. +- **Melhorias de acessibilidade** - Aprimorada a acessibilidade dos interruptores de Auto-Aprovação e outros elementos da interface. --- @@ -178,32 +179,34 @@ Adoramos contribuições da comunidade! Comece lendo nosso [CONTRIBUTING.md](CON Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melhor! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Licença diff --git a/locales/ru/README.md b/locales/ru/README.md index d61b395731e..45dd782297c 100644 --- a/locales/ru/README.md +++ b/locales/ru/README.md @@ -49,13 +49,14 @@ --- -## 🎉 Выпущен Roo Code 3.15 +## 🎉 Выпущен Roo Code 3.16 -Roo Code 3.15 приносит новые функции и улучшения на основе ваших отзывов! +Roo Code 3.16 приносит новые функции и улучшения на основе ваших отзывов! -- **Кэширование промптов для Vertex** - Vertex AI теперь поддерживает кэширование промптов, улучшая время отклика и снижая затраты на API. -- **Резервный механизм для терминала** - Реализован резервный механизм на случай сбоя интеграции оболочки терминала VSCode, обеспечивающий более надежную работу терминала. -- **Улучшенные фрагменты кода** - Улучшены отображение и взаимодействие с фрагментами кода в интерфейсе чата для лучшей читаемости и удобства использования. +- **Поставщики API Groq и Chutes** - Добавлена поддержка поставщиков API Groq и Chutes, расширяющая ваши возможности выбора моделей. +- **Кликабельные ссылки на код** - Ссылки на код в ответах модели теперь позволяют переходить непосредственно к строкам исходного кода. +- **Улучшения стабильности MCP** - Исправлено несколько ошибок для повышения стабильности интеграций MCP. +- **Улучшения доступности** - Повышена доступность переключателей автоматического одобрения и других элементов пользовательского интерфейса. --- @@ -180,32 +181,34 @@ code --install-extension bin/roo-cline-.vsix Спасибо всем нашим участникам, которые помогли сделать Roo Code лучше! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Лицензия diff --git a/locales/tr/README.md b/locales/tr/README.md index 44b5f286187..3236c04c5ea 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -47,13 +47,14 @@ Detaylı güncellemeler ve düzeltmeler için [CHANGELOG](../CHANGELOG.md) dosya --- -## 🎉 Roo Code 3.15 Yayınlandı +## 🎉 Roo Code 3.16 Yayınlandı -Roo Code 3.15 geri bildirimlerinize dayanarak yeni özellikler ve iyileştirmeler getiriyor! +Roo Code 3.16 geri bildirimlerinize dayanarak yeni özellikler ve iyileştirmeler getiriyor! -- **Vertex için Prompt Önbelleği** - Vertex AI artık prompt önbelleklemeyi destekliyor, yanıt sürelerini iyileştiriyor ve API maliyetlerini azaltıyor. -- **Terminal Yedek Mekanizması** - VSCode terminal kabuk entegrasyonu başarısız olduğunda devreye giren bir yedek mekanizma uygulandı, daha güvenilir terminal işlemleri sağlanıyor. -- **Geliştirilmiş Kod Parçacıkları** - Daha iyi okunabilirlik ve kullanılabilirlik için sohbet arayüzünde kod parçacıklarının görüntülenmesi ve etkileşimi geliştirildi. +- **Groq ve Chutes API Sağlayıcıları** - Model seçeneklerinizi genişleten Groq ve Chutes API sağlayıcıları için destek eklendi. +- **Tıklanabilir Kod Referansları** - Model yanıtlarındaki kod referansları artık doğrudan kaynak satırlarına gidiyor. +- **MCP Kararlılık İyileştirmeleri** - MCP entegrasyonlarının kararlılığını artırmak için çeşitli hatalar düzeltildi. +- **Erişilebilirlik İyileştirmeleri** - Otomatik Onay düğmeleri ve diğer UI öğelerinin erişilebilirliği geliştirildi. --- @@ -178,32 +179,34 @@ Topluluk katkılarını seviyoruz! [CONTRIBUTING.md](CONTRIBUTING.md) dosyasın Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara teşekkür ederiz! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Lisans diff --git a/locales/vi/README.md b/locales/vi/README.md index 5a63bd65348..5019eea4166 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -47,13 +47,14 @@ Kiểm tra [CHANGELOG](../CHANGELOG.md) để biết thông tin chi tiết về --- -## 🎉 Đã Phát Hành Roo Code 3.15 +## 🎉 Đã Phát Hành Roo Code 3.16 -Roo Code 3.15 mang đến những tính năng mới và cải tiến dựa trên phản hồi của bạn! +Roo Code 3.16 mang đến những tính năng mới và cải tiến dựa trên phản hồi của bạn! -- **Bộ nhớ đệm cho prompt trên Vertex** - Vertex AI giờ đây hỗ trợ bộ nhớ đệm prompt, cải thiện thời gian phản hồi và giảm chi phí API. -- **Cơ chế dự phòng cho Terminal** - Đã triển khai cơ chế dự phòng khi tích hợp shell terminal VSCode thất bại, đảm bảo hoạt động terminal đáng tin cậy hơn. -- **Cải thiện đoạn mã (code snippets)** - Nâng cao hiển thị và tương tác với đoạn mã trong giao diện trò chuyện để dễ đọc và sử dụng hơn. +- **Nhà cung cấp API Groq và Chutes** - Đã thêm hỗ trợ cho nhà cung cấp API Groq và Chutes, mở rộng lựa chọn mô hình của bạn. +- **Tham chiếu mã có thể nhấp** - Tham chiếu mã trong phản hồi của mô hình giờ đây có thể điều hướng trực tiếp đến dòng mã nguồn. +- **Cải thiện độ ổn định MCP** - Đã sửa nhiều lỗi để nâng cao độ ổn định của tích hợp MCP. +- **Cải thiện khả năng tiếp cận** - Nâng cao khả năng tiếp cận của các nút Tự động phê duyệt và các phần tử giao diện khác. --- @@ -178,32 +179,34 @@ Chúng tôi rất hoan nghênh đóng góp từ cộng đồng! Bắt đầu b Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo Code! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## Giấy Phép diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index 98e29ac8102..974c028f368 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -47,13 +47,14 @@ --- -## 🎉 Roo Code 3.15 已发布 +## 🎉 Roo Code 3.16 已发布 -Roo Code 3.15 基于您的反馈带来新功能和改进! +Roo Code 3.16 基于您的反馈带来新功能和改进! -- **Vertex 提示词缓存** - Vertex AI 现已支持提示词缓存,改善响应时间并降低 API 费用。 -- **终端回退机制** - 实现了 VSCode 终端 shell 集成失败时的回退机制,确保更可靠的终端操作。 -- **代码片段优化** - 增强了聊天界面中代码片段的渲染和交互,提高了可读性和易用性。 +- **Groq 和 Chutes API 提供商** - 添加对 Groq 和 Chutes API 提供商的支持,扩展了您的模型选择。 +- **可点击代码引用** - 模型响应中的代码引用现在可直接导航到源代码行。 +- **MCP 稳定性改进** - 修复了多个 bug 以增强 MCP 集成的稳定性。 +- **无障碍性改进** - 增强了自动批准开关和其他 UI 元素的无障碍性。 --- @@ -178,32 +179,34 @@ code --install-extension bin/roo-cline-.vsix 感谢所有帮助改进 Roo Code 的贡献者! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## 许可证 diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 91151e07638..5611594b1ef 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -48,13 +48,14 @@ --- -## 🎉 Roo Code 3.15 已發布 +## 🎉 Roo Code 3.16 已發布 -Roo Code 3.15 根據您的回饋帶來新功能和改進! +Roo Code 3.16 根據您的回饋帶來新功能和改進! -- **Vertex 提示詞快取** - Vertex AI 現已支援提示詞快取,改善回應時間並降低 API 成本。 -- **終端機備用機制** - 實作了 VSCode 終端機 shell 整合失敗時的備用機制,確保更可靠的終端機操作。 -- **程式碼片段優化** - 增強了聊天介面中程式碼片段的渲染和互動,提高了可讀性和易用性。 +- **Groq 和 Chutes API 提供者** - 新增對 Groq 和 Chutes API 提供者的支援,擴展您的模型選擇。 +- **可點擊程式碼參考** - 模型回應中的程式碼參考現在可直接導航至原始碼行。 +- **MCP 穩定性改進** - 修復了多個錯誤以增強 MCP 整合的穩定性。 +- **無障礙性改進** - 增強了自動核准開關和其他 UI 元素的無障礙性。 --- @@ -179,32 +180,34 @@ code --install-extension bin/roo-cline-.vsix 感謝所有幫助改進 Roo Code 的貢獻者! -|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| -|:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
| -|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|wkordalski
wkordalski
|feifei325
feifei325
|lloydchang
lloydchang
|cannuri
cannuri
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|sachasayan
sachasayan
|qdaxb
qdaxb
|zhangtony239
zhangtony239
|lupuletic
lupuletic
| -|Premshay
Premshay
|psv2522
psv2522
|elianiva
elianiva
|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
|nbihan-mediware
nbihan-mediware
|dtrugman
dtrugman
| -|emshvac
emshvac
|kyle-apex
kyle-apex
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
|upamune
upamune
| -|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|anton-otee
anton-otee
| -|philfung
philfung
|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| -|vagadiya
vagadiya
|vincentsong
vincentsong
|yongjer
yongjer
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| -|benzntech
benzntech
|axkirillov
axkirillov
|bramburn
bramburn
|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
| -|Chenjiayuan195
Chenjiayuan195
|jr
jr
|julionav
julionav
|SplittyDev
SplittyDev
|mdp
mdp
|napter
napter
| -|nevermorec
nevermorec
|mecab
mecab
|olup
olup
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| -|jwcraig
jwcraig
|shoopapa
shoopapa
|im47cn
im47cn
|hongzio
hongzio
|GOODBOY008
GOODBOY008
|dqroid
dqroid
| -|dlab-anton
dlab-anton
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|asychin
asychin
|PretzelVector
PretzelVector
| -|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|shariqriazz
shariqriazz
|seedlord
seedlord
| -|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
|oprstchn
oprstchn
| -|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|Jdo300
Jdo300
|hesara
hesara
| -|DeXtroTip
DeXtroTip
|celestial-vault
celestial-vault
|linegel
linegel
|dbasclpy
dbasclpy
|dleen
dleen
|chadgauth
chadgauth
| -|olearycrew
olearycrew
|bogdan0083
bogdan0083
|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|QuinsZouls
QuinsZouls
| -|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
| -|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
| -|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|Sarke
Sarke
|kvokka
kvokka
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| -|libertyteeth
libertyteeth
|shtse8
shtse8
| | | | | + +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| +| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| wkordalski
wkordalski
| feifei325
feifei325
| lloydchang
lloydchang
| cannuri
cannuri
| +| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| sachasayan
sachasayan
| qdaxb
qdaxb
| zhangtony239
zhangtony239
| lupuletic
lupuletic
| +| Premshay
Premshay
| psv2522
psv2522
| elianiva
elianiva
| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| +| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| nbihan-mediware
nbihan-mediware
| dtrugman
dtrugman
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| upamune
upamune
| +| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| anton-otee
anton-otee
| +| philfung
philfung
| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| +| vagadiya
vagadiya
| vincentsong
vincentsong
| yongjer
yongjer
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| +| benzntech
benzntech
| axkirillov
axkirillov
| bramburn
bramburn
| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| +| Chenjiayuan195
Chenjiayuan195
| jr
jr
| julionav
julionav
| SplittyDev
SplittyDev
| mdp
mdp
| napter
napter
| +| nevermorec
nevermorec
| mecab
mecab
| olup
olup
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| shoopapa
shoopapa
| im47cn
im47cn
| hongzio
hongzio
| GOODBOY008
GOODBOY008
| dqroid
dqroid
| +| dlab-anton
dlab-anton
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| asychin
asychin
| PretzelVector
PretzelVector
| +| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| shariqriazz
shariqriazz
| seedlord
seedlord
| +| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| oprstchn
oprstchn
| +| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| Jdo300
Jdo300
| hesara
hesara
| +| DeXtroTip
DeXtroTip
| celestial-vault
celestial-vault
| linegel
linegel
| dbasclpy
dbasclpy
| dleen
dleen
| chadgauth
chadgauth
| +| olearycrew
olearycrew
| bogdan0083
bogdan0083
| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| QuinsZouls
QuinsZouls
| +| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| +| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| +| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| Sarke
Sarke
| kvokka
kvokka
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| +| libertyteeth
libertyteeth
| shtse8
shtse8
| | | | | + ## 授權 diff --git a/package-lock.json b/package-lock.json index 53bdd4ee386..621a1e01866 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "tsup": "^8.4.0", "tsx": "^4.19.3", "typescript": "^5.4.5", - "vitest": "^3.1.2", + "vitest": "^3.1.3", "zod-to-ts": "^1.2.0" }, "engines": { @@ -9328,14 +9328,14 @@ "dev": true }, "node_modules/@vitest/expect": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.2.tgz", - "integrity": "sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", + "integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.2", - "@vitest/utils": "3.1.2", + "@vitest/spy": "3.1.3", + "@vitest/utils": "3.1.3", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -9344,13 +9344,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.2.tgz", - "integrity": "sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", + "integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.2", + "@vitest/spy": "3.1.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -9371,9 +9371,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.2.tgz", - "integrity": "sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", + "integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", "dev": true, "license": "MIT", "dependencies": { @@ -9384,13 +9384,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.2.tgz", - "integrity": "sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", + "integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.2", + "@vitest/utils": "3.1.3", "pathe": "^2.0.3" }, "funding": { @@ -9398,13 +9398,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.2.tgz", - "integrity": "sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", + "integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.2", + "@vitest/pretty-format": "3.1.3", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -9413,9 +9413,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.2.tgz", - "integrity": "sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", + "integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9426,13 +9426,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.2.tgz", - "integrity": "sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.2", + "@vitest/pretty-format": "3.1.3", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -22105,9 +22105,9 @@ } }, "node_modules/vite": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", - "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -22180,15 +22180,15 @@ } }, "node_modules/vite-node": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.2.tgz", - "integrity": "sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", + "integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", + "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, @@ -22203,9 +22203,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -22220,9 +22220,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -22237,9 +22237,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -22254,9 +22254,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -22271,9 +22271,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -22288,9 +22288,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -22305,9 +22305,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -22322,9 +22322,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -22339,9 +22339,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -22356,9 +22356,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -22373,9 +22373,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -22390,9 +22390,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -22407,9 +22407,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -22424,9 +22424,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -22441,9 +22441,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -22458,9 +22458,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -22475,9 +22475,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -22492,9 +22492,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "cpu": [ "arm64" ], @@ -22509,9 +22509,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -22526,9 +22526,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -22543,9 +22543,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -22560,9 +22560,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -22577,9 +22577,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -22594,9 +22594,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -22611,9 +22611,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -22628,9 +22628,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -22641,31 +22641,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.3", - "@esbuild/android-arm": "0.25.3", - "@esbuild/android-arm64": "0.25.3", - "@esbuild/android-x64": "0.25.3", - "@esbuild/darwin-arm64": "0.25.3", - "@esbuild/darwin-x64": "0.25.3", - "@esbuild/freebsd-arm64": "0.25.3", - "@esbuild/freebsd-x64": "0.25.3", - "@esbuild/linux-arm": "0.25.3", - "@esbuild/linux-arm64": "0.25.3", - "@esbuild/linux-ia32": "0.25.3", - "@esbuild/linux-loong64": "0.25.3", - "@esbuild/linux-mips64el": "0.25.3", - "@esbuild/linux-ppc64": "0.25.3", - "@esbuild/linux-riscv64": "0.25.3", - "@esbuild/linux-s390x": "0.25.3", - "@esbuild/linux-x64": "0.25.3", - "@esbuild/netbsd-arm64": "0.25.3", - "@esbuild/netbsd-x64": "0.25.3", - "@esbuild/openbsd-arm64": "0.25.3", - "@esbuild/openbsd-x64": "0.25.3", - "@esbuild/sunos-x64": "0.25.3", - "@esbuild/win32-arm64": "0.25.3", - "@esbuild/win32-ia32": "0.25.3", - "@esbuild/win32-x64": "0.25.3" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/vite/node_modules/fdir": { @@ -22697,19 +22697,19 @@ } }, "node_modules/vitest": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.2.tgz", - "integrity": "sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", + "integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.1.2", - "@vitest/mocker": "3.1.2", - "@vitest/pretty-format": "^3.1.2", - "@vitest/runner": "3.1.2", - "@vitest/snapshot": "3.1.2", - "@vitest/spy": "3.1.2", - "@vitest/utils": "3.1.2", + "@vitest/expect": "3.1.3", + "@vitest/mocker": "3.1.3", + "@vitest/pretty-format": "^3.1.3", + "@vitest/runner": "3.1.3", + "@vitest/snapshot": "3.1.3", + "@vitest/spy": "3.1.3", + "@vitest/utils": "3.1.3", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", @@ -22722,7 +22722,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.2", + "vite-node": "3.1.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -22738,8 +22738,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.2", - "@vitest/ui": "3.1.2", + "@vitest/browser": "3.1.3", + "@vitest/ui": "3.1.3", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 13b0a4a4a70..a8cff070113 100644 --- a/package.json +++ b/package.json @@ -460,7 +460,7 @@ "tsup": "^8.4.0", "tsx": "^4.19.3", "typescript": "^5.4.5", - "vitest": "^3.1.2", + "vitest": "^3.1.3", "zod-to-ts": "^1.2.0" }, "lint-staged": { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 899d16aae2c..64a84d91a3b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -75,7 +75,7 @@ export class ClineProvider extends EventEmitter implements public isViewLaunched = false public settingsImportedAt?: number - public readonly latestAnnouncementId = "apr-30-2025-3-15" // Update for v3.15.0 announcement + public readonly latestAnnouncementId = "may-06-2025-3-16" // Update for v3.16.0 announcement public readonly providerSettingsManager: ProviderSettingsManager public readonly customModesManager: CustomModesManager diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 2dbf818edcf..bad37a04bdf 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Copiar a l'entrada (o Shift + clic)" }, "announcement": { - "title": "🎉 Roo Code 3.15 publicat", - "description": "Roo Code 3.15 porta noves funcionalitats i millores basades en els teus comentaris.", + "title": "🎉 Roo Code 3.16 publicat", + "description": "Roo Code 3.16 porta noves funcionalitats i millores basades en els teus comentaris.", "whatsNew": "Novetats", - "feature1": "Memòria cau de prompts per a Vertex: S'ha afegit suport de memòria cau de prompts per a Vertex AI, millorant els temps de resposta i reduint els costos d'API", - "feature2": "Mecanisme alternatiu per a Terminal: S'ha implementat un mecanisme alternatiu quan la integració de shell del terminal VSCode falla", - "feature3": "Fragments de codi millorats: S'ha millorat la renderització i interacció amb fragments de codi a la interfície de xat", + "feature1": "Proveïdors d'API Groq i Chutes: S'ha afegit suport per als proveïdors d'API Groq i Chutes (gràcies @shariqriazz!)", + "feature2": "Referències de codi clicables: Les referències de codi a les respostes del model ara naveguen directament a les línies de codi font (gràcies @KJ7LNW!)", + "feature3": "Millores d'estabilitat MCP: S'han corregit diversos errors per millorar l'estabilitat de les integracions MCP", "hideButton": "Amagar anunci", "detailsDiscussLinks": "Obtingues més detalls i participa a Discord i Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 058c4436fc5..1b7b199f5b7 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -202,12 +202,12 @@ "copyToInput": "In Eingabefeld kopieren (oder Shift + Klick)" }, "announcement": { - "title": "🎉 Roo Code 3.15 veröffentlicht", - "description": "Roo Code 3.15 bringt neue Funktionen und Verbesserungen basierend auf deinem Feedback.", + "title": "🎉 Roo Code 3.16 veröffentlicht", + "description": "Roo Code 3.16 bringt neue Funktionen und Verbesserungen basierend auf deinem Feedback.", "whatsNew": "Was ist neu", - "feature1": "Prompt-Caching für Vertex: Prompt-Caching-Unterstützung für Vertex AI hinzugefügt, verbessert Antwortzeiten und reduziert API-Kosten", - "feature2": "Terminal-Fallback: Implementierung eines Fallback-Mechanismus, wenn die VSCode-Terminal-Shell-Integration fehlschlägt", - "feature3": "Verbesserte Code-Snippets: Verbesserte Darstellung und Interaktion mit Code-Snippets in der Chat-Oberfläche", + "feature1": "Groq und Chutes API-Provider: Unterstützung für Groq und Chutes API-Provider hinzugefügt (danke @shariqriazz!)", + "feature2": "Klickbare Code-Referenzen: Code-Referenzen in Modellantworten navigieren jetzt direkt zu Quellzeilen (danke @KJ7LNW!)", + "feature3": "MCP-Stabilitätsverbesserungen: Mehrere Fehler behoben, um die Stabilität von MCP-Integrationen zu verbessern", "hideButton": "Ankündigung ausblenden", "detailsDiscussLinks": "Erhalte mehr Details und diskutiere auf Discord und Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 05efd561a5e..dc04575b837 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -195,12 +195,12 @@ "description": "Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for actions you fully trust. More detailed configuration available in Settings." }, "announcement": { - "title": "🎉 Roo Code 3.15 Released", - "description": "Roo Code 3.15 brings new features and improvements based on your feedback.", + "title": "🎉 Roo Code 3.16 Released", + "description": "Roo Code 3.16 brings new features and improvements based on your feedback.", "whatsNew": "What's New", - "feature1": "Prompt Caching for Vertex: Added prompt caching support for Vertex AI, improving response times and reducing API costs", - "feature2": "Terminal Fallback: Implemented a fallback mechanism when VSCode terminal shell integration fails", - "feature3": "Improved Code Snippets: Enhanced code snippet rendering and interaction in the chat interface", + "feature1": "Groq and Chutes API Providers: Added support for Groq and Chutes API providers (thanks @shariqriazz!)", + "feature2": "Clickable Code References: Code references in model responses now navigate to source lines (thanks @KJ7LNW!)", + "feature3": "MCP Stability Improvements: Fixed several bugs to enhance the stability of MCP integrations", "hideButton": "Hide announcement", "detailsDiscussLinks": "Get more details and discuss in Discord and Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 84b40091d11..6e7fec9862e 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Copiar a la entrada (o Shift + clic)" }, "announcement": { - "title": "🎉 Roo Code 3.15 publicado", - "description": "Roo Code 3.15 trae nuevas funcionalidades y mejoras basadas en tus comentarios.", + "title": "🎉 Roo Code 3.16 publicado", + "description": "Roo Code 3.16 trae nuevas funcionalidades y mejoras basadas en tus comentarios.", "whatsNew": "Novedades", - "feature1": "Caché de prompts para Vertex: Se ha añadido soporte de caché de prompts para Vertex AI, mejorando los tiempos de respuesta y reduciendo los costos de API", - "feature2": "Mecanismo alternativo para Terminal: Se ha implementado un mecanismo alternativo cuando falla la integración de shell de terminal de VSCode", - "feature3": "Fragmentos de código mejorados: Renderizado e interacción mejorados de fragmentos de código en la interfaz de chat", + "feature1": "Proveedores de API Groq y Chutes: Se ha añadido soporte para proveedores de API Groq y Chutes (¡gracias @shariqriazz!)", + "feature2": "Referencias de código clicables: Las referencias de código en las respuestas del modelo ahora navegan directamente a las líneas de origen (¡gracias @KJ7LNW!)", + "feature3": "Mejoras de estabilidad MCP: Se han corregido varios errores para mejorar la estabilidad de las integraciones MCP", "hideButton": "Ocultar anuncio", "detailsDiscussLinks": "Obtén más detalles y participa en Discord y Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 4af00e9fc67..b4b6139cff8 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Copier vers l'entrée (ou Shift + clic)" }, "announcement": { - "title": "🎉 Roo Code 3.15 est sortie", - "description": "Roo Code 3.15 apporte de nouvelles fonctionnalités et améliorations basées sur vos retours.", + "title": "🎉 Roo Code 3.16 est sortie", + "description": "Roo Code 3.16 apporte de nouvelles fonctionnalités et améliorations basées sur vos retours.", "whatsNew": "Quoi de neuf", - "feature1": "Mise en cache des prompts pour Vertex : Ajout du support de mise en cache des prompts pour Vertex AI, améliorant les temps de réponse et réduisant les coûts API", - "feature2": "Solution de secours pour Terminal : Implémentation d'un mécanisme de secours lorsque l'intégration shell du terminal VSCode échoue", - "feature3": "Extraits de code améliorés : Rendu et interaction améliorés des extraits de code dans l'interface de chat", + "feature1": "Fournisseurs d'API Groq et Chutes : Ajout du support pour les fournisseurs d'API Groq et Chutes (merci @shariqriazz !)", + "feature2": "Références de code cliquables : Les références de code dans les réponses du modèle permettent maintenant de naviguer directement vers les lignes source (merci @KJ7LNW !)", + "feature3": "Améliorations de stabilité MCP : Correction de plusieurs bugs pour améliorer la stabilité des intégrations MCP", "hideButton": "Masquer l'annonce", "detailsDiscussLinks": "Obtenez plus de détails et participez aux discussions sur Discord et Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 7845ca97ad2..7ee3aad0b18 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -202,12 +202,12 @@ "copyToInput": "इनपुट में कॉपी करें (या Shift + क्लिक)" }, "announcement": { - "title": "🎉 Roo Code 3.15 रिलीज़ हुआ", - "description": "Roo Code 3.15 आपके फीडबैक के आधार पर नई सुविधाएँ और सुधार लाता है।", + "title": "🎉 Roo Code 3.16 रिलीज़ हुआ", + "description": "Roo Code 3.16 आपके फीडबैक के आधार पर नई सुविधाएँ और सुधार लाता है।", "whatsNew": "नई सुविधाएँ", - "feature1": "Vertex के लिए प्रॉम्प्ट कैशिंग: Vertex AI के लिए प्रॉम्प्ट कैशिंग समर्थन जोड़ा गया, प्रतिक्रिया समय में सुधार और API लागत कम करता है", - "feature2": "टर्मिनल फॉलबैक: VSCode टर्मिनल शेल इंटीग्रेशन विफल होने पर फॉलबैक तंत्र लागू किया गया", - "feature3": "बेहतर कोड स्निपेट: चैट इंटरफेस में कोड स्निपेट रेंडरिंग और इंटरैक्शन में सुधार", + "feature1": "Groq और Chutes API प्रदाता: Groq और Chutes API प्रदाताओं के लिए समर्थन जोड़ा गया (धन्यवाद @shariqriazz!)", + "feature2": "क्लिक करने योग्य कोड संदर्भ: मॉडल प्रतिक्रियाओं में कोड संदर्भ अब सीधे स्रोत लाइनों पर नेविगेट करते हैं (धन्यवाद @KJ7LNW!)", + "feature3": "MCP स्थिरता सुधार: MCP एकीकरण की स्थिरता बढ़ाने के लिए कई बग्स को ठीक किया गया", "hideButton": "घोषणा छिपाएँ", "detailsDiscussLinks": "Discord और Reddit पर अधिक जानकारी प्राप्त करें और चर्चा में भाग लें 🚀" }, diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 55e2d272627..30cae6a29b7 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Copia nell'input (o Shift + clic)" }, "announcement": { - "title": "🎉 Rilasciato Roo Code 3.15", - "description": "Roo Code 3.15 porta nuove funzionalità e miglioramenti basati sui tuoi feedback.", + "title": "🎉 Rilasciato Roo Code 3.16", + "description": "Roo Code 3.16 porta nuove funzionalità e miglioramenti basati sui tuoi feedback.", "whatsNew": "Novità", - "feature1": "Caching dei prompt per Vertex: Aggiunto supporto per il caching dei prompt per Vertex AI, migliorando i tempi di risposta e riducendo i costi API", - "feature2": "Fallback del Terminale: Implementato un meccanismo di fallback quando l'integrazione shell del terminale VSCode fallisce", - "feature3": "Frammenti di codice migliorati: Migliorato il rendering e l'interazione dei frammenti di codice nell'interfaccia di chat", + "feature1": "Provider API Groq e Chutes: Aggiunto supporto per i provider API Groq e Chutes (grazie @shariqriazz!)", + "feature2": "Riferimenti di codice cliccabili: I riferimenti di codice nelle risposte del modello ora navigano direttamente alle righe di origine (grazie @KJ7LNW!)", + "feature3": "Miglioramenti di stabilità MCP: Risolti diversi bug per migliorare la stabilità delle integrazioni MCP", "hideButton": "Nascondi annuncio", "detailsDiscussLinks": "Ottieni maggiori dettagli e partecipa alle discussioni su Discord e Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 6530e3f3c47..95ed9a275b0 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -202,12 +202,12 @@ "copyToInput": "入力欄にコピー(またはShift + クリック)" }, "announcement": { - "title": "🎉 Roo Code 3.15 リリース", - "description": "Roo Code 3.15は新機能とあなたのフィードバックに基づく改善をもたらします。", + "title": "🎉 Roo Code 3.16 リリース", + "description": "Roo Code 3.16は新機能とあなたのフィードバックに基づく改善をもたらします。", "whatsNew": "新機能", - "feature1": "Vertex用プロンプトキャッシング: Vertex AI向けのプロンプトキャッシングサポートを追加し、応答時間を改善しAPIコストを削減", - "feature2": "ターミナルフォールバック: VSCodeターミナルシェル統合が失敗した場合のフォールバックメカニズムを実装", - "feature3": "コードスニペットの改善: チャットインターフェースでのコードスニペットのレンダリングと操作性を向上", + "feature1": "GroqとChutes APIプロバイダー: GroqとChutes APIプロバイダーのサポートを追加(@shariqriazzさんに感謝!)", + "feature2": "クリック可能なコード参照: モデル応答内のコード参照がソースコードの行に直接ナビゲートできるようになりました(@KJ7LNWさんに感謝!)", + "feature3": "MCP安定性の向上: MCP統合の安定性を高めるために複数のバグを修正", "hideButton": "通知を非表示", "detailsDiscussLinks": "詳細はDiscordRedditでご確認・ディスカッションください 🚀" }, diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 827ec0cfea5..3b68f351ad3 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -202,12 +202,12 @@ "copyToInput": "입력창에 복사 (또는 Shift + 클릭)" }, "announcement": { - "title": "🎉 Roo Code 3.15 출시", - "description": "Roo Code 3.15는 사용자 피드백을 기반으로 새로운 기능과 개선사항을 제공합니다.", + "title": "🎉 Roo Code 3.16 출시", + "description": "Roo Code 3.16은 사용자 피드백을 기반으로 새로운 기능과 개선사항을 제공합니다.", "whatsNew": "새로운 기능", - "feature1": "Vertex용 프롬프트 캐싱: Vertex AI에 프롬프트 캐싱 지원이 추가되어 응답 시간 개선 및 API 비용 절감", - "feature2": "터미널 대체 메커니즘: VSCode 터미널 쉘 통합이 실패할 때 작동하는 대체 메커니즘 구현", - "feature3": "개선된 코드 스니펫: 채팅 인터페이스에서 코드 스니펫 렌더링 및 상호작용 향상", + "feature1": "Groq 및 Chutes API 제공자: Groq 및 Chutes API 제공자 지원 추가 (감사합니다 @shariqriazz!)", + "feature2": "클릭 가능한 코드 참조: 모델 응답의 코드 참조가 이제 소스 코드 라인으로 직접 이동 가능 (감사합니다 @KJ7LNW!)", + "feature3": "MCP 안정성 개선: MCP 통합의 안정성을 향상시키기 위한 여러 버그 수정", "hideButton": "공지 숨기기", "detailsDiscussLinks": "DiscordReddit에서 더 자세한 정보를 확인하고 논의하세요 🚀" }, diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 2597f2ba13d..6646b4e36ff 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Kopiuj do pola wprowadzania (lub Shift + kliknięcie)" }, "announcement": { - "title": "🎉 Roo Code 3.15 wydany", - "description": "Roo Code 3.15 przynosi nowe funkcje i ulepszenia na podstawie Twoich opinii.", + "title": "🎉 Roo Code 3.16 wydany", + "description": "Roo Code 3.16 przynosi nowe funkcje i ulepszenia na podstawie Twoich opinii.", "whatsNew": "Co nowego", - "feature1": "Buforowanie podpowiedzi dla Vertex: Dodano obsługę buforowania podpowiedzi dla Vertex AI, poprawiając czasy odpowiedzi i zmniejszając koszty API", - "feature2": "Mechanizm awaryjny dla Terminala: Zaimplementowano mechanizm awaryjny w przypadku awarii integracji powłoki terminala VSCode", - "feature3": "Ulepszone fragmenty kodu: Ulepszono renderowanie i interakcję z fragmentami kodu w interfejsie czatu", + "feature1": "Dostawcy API Groq i Chutes: Dodano obsługę dostawców API Groq i Chutes (dzięki @shariqriazz!)", + "feature2": "Klikalne odniesienia do kodu: Odniesienia do kodu w odpowiedziach modelu teraz nawigują bezpośrednio do linii źródłowych (dzięki @KJ7LNW!)", + "feature3": "Ulepszenia stabilności MCP: Naprawiono kilka błędów, aby zwiększyć stabilność integracji MCP", "hideButton": "Ukryj ogłoszenie", "detailsDiscussLinks": "Uzyskaj więcej szczegółów i dołącz do dyskusji na Discord i Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 37820facbd2..911464d434a 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Copiar para entrada (ou Shift + clique)" }, "announcement": { - "title": "🎉 Roo Code 3.15 Lançado", - "description": "Roo Code 3.15 traz novos recursos e melhorias baseados no seu feedback.", + "title": "🎉 Roo Code 3.16 Lançado", + "description": "Roo Code 3.16 traz novos recursos e melhorias baseados no seu feedback.", "whatsNew": "O que há de novo", - "feature1": "Cache de prompts para Vertex: Adicionado suporte de cache de prompts para Vertex AI, melhorando tempos de resposta e reduzindo custos de API", - "feature2": "Fallback de Terminal: Implementado um mecanismo de fallback quando a integração de shell do terminal VSCode falha", - "feature3": "Snippets de código aprimorados: Renderização e interação aprimoradas de snippets de código na interface de chat", + "feature1": "Provedores de API Groq e Chutes: Adicionado suporte para provedores de API Groq e Chutes (obrigado @shariqriazz!)", + "feature2": "Referências de código clicáveis: Referências de código nas respostas do modelo agora navegam diretamente para as linhas de origem (obrigado @KJ7LNW!)", + "feature3": "Melhorias de estabilidade MCP: Corrigidos vários bugs para melhorar a estabilidade das integrações MCP", "hideButton": "Ocultar anúncio", "detailsDiscussLinks": "Obtenha mais detalhes e participe da discussão no Discord e Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 5bd13934f1b..a56f9afb378 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -195,12 +195,12 @@ "description": "Автоодобрение позволяет Roo Code выполнять действия без запроса разрешения. Включайте только для полностью доверенных действий. Более подробная настройка доступна в Настройках." }, "announcement": { - "title": "🎉 Выпущен Roo Code 3.15", - "description": "Roo Code 3.15 приносит новые функции и улучшения на основе ваших отзывов.", + "title": "🎉 Выпущен Roo Code 3.16", + "description": "Roo Code 3.16 приносит новые функции и улучшения на основе ваших отзывов.", "whatsNew": "Что нового", - "feature1": "Кэширование запросов для Vertex: Добавлена поддержка кэширования запросов для Vertex AI, улучшающая время отклика и снижающая затраты на API", - "feature2": "Резервный механизм для терминала: Реализован резервный механизм на случай сбоя интеграции оболочки терминала VSCode", - "feature3": "Улучшенные фрагменты кода: Улучшено отображение и взаимодействие с фрагментами кода в интерфейсе чата", + "feature1": "Поставщики API Groq и Chutes: Добавлена поддержка поставщиков API Groq и Chutes (спасибо @shariqriazz!)", + "feature2": "Кликабельные ссылки на код: Ссылки на код в ответах модели теперь позволяют переходить непосредственно к строкам исходного кода (спасибо @KJ7LNW!)", + "feature3": "Улучшения стабильности MCP: Исправлено несколько ошибок для повышения стабильности интеграций MCP", "hideButton": "Скрыть объявление", "detailsDiscussLinks": "Подробнее и обсуждение в Discord и Reddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 2ea0eaaf059..448602b9d59 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Giriş alanına kopyala (veya Shift + tıklama)" }, "announcement": { - "title": "🎉 Roo Code 3.15 Yayınlandı", - "description": "Roo Code 3.15 geri bildirimlerinize dayalı yeni özellikler ve iyileştirmeler getiriyor.", + "title": "🎉 Roo Code 3.16 Yayınlandı", + "description": "Roo Code 3.16 geri bildirimlerinize dayalı yeni özellikler ve iyileştirmeler getiriyor.", "whatsNew": "Yenilikler", - "feature1": "Vertex için İstem Önbelleği: Vertex AI için istem önbelleği desteği eklendi, yanıt sürelerini iyileştiriyor ve API maliyetlerini azaltıyor", - "feature2": "Terminal Yedek Mekanizması: VSCode terminal kabuk entegrasyonu başarısız olduğunda devreye giren yedek mekanizma uygulandı", - "feature3": "Geliştirilmiş Kod Parçacıkları: Sohbet arayüzünde kod parçacıklarının görüntülenmesi ve etkileşimi iyileştirildi", + "feature1": "Groq ve Chutes API Sağlayıcıları: Groq ve Chutes API sağlayıcıları için destek eklendi (teşekkürler @shariqriazz!)", + "feature2": "Tıklanabilir Kod Referansları: Model yanıtlarındaki kod referansları artık doğrudan kaynak satırlarına gidiyor (teşekkürler @KJ7LNW!)", + "feature3": "MCP Kararlılık İyileştirmeleri: MCP entegrasyonlarının kararlılığını artırmak için çeşitli hatalar düzeltildi", "hideButton": "Duyuruyu gizle", "detailsDiscussLinks": "Discord ve Reddit üzerinde daha fazla ayrıntı edinin ve tartışmalara katılın 🚀" }, diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 7b9091dc08c..697286ace39 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -202,12 +202,12 @@ "copyToInput": "Sao chép vào ô nhập liệu (hoặc Shift + nhấp chuột)" }, "announcement": { - "title": "🎉 Roo Code 3.15 Đã phát hành", - "description": "Roo Code 3.15 mang đến các tính năng và cải tiến mới dựa trên phản hồi của bạn.", + "title": "🎉 Roo Code 3.16 Đã phát hành", + "description": "Roo Code 3.16 mang đến các tính năng và cải tiến mới dựa trên phản hồi của bạn.", "whatsNew": "Có gì mới", - "feature1": "Bộ nhớ đệm lời nhắc cho Vertex: Đã thêm hỗ trợ bộ nhớ đệm lời nhắc cho Vertex AI, cải thiện thời gian phản hồi và giảm chi phí API", - "feature2": "Cơ chế dự phòng cho Terminal: Đã triển khai cơ chế dự phòng khi tích hợp shell terminal VSCode gặp sự cố", - "feature3": "Cải thiện đoạn mã: Nâng cao hiển thị và tương tác với đoạn mã trong giao diện trò chuyện", + "feature1": "Nhà cung cấp API Groq và Chutes: Đã thêm hỗ trợ cho nhà cung cấp API Groq và Chutes (cảm ơn @shariqriazz!)", + "feature2": "Tham chiếu mã có thể nhấp: Tham chiếu mã trong phản hồi của mô hình giờ đây có thể điều hướng trực tiếp đến dòng mã nguồn (cảm ơn @KJ7LNW!)", + "feature3": "Cải thiện độ ổn định MCP: Đã sửa nhiều lỗi để nâng cao độ ổn định của tích hợp MCP", "hideButton": "Ẩn thông báo", "detailsDiscussLinks": "Nhận thêm chi tiết và thảo luận tại DiscordReddit 🚀" }, diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 0caff90987a..3f6bf8548e7 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -202,12 +202,12 @@ "copyToInput": "复制到输入框(或按住Shift点击)" }, "announcement": { - "title": "🎉 Roo Code 3.15 已发布", - "description": "Roo Code 3.15 带来基于您反馈的新功能和改进。", + "title": "🎉 Roo Code 3.16 已发布", + "description": "Roo Code 3.16 带来基于您反馈的新功能和改进。", "whatsNew": "新特性", - "feature1": "Vertex 提示词缓存: 为 Vertex AI 添加提示词缓存支持,提高响应速度并降低 API 费用", - "feature2": "终端备选方案: 实现 VSCode 终端 shell 集成失败时的备选机制", - "feature3": "代码片段优化: 增强聊天界面中代码片段的渲染和交互体验", + "feature1": "Groq 和 Chutes API 提供商: 添加对 Groq 和 Chutes API 提供商的支持(感谢 @shariqriazz!)", + "feature2": "可点击代码引用: 模型响应中的代码引用现在可直接导航到源代码行(感谢 @KJ7LNW!)", + "feature3": "MCP 稳定性改进: 修复多个 bug 以增强 MCP 集成的稳定性", "hideButton": "隐藏公告", "detailsDiscussLinks": "在 DiscordReddit 获取更多详情并参与讨论 🚀" }, diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 884d86ddd7f..b16231eea0f 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -202,12 +202,12 @@ "copyToInput": "複製到輸入框(或按住 Shift 並點選)" }, "announcement": { - "title": "🎉 Roo Code 3.15 已發布", - "description": "Roo Code 3.15 帶來基於您意見回饋的新功能與改進。", + "title": "🎉 Roo Code 3.16 已發布", + "description": "Roo Code 3.16 帶來基於您意見回饋的新功能與改進。", "whatsNew": "新功能", - "feature1": "Vertex 提示詞快取: 為 Vertex AI 新增提示詞快取支援,改善回應時間並降低 API 費用", - "feature2": "終端機備援機制: 實作 VSCode 終端機 shell 整合失敗時的備援機制", - "feature3": "程式碼片段強化: 增強聊天介面中程式碼片段的渲染與互動體驗", + "feature1": "Groq 和 Chutes API 提供者: 新增對 Groq 和 Chutes API 提供者的支援(感謝 @shariqriazz!)", + "feature2": "可點擊程式碼參考: 模型回應中的程式碼參考現在可直接導航至原始碼行(感謝 @KJ7LNW!)", + "feature3": "MCP 穩定性改進: 修復多個錯誤以增強 MCP 整合的穩定性", "hideButton": "隱藏公告", "detailsDiscussLinks": "在 DiscordReddit 取得更多詳細資訊並參與討論 🚀" }, From 883cb927a3846763a8559defda9e6850db6b866c Mon Sep 17 00:00:00 2001 From: Dicha Zelianivan Arkana <51877647+elianiva@users.noreply.github.com> Date: Tue, 6 May 2025 23:06:20 +0700 Subject: [PATCH 030/228] refactor: general UI improvements (#2987) --- .../src/components/prompts/PromptsView.tsx | 669 ++++++++---------- .../prompts/__tests__/PromptsView.test.tsx | 139 ++-- .../settings/AutoApproveSettings.tsx | 12 +- .../settings/ContextManagementSettings.tsx | 4 +- webview-ui/src/components/ui/alert-dialog.tsx | 4 +- webview-ui/src/components/ui/button.tsx | 9 +- webview-ui/src/components/ui/slider.tsx | 4 +- webview-ui/src/components/ui/textarea.tsx | 4 +- webview-ui/src/i18n/locales/ca/prompts.json | 3 +- webview-ui/src/i18n/locales/de/prompts.json | 3 +- webview-ui/src/i18n/locales/en/prompts.json | 3 +- webview-ui/src/i18n/locales/es/prompts.json | 3 +- webview-ui/src/i18n/locales/fr/prompts.json | 3 +- webview-ui/src/i18n/locales/hi/prompts.json | 3 +- webview-ui/src/i18n/locales/it/prompts.json | 3 +- webview-ui/src/i18n/locales/ja/prompts.json | 3 +- webview-ui/src/i18n/locales/ko/prompts.json | 3 +- webview-ui/src/i18n/locales/pl/prompts.json | 3 +- .../src/i18n/locales/pt-BR/prompts.json | 3 +- webview-ui/src/i18n/locales/ru/prompts.json | 3 +- webview-ui/src/i18n/locales/tr/prompts.json | 3 +- webview-ui/src/i18n/locales/vi/prompts.json | 3 +- .../src/i18n/locales/zh-CN/prompts.json | 3 +- .../src/i18n/locales/zh-TW/prompts.json | 3 +- 24 files changed, 403 insertions(+), 490 deletions(-) diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 4c84416a2c2..c4943870428 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -1,14 +1,6 @@ -import React, { useState, useEffect, useMemo, useCallback } from "react" +import React, { useState, useEffect, useMemo, useCallback, useRef } from "react" import { Button } from "@/components/ui/button" -import { - VSCodeTextArea, - VSCodeDropdown, - VSCodeOption, - VSCodeTextField, - VSCodeCheckbox, - VSCodeRadioGroup, - VSCodeRadio, -} from "@vscode/webview-ui-toolkit/react" +import { VSCodeCheckbox, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react" import { useExtensionState } from "@src/context/ExtensionStateContext" import { @@ -29,6 +21,25 @@ import { Tab, TabContent, TabHeader } from "../common/Tab" import i18next from "i18next" import { useAppTranslation } from "@src/i18n/TranslationContext" import { Trans } from "react-i18next" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Textarea, + Popover, + PopoverContent, + PopoverTrigger, + Command, + CommandInput, + CommandList, + CommandEmpty, + CommandItem, + CommandGroup, + Input, +} from "../ui" +import { ChevronsUpDown, X } from "lucide-react" // Get all available groups that should show in prompts view const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable) @@ -78,9 +89,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { const [isToolsEditMode, setIsToolsEditMode] = useState(false) const [showConfigMenu, setShowConfigMenu] = useState(false) const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false) - const [activeSupportTab, setActiveSupportTab] = useState("ENHANCE") + const [activeSupportOption, setActiveSupportOption] = useState("ENHANCE") const [isSystemPromptDisclosureOpen, setIsSystemPromptDisclosureOpen] = useState(false) + // State for mode selection popover and search + const [open, setOpen] = useState(false) + const [searchValue, setSearchValue] = useState("") + const searchInputRef = useRef(null) + // Direct update functions const updateAgentPrompt = useCallback( (mode: Mode, promptData: PromptComponent) => { @@ -144,9 +160,24 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { // Exit tools edit mode when switching modes setIsToolsEditMode(false) }, - [visualMode, switchMode, setIsToolsEditMode], + [visualMode, switchMode], ) + // Handler for popover open state change + const onOpenChange = useCallback((open: boolean) => { + setOpen(open) + // Reset search when closing the popover + if (!open) { + setTimeout(() => setSearchValue(""), 100) + } + }, []) + + // Handler for clearing search input + const onClearSearch = useCallback(() => { + setSearchValue("") + searchInputRef.current?.focus() + }, []) + // Helper function to get current mode's config const getCurrentMode = useCallback((): ModeConfig | undefined => { const findMode = (m: ModeConfig): boolean => m.slug === visualMode @@ -480,45 +511,95 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { {t("prompts:modes.createModeHelpText")}
-
- {modes.map((modeConfig) => { - const isActive = visualMode === modeConfig.slug - return ( - - ) - })} +
+ + + + + + +
+ + {searchValue.length > 0 && ( +
+ +
+ )} +
+ + + {searchValue && ( +
+ {t("prompts:modes.noMatchFound")} +
+ )} +
+ + {modes + .filter((modeConfig) => + searchValue + ? modeConfig.name + .toLowerCase() + .includes(searchValue.toLowerCase()) + : true, + ) + .map((modeConfig) => ( + { + handleModeSwitch(modeConfig) + setOpen(false) + }} + data-testid={`mode-option-${modeConfig.slug}`}> +
+ {modeConfig.name} + {modeConfig.slug} +
+
+ ))} +
+
+
+
+
-
+
{/* Only show name and delete for custom modes */} {visualMode && findModeBySlug(visualMode, customModes) && (
{t("prompts:createModeDialog.name.label")}
- ) => { - const target = - (e as CustomEvent)?.detail?.target || - ((e as any).target as HTMLInputElement) + onChange={(e) => { const customMode = findModeBySlug(visualMode, customModes) if (customMode) { updateCustomMode(visualMode, { ...customMode, - name: target.value, + name: e.target.value, source: customMode.source || "global", }) } @@ -541,7 +622,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)} -
+
{t("prompts:roleDefinition.title")}
{!findModeBySlug(visualMode, customModes) && ( @@ -563,7 +644,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{t("prompts:roleDefinition.description")}
- { const customMode = findModeBySlug(visualMode, customModes) const prompt = customModePrompts?.[visualMode] as PromptComponent @@ -575,7 +656,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { })()} onChange={(e) => { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const customMode = findModeBySlug(visualMode, customModes) if (customMode) { @@ -592,35 +673,35 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { }) } }} + className="resize-y w-full" rows={4} - resize="vertical" - style={{ width: "100%" }} data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`} />
{/* Mode settings */} <> -
-
- {t("prompts:apiConfiguration.title")} -
-
- { - const value = e.detail?.target?.value || e.target?.value +
+
{t("prompts:apiConfiguration.title")}
+
+
{t("prompts:apiConfiguration.select")}
@@ -721,15 +802,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { {/* Role definition for both built-in and custom modes */} -
-
-
{t("prompts:customInstructions.title")}
+
+
+
{t("prompts:customInstructions.title")}
{!findModeBySlug(visualMode, customModes) && ( )}
-
+
{t("prompts:customInstructions.description", { modeName: getCurrentMode()?.name || "Code", })}
- { const customMode = findModeBySlug(visualMode, customModes) const prompt = customModePrompts?.[visualMode] as PromptComponent @@ -768,7 +838,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { })()} onChange={(e) => { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const customMode = findModeBySlug(visualMode, customModes) if (customMode) { @@ -788,16 +858,10 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { } }} rows={4} - resize="vertical" - style={{ width: "100%" }} + className="w-full resize-y" data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`} /> -
+
{ components={{ span: ( { const currentMode = getCurrentMode() if (!currentMode) return @@ -834,13 +894,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
-
-
+
+
{/* Custom System Prompt Disclosure */} -
+
-

- {t("prompts:globalCustomInstructions.title")} -

+

{t("prompts:globalCustomInstructions.title")}

- {t("prompts:globalCustomInstructions.description", { language: i18next.language })} + {t("prompts:globalCustomInstructions.description", { + language: i18next.language, + })}
- { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value setCustomInstructions(value || undefined) vscode.postMessage({ @@ -938,21 +993,16 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { }) }} rows={4} - resize="vertical" - className="w-full" + className="w-full resize-y" data-testid="global-custom-instructions-textarea" /> -
+
vscode.postMessage({ type: "openFile", @@ -970,156 +1020,113 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
-
-

- {t("prompts:supportPrompts.title")} -

-
- {Object.keys(supportPrompt.default).map((type) => ( - - ))} +
+

{t("prompts:supportPrompts.title")}

+
+
{/* Support prompt description */} -
- {t(`prompts:supportPrompts.types.${activeSupportTab}.description`)} +
+ {t(`prompts:supportPrompts.types.${activeSupportOption}.description`)}
- {/* Show active tab content */} -
-
-
{t("prompts:supportPrompts.prompt")}
+
+
+
{t("prompts:supportPrompts.prompt")}
- { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const trimmedValue = value.trim() - updateSupportPrompt(activeSupportTab, trimmedValue || undefined) + updateSupportPrompt(activeSupportOption, trimmedValue || undefined) }} rows={6} - resize="vertical" - style={{ width: "100%" }} + className="resize-y w-full" /> - {activeSupportTab === "ENHANCE" && ( + {activeSupportOption === "ENHANCE" && ( <>
-
-
-
-
+
+
+
+
{t("prompts:supportPrompts.enhance.apiConfiguration")}
-
+
{t("prompts:supportPrompts.enhance.apiConfigDescription")}
- { - const value = e.detail?.target?.value || e.target?.value - setEnhancementApiConfigId(value) +
-
- +