From 181e070475a5a228c27f2b3c3f5d6e9df5177dfa Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 22:22:58 +0500 Subject: [PATCH 1/8] feat: Add Groq and Chutes API providers - Implemented provider logic for Groq and Chutes in `src/api/providers/`. - Added corresponding unit tests. - Integrated providers into the API index, exports, schemas, and types. - Updated `ApiOptions.tsx` and related constants/hooks in `webview-ui` to display settings for Groq and Chutes. - Added missing translation keys for Groq and Chutes settings labels to `webview-ui/src/i18n/locales/en/settings.json`. - Synchronized new translation keys across all other supported languages in `webview-ui`. - Removed redundant Groq translation keys from `src/i18n/locales/en/common.json`. Fixes UI bug where raw translation keys were displayed for Groq/Chutes settings. --- src/api/index.ts | 8 + src/api/providers/__tests__/chutes.test.ts | 207 +++++++ src/api/providers/__tests__/groq.test.ts | 209 +++++++ src/api/providers/chutes.ts | 100 ++++ src/api/providers/groq.ts | 104 ++++ src/exports/api.ts | 2 +- 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 | 525 +++++++++++++++++- .../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, 1390 insertions(+), 2 deletions(-) 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/chutes.ts create mode 100644 src/api/providers/groq.ts diff --git a/src/api/index.ts b/src/api/index.ts index 861ba59b99c..d4b233d6519 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,7 +90,13 @@ 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: + // Ensure the default case handles unknown providers gracefully or throws an error + // For now, defaulting to Anthropic as before 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..6f3b0d51a48 --- /dev/null +++ b/src/api/providers/__tests__/chutes.test.ts @@ -0,0 +1,207 @@ +import { ChutesHandler } from "../chutes" // Import ChutesHandler +// TODO: Update imports for Chutes once defined in shared/api.ts +import { ChutesModelId, chutesDefaultModelId, chutesModels } from "../../../shared/api" +import OpenAI from "openai" +import { Anthropic } from "@anthropic-ai/sdk" + +// Mock OpenAI client +jest.mock("openai", () => { + const createMock = jest.fn() + return jest.fn(() => ({ + chat: { + completions: { + create: createMock, + }, + }, + })) +}) + +// Test suite for ChutesHandler +describe("ChutesHandler", () => { + let handler: ChutesHandler // Use ChutesHandler type + let mockCreate: jest.Mock + + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks() + + // Get the mock create function + mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create + + // Create handler with mock + handler = new ChutesHandler({}) // Instantiate ChutesHandler + }) + + test("should use the correct Chutes base URL", () => { + // Instantiate handler inside the test to ensure clean state for this check + new ChutesHandler({}) + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://llm.chutes.ai/v1", // Verify Chutes base URL + }), + ) + }) + + test("should use the provided API key", () => { + // Clear mocks before this specific test + jest.clearAllMocks() + + // Create a handler with our API key + const chutesApiKey = "test-chutes-api-key" // Use chutesApiKey + new ChutesHandler({ chutesApiKey }) // Instantiate ChutesHandler + + // Verify the OpenAI constructor was called with our API key + 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) // Use chutesDefaultModelId + expect(model.info).toEqual(chutesModels[chutesDefaultModelId]) // Use chutesModels + }) + + test("should return specified model when valid model is provided", () => { + // Using an actual model ID from the Chutes API response + const testModelId: ChutesModelId = "Qwen/Qwen2.5-72B-Instruct" + const handlerWithModel = new ChutesHandler({ apiModelId: testModelId }) // Instantiate ChutesHandler + const model = handlerWithModel.getModel() + + expect(model.id).toBe(testModelId) + expect(model.info).toEqual(chutesModels[testModelId]) // Use chutesModels + }) + + 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 AI completion error: ${errorMessage}`) // Updated error message prefix + }) + + test("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from Chutes stream" + + // Setup mock for streaming response + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { + choices: [{ delta: { content: testContent } }], + }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + // Create and consume the stream + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + // Verify the content + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ + type: "text", + text: testContent, + }) + }) + + test("createMessage should yield usage data from stream", async () => { + // Setup mock for streaming response that includes usage data + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { + choices: [{ delta: {} }], // Needs to have choices array to avoid error + usage: { // Assuming standard OpenAI usage fields + prompt_tokens: 10, + completion_tokens: 20, + }, + }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + // Create and consume the stream + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + // Verify the usage data + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ // Updated expected usage structure + type: "usage", + inputTokens: 10, + outputTokens: 20, + cacheReadTokens: 0, // Assuming 0 for Chutes + cacheWriteTokens: 0, // Assuming 0 for Chutes + }) + }) + + test("createMessage should pass correct parameters to Chutes client", async () => { + // Setup a handler with specific model + const modelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" // Use an actual Chutes model ID and type + const modelInfo = chutesModels[modelId] // Use chutesModels + const handlerWithModel = new ChutesHandler({ apiModelId: modelId }) // Instantiate ChutesHandler + + // Setup mock for streaming response + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + async next() { + return { done: true } + }, + }), + } + }) + + // System prompt and messages + const systemPrompt = "Test system prompt for Chutes" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Chutes" }] + + // Start generating a message + const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) + await messageGenerator.next() // Start the generator + + // Check that all parameters were passed correctly + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + max_tokens: modelInfo.maxTokens, // Assuming standard max_tokens + temperature: 0.5, // Using CHUTES_DEFAULT_TEMPERATURE + messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), + stream: true, + stream_options: { include_usage: true }, // Assuming standard support + }), + ) + }) +}) \ No newline at end of file diff --git a/src/api/providers/__tests__/groq.test.ts b/src/api/providers/__tests__/groq.test.ts new file mode 100644 index 00000000000..af69c1d4138 --- /dev/null +++ b/src/api/providers/__tests__/groq.test.ts @@ -0,0 +1,209 @@ +import { GroqHandler } from "../groq" // Import GroqHandler +import { GroqModelId, groqDefaultModelId, groqModels } from "../../../shared/api" // Update imports for Groq +import OpenAI from "openai" +import { Anthropic } from "@anthropic-ai/sdk" + +// Mock OpenAI client +jest.mock("openai", () => { + const createMock = jest.fn() + return jest.fn(() => ({ + chat: { + completions: { + create: createMock, + }, + }, + })) +}) + +// Updated describe block +describe("GroqHandler", () => { + let handler: GroqHandler // Use GroqHandler type + let mockCreate: jest.Mock + + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks() + + // Get the mock create function + mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create + + // Create handler with mock + handler = new GroqHandler({}) // Instantiate GroqHandler + }) + + test("should use the correct Groq base URL", () => { + // Instantiate handler inside the test to ensure clean state for this check + new GroqHandler({}) + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + baseURL: "https://api.groq.com/openai/v1", // Verify Groq base URL + }), + ) + }) + + test("should use the provided API key", () => { + // Clear mocks before this specific test + jest.clearAllMocks() + + // Create a handler with our API key + const groqApiKey = "test-groq-api-key" // Use groqApiKey + new GroqHandler({ groqApiKey }) // Instantiate GroqHandler + + // Verify the OpenAI constructor was called with our API key + 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 = "llama3-70b-8192" // 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 + }) + + // Removed reasoning_effort tests + + 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}`) // Updated error message prefix + }) + + test("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from Groq stream" + + // Setup mock for streaming response + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { + choices: [{ delta: { content: testContent } }], + }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + // Create and consume the stream + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + // Verify the content + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ + type: "text", + text: testContent, + }) + }) + + // Removed reasoning content test + + test("createMessage should yield usage data from stream", async () => { + // Setup mock for streaming response that includes usage data + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: jest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { + choices: [{ delta: {} }], // Needs to have choices array to avoid error + usage: { // Assuming standard OpenAI usage fields + prompt_tokens: 10, + completion_tokens: 20, + }, + }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + // Create and consume the stream + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + // Verify the usage data + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ // Updated expected usage structure + type: "usage", + inputTokens: 10, + outputTokens: 20, + cacheReadTokens: 0, // Assuming 0 for Groq + cacheWriteTokens: 0, // Assuming 0 for Groq + }) + }) + + test("createMessage should pass correct parameters to Groq client", async () => { + // Setup a handler with specific model + const modelId: GroqModelId = "llama3-8b-8192" // Use a valid Groq model ID and type + const modelInfo = groqModels[modelId] // Use groqModels + const handlerWithModel = new GroqHandler({ apiModelId: modelId }) // Instantiate GroqHandler + + // Setup mock for streaming response + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + async next() { + return { done: true } + }, + }), + } + }) + + // System prompt and messages + const systemPrompt = "Test system prompt for Groq" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Groq" }] + + // Start generating a message + const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) + await messageGenerator.next() // Start the generator + + // Check that all parameters were passed correctly + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + max_tokens: modelInfo.maxTokens, // Assuming Groq uses max_tokens + temperature: 0.5, // Using GROQ_DEFAULT_TEMPERATURE + messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), + stream: true, + stream_options: { include_usage: true }, // Assuming Groq supports this + }), + ) + }) +}) \ No newline at end of file diff --git a/src/api/providers/chutes.ts b/src/api/providers/chutes.ts new file mode 100644 index 00000000000..f6ba629ca35 --- /dev/null +++ b/src/api/providers/chutes.ts @@ -0,0 +1,100 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + +// TODO: Update imports for Chutes once defined in shared/api.ts +import { ApiHandlerOptions, ChutesModelId, chutesDefaultModelId, chutesModels } 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" + +// Assuming a default temperature, adjust if needed based on Chutes documentation if found +const CHUTES_DEFAULT_TEMPERATURE = 0.5 + +// Handler for Chutes AI (OpenAI compatible) +export class ChutesHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions + private client: OpenAI + + constructor(options: ApiHandlerOptions) { + super() + this.options = options + this.client = new OpenAI({ + baseURL: "https://llm.chutes.ai/v1", // Chutes AI endpoint + apiKey: this.options.chutesApiKey ?? "not-provided", // Use chutesApiKey + defaultHeaders: DEFAULT_HEADERS, + }) + } + + override getModel() { + // Logic for Chutes models (using placeholders for now) + // Determine which model ID to use (specified or default) + const id = + this.options.apiModelId && this.options.apiModelId in chutesModels + ? (this.options.apiModelId as ChutesModelId) + : chutesDefaultModelId + + // Chutes is OpenAI compatible, likely no reasoning effort + return { + id, + info: chutesModels[id], + } + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const { id: modelId, info: modelInfo } = this.getModel() + + // Use the OpenAI-compatible API. + const stream = await this.client.chat.completions.create({ + model: modelId, + max_tokens: modelInfo.maxTokens, // Assuming standard max_tokens parameter + temperature: this.options.modelTemperature ?? CHUTES_DEFAULT_TEMPERATURE, + messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], + stream: true, + stream_options: { include_usage: true }, // Assuming standard include_usage support + }) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + + if (delta?.content) { + yield { + type: "text", + text: delta.content, + } + } + + if (chunk.usage) { + // Assuming standard OpenAI usage fields + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + cacheReadTokens: 0, // Assuming 0 for Chutes + cacheWriteTokens: 0, // Assuming 0 for Chutes + } + } + } + } + + 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(`Chutes AI completion error: ${error.message}`) + } + + throw error + } + } +} \ No newline at end of file diff --git a/src/api/providers/groq.ts b/src/api/providers/groq.ts new file mode 100644 index 00000000000..f621c7c4a40 --- /dev/null +++ b/src/api/providers/groq.ts @@ -0,0 +1,104 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + +import { ApiHandlerOptions, GroqModelId, groqDefaultModelId, groqModels } from "../../shared/api" // Updated imports for Groq +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" + +const GROQ_DEFAULT_TEMPERATURE = 0.5 // Adjusted default temperature for Groq (common default) + +// Renamed class to GroqHandler +export class GroqHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions + private client: OpenAI + + constructor(options: ApiHandlerOptions) { + super() + this.options = options + this.client = new OpenAI({ + baseURL: "https://api.groq.com/openai/v1", // Using Groq base URL + apiKey: this.options.groqApiKey ?? "not-provided", // Using groqApiKey + defaultHeaders: DEFAULT_HEADERS, + }) + } + + override getModel() { + // Updated logic for Groq models + // Determine which model ID to use (specified or default) + const id = + this.options.apiModelId && this.options.apiModelId in groqModels // Use groqModels + ? (this.options.apiModelId as GroqModelId) // Use GroqModelId + : groqDefaultModelId // Use groqDefaultModelId + + // Groq does not support reasoning effort + return { + id, + info: groqModels[id], // Use groqModels + } + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const { id: modelId, info: modelInfo } = this.getModel() // TODO: Remove reasoningEffort from destructuring + + // Use the OpenAI-compatible API. + const stream = await this.client.chat.completions.create({ + model: modelId, + max_tokens: modelInfo.maxTokens, // Assuming Groq uses max_tokens + temperature: this.options.modelTemperature ?? GROQ_DEFAULT_TEMPERATURE, + messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], + stream: true, + stream_options: { include_usage: true }, // Assuming Groq supports include_usage + // Removed reasoning logic + }) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + + if (delta?.content) { + yield { + type: "text", + text: delta.content, + } + } + + // Removed reasoning content handling + + if (chunk.usage) { + // Assuming Groq usage fields match OpenAI standard + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + // Assuming Groq doesn't provide cache tokens currently + cacheReadTokens: 0, + cacheWriteTokens: 0, + } + } + } + } + + async completePrompt(prompt: string): Promise { + const { id: modelId } = this.getModel() // Removed reasoningEffort + + try { + const response = await this.client.chat.completions.create({ + model: modelId, + messages: [{ role: "user", content: prompt }], + // Removed reasoning logic + }) + + return response.choices[0]?.message.content || "" + } catch (error) { + if (error instanceof Error) { + // Updated error message prefix + throw new Error(`Groq completion error: ${error.message}`) + } + + throw error + } + } +} \ No newline at end of file diff --git a/src/exports/api.ts b/src/exports/api.ts index 0d70d7dc04d..50b5b1a9d7d 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -88,7 +88,7 @@ export class API extends EventEmitter implements RooCodeAPI { images, newTab, }: { - configuration: RooCodeSettings + configuration?: RooCodeSettings // Made configuration optional text?: string images?: string[] newTab?: boolean diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 2c552b17021..8685f90686f 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 @@ -114,6 +116,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 @@ -152,6 +156,8 @@ type GlobalSettings = { | "human-relay" | "fake-ai" | "xai" + | "groq" + | "chutes" ) | undefined }[] diff --git a/src/exports/types.ts b/src/exports/types.ts index 10724695098..df411835206 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 @@ -115,6 +117,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 @@ -155,6 +159,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..ba4c7fb5458 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én 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 a3ec3381313..c28511e8fb5 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) @@ -392,6 +394,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(), @@ -496,6 +502,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[] @@ -688,6 +698,8 @@ export type SecretState = Pick< | "unboundApiKey" | "requestyApiKey" | "xaiApiKey" + | "groqApiKey" + | "chutesApiKey" > type SecretStateRecord = Record, undefined> @@ -707,6 +719,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..7dba58be856 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -2,7 +2,11 @@ import { ModelInfo, ProviderName, ProviderSettings } from "../schemas" export type { ModelInfo, ProviderName as ApiProvider } -export type ApiHandlerOptions = Omit +export type ApiHandlerOptions = Omit & { + // Add provider-specific API keys here as needed + groqApiKey?: string + chutesApiKey?: string +} export type ApiConfiguration = ProviderSettings @@ -1217,6 +1221,525 @@ export const xaiModels = { }, } as const satisfies Record +// Groq +// https://console.groq.com/docs/models +export type GroqModelId = + | "llama3-70b-8192" + | "llama3-8b-8192" + | "gemma2-9b-it" + | "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" + | "allam-2-7b" +export const groqDefaultModelId: GroqModelId = "llama3-70b-8192" // Defaulting to Llama3 70B +export const groqModels = { + // Models based on API response: https://api.groq.com/openai/v1/models + "llama3-70b-8192": { + maxTokens: 8192, + contextWindow: 8192, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Meta Llama 3 70B model, 8K context.", + }, + "llama3-8b-8192": { + maxTokens: 8192, + contextWindow: 8192, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Meta Llama 3 8B model, 8K context.", + }, + "gemma2-9b-it": { + maxTokens: 8192, + contextWindow: 8192, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Google Gemma 2 9B IT model, 8K context.", + }, + "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.", + }, + "allam-2-7b": { + maxTokens: 4096, + contextWindow: 4096, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "SDAIA Allam 2 7B model, 4K context.", + }, +} as const satisfies Record + +// Chutes AI +// https://llm.chutes.ai/v1 (OpenAI compatible) +export type ChutesModelId = + | "Qwen/Qwen2.5-72B-Instruct" + | "Qwen/Qwen3-32B" + | "Qwen/Qwen3-8B" + | "deepseek-ai/DeepSeek-R1" + | "deepseek-ai/DeepSeek-V3" + | "unsloth/Llama-3.3-70B-Instruct" + | "chutesai/Llama-4-Scout-17B-16E-Instruct" + | "unsloth/gemma-3-27b-it" + | "unsloth/gemma-2-9b-it" + | "unsloth/Mistral-Nemo-Instruct-2407" + | "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B" + | "unsloth/Llama-3.2-3B-Instruct" + | "open-r1/OlympicCoder-32B" + | "RekaAI/reka-flash-3" + | "moonshotai/Moonlight-16B-A3B-Instruct" + | "unsloth/gemma-3-12b-it" + | "unsloth/gemma-3-1b-it" + | "allenai/Molmo-7B-D-0924" + | "NousResearch/DeepHermes-3-Llama-3-8B-Preview" + | "chutesai/Mistral-Small-3.1-24B-Instruct-2503" + | "unsloth/gemma-3-4b-it" + | "mrfakename/mistral-small-3.1-24b-instruct-2503-hf" + | "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" + | "ArliAI/QwQ-32B-ArliAI-RpR-v1" + | "agentica-org/DeepCoder-14B-Preview" + | "shisa-ai/shisa-v2-llama3.3-70b" + | "THUDM/GLM-4-32B-0414" + | "THUDM/GLM-Z1-32B-0414" + | "microsoft/MAI-DS-R1-FP8" + | "chutesai/Llama-3.1-405B-FP8" + | "tngtech/DeepSeek-R1T-Chimera" + | "Qwen/Qwen3-235B-A22B" + | "Qwen/Qwen3-14B" + | "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4" +// Defaulting to a capable model found in the API response +export const chutesDefaultModelId: ChutesModelId = "Qwen/Qwen2.5-72B-Instruct" +export const chutesModels = { + // Models based on API response: https://llm.chutes.ai/v1/models + // Note: maxTokens (output limit) is not explicitly provided, using contextWindow or a default. + // Pricing is typically 0 for these models via Chutes. + "Qwen/Qwen2.5-72B-Instruct": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen 2.5 72B Instruct model.", + }, + "Qwen/Qwen3-32B": { + maxTokens: 32768, // Estimate + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen 3 32B model.", + }, + "Qwen/Qwen3-8B": { + maxTokens: 32768, // Estimate + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen 3 8B model.", + }, + "deepseek-ai/DeepSeek-R1": { + maxTokens: 32768, // Estimate + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 model.", + }, + "deepseek-ai/DeepSeek-V3": { + maxTokens: 32768, // Estimate + 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, // Estimate + contextWindow: 512000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "ChutesAI Llama 4 Scout 17B Instruct model, 512K context.", + }, + "unsloth/gemma-3-27b-it": { + maxTokens: 32768, // Estimate + contextWindow: 96000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 3 27B IT model.", + }, + "unsloth/gemma-2-9b-it": { + maxTokens: 8192, + contextWindow: 8192, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 2 9B IT model.", + }, + "unsloth/Mistral-Nemo-Instruct-2407": { + maxTokens: 32768, // Estimate + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Mistral Nemo Instruct model.", + }, + "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B": { + maxTokens: 32768, // Estimate + contextWindow: 64000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 Distill Qwen 14B model.", + }, + "unsloth/Llama-3.2-3B-Instruct": { + maxTokens: 32768, // Estimate + contextWindow: 64000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Llama 3.2 3B Instruct model.", + }, + "open-r1/OlympicCoder-32B": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Open R1 OlympicCoder 32B model.", + }, + "RekaAI/reka-flash-3": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, // Reka Flash usually supports images, but API doesn't specify + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Reka Flash 3 model.", + }, + "moonshotai/Moonlight-16B-A3B-Instruct": { + maxTokens: 8192, + contextWindow: 8192, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Moonshot AI Moonlight 16B Instruct model.", + }, + "unsloth/gemma-3-12b-it": { + maxTokens: 32768, // Estimate + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 3 12B IT model.", + }, + "unsloth/gemma-3-1b-it": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 3 1B IT model.", + }, + "allenai/Molmo-7B-D-0924": { + maxTokens: 4096, + contextWindow: 4096, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "AllenAI Molmo 7B model.", + }, + "NousResearch/DeepHermes-3-Llama-3-8B-Preview": { + maxTokens: 32768, // Estimate + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Nous DeepHermes 3 Llama 3 8B Preview model.", + }, + "chutesai/Mistral-Small-3.1-24B-Instruct-2503": { + maxTokens: 32768, // Estimate + contextWindow: 96000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "ChutesAI Mistral Small 3.1 24B Instruct model.", + }, + "unsloth/gemma-3-4b-it": { + maxTokens: 32768, // Estimate + contextWindow: 131072, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Unsloth Gemma 3 4B IT model.", + }, + "mrfakename/mistral-small-3.1-24b-instruct-2503-hf": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Mistral Small 3.1 24B Instruct HF model.", + }, + "nvidia/Llama-3_3-Nemotron-Super-49B-v1": { + maxTokens: 32768, // Estimate + 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, // Estimate + 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, // Estimate + 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, // Estimate + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek V3 Base model.", + }, + "deepseek-ai/DeepSeek-R1-Zero": { + maxTokens: 32768, // Estimate + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 Zero model.", + }, + "deepseek-ai/DeepSeek-V3-0324": { + maxTokens: 32768, // Estimate + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek V3 (0324) model.", + }, + "ArliAI/QwQ-32B-ArliAI-RpR-v1": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "ArliAI QwQ 32B RpR v1 model.", + }, + "agentica-org/DeepCoder-14B-Preview": { + maxTokens: 32768, // Estimate + contextWindow: 96000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Agentica DeepCoder 14B Preview model.", + }, + "shisa-ai/shisa-v2-llama3.3-70b": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Shisa V2 Llama 3.3 70B model.", + }, + "THUDM/GLM-4-32B-0414": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "THUDM GLM-4 32B (0414) model.", + }, + "THUDM/GLM-Z1-32B-0414": { + maxTokens: 32768, + contextWindow: 32768, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "THUDM GLM-Z1 32B (0414) model.", + }, + "microsoft/MAI-DS-R1-FP8": { + maxTokens: 32768, // Estimate + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Microsoft MAI-DS-R1 FP8 model.", + }, + "chutesai/Llama-3.1-405B-FP8": { + maxTokens: 32768, // Estimate + contextWindow: 64000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "ChutesAI Llama 3.1 405B FP8 model.", + }, + "tngtech/DeepSeek-R1T-Chimera": { + maxTokens: 32768, // Estimate + contextWindow: 163840, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "TNGTech DeepSeek R1T Chimera model.", + }, + "Qwen/Qwen3-235B-A22B": { + maxTokens: 32768, // Estimate + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen 3 235B A22B model.", + }, + "Qwen/Qwen3-14B": { + maxTokens: 32768, // Estimate + contextWindow: 40960, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen 3 14B model.", + }, + "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4": { + maxTokens: 32768, // Estimate + contextWindow: 96000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "HuggingQuants Llama 3.1 70B Instruct AWQ INT4 model.", + }, +} as const satisfies Record + export type VscodeLlmModelId = keyof typeof vscodeLlmModels export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-3.5-sonnet" export const vscodeLlmModels = { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 917947ac056..ea18e0b8176 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1482,6 +1482,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..012b3ae7e01 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, // Added + groqDefaultModelId, // Added + chutesModels, // Added + chutesDefaultModelId, // Added 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": // Added case for groq + return groqModels[id as keyof typeof groqModels] ?? groqModels[groqDefaultModelId] + case "chutes": // Added case for 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 a3896118644..b5466037e49 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -117,9 +117,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 e0f470e8e07..67f50d16a27 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -117,10 +117,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 b057d94043c..0e7ff18fffb 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -117,9 +117,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 5b810ea168c..afa4dea629b 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -117,9 +117,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 25efb24c1fb..ad433f11d92 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -117,9 +117,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 5cc20642dac..b1c18543ece 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -117,9 +117,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 55866e53504..e5b3564712d 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -117,9 +117,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 b7960b9b455..d5dc4dead32 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -117,9 +117,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 b513a308cee..4ce255c4992 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -117,9 +117,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 8f80043fa0a..4009fb59b41 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -117,9 +117,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 d27a053247c..7368d8e0546 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -117,9 +117,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 27f1240a89f..8db729e8e44 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -117,9 +117,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 94861a6139c..373d9109dfa 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -117,9 +117,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 87941b5b8e9..0537845b0fd 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -116,9 +116,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 fd10b77b63b..932fd2fc799 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -117,9 +117,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 a40be4d17fd..e53c6bd94c0 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -117,9 +117,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 4105ee521ebc79002b2f7a1517e763ba75b94021 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Tue, 29 Apr 2025 22:31:00 +0500 Subject: [PATCH 2/8] Update src/i18n/locales/ca/common.json Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src/i18n/locales/ca/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index ba4c7fb5458..2bd61dfd930 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -93,7 +93,7 @@ "settings": { "providers": { "groqApiKey": "Clau API de Groq", - "getGroqApiKey": "Obtén la clau API de Groq" + "getGroqApiKey": "Obté la clau API de Groq" } } } From 250d9409c6c563f8dbb368efd163c94b0a23e953 Mon Sep 17 00:00:00 2001 From: Shariq Riaz Date: Wed, 30 Apr 2025 00:53:40 +0500 Subject: [PATCH 3/8] fix(api): remove chutes models <= 100k context, fix tests --- src/api/providers/__tests__/chutes.test.ts | 14 +- src/api/providers/__tests__/groq.test.ts | 12 +- src/shared/api.ts | 274 +-------------------- 3 files changed, 18 insertions(+), 282 deletions(-) diff --git a/src/api/providers/__tests__/chutes.test.ts b/src/api/providers/__tests__/chutes.test.ts index 6f3b0d51a48..8941ee768de 100644 --- a/src/api/providers/__tests__/chutes.test.ts +++ b/src/api/providers/__tests__/chutes.test.ts @@ -66,7 +66,7 @@ describe("ChutesHandler", () => { test("should return specified model when valid model is provided", () => { // Using an actual model ID from the Chutes API response - const testModelId: ChutesModelId = "Qwen/Qwen2.5-72B-Instruct" + const testModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" const handlerWithModel = new ChutesHandler({ apiModelId: testModelId }) // Instantiate ChutesHandler const model = handlerWithModel.getModel() @@ -95,7 +95,9 @@ describe("ChutesHandler", () => { const errorMessage = "Chutes API error" mockCreate.mockRejectedValueOnce(new Error(errorMessage)) - await expect(handler.completePrompt("test prompt")).rejects.toThrow(`Chutes AI completion error: ${errorMessage}`) // Updated error message prefix + await expect(handler.completePrompt("test prompt")).rejects.toThrow( + `Chutes AI completion error: ${errorMessage}`, + ) // Updated error message prefix }) test("createMessage should yield text content from stream", async () => { @@ -141,7 +143,8 @@ describe("ChutesHandler", () => { done: false, value: { choices: [{ delta: {} }], // Needs to have choices array to avoid error - usage: { // Assuming standard OpenAI usage fields + usage: { + // Assuming standard OpenAI usage fields prompt_tokens: 10, completion_tokens: 20, }, @@ -158,7 +161,8 @@ describe("ChutesHandler", () => { // Verify the usage data expect(firstChunk.done).toBe(false) - expect(firstChunk.value).toEqual({ // Updated expected usage structure + expect(firstChunk.value).toEqual({ + // Updated expected usage structure type: "usage", inputTokens: 10, outputTokens: 20, @@ -204,4 +208,4 @@ describe("ChutesHandler", () => { }), ) }) -}) \ No newline at end of file +}) diff --git a/src/api/providers/__tests__/groq.test.ts b/src/api/providers/__tests__/groq.test.ts index af69c1d4138..88e509fc561 100644 --- a/src/api/providers/__tests__/groq.test.ts +++ b/src/api/providers/__tests__/groq.test.ts @@ -64,7 +64,7 @@ describe("GroqHandler", () => { }) test("should return specified model when valid model is provided", () => { - const testModelId: GroqModelId = "llama3-70b-8192" // Use a valid Groq model ID and type + 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() @@ -143,7 +143,8 @@ describe("GroqHandler", () => { done: false, value: { choices: [{ delta: {} }], // Needs to have choices array to avoid error - usage: { // Assuming standard OpenAI usage fields + usage: { + // Assuming standard OpenAI usage fields prompt_tokens: 10, completion_tokens: 20, }, @@ -160,7 +161,8 @@ describe("GroqHandler", () => { // Verify the usage data expect(firstChunk.done).toBe(false) - expect(firstChunk.value).toEqual({ // Updated expected usage structure + expect(firstChunk.value).toEqual({ + // Updated expected usage structure type: "usage", inputTokens: 10, outputTokens: 20, @@ -171,7 +173,7 @@ describe("GroqHandler", () => { test("createMessage should pass correct parameters to Groq client", async () => { // Setup a handler with specific model - const modelId: GroqModelId = "llama3-8b-8192" // Use a valid Groq model ID and type + const modelId: GroqModelId = "llama-3.1-8b-instant" // Use a valid Groq model ID and type const modelInfo = groqModels[modelId] // Use groqModels const handlerWithModel = new GroqHandler({ apiModelId: modelId }) // Instantiate GroqHandler @@ -206,4 +208,4 @@ describe("GroqHandler", () => { }), ) }) -}) \ No newline at end of file +}) diff --git a/src/shared/api.ts b/src/shared/api.ts index 7dba58be856..c5d5b3a5508 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -1224,9 +1224,6 @@ export const xaiModels = { // Groq // https://console.groq.com/docs/models export type GroqModelId = - | "llama3-70b-8192" - | "llama3-8b-8192" - | "gemma2-9b-it" | "llama-3.1-8b-instant" | "llama-3.3-70b-versatile" | "meta-llama/llama-4-scout-17b-16e-instruct" @@ -1234,37 +1231,9 @@ export type GroqModelId = | "mistral-saba-24b" | "qwen-qwq-32b" | "deepseek-r1-distill-llama-70b" - | "allam-2-7b" -export const groqDefaultModelId: GroqModelId = "llama3-70b-8192" // Defaulting to Llama3 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 - "llama3-70b-8192": { - maxTokens: 8192, - contextWindow: 8192, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Meta Llama 3 70B model, 8K context.", - }, - "llama3-8b-8192": { - maxTokens: 8192, - contextWindow: 8192, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Meta Llama 3 8B model, 8K context.", - }, - "gemma2-9b-it": { - maxTokens: 8192, - contextWindow: 8192, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Google Gemma 2 9B IT model, 8K context.", - }, "llama-3.1-8b-instant": { maxTokens: 131072, contextWindow: 131072, @@ -1328,92 +1297,33 @@ export const groqModels = { outputPrice: 0, description: "DeepSeek R1 Distill Llama 70B model, 128K context.", }, - "allam-2-7b": { - maxTokens: 4096, - contextWindow: 4096, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "SDAIA Allam 2 7B model, 4K context.", - }, } as const satisfies Record // Chutes AI // https://llm.chutes.ai/v1 (OpenAI compatible) export type ChutesModelId = - | "Qwen/Qwen2.5-72B-Instruct" - | "Qwen/Qwen3-32B" - | "Qwen/Qwen3-8B" | "deepseek-ai/DeepSeek-R1" | "deepseek-ai/DeepSeek-V3" | "unsloth/Llama-3.3-70B-Instruct" | "chutesai/Llama-4-Scout-17B-16E-Instruct" - | "unsloth/gemma-3-27b-it" - | "unsloth/gemma-2-9b-it" | "unsloth/Mistral-Nemo-Instruct-2407" - | "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B" - | "unsloth/Llama-3.2-3B-Instruct" - | "open-r1/OlympicCoder-32B" - | "RekaAI/reka-flash-3" - | "moonshotai/Moonlight-16B-A3B-Instruct" | "unsloth/gemma-3-12b-it" - | "unsloth/gemma-3-1b-it" - | "allenai/Molmo-7B-D-0924" | "NousResearch/DeepHermes-3-Llama-3-8B-Preview" - | "chutesai/Mistral-Small-3.1-24B-Instruct-2503" | "unsloth/gemma-3-4b-it" - | "mrfakename/mistral-small-3.1-24b-instruct-2503-hf" | "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" - | "ArliAI/QwQ-32B-ArliAI-RpR-v1" - | "agentica-org/DeepCoder-14B-Preview" - | "shisa-ai/shisa-v2-llama3.3-70b" - | "THUDM/GLM-4-32B-0414" - | "THUDM/GLM-Z1-32B-0414" | "microsoft/MAI-DS-R1-FP8" - | "chutesai/Llama-3.1-405B-FP8" | "tngtech/DeepSeek-R1T-Chimera" - | "Qwen/Qwen3-235B-A22B" - | "Qwen/Qwen3-14B" - | "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4" // Defaulting to a capable model found in the API response -export const chutesDefaultModelId: ChutesModelId = "Qwen/Qwen2.5-72B-Instruct" +export const chutesDefaultModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" export const chutesModels = { // Models based on API response: https://llm.chutes.ai/v1/models // Note: maxTokens (output limit) is not explicitly provided, using contextWindow or a default. // Pricing is typically 0 for these models via Chutes. - "Qwen/Qwen2.5-72B-Instruct": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Qwen 2.5 72B Instruct model.", - }, - "Qwen/Qwen3-32B": { - maxTokens: 32768, // Estimate - contextWindow: 40960, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Qwen 3 32B model.", - }, - "Qwen/Qwen3-8B": { - maxTokens: 32768, // Estimate - contextWindow: 40960, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Qwen 3 8B model.", - }, "deepseek-ai/DeepSeek-R1": { maxTokens: 32768, // Estimate contextWindow: 163840, @@ -1450,24 +1360,6 @@ export const chutesModels = { outputPrice: 0, description: "ChutesAI Llama 4 Scout 17B Instruct model, 512K context.", }, - "unsloth/gemma-3-27b-it": { - maxTokens: 32768, // Estimate - contextWindow: 96000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Unsloth Gemma 3 27B IT model.", - }, - "unsloth/gemma-2-9b-it": { - maxTokens: 8192, - contextWindow: 8192, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Unsloth Gemma 2 9B IT model.", - }, "unsloth/Mistral-Nemo-Instruct-2407": { maxTokens: 32768, // Estimate contextWindow: 128000, @@ -1477,51 +1369,6 @@ export const chutesModels = { outputPrice: 0, description: "Unsloth Mistral Nemo Instruct model.", }, - "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B": { - maxTokens: 32768, // Estimate - contextWindow: 64000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "DeepSeek R1 Distill Qwen 14B model.", - }, - "unsloth/Llama-3.2-3B-Instruct": { - maxTokens: 32768, // Estimate - contextWindow: 64000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Unsloth Llama 3.2 3B Instruct model.", - }, - "open-r1/OlympicCoder-32B": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Open R1 OlympicCoder 32B model.", - }, - "RekaAI/reka-flash-3": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, // Reka Flash usually supports images, but API doesn't specify - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Reka Flash 3 model.", - }, - "moonshotai/Moonlight-16B-A3B-Instruct": { - maxTokens: 8192, - contextWindow: 8192, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Moonshot AI Moonlight 16B Instruct model.", - }, "unsloth/gemma-3-12b-it": { maxTokens: 32768, // Estimate contextWindow: 131072, @@ -1531,24 +1378,6 @@ export const chutesModels = { outputPrice: 0, description: "Unsloth Gemma 3 12B IT model.", }, - "unsloth/gemma-3-1b-it": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Unsloth Gemma 3 1B IT model.", - }, - "allenai/Molmo-7B-D-0924": { - maxTokens: 4096, - contextWindow: 4096, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "AllenAI Molmo 7B model.", - }, "NousResearch/DeepHermes-3-Llama-3-8B-Preview": { maxTokens: 32768, // Estimate contextWindow: 131072, @@ -1558,15 +1387,6 @@ export const chutesModels = { outputPrice: 0, description: "Nous DeepHermes 3 Llama 3 8B Preview model.", }, - "chutesai/Mistral-Small-3.1-24B-Instruct-2503": { - maxTokens: 32768, // Estimate - contextWindow: 96000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "ChutesAI Mistral Small 3.1 24B Instruct model.", - }, "unsloth/gemma-3-4b-it": { maxTokens: 32768, // Estimate contextWindow: 131072, @@ -1576,15 +1396,6 @@ export const chutesModels = { outputPrice: 0, description: "Unsloth Gemma 3 4B IT model.", }, - "mrfakename/mistral-small-3.1-24b-instruct-2503-hf": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Mistral Small 3.1 24B Instruct HF model.", - }, "nvidia/Llama-3_3-Nemotron-Super-49B-v1": { maxTokens: 32768, // Estimate contextWindow: 131072, @@ -1639,51 +1450,6 @@ export const chutesModels = { outputPrice: 0, description: "DeepSeek V3 (0324) model.", }, - "ArliAI/QwQ-32B-ArliAI-RpR-v1": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "ArliAI QwQ 32B RpR v1 model.", - }, - "agentica-org/DeepCoder-14B-Preview": { - maxTokens: 32768, // Estimate - contextWindow: 96000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Agentica DeepCoder 14B Preview model.", - }, - "shisa-ai/shisa-v2-llama3.3-70b": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Shisa V2 Llama 3.3 70B model.", - }, - "THUDM/GLM-4-32B-0414": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "THUDM GLM-4 32B (0414) model.", - }, - "THUDM/GLM-Z1-32B-0414": { - maxTokens: 32768, - contextWindow: 32768, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "THUDM GLM-Z1 32B (0414) model.", - }, "microsoft/MAI-DS-R1-FP8": { maxTokens: 32768, // Estimate contextWindow: 163840, @@ -1693,15 +1459,6 @@ export const chutesModels = { outputPrice: 0, description: "Microsoft MAI-DS-R1 FP8 model.", }, - "chutesai/Llama-3.1-405B-FP8": { - maxTokens: 32768, // Estimate - contextWindow: 64000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "ChutesAI Llama 3.1 405B FP8 model.", - }, "tngtech/DeepSeek-R1T-Chimera": { maxTokens: 32768, // Estimate contextWindow: 163840, @@ -1711,33 +1468,6 @@ export const chutesModels = { outputPrice: 0, description: "TNGTech DeepSeek R1T Chimera model.", }, - "Qwen/Qwen3-235B-A22B": { - maxTokens: 32768, // Estimate - contextWindow: 40960, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Qwen 3 235B A22B model.", - }, - "Qwen/Qwen3-14B": { - maxTokens: 32768, // Estimate - contextWindow: 40960, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "Qwen 3 14B model.", - }, - "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4": { - maxTokens: 32768, // Estimate - contextWindow: 96000, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - description: "HuggingQuants Llama 3.1 70B Instruct AWQ INT4 model.", - }, } as const satisfies Record export type VscodeLlmModelId = keyof typeof vscodeLlmModels From 960c50ba998872f6b0b961a51bb1f6d3959749bd Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Tue, 29 Apr 2025 13:02:37 -0700 Subject: [PATCH 4/8] Update useSelectedModel.ts --- .../src/components/ui/hooks/useSelectedModel.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index 012b3ae7e01..b02a4d024b0 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -19,10 +19,10 @@ import { vertexModels, xaiDefaultModelId, xaiModels, - groqModels, // Added - groqDefaultModelId, // Added - chutesModels, // Added - chutesDefaultModelId, // Added + groqModels, + groqDefaultModelId, + chutesModels, + chutesDefaultModelId, vscodeLlmModels, vscodeLlmDefaultModelId, openRouterDefaultModelId, @@ -88,9 +88,9 @@ function getSelectedModelInfo({ return routerModels.unbound[id] ?? routerModels.unbound[unboundDefaultModelId] case "xai": return xaiModels[id as keyof typeof xaiModels] ?? xaiModels[xaiDefaultModelId] - case "groq": // Added case for groq + case "groq": return groqModels[id as keyof typeof groqModels] ?? groqModels[groqDefaultModelId] - case "chutes": // Added case for chutes + case "chutes": return chutesModels[id as keyof typeof chutesModels] ?? chutesModels[chutesDefaultModelId] case "bedrock": // Special case for custom ARN. From 4686a831b4274cb44124da662b0fa81f8a3b6618 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Tue, 29 Apr 2025 13:03:06 -0700 Subject: [PATCH 5/8] Update index.ts --- src/api/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index d4b233d6519..24007c66827 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -95,8 +95,6 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler { case "chutes": return new ChutesHandler(options) default: - // Ensure the default case handles unknown providers gracefully or throws an error - // For now, defaulting to Anthropic as before return new AnthropicHandler(options) } } From af2f204c692eacc79eebcfa9769da6a4789f9381 Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 5 May 2025 13:27:47 -0700 Subject: [PATCH 6/8] Use common base class --- src/api/providers/__tests__/chutes.test.ts | 129 ++---- src/api/providers/__tests__/groq.test.ts | 121 ++---- .../base-openai-compatible-provider.ts | 118 +++++ src/api/providers/chutes.ts | 105 +---- src/api/providers/groq.ts | 109 +---- src/shared/api.ts | 406 +++++++++--------- 6 files changed, 395 insertions(+), 593 deletions(-) create mode 100644 src/api/providers/base-openai-compatible-provider.ts diff --git a/src/api/providers/__tests__/chutes.test.ts b/src/api/providers/__tests__/chutes.test.ts index 8941ee768de..946aff96c88 100644 --- a/src/api/providers/__tests__/chutes.test.ts +++ b/src/api/providers/__tests__/chutes.test.ts @@ -1,92 +1,56 @@ -import { ChutesHandler } from "../chutes" // Import ChutesHandler -// TODO: Update imports for Chutes once defined in shared/api.ts -import { ChutesModelId, chutesDefaultModelId, chutesModels } from "../../../shared/api" +// npx jest src/api/providers/__tests__/chutes.test.ts + import OpenAI from "openai" import { Anthropic } from "@anthropic-ai/sdk" -// Mock OpenAI client +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, - }, - }, - })) + return jest.fn(() => ({ chat: { completions: { create: createMock } } })) }) -// Test suite for ChutesHandler describe("ChutesHandler", () => { - let handler: ChutesHandler // Use ChutesHandler type + let handler: ChutesHandler let mockCreate: jest.Mock beforeEach(() => { - // Reset all mocks jest.clearAllMocks() - - // Get the mock create function mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create - - // Create handler with mock - handler = new ChutesHandler({}) // Instantiate ChutesHandler + handler = new ChutesHandler({}) }) test("should use the correct Chutes base URL", () => { - // Instantiate handler inside the test to ensure clean state for this check new ChutesHandler({}) - expect(OpenAI).toHaveBeenCalledWith( - expect.objectContaining({ - baseURL: "https://llm.chutes.ai/v1", // Verify Chutes base URL - }), - ) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://llm.chutes.ai/v1" })) }) test("should use the provided API key", () => { - // Clear mocks before this specific test - jest.clearAllMocks() - - // Create a handler with our API key - const chutesApiKey = "test-chutes-api-key" // Use chutesApiKey - new ChutesHandler({ chutesApiKey }) // Instantiate ChutesHandler - - // Verify the OpenAI constructor was called with our API key - expect(OpenAI).toHaveBeenCalledWith( - expect.objectContaining({ - apiKey: chutesApiKey, - }), - ) + 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) // Use chutesDefaultModelId - expect(model.info).toEqual(chutesModels[chutesDefaultModelId]) // Use chutesModels + expect(model.id).toBe(chutesDefaultModelId) + expect(model.info).toEqual(chutesModels[chutesDefaultModelId]) }) test("should return specified model when valid model is provided", () => { - // Using an actual model ID from the Chutes API response const testModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" - const handlerWithModel = new ChutesHandler({ apiModelId: testModelId }) // Instantiate ChutesHandler + const handlerWithModel = new ChutesHandler({ apiModelId: testModelId }) const model = handlerWithModel.getModel() expect(model.id).toBe(testModelId) - expect(model.info).toEqual(chutesModels[testModelId]) // Use chutesModels + 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, - }, - }, - ], - }) - + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) const result = await handler.completePrompt("test prompt") expect(result).toBe(expectedResponse) }) @@ -94,16 +58,12 @@ describe("ChutesHandler", () => { 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 AI completion error: ${errorMessage}`, - ) // Updated error message prefix + 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" - // Setup mock for streaming response mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -111,29 +71,21 @@ describe("ChutesHandler", () => { .fn() .mockResolvedValueOnce({ done: false, - value: { - choices: [{ delta: { content: testContent } }], - }, + value: { choices: [{ delta: { content: testContent } }] }, }) .mockResolvedValueOnce({ done: true }), }), } }) - // Create and consume the stream const stream = handler.createMessage("system prompt", []) const firstChunk = await stream.next() - // Verify the content expect(firstChunk.done).toBe(false) - expect(firstChunk.value).toEqual({ - type: "text", - text: testContent, - }) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) }) test("createMessage should yield usage data from stream", async () => { - // Setup mock for streaming response that includes usage data mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -141,43 +93,25 @@ describe("ChutesHandler", () => { .fn() .mockResolvedValueOnce({ done: false, - value: { - choices: [{ delta: {} }], // Needs to have choices array to avoid error - usage: { - // Assuming standard OpenAI usage fields - prompt_tokens: 10, - completion_tokens: 20, - }, - }, + value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } }, }) .mockResolvedValueOnce({ done: true }), }), } }) - // Create and consume the stream const stream = handler.createMessage("system prompt", []) const firstChunk = await stream.next() - // Verify the usage data expect(firstChunk.done).toBe(false) - expect(firstChunk.value).toEqual({ - // Updated expected usage structure - type: "usage", - inputTokens: 10, - outputTokens: 20, - cacheReadTokens: 0, // Assuming 0 for Chutes - cacheWriteTokens: 0, // Assuming 0 for Chutes - }) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) }) test("createMessage should pass correct parameters to Chutes client", async () => { - // Setup a handler with specific model - const modelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" // Use an actual Chutes model ID and type - const modelInfo = chutesModels[modelId] // Use chutesModels - const handlerWithModel = new ChutesHandler({ apiModelId: modelId }) // Instantiate ChutesHandler + const modelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" + const modelInfo = chutesModels[modelId] + const handlerWithModel = new ChutesHandler({ apiModelId: modelId }) - // Setup mock for streaming response mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -188,23 +122,20 @@ describe("ChutesHandler", () => { } }) - // System prompt and messages const systemPrompt = "Test system prompt for Chutes" const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Chutes" }] - // Start generating a message const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) - await messageGenerator.next() // Start the generator + await messageGenerator.next() - // Check that all parameters were passed correctly expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ model: modelId, - max_tokens: modelInfo.maxTokens, // Assuming standard max_tokens - temperature: 0.5, // Using CHUTES_DEFAULT_TEMPERATURE + max_tokens: modelInfo.maxTokens, + temperature: 0.5, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, - stream_options: { include_usage: true }, // Assuming standard support + stream_options: { include_usage: true }, }), ) }) diff --git a/src/api/providers/__tests__/groq.test.ts b/src/api/providers/__tests__/groq.test.ts index 88e509fc561..6b38dcc9274 100644 --- a/src/api/providers/__tests__/groq.test.ts +++ b/src/api/providers/__tests__/groq.test.ts @@ -1,60 +1,36 @@ -import { GroqHandler } from "../groq" // Import GroqHandler -import { GroqModelId, groqDefaultModelId, groqModels } from "../../../shared/api" // Update imports for Groq +// npx jest src/api/providers/__tests__/groq.test.ts + import OpenAI from "openai" import { Anthropic } from "@anthropic-ai/sdk" -// Mock OpenAI client +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, - }, - }, - })) + return jest.fn(() => ({ chat: { completions: { create: createMock } } })) }) -// Updated describe block describe("GroqHandler", () => { - let handler: GroqHandler // Use GroqHandler type + let handler: GroqHandler let mockCreate: jest.Mock beforeEach(() => { - // Reset all mocks jest.clearAllMocks() - - // Get the mock create function mockCreate = (OpenAI as unknown as jest.Mock)().chat.completions.create - - // Create handler with mock - handler = new GroqHandler({}) // Instantiate GroqHandler + handler = new GroqHandler({}) }) test("should use the correct Groq base URL", () => { - // Instantiate handler inside the test to ensure clean state for this check new GroqHandler({}) - expect(OpenAI).toHaveBeenCalledWith( - expect.objectContaining({ - baseURL: "https://api.groq.com/openai/v1", // Verify Groq base URL - }), - ) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.groq.com/openai/v1" })) }) test("should use the provided API key", () => { - // Clear mocks before this specific test - jest.clearAllMocks() - - // Create a handler with our API key - const groqApiKey = "test-groq-api-key" // Use groqApiKey - new GroqHandler({ groqApiKey }) // Instantiate GroqHandler - - // Verify the OpenAI constructor was called with our API key - expect(OpenAI).toHaveBeenCalledWith( - expect.objectContaining({ - apiKey: groqApiKey, - }), - ) + 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", () => { @@ -72,21 +48,9 @@ describe("GroqHandler", () => { expect(model.info).toEqual(groqModels[testModelId]) // Use groqModels }) - // Removed reasoning_effort tests - 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, - }, - }, - ], - }) - + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) const result = await handler.completePrompt("test prompt") expect(result).toBe(expectedResponse) }) @@ -94,14 +58,12 @@ describe("GroqHandler", () => { 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}`) // Updated error message prefix + 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" - // Setup mock for streaming response mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -109,31 +71,21 @@ describe("GroqHandler", () => { .fn() .mockResolvedValueOnce({ done: false, - value: { - choices: [{ delta: { content: testContent } }], - }, + value: { choices: [{ delta: { content: testContent } }] }, }) .mockResolvedValueOnce({ done: true }), }), } }) - // Create and consume the stream const stream = handler.createMessage("system prompt", []) const firstChunk = await stream.next() - // Verify the content expect(firstChunk.done).toBe(false) - expect(firstChunk.value).toEqual({ - type: "text", - text: testContent, - }) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) }) - // Removed reasoning content test - test("createMessage should yield usage data from stream", async () => { - // Setup mock for streaming response that includes usage data mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -141,43 +93,25 @@ describe("GroqHandler", () => { .fn() .mockResolvedValueOnce({ done: false, - value: { - choices: [{ delta: {} }], // Needs to have choices array to avoid error - usage: { - // Assuming standard OpenAI usage fields - prompt_tokens: 10, - completion_tokens: 20, - }, - }, + value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } }, }) .mockResolvedValueOnce({ done: true }), }), } }) - // Create and consume the stream const stream = handler.createMessage("system prompt", []) const firstChunk = await stream.next() - // Verify the usage data expect(firstChunk.done).toBe(false) - expect(firstChunk.value).toEqual({ - // Updated expected usage structure - type: "usage", - inputTokens: 10, - outputTokens: 20, - cacheReadTokens: 0, // Assuming 0 for Groq - cacheWriteTokens: 0, // Assuming 0 for Groq - }) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) }) test("createMessage should pass correct parameters to Groq client", async () => { - // Setup a handler with specific model - const modelId: GroqModelId = "llama-3.1-8b-instant" // Use a valid Groq model ID and type - const modelInfo = groqModels[modelId] // Use groqModels - const handlerWithModel = new GroqHandler({ apiModelId: modelId }) // Instantiate GroqHandler + const modelId: GroqModelId = "llama-3.1-8b-instant" + const modelInfo = groqModels[modelId] + const handlerWithModel = new GroqHandler({ apiModelId: modelId }) - // Setup mock for streaming response mockCreate.mockImplementationOnce(() => { return { [Symbol.asyncIterator]: () => ({ @@ -188,23 +122,20 @@ describe("GroqHandler", () => { } }) - // System prompt and messages const systemPrompt = "Test system prompt for Groq" const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Groq" }] - // Start generating a message const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) - await messageGenerator.next() // Start the generator + await messageGenerator.next() - // Check that all parameters were passed correctly expect(mockCreate).toHaveBeenCalledWith( expect.objectContaining({ model: modelId, - max_tokens: modelInfo.maxTokens, // Assuming Groq uses max_tokens - temperature: 0.5, // Using GROQ_DEFAULT_TEMPERATURE + max_tokens: modelInfo.maxTokens, + temperature: 0.5, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, - stream_options: { include_usage: true }, // Assuming Groq supports this + 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..3c5e5660cf7 --- /dev/null +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -0,0 +1,118 @@ +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 + + this.client = new OpenAI({ + baseURL, + apiKey: this.options.apiKey ?? "not-provided", + defaultHeaders: DEFAULT_HEADERS, + }) + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const { id: modelId, info: modelInfo } = this.getModel() + + const stream = await this.client.chat.completions.create({ + model: modelId, + max_tokens: modelInfo.maxTokens, + temperature: this.options.modelTemperature ?? this.defaultTemperature, + messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], + stream: true, + stream_options: { include_usage: true }, + }) + + 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 index f6ba629ca35..fe2a00ce2bd 100644 --- a/src/api/providers/chutes.ts +++ b/src/api/providers/chutes.ts @@ -1,100 +1,17 @@ -import { Anthropic } from "@anthropic-ai/sdk" -import OpenAI from "openai" - -// TODO: Update imports for Chutes once defined in shared/api.ts import { ApiHandlerOptions, ChutesModelId, chutesDefaultModelId, chutesModels } 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" - -// Assuming a default temperature, adjust if needed based on Chutes documentation if found -const CHUTES_DEFAULT_TEMPERATURE = 0.5 -// Handler for Chutes AI (OpenAI compatible) -export class ChutesHandler extends BaseProvider implements SingleCompletionHandler { - protected options: ApiHandlerOptions - private client: OpenAI +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" +export class ChutesHandler extends BaseOpenAiCompatibleProvider { constructor(options: ApiHandlerOptions) { - super() - this.options = options - this.client = new OpenAI({ - baseURL: "https://llm.chutes.ai/v1", // Chutes AI endpoint - apiKey: this.options.chutesApiKey ?? "not-provided", // Use chutesApiKey - defaultHeaders: DEFAULT_HEADERS, - }) - } - - override getModel() { - // Logic for Chutes models (using placeholders for now) - // Determine which model ID to use (specified or default) - const id = - this.options.apiModelId && this.options.apiModelId in chutesModels - ? (this.options.apiModelId as ChutesModelId) - : chutesDefaultModelId - - // Chutes is OpenAI compatible, likely no reasoning effort - return { - id, - info: chutesModels[id], - } - } - - override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { - const { id: modelId, info: modelInfo } = this.getModel() - - // Use the OpenAI-compatible API. - const stream = await this.client.chat.completions.create({ - model: modelId, - max_tokens: modelInfo.maxTokens, // Assuming standard max_tokens parameter - temperature: this.options.modelTemperature ?? CHUTES_DEFAULT_TEMPERATURE, - messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], - stream: true, - stream_options: { include_usage: true }, // Assuming standard include_usage support + super({ + providerName: "Chutes", + baseURL: "https://llm.chutes.ai/v1", + apiKey: options.chutesApiKey, + defaultProviderModelId: chutesDefaultModelId, + providerModels: chutesModels, + defaultTemperature: 0.5, + ...options, }) - - for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta - - if (delta?.content) { - yield { - type: "text", - text: delta.content, - } - } - - if (chunk.usage) { - // Assuming standard OpenAI usage fields - yield { - type: "usage", - inputTokens: chunk.usage.prompt_tokens || 0, - outputTokens: chunk.usage.completion_tokens || 0, - cacheReadTokens: 0, // Assuming 0 for Chutes - cacheWriteTokens: 0, // Assuming 0 for Chutes - } - } - } - } - - 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(`Chutes AI completion error: ${error.message}`) - } - - throw error - } } -} \ No newline at end of file +} diff --git a/src/api/providers/groq.ts b/src/api/providers/groq.ts index f621c7c4a40..adcd09a6ca7 100644 --- a/src/api/providers/groq.ts +++ b/src/api/providers/groq.ts @@ -1,104 +1,17 @@ -import { Anthropic } from "@anthropic-ai/sdk" -import OpenAI from "openai" - import { ApiHandlerOptions, GroqModelId, groqDefaultModelId, groqModels } from "../../shared/api" // Updated imports for Groq -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" - -const GROQ_DEFAULT_TEMPERATURE = 0.5 // Adjusted default temperature for Groq (common default) -// Renamed class to GroqHandler -export class GroqHandler extends BaseProvider implements SingleCompletionHandler { - protected options: ApiHandlerOptions - private client: OpenAI +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" +export class GroqHandler extends BaseOpenAiCompatibleProvider { constructor(options: ApiHandlerOptions) { - super() - this.options = options - this.client = new OpenAI({ - baseURL: "https://api.groq.com/openai/v1", // Using Groq base URL - apiKey: this.options.groqApiKey ?? "not-provided", // Using groqApiKey - defaultHeaders: DEFAULT_HEADERS, - }) - } - - override getModel() { - // Updated logic for Groq models - // Determine which model ID to use (specified or default) - const id = - this.options.apiModelId && this.options.apiModelId in groqModels // Use groqModels - ? (this.options.apiModelId as GroqModelId) // Use GroqModelId - : groqDefaultModelId // Use groqDefaultModelId - - // Groq does not support reasoning effort - return { - id, - info: groqModels[id], // Use groqModels - } - } - - override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { - const { id: modelId, info: modelInfo } = this.getModel() // TODO: Remove reasoningEffort from destructuring - - // Use the OpenAI-compatible API. - const stream = await this.client.chat.completions.create({ - model: modelId, - max_tokens: modelInfo.maxTokens, // Assuming Groq uses max_tokens - temperature: this.options.modelTemperature ?? GROQ_DEFAULT_TEMPERATURE, - messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], - stream: true, - stream_options: { include_usage: true }, // Assuming Groq supports include_usage - // Removed reasoning logic + super({ + providerName: "Groq", + baseURL: "https://api.groq.com/openai/v1", + apiKey: options.groqApiKey, + defaultProviderModelId: groqDefaultModelId, + providerModels: groqModels, + defaultTemperature: 0.5, + ...options, }) - - for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta - - if (delta?.content) { - yield { - type: "text", - text: delta.content, - } - } - - // Removed reasoning content handling - - if (chunk.usage) { - // Assuming Groq usage fields match OpenAI standard - yield { - type: "usage", - inputTokens: chunk.usage.prompt_tokens || 0, - outputTokens: chunk.usage.completion_tokens || 0, - // Assuming Groq doesn't provide cache tokens currently - cacheReadTokens: 0, - cacheWriteTokens: 0, - } - } - } - } - - async completePrompt(prompt: string): Promise { - const { id: modelId } = this.getModel() // Removed reasoningEffort - - try { - const response = await this.client.chat.completions.create({ - model: modelId, - messages: [{ role: "user", content: prompt }], - // Removed reasoning logic - }) - - return response.choices[0]?.message.content || "" - } catch (error) { - if (error instanceof Error) { - // Updated error message prefix - throw new Error(`Groq completion error: ${error.message}`) - } - - throw error - } } -} \ No newline at end of file +} diff --git a/src/shared/api.ts b/src/shared/api.ts index c5d5b3a5508..0acb82d45e4 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -2,11 +2,7 @@ import { ModelInfo, ProviderName, ProviderSettings } from "../schemas" export type { ModelInfo, ProviderName as ApiProvider } -export type ApiHandlerOptions = Omit & { - // Add provider-specific API keys here as needed - groqApiKey?: string - chutesApiKey?: string -} +export type ApiHandlerOptions = Omit export type ApiConfiguration = ProviderSettings @@ -1221,6 +1217,189 @@ export const xaiModels = { }, } as const satisfies Record +export type VscodeLlmModelId = keyof typeof vscodeLlmModels +export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-3.5-sonnet" +export const vscodeLlmModels = { + "gpt-3.5-turbo": { + contextWindow: 12114, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-3.5-turbo", + version: "gpt-3.5-turbo-0613", + name: "GPT 3.5 Turbo", + supportsToolCalling: true, + maxInputTokens: 12114, + }, + "gpt-4o-mini": { + contextWindow: 12115, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-4o-mini", + version: "gpt-4o-mini-2024-07-18", + name: "GPT-4o mini", + supportsToolCalling: true, + maxInputTokens: 12115, + }, + "gpt-4": { + contextWindow: 28501, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-4", + version: "gpt-4-0613", + name: "GPT 4", + supportsToolCalling: true, + maxInputTokens: 28501, + }, + "gpt-4-0125-preview": { + contextWindow: 63826, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-4-turbo", + version: "gpt-4-0125-preview", + name: "GPT 4 Turbo", + supportsToolCalling: true, + maxInputTokens: 63826, + }, + "gpt-4o": { + contextWindow: 63827, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-4o", + version: "gpt-4o-2024-11-20", + name: "GPT-4o", + supportsToolCalling: true, + maxInputTokens: 63827, + }, + o1: { + contextWindow: 19827, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "o1-ga", + version: "o1-2024-12-17", + name: "o1 (Preview)", + supportsToolCalling: true, + maxInputTokens: 19827, + }, + "o3-mini": { + contextWindow: 63827, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "o3-mini", + version: "o3-mini-2025-01-31", + name: "o3-mini", + supportsToolCalling: true, + maxInputTokens: 63827, + }, + "claude-3.5-sonnet": { + contextWindow: 81638, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "claude-3.5-sonnet", + version: "claude-3.5-sonnet", + name: "Claude 3.5 Sonnet", + supportsToolCalling: true, + maxInputTokens: 81638, + }, + "claude-3.7-sonnet": { + contextWindow: 89827, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "claude-3.7-sonnet", + version: "claude-3.7-sonnet", + name: "Claude 3.7 Sonnet", + supportsToolCalling: true, + maxInputTokens: 89827, + }, + "claude-3.7-sonnet-thought": { + contextWindow: 89827, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "claude-3.7-sonnet-thought", + version: "claude-3.7-sonnet-thought", + name: "Claude 3.7 Sonnet Thinking", + supportsToolCalling: false, + maxInputTokens: 89827, + thinking: true, + }, + "gemini-2.0-flash-001": { + contextWindow: 127827, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gemini-2.0-flash", + version: "gemini-2.0-flash-001", + name: "Gemini 2.0 Flash", + supportsToolCalling: false, + maxInputTokens: 127827, + }, + "gemini-2.5-pro": { + contextWindow: 63830, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gemini-2.5-pro", + version: "gemini-2.5-pro-preview-03-25", + name: "Gemini 2.5 Pro (Preview)", + supportsToolCalling: true, + maxInputTokens: 63830, + }, + "o4-mini": { + contextWindow: 111446, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "o4-mini", + version: "o4-mini-2025-04-16", + name: "o4-mini (Preview)", + supportsToolCalling: true, + maxInputTokens: 111446, + }, + "gpt-4.1": { + contextWindow: 111446, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-4.1", + version: "gpt-4.1-2025-04-14", + name: "GPT-4.1 (Preview)", + supportsToolCalling: true, + maxInputTokens: 111446, + }, +} as const satisfies Record< + string, + ModelInfo & { + family: string + version: string + name: string + supportsToolCalling: boolean + maxInputTokens: number + } +> + // Groq // https://console.groq.com/docs/models export type GroqModelId = @@ -1318,14 +1497,10 @@ export type ChutesModelId = | "deepseek-ai/DeepSeek-V3-0324" | "microsoft/MAI-DS-R1-FP8" | "tngtech/DeepSeek-R1T-Chimera" -// Defaulting to a capable model found in the API response export const chutesDefaultModelId: ChutesModelId = "deepseek-ai/DeepSeek-R1" export const chutesModels = { - // Models based on API response: https://llm.chutes.ai/v1/models - // Note: maxTokens (output limit) is not explicitly provided, using contextWindow or a default. - // Pricing is typically 0 for these models via Chutes. "deepseek-ai/DeepSeek-R1": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1334,7 +1509,7 @@ export const chutesModels = { description: "DeepSeek R1 model.", }, "deepseek-ai/DeepSeek-V3": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1352,7 +1527,7 @@ export const chutesModels = { description: "Unsloth Llama 3.3 70B Instruct model.", }, "chutesai/Llama-4-Scout-17B-16E-Instruct": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 512000, supportsImages: false, supportsPromptCache: false, @@ -1361,7 +1536,7 @@ export const chutesModels = { description: "ChutesAI Llama 4 Scout 17B Instruct model, 512K context.", }, "unsloth/Mistral-Nemo-Instruct-2407": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 128000, supportsImages: false, supportsPromptCache: false, @@ -1370,7 +1545,7 @@ export const chutesModels = { description: "Unsloth Mistral Nemo Instruct model.", }, "unsloth/gemma-3-12b-it": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 131072, supportsImages: false, supportsPromptCache: false, @@ -1379,7 +1554,7 @@ export const chutesModels = { description: "Unsloth Gemma 3 12B IT model.", }, "NousResearch/DeepHermes-3-Llama-3-8B-Preview": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 131072, supportsImages: false, supportsPromptCache: false, @@ -1388,7 +1563,7 @@ export const chutesModels = { description: "Nous DeepHermes 3 Llama 3 8B Preview model.", }, "unsloth/gemma-3-4b-it": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 131072, supportsImages: false, supportsPromptCache: false, @@ -1397,7 +1572,7 @@ export const chutesModels = { description: "Unsloth Gemma 3 4B IT model.", }, "nvidia/Llama-3_3-Nemotron-Super-49B-v1": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 131072, supportsImages: false, supportsPromptCache: false, @@ -1406,7 +1581,7 @@ export const chutesModels = { description: "Nvidia Llama 3.3 Nemotron Super 49B model.", }, "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 131072, supportsImages: false, supportsPromptCache: false, @@ -1415,7 +1590,7 @@ export const chutesModels = { description: "Nvidia Llama 3.1 Nemotron Ultra 253B model.", }, "chutesai/Llama-4-Maverick-17B-128E-Instruct-FP8": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 256000, supportsImages: false, supportsPromptCache: false, @@ -1424,7 +1599,7 @@ export const chutesModels = { description: "ChutesAI Llama 4 Maverick 17B Instruct FP8 model.", }, "deepseek-ai/DeepSeek-V3-Base": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1433,7 +1608,7 @@ export const chutesModels = { description: "DeepSeek V3 Base model.", }, "deepseek-ai/DeepSeek-R1-Zero": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1442,7 +1617,7 @@ export const chutesModels = { description: "DeepSeek R1 Zero model.", }, "deepseek-ai/DeepSeek-V3-0324": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1451,7 +1626,7 @@ export const chutesModels = { description: "DeepSeek V3 (0324) model.", }, "microsoft/MAI-DS-R1-FP8": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1460,7 +1635,7 @@ export const chutesModels = { description: "Microsoft MAI-DS-R1 FP8 model.", }, "tngtech/DeepSeek-R1T-Chimera": { - maxTokens: 32768, // Estimate + maxTokens: 32768, contextWindow: 163840, supportsImages: false, supportsPromptCache: false, @@ -1470,189 +1645,6 @@ export const chutesModels = { }, } as const satisfies Record -export type VscodeLlmModelId = keyof typeof vscodeLlmModels -export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-3.5-sonnet" -export const vscodeLlmModels = { - "gpt-3.5-turbo": { - contextWindow: 12114, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gpt-3.5-turbo", - version: "gpt-3.5-turbo-0613", - name: "GPT 3.5 Turbo", - supportsToolCalling: true, - maxInputTokens: 12114, - }, - "gpt-4o-mini": { - contextWindow: 12115, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gpt-4o-mini", - version: "gpt-4o-mini-2024-07-18", - name: "GPT-4o mini", - supportsToolCalling: true, - maxInputTokens: 12115, - }, - "gpt-4": { - contextWindow: 28501, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gpt-4", - version: "gpt-4-0613", - name: "GPT 4", - supportsToolCalling: true, - maxInputTokens: 28501, - }, - "gpt-4-0125-preview": { - contextWindow: 63826, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gpt-4-turbo", - version: "gpt-4-0125-preview", - name: "GPT 4 Turbo", - supportsToolCalling: true, - maxInputTokens: 63826, - }, - "gpt-4o": { - contextWindow: 63827, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gpt-4o", - version: "gpt-4o-2024-11-20", - name: "GPT-4o", - supportsToolCalling: true, - maxInputTokens: 63827, - }, - o1: { - contextWindow: 19827, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "o1-ga", - version: "o1-2024-12-17", - name: "o1 (Preview)", - supportsToolCalling: true, - maxInputTokens: 19827, - }, - "o3-mini": { - contextWindow: 63827, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "o3-mini", - version: "o3-mini-2025-01-31", - name: "o3-mini", - supportsToolCalling: true, - maxInputTokens: 63827, - }, - "claude-3.5-sonnet": { - contextWindow: 81638, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "claude-3.5-sonnet", - version: "claude-3.5-sonnet", - name: "Claude 3.5 Sonnet", - supportsToolCalling: true, - maxInputTokens: 81638, - }, - "claude-3.7-sonnet": { - contextWindow: 89827, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "claude-3.7-sonnet", - version: "claude-3.7-sonnet", - name: "Claude 3.7 Sonnet", - supportsToolCalling: true, - maxInputTokens: 89827, - }, - "claude-3.7-sonnet-thought": { - contextWindow: 89827, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "claude-3.7-sonnet-thought", - version: "claude-3.7-sonnet-thought", - name: "Claude 3.7 Sonnet Thinking", - supportsToolCalling: false, - maxInputTokens: 89827, - thinking: true, - }, - "gemini-2.0-flash-001": { - contextWindow: 127827, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gemini-2.0-flash", - version: "gemini-2.0-flash-001", - name: "Gemini 2.0 Flash", - supportsToolCalling: false, - maxInputTokens: 127827, - }, - "gemini-2.5-pro": { - contextWindow: 63830, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gemini-2.5-pro", - version: "gemini-2.5-pro-preview-03-25", - name: "Gemini 2.5 Pro (Preview)", - supportsToolCalling: true, - maxInputTokens: 63830, - }, - "o4-mini": { - contextWindow: 111446, - supportsImages: false, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "o4-mini", - version: "o4-mini-2025-04-16", - name: "o4-mini (Preview)", - supportsToolCalling: true, - maxInputTokens: 111446, - }, - "gpt-4.1": { - contextWindow: 111446, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, - family: "gpt-4.1", - version: "gpt-4.1-2025-04-14", - name: "GPT-4.1 (Preview)", - supportsToolCalling: true, - maxInputTokens: 111446, - }, -} as const satisfies Record< - string, - ModelInfo & { - family: string - version: string - name: string - supportsToolCalling: boolean - maxInputTokens: number - } -> - /** * Constants */ From 3258b15ecae18407a0285e105690589193443d3f Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 5 May 2025 13:29:39 -0700 Subject: [PATCH 7/8] Revert this --- src/exports/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exports/api.ts b/src/exports/api.ts index 50b5b1a9d7d..0d70d7dc04d 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -88,7 +88,7 @@ export class API extends EventEmitter implements RooCodeAPI { images, newTab, }: { - configuration?: RooCodeSettings // Made configuration optional + configuration: RooCodeSettings text?: string images?: string[] newTab?: boolean From aae647ea0912ceeec7e05cd2e2a60f6423667870 Mon Sep 17 00:00:00 2001 From: cte Date: Mon, 5 May 2025 15:15:38 -0700 Subject: [PATCH 8/8] Fix options passed to super --- .../base-openai-compatible-provider.ts | 25 +++++++++++++------ src/api/providers/chutes.ts | 2 +- src/api/providers/groq.ts | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index 3c5e5660cf7..82eeb830331 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -49,24 +49,35 @@ export abstract class BaseOpenAiCompatibleProvider this.options = options + if (!this.options.apiKey) { + throw new Error("API key is required") + } + this.client = new OpenAI({ baseURL, - apiKey: this.options.apiKey ?? "not-provided", + apiKey: this.options.apiKey, defaultHeaders: DEFAULT_HEADERS, }) } override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { - const { id: modelId, info: modelInfo } = this.getModel() + const { + id: model, + info: { maxTokens: max_tokens }, + } = this.getModel() + + const temperature = this.options.modelTemperature ?? this.defaultTemperature - const stream = await this.client.chat.completions.create({ - model: modelId, - max_tokens: modelInfo.maxTokens, - 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 diff --git a/src/api/providers/chutes.ts b/src/api/providers/chutes.ts index fe2a00ce2bd..6f7481f1809 100644 --- a/src/api/providers/chutes.ts +++ b/src/api/providers/chutes.ts @@ -5,13 +5,13 @@ 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, - ...options, }) } } diff --git a/src/api/providers/groq.ts b/src/api/providers/groq.ts index adcd09a6ca7..2f4e763b8e5 100644 --- a/src/api/providers/groq.ts +++ b/src/api/providers/groq.ts @@ -5,13 +5,13 @@ 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, - ...options, }) } }