Browse Source

Add the fireworks AI provider (#6652)

* add fireworks provider

* add tests

* Update packages/types/src/providers/fireworks.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix typo

* another typo

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
ershang-fireworks 6 months ago
parent
commit
4a9222b50e
35 changed files with 560 additions and 0 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug_report.yml
  2. 1 0
      packages/types/src/global-settings.ts
  3. 7 0
      packages/types/src/provider-settings.ts
  4. 61 0
      packages/types/src/providers/fireworks.ts
  5. 1 0
      packages/types/src/providers/index.ts
  6. 3 0
      src/api/index.ts
  7. 355 0
      src/api/providers/__tests__/fireworks.spec.ts
  8. 19 0
      src/api/providers/fireworks.ts
  9. 1 0
      src/api/providers/index.ts
  10. 1 0
      src/shared/ProfileValidator.ts
  11. 1 0
      src/shared/__tests__/ProfileValidator.spec.ts
  12. 7 0
      webview-ui/src/components/settings/ApiOptions.tsx
  13. 3 0
      webview-ui/src/components/settings/constants.ts
  14. 50 0
      webview-ui/src/components/settings/providers/Fireworks.tsx
  15. 1 0
      webview-ui/src/components/settings/providers/index.ts
  16. 7 0
      webview-ui/src/components/ui/hooks/useSelectedModel.ts
  17. 2 0
      webview-ui/src/i18n/locales/ca/settings.json
  18. 2 0
      webview-ui/src/i18n/locales/de/settings.json
  19. 2 0
      webview-ui/src/i18n/locales/en/settings.json
  20. 2 0
      webview-ui/src/i18n/locales/es/settings.json
  21. 2 0
      webview-ui/src/i18n/locales/fr/settings.json
  22. 2 0
      webview-ui/src/i18n/locales/hi/settings.json
  23. 2 0
      webview-ui/src/i18n/locales/id/settings.json
  24. 2 0
      webview-ui/src/i18n/locales/it/settings.json
  25. 2 0
      webview-ui/src/i18n/locales/ja/settings.json
  26. 2 0
      webview-ui/src/i18n/locales/ko/settings.json
  27. 2 0
      webview-ui/src/i18n/locales/nl/settings.json
  28. 2 0
      webview-ui/src/i18n/locales/pl/settings.json
  29. 2 0
      webview-ui/src/i18n/locales/pt-BR/settings.json
  30. 2 0
      webview-ui/src/i18n/locales/ru/settings.json
  31. 2 0
      webview-ui/src/i18n/locales/tr/settings.json
  32. 2 0
      webview-ui/src/i18n/locales/vi/settings.json
  33. 2 0
      webview-ui/src/i18n/locales/zh-CN/settings.json
  34. 2 0
      webview-ui/src/i18n/locales/zh-TW/settings.json
  35. 5 0
      webview-ui/src/utils/validate.ts

+ 1 - 0
.github/ISSUE_TEMPLATE/bug_report.yml

@@ -25,6 +25,7 @@ body:
         - AWS Bedrock
         - Chutes AI
         - DeepSeek
+        - Fireworks AI
         - Glama
         - Google Gemini
         - Google Vertex AI

+ 1 - 0
packages/types/src/global-settings.ts

@@ -193,6 +193,7 @@ export const SECRET_STATE_KEYS = [
 	"codebaseIndexMistralApiKey",
 	"huggingFaceApiKey",
 	"sambaNovaApiKey",
+	"fireworksApiKey",
 ] as const satisfies readonly (keyof ProviderSettings)[]
 export type SecretState = Pick<ProviderSettings, (typeof SECRET_STATE_KEYS)[number]>
 

+ 7 - 0
packages/types/src/provider-settings.ts

@@ -37,6 +37,7 @@ export const providerNames = [
 	"cerebras",
 	"sambanova",
 	"zai",
+	"fireworks",
 ] as const
 
 export const providerNamesSchema = z.enum(providerNames)
@@ -263,6 +264,10 @@ const zaiSchema = apiModelIdProviderModelSchema.extend({
 	zaiApiLine: z.union([z.literal("china"), z.literal("international")]).optional(),
 })
 
+const fireworksSchema = apiModelIdProviderModelSchema.extend({
+	fireworksApiKey: z.string().optional(),
+})
+
 const defaultSchema = z.object({
 	apiProvider: z.undefined(),
 })
@@ -297,6 +302,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
 	cerebrasSchema.merge(z.object({ apiProvider: z.literal("cerebras") })),
 	sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
 	zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
+	fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
 	defaultSchema,
 ])
 
@@ -331,6 +337,7 @@ export const providerSettingsSchema = z.object({
 	...cerebrasSchema.shape,
 	...sambaNovaSchema.shape,
 	...zaiSchema.shape,
+	...fireworksSchema.shape,
 	...codebaseIndexProviderSchema.shape,
 })
 

+ 61 - 0
packages/types/src/providers/fireworks.ts

@@ -0,0 +1,61 @@
+import type { ModelInfo } from "../model.js"
+
+export type FireworksModelId =
+	| "accounts/fireworks/models/kimi-k2-instruct"
+	| "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
+	| "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct"
+	| "accounts/fireworks/models/deepseek-r1-0528"
+	| "accounts/fireworks/models/deepseek-v3"
+
+export const fireworksDefaultModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct"
+
+export const fireworksModels = {
+	"accounts/fireworks/models/kimi-k2-instruct": {
+		maxTokens: 16384,
+		contextWindow: 128000,
+		supportsImages: false,
+		supportsPromptCache: false,
+		inputPrice: 0.6,
+		outputPrice: 2.5,
+		description:
+			"Kimi K2 is a state-of-the-art mixture-of-experts (MoE) language model with 32 billion activated parameters and 1 trillion total parameters. Trained with the Muon optimizer, Kimi K2 achieves exceptional performance across frontier knowledge, reasoning, and coding tasks while being meticulously optimized for agentic capabilities.",
+	},
+	"accounts/fireworks/models/qwen3-235b-a22b-instruct-2507": {
+		maxTokens: 32768,
+		contextWindow: 256000,
+		supportsImages: false,
+		supportsPromptCache: false,
+		inputPrice: 0.22,
+		outputPrice: 0.88,
+		description: "Latest Qwen3 thinking model, competitive against the best closed source models in Jul 2025.",
+	},
+	"accounts/fireworks/models/qwen3-coder-480b-a35b-instruct": {
+		maxTokens: 32768,
+		contextWindow: 256000,
+		supportsImages: false,
+		supportsPromptCache: false,
+		inputPrice: 0.45,
+		outputPrice: 1.8,
+		description: "Qwen3's most agentic code model to date.",
+	},
+	"accounts/fireworks/models/deepseek-r1-0528": {
+		maxTokens: 20480,
+		contextWindow: 160000,
+		supportsImages: false,
+		supportsPromptCache: false,
+		inputPrice: 3,
+		outputPrice: 8,
+		description:
+			"05/28 updated checkpoint of Deepseek R1. Its overall performance is now approaching that of leading models, such as O3 and Gemini 2.5 Pro. Compared to the previous version, the upgraded model shows significant improvements in handling complex reasoning tasks, and this version also offers a reduced hallucination rate, enhanced support for function calling, and better experience for vibe coding. Note that fine-tuning for this model is only available through contacting fireworks at https://fireworks.ai/company/contact-us.",
+	},
+	"accounts/fireworks/models/deepseek-v3": {
+		maxTokens: 16384,
+		contextWindow: 128000,
+		supportsImages: false,
+		supportsPromptCache: false,
+		inputPrice: 0.9,
+		outputPrice: 0.9,
+		description:
+			"A strong Mixture-of-Experts (MoE) language model with 671B total parameters with 37B activated for each token from Deepseek. Note that fine-tuning for this model is only available through contacting fireworks at https://fireworks.ai/company/contact-us.",
+	},
+} as const satisfies Record<string, ModelInfo>

+ 1 - 0
packages/types/src/providers/index.ts

@@ -23,3 +23,4 @@ export * from "./vscode-llm.js"
 export * from "./xai.js"
 export * from "./doubao.js"
 export * from "./zai.js"
+export * from "./fireworks.js"

+ 3 - 0
src/api/index.ts

@@ -34,6 +34,7 @@ import {
 	SambaNovaHandler,
 	DoubaoHandler,
 	ZAiHandler,
+	FireworksHandler,
 } from "./providers"
 
 export interface SingleCompletionHandler {
@@ -127,6 +128,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
 			return new SambaNovaHandler(options)
 		case "zai":
 			return new ZAiHandler(options)
+		case "fireworks":
+			return new FireworksHandler(options)
 		default:
 			apiProvider satisfies "gemini-cli" | undefined
 			return new AnthropicHandler(options)

+ 355 - 0
src/api/providers/__tests__/fireworks.spec.ts

@@ -0,0 +1,355 @@
+// npx vitest run api/providers/__tests__/fireworks.spec.ts
+
+import { Anthropic } from "@anthropic-ai/sdk"
+import OpenAI from "openai"
+
+import { type FireworksModelId, fireworksDefaultModelId, fireworksModels } from "@roo-code/types"
+
+import { FireworksHandler } from "../fireworks"
+
+// Create mock functions
+const mockCreate = vi.fn()
+
+// Mock OpenAI module
+vi.mock("openai", () => ({
+	default: vi.fn(() => ({
+		chat: {
+			completions: {
+				create: mockCreate,
+			},
+		},
+	})),
+}))
+
+describe("FireworksHandler", () => {
+	let handler: FireworksHandler
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+		// Set up default mock implementation
+		mockCreate.mockImplementation(async () => ({
+			[Symbol.asyncIterator]: async function* () {
+				yield {
+					choices: [
+						{
+							delta: { content: "Test response" },
+							index: 0,
+						},
+					],
+					usage: null,
+				}
+				yield {
+					choices: [
+						{
+							delta: {},
+							index: 0,
+						},
+					],
+					usage: {
+						prompt_tokens: 10,
+						completion_tokens: 5,
+						total_tokens: 15,
+					},
+				}
+			},
+		}))
+		handler = new FireworksHandler({ fireworksApiKey: "test-key" })
+	})
+
+	afterEach(() => {
+		vi.restoreAllMocks()
+	})
+
+	it("should use the correct Fireworks base URL", () => {
+		new FireworksHandler({ fireworksApiKey: "test-fireworks-api-key" })
+		expect(OpenAI).toHaveBeenCalledWith(
+			expect.objectContaining({ baseURL: "https://api.fireworks.ai/inference/v1" }),
+		)
+	})
+
+	it("should use the provided API key", () => {
+		const fireworksApiKey = "test-fireworks-api-key"
+		new FireworksHandler({ fireworksApiKey })
+		expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: fireworksApiKey }))
+	})
+
+	it("should throw error when API key is not provided", () => {
+		expect(() => new FireworksHandler({})).toThrow("API key is required")
+	})
+
+	it("should return default model when no model is specified", () => {
+		const model = handler.getModel()
+		expect(model.id).toBe(fireworksDefaultModelId)
+		expect(model.info).toEqual(expect.objectContaining(fireworksModels[fireworksDefaultModelId]))
+	})
+
+	it("should return specified model when valid model is provided", () => {
+		const testModelId: FireworksModelId = "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: testModelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+		const model = handlerWithModel.getModel()
+		expect(model.id).toBe(testModelId)
+		expect(model.info).toEqual(expect.objectContaining(fireworksModels[testModelId]))
+	})
+
+	it("should return Kimi K2 Instruct model with correct configuration", () => {
+		const testModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct"
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: testModelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+		const model = handlerWithModel.getModel()
+		expect(model.id).toBe(testModelId)
+		expect(model.info).toEqual(
+			expect.objectContaining({
+				maxTokens: 16384,
+				contextWindow: 128000,
+				supportsImages: false,
+				supportsPromptCache: false,
+				inputPrice: 0.6,
+				outputPrice: 2.5,
+				description: expect.stringContaining("Kimi K2 is a state-of-the-art mixture-of-experts"),
+			}),
+		)
+	})
+
+	it("should return Qwen3 235B model with correct configuration", () => {
+		const testModelId: FireworksModelId = "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: testModelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+		const model = handlerWithModel.getModel()
+		expect(model.id).toBe(testModelId)
+		expect(model.info).toEqual(
+			expect.objectContaining({
+				maxTokens: 32768,
+				contextWindow: 256000,
+				supportsImages: false,
+				supportsPromptCache: false,
+				inputPrice: 0.22,
+				outputPrice: 0.88,
+				description:
+					"Latest Qwen3 thinking model, competitive against the best closed source models in Jul 2025.",
+			}),
+		)
+	})
+
+	it("should return DeepSeek R1 model with correct configuration", () => {
+		const testModelId: FireworksModelId = "accounts/fireworks/models/deepseek-r1-0528"
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: testModelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+		const model = handlerWithModel.getModel()
+		expect(model.id).toBe(testModelId)
+		expect(model.info).toEqual(
+			expect.objectContaining({
+				maxTokens: 20480,
+				contextWindow: 160000,
+				supportsImages: false,
+				supportsPromptCache: false,
+				inputPrice: 3,
+				outputPrice: 8,
+				description: expect.stringContaining("05/28 updated checkpoint of Deepseek R1"),
+			}),
+		)
+	})
+
+	it("should return DeepSeek V3 model with correct configuration", () => {
+		const testModelId: FireworksModelId = "accounts/fireworks/models/deepseek-v3"
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: testModelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+		const model = handlerWithModel.getModel()
+		expect(model.id).toBe(testModelId)
+		expect(model.info).toEqual(
+			expect.objectContaining({
+				maxTokens: 16384,
+				contextWindow: 128000,
+				supportsImages: false,
+				supportsPromptCache: false,
+				inputPrice: 0.9,
+				outputPrice: 0.9,
+				description: expect.stringContaining("strong Mixture-of-Experts (MoE) language model"),
+			}),
+		)
+	})
+
+	it("completePrompt method should return text from Fireworks API", async () => {
+		const expectedResponse = "This is a test response from Fireworks"
+		mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
+		const result = await handler.completePrompt("test prompt")
+		expect(result).toBe(expectedResponse)
+	})
+
+	it("should handle errors in completePrompt", async () => {
+		const errorMessage = "Fireworks API error"
+		mockCreate.mockRejectedValueOnce(new Error(errorMessage))
+		await expect(handler.completePrompt("test prompt")).rejects.toThrow(
+			`Fireworks completion error: ${errorMessage}`,
+		)
+	})
+
+	it("createMessage should yield text content from stream", async () => {
+		const testContent = "This is test content from Fireworks stream"
+
+		mockCreate.mockImplementationOnce(() => {
+			return {
+				[Symbol.asyncIterator]: () => ({
+					next: vi
+						.fn()
+						.mockResolvedValueOnce({
+							done: false,
+							value: { choices: [{ delta: { content: testContent } }] },
+						})
+						.mockResolvedValueOnce({ done: true }),
+				}),
+			}
+		})
+
+		const stream = handler.createMessage("system prompt", [])
+		const firstChunk = await stream.next()
+
+		expect(firstChunk.done).toBe(false)
+		expect(firstChunk.value).toEqual({ type: "text", text: testContent })
+	})
+
+	it("createMessage should yield usage data from stream", async () => {
+		mockCreate.mockImplementationOnce(() => {
+			return {
+				[Symbol.asyncIterator]: () => ({
+					next: vi
+						.fn()
+						.mockResolvedValueOnce({
+							done: false,
+							value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } },
+						})
+						.mockResolvedValueOnce({ done: true }),
+				}),
+			}
+		})
+
+		const stream = handler.createMessage("system prompt", [])
+		const firstChunk = await stream.next()
+
+		expect(firstChunk.done).toBe(false)
+		expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 })
+	})
+
+	it("createMessage should pass correct parameters to Fireworks client", async () => {
+		const modelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct"
+		const modelInfo = fireworksModels[modelId]
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: modelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+
+		mockCreate.mockImplementationOnce(() => {
+			return {
+				[Symbol.asyncIterator]: () => ({
+					async next() {
+						return { done: true }
+					},
+				}),
+			}
+		})
+
+		const systemPrompt = "Test system prompt for Fireworks"
+		const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Fireworks" }]
+
+		const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages)
+		await messageGenerator.next()
+
+		expect(mockCreate).toHaveBeenCalledWith(
+			expect.objectContaining({
+				model: modelId,
+				max_tokens: modelInfo.maxTokens,
+				temperature: 0.5,
+				messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]),
+				stream: true,
+				stream_options: { include_usage: true },
+			}),
+		)
+	})
+
+	it("should use default temperature of 0.5", () => {
+		const testModelId: FireworksModelId = "accounts/fireworks/models/kimi-k2-instruct"
+		const handlerWithModel = new FireworksHandler({
+			apiModelId: testModelId,
+			fireworksApiKey: "test-fireworks-api-key",
+		})
+		const model = handlerWithModel.getModel()
+		// The temperature is set in the constructor as defaultTemperature: 0.5
+		// This test verifies the handler is configured with the correct default temperature
+		expect(handlerWithModel).toBeDefined()
+	})
+
+	it("should handle empty response in completePrompt", async () => {
+		mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: null } }] })
+		const result = await handler.completePrompt("test prompt")
+		expect(result).toBe("")
+	})
+
+	it("should handle missing choices in completePrompt", async () => {
+		mockCreate.mockResolvedValueOnce({ choices: [] })
+		const result = await handler.completePrompt("test prompt")
+		expect(result).toBe("")
+	})
+
+	it("createMessage should handle stream with multiple chunks", async () => {
+		mockCreate.mockImplementationOnce(async () => ({
+			[Symbol.asyncIterator]: async function* () {
+				yield {
+					choices: [
+						{
+							delta: { content: "Hello" },
+							index: 0,
+						},
+					],
+					usage: null,
+				}
+				yield {
+					choices: [
+						{
+							delta: { content: " world" },
+							index: 0,
+						},
+					],
+					usage: null,
+				}
+				yield {
+					choices: [
+						{
+							delta: {},
+							index: 0,
+						},
+					],
+					usage: {
+						prompt_tokens: 5,
+						completion_tokens: 10,
+						total_tokens: 15,
+					},
+				}
+			},
+		}))
+
+		const systemPrompt = "You are a helpful assistant."
+		const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
+
+		const stream = handler.createMessage(systemPrompt, messages)
+		const chunks = []
+		for await (const chunk of stream) {
+			chunks.push(chunk)
+		}
+
+		expect(chunks).toEqual([
+			{ type: "text", text: "Hello" },
+			{ type: "text", text: " world" },
+			{ type: "usage", inputTokens: 5, outputTokens: 10 },
+		])
+	})
+})

+ 19 - 0
src/api/providers/fireworks.ts

@@ -0,0 +1,19 @@
+import { type FireworksModelId, fireworksDefaultModelId, fireworksModels } from "@roo-code/types"
+
+import type { ApiHandlerOptions } from "../../shared/api"
+
+import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
+
+export class FireworksHandler extends BaseOpenAiCompatibleProvider<FireworksModelId> {
+	constructor(options: ApiHandlerOptions) {
+		super({
+			...options,
+			providerName: "Fireworks",
+			baseURL: "https://api.fireworks.ai/inference/v1",
+			apiKey: options.fireworksApiKey,
+			defaultProviderModelId: fireworksDefaultModelId,
+			providerModels: fireworksModels,
+			defaultTemperature: 0.5,
+		})
+	}
+}

+ 1 - 0
src/api/providers/index.ts

@@ -27,3 +27,4 @@ export { VertexHandler } from "./vertex"
 export { VsCodeLmHandler } from "./vscode-lm"
 export { XAIHandler } from "./xai"
 export { ZAiHandler } from "./zai"
+export { FireworksHandler } from "./fireworks"

+ 1 - 0
src/shared/ProfileValidator.ts

@@ -68,6 +68,7 @@ export class ProfileValidator {
 			case "groq":
 			case "sambanova":
 			case "chutes":
+			case "fireworks":
 				return profile.apiModelId
 			case "litellm":
 				return profile.litellmModelId

+ 1 - 0
src/shared/__tests__/ProfileValidator.spec.ts

@@ -193,6 +193,7 @@ describe("ProfileValidator", () => {
 			"groq",
 			"chutes",
 			"sambanova",
+			"fireworks",
 		]
 
 		apiModelProviders.forEach((provider) => {

+ 7 - 0
webview-ui/src/components/settings/ApiOptions.tsx

@@ -30,6 +30,7 @@ import {
 	sambaNovaDefaultModelId,
 	internationalZAiDefaultModelId,
 	mainlandZAiDefaultModelId,
+	fireworksDefaultModelId,
 } from "@roo-code/types"
 
 import { vscode } from "@src/utils/vscode"
@@ -82,6 +83,7 @@ import {
 	VSCodeLM,
 	XAI,
 	ZAi,
+	Fireworks,
 } from "./providers"
 
 import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants"
@@ -316,6 +318,7 @@ const ApiOptions = ({
 							? mainlandZAiDefaultModelId
 							: internationalZAiDefaultModelId,
 				},
+				fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
 				openai: { field: "openAiModelId" },
 				ollama: { field: "ollamaModelId" },
 				lmstudio: { field: "lmStudioModelId" },
@@ -555,6 +558,10 @@ const ApiOptions = ({
 				</>
 			)}
 
+			{selectedProvider === "fireworks" && (
+				<Fireworks apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
+			)}
+
 			{selectedProviderModels.length > 0 && (
 				<>
 					<div>

+ 3 - 0
webview-ui/src/components/settings/constants.ts

@@ -17,6 +17,7 @@ import {
 	sambaNovaModels,
 	doubaoModels,
 	internationalZAiModels,
+	fireworksModels,
 } from "@roo-code/types"
 
 export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
@@ -36,6 +37,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
 	chutes: chutesModels,
 	sambanova: sambaNovaModels,
 	zai: internationalZAiModels,
+	fireworks: fireworksModels,
 }
 
 export const PROVIDERS = [
@@ -66,4 +68,5 @@ export const PROVIDERS = [
 	{ value: "litellm", label: "LiteLLM" },
 	{ value: "sambanova", label: "SambaNova" },
 	{ value: "zai", label: "Z AI" },
+	{ value: "fireworks", label: "Fireworks AI" },
 ].sort((a, b) => a.label.localeCompare(b.label))

+ 50 - 0
webview-ui/src/components/settings/providers/Fireworks.tsx

@@ -0,0 +1,50 @@
+import { useCallback } from "react"
+import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
+
+import type { ProviderSettings } from "@roo-code/types"
+
+import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
+
+import { inputEventTransform } from "../transforms"
+
+type FireworksProps = {
+	apiConfiguration: ProviderSettings
+	setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
+}
+
+export const Fireworks = ({ apiConfiguration, setApiConfigurationField }: FireworksProps) => {
+	const { t } = useAppTranslation()
+
+	const handleInputChange = useCallback(
+		<K extends keyof ProviderSettings, E>(
+			field: K,
+			transform: (event: E) => ProviderSettings[K] = inputEventTransform,
+		) =>
+			(event: E | Event) => {
+				setApiConfigurationField(field, transform(event as E))
+			},
+		[setApiConfigurationField],
+	)
+
+	return (
+		<>
+			<VSCodeTextField
+				value={apiConfiguration?.fireworksApiKey || ""}
+				type="password"
+				onInput={handleInputChange("fireworksApiKey")}
+				placeholder={t("settings:placeholders.apiKey")}
+				className="w-full">
+				<label className="block font-medium mb-1">{t("settings:providers.fireworksApiKey")}</label>
+			</VSCodeTextField>
+			<div className="text-sm text-vscode-descriptionForeground -mt-2">
+				{t("settings:providers.apiKeyStorageNotice")}
+			</div>
+			{!apiConfiguration?.fireworksApiKey && (
+				<VSCodeButtonLink href="https://fireworks.ai/" appearance="secondary">
+					{t("settings:providers.getFireworksApiKey")}
+				</VSCodeButtonLink>
+			)}
+		</>
+	)
+}

+ 1 - 0
webview-ui/src/components/settings/providers/index.ts

@@ -24,3 +24,4 @@ export { VSCodeLM } from "./VSCodeLM"
 export { XAI } from "./XAI"
 export { ZAi } from "./ZAi"
 export { LiteLLM } from "./LiteLLM"
+export { Fireworks } from "./Fireworks"

+ 7 - 0
webview-ui/src/components/ui/hooks/useSelectedModel.ts

@@ -44,6 +44,8 @@ import {
 	mainlandZAiDefaultModelId,
 	internationalZAiModels,
 	mainlandZAiModels,
+	fireworksModels,
+	fireworksDefaultModelId,
 } from "@roo-code/types"
 
 import type { ModelRecord, RouterModels } from "@roo/api"
@@ -270,6 +272,11 @@ function getSelectedModel({
 			const info = sambaNovaModels[id as keyof typeof sambaNovaModels]
 			return { id, info }
 		}
+		case "fireworks": {
+			const id = apiConfiguration.apiModelId ?? fireworksDefaultModelId
+			const info = fireworksModels[id as keyof typeof fireworksModels]
+			return { id, info }
+		}
 		// case "anthropic":
 		// case "human-relay":
 		// case "fake-ai":

+ 2 - 0
webview-ui/src/i18n/locales/ca/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Obtenir clau API de Cerebras",
 		"chutesApiKey": "Clau API de Chutes",
 		"getChutesApiKey": "Obtenir clau API de Chutes",
+		"fireworksApiKey": "Clau API de Fireworks",
+		"getFireworksApiKey": "Obtenir clau API de Fireworks",
 		"deepSeekApiKey": "Clau API de DeepSeek",
 		"getDeepSeekApiKey": "Obtenir clau API de DeepSeek",
 		"doubaoApiKey": "Clau API de Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/de/settings.json

@@ -263,6 +263,8 @@
 		"getCerebrasApiKey": "Cerebras API-Schlüssel erhalten",
 		"chutesApiKey": "Chutes API-Schlüssel",
 		"getChutesApiKey": "Chutes API-Schlüssel erhalten",
+		"fireworksApiKey": "Fireworks API-Schlüssel",
+		"getFireworksApiKey": "Fireworks API-Schlüssel erhalten",
 		"deepSeekApiKey": "DeepSeek API-Schlüssel",
 		"getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten",
 		"moonshotApiKey": "Moonshot API-Schlüssel",

+ 2 - 0
webview-ui/src/i18n/locales/en/settings.json

@@ -260,6 +260,8 @@
 		"getCerebrasApiKey": "Get Cerebras API Key",
 		"chutesApiKey": "Chutes API Key",
 		"getChutesApiKey": "Get Chutes API Key",
+		"fireworksApiKey": "Fireworks API Key",
+		"getFireworksApiKey": "Get Fireworks API Key",
 		"deepSeekApiKey": "DeepSeek API Key",
 		"getDeepSeekApiKey": "Get DeepSeek API Key",
 		"doubaoApiKey": "Doubao API Key",

+ 2 - 0
webview-ui/src/i18n/locales/es/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Obtener clave API de Cerebras",
 		"chutesApiKey": "Clave API de Chutes",
 		"getChutesApiKey": "Obtener clave API de Chutes",
+		"fireworksApiKey": "Clave API de Fireworks",
+		"getFireworksApiKey": "Obtener clave API de Fireworks",
 		"deepSeekApiKey": "Clave API de DeepSeek",
 		"getDeepSeekApiKey": "Obtener clave API de DeepSeek",
 		"doubaoApiKey": "Clave API de Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/fr/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Obtenir la clé API Cerebras",
 		"chutesApiKey": "Clé API Chutes",
 		"getChutesApiKey": "Obtenir la clé API Chutes",
+		"fireworksApiKey": "Clé API Fireworks",
+		"getFireworksApiKey": "Obtenir la clé API Fireworks",
 		"deepSeekApiKey": "Clé API DeepSeek",
 		"getDeepSeekApiKey": "Obtenir la clé API DeepSeek",
 		"doubaoApiKey": "Clé API Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/hi/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Cerebras API कुंजी प्राप्त करें",
 		"chutesApiKey": "Chutes API कुंजी",
 		"getChutesApiKey": "Chutes API कुंजी प्राप्त करें",
+		"fireworksApiKey": "Fireworks API कुंजी",
+		"getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें",
 		"deepSeekApiKey": "DeepSeek API कुंजी",
 		"getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें",
 		"doubaoApiKey": "डौबाओ API कुंजी",

+ 2 - 0
webview-ui/src/i18n/locales/id/settings.json

@@ -265,6 +265,8 @@
 		"getCerebrasApiKey": "Dapatkan Cerebras API Key",
 		"chutesApiKey": "Chutes API Key",
 		"getChutesApiKey": "Dapatkan Chutes API Key",
+		"fireworksApiKey": "Fireworks API Key",
+		"getFireworksApiKey": "Dapatkan Fireworks API Key",
 		"deepSeekApiKey": "DeepSeek API Key",
 		"getDeepSeekApiKey": "Dapatkan DeepSeek API Key",
 		"doubaoApiKey": "Kunci API Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/it/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Ottieni chiave API Cerebras",
 		"chutesApiKey": "Chiave API Chutes",
 		"getChutesApiKey": "Ottieni chiave API Chutes",
+		"fireworksApiKey": "Chiave API Fireworks",
+		"getFireworksApiKey": "Ottieni chiave API Fireworks",
 		"deepSeekApiKey": "Chiave API DeepSeek",
 		"getDeepSeekApiKey": "Ottieni chiave API DeepSeek",
 		"doubaoApiKey": "Chiave API Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/ja/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Cerebras APIキーを取得",
 		"chutesApiKey": "Chutes APIキー",
 		"getChutesApiKey": "Chutes APIキーを取得",
+		"fireworksApiKey": "Fireworks APIキー",
+		"getFireworksApiKey": "Fireworks APIキーを取得",
 		"deepSeekApiKey": "DeepSeek APIキー",
 		"getDeepSeekApiKey": "DeepSeek APIキーを取得",
 		"doubaoApiKey": "Doubao APIキー",

+ 2 - 0
webview-ui/src/i18n/locales/ko/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Cerebras API 키 가져오기",
 		"chutesApiKey": "Chutes API 키",
 		"getChutesApiKey": "Chutes API 키 받기",
+		"fireworksApiKey": "Fireworks API 키",
+		"getFireworksApiKey": "Fireworks API 키 받기",
 		"deepSeekApiKey": "DeepSeek API 키",
 		"getDeepSeekApiKey": "DeepSeek API 키 받기",
 		"doubaoApiKey": "Doubao API 키",

+ 2 - 0
webview-ui/src/i18n/locales/nl/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Cerebras API-sleutel verkrijgen",
 		"chutesApiKey": "Chutes API-sleutel",
 		"getChutesApiKey": "Chutes API-sleutel ophalen",
+		"fireworksApiKey": "Fireworks API-sleutel",
+		"getFireworksApiKey": "Fireworks API-sleutel ophalen",
 		"deepSeekApiKey": "DeepSeek API-sleutel",
 		"getDeepSeekApiKey": "DeepSeek API-sleutel ophalen",
 		"doubaoApiKey": "Doubao API-sleutel",

+ 2 - 0
webview-ui/src/i18n/locales/pl/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Pobierz klucz API Cerebras",
 		"chutesApiKey": "Klucz API Chutes",
 		"getChutesApiKey": "Uzyskaj klucz API Chutes",
+		"fireworksApiKey": "Klucz API Fireworks",
+		"getFireworksApiKey": "Uzyskaj klucz API Fireworks",
 		"deepSeekApiKey": "Klucz API DeepSeek",
 		"getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek",
 		"doubaoApiKey": "Klucz API Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/pt-BR/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Obter chave de API Cerebras",
 		"chutesApiKey": "Chave de API Chutes",
 		"getChutesApiKey": "Obter chave de API Chutes",
+		"fireworksApiKey": "Chave de API Fireworks",
+		"getFireworksApiKey": "Obter chave de API Fireworks",
 		"deepSeekApiKey": "Chave de API DeepSeek",
 		"getDeepSeekApiKey": "Obter chave de API DeepSeek",
 		"doubaoApiKey": "Chave de API Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/ru/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Получить Cerebras API-ключ",
 		"chutesApiKey": "Chutes API-ключ",
 		"getChutesApiKey": "Получить Chutes API-ключ",
+		"fireworksApiKey": "Fireworks API-ключ",
+		"getFireworksApiKey": "Получить Fireworks API-ключ",
 		"deepSeekApiKey": "DeepSeek API-ключ",
 		"getDeepSeekApiKey": "Получить DeepSeek API-ключ",
 		"doubaoApiKey": "Doubao API-ключ",

+ 2 - 0
webview-ui/src/i18n/locales/tr/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Cerebras API Anahtarını Al",
 		"chutesApiKey": "Chutes API Anahtarı",
 		"getChutesApiKey": "Chutes API Anahtarı Al",
+		"fireworksApiKey": "Fireworks API Anahtarı",
+		"getFireworksApiKey": "Fireworks API Anahtarı Al",
 		"deepSeekApiKey": "DeepSeek API Anahtarı",
 		"getDeepSeekApiKey": "DeepSeek API Anahtarı Al",
 		"doubaoApiKey": "Doubao API Anahtarı",

+ 2 - 0
webview-ui/src/i18n/locales/vi/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "Lấy khóa API Cerebras",
 		"chutesApiKey": "Khóa API Chutes",
 		"getChutesApiKey": "Lấy khóa API Chutes",
+		"fireworksApiKey": "Khóa API Fireworks",
+		"getFireworksApiKey": "Lấy khóa API Fireworks",
 		"deepSeekApiKey": "Khóa API DeepSeek",
 		"getDeepSeekApiKey": "Lấy khóa API DeepSeek",
 		"doubaoApiKey": "Khóa API Doubao",

+ 2 - 0
webview-ui/src/i18n/locales/zh-CN/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "获取 Cerebras API 密钥",
 		"chutesApiKey": "Chutes API 密钥",
 		"getChutesApiKey": "获取 Chutes API 密钥",
+		"fireworksApiKey": "Fireworks API 密钥",
+		"getFireworksApiKey": "获取 Fireworks API 密钥",
 		"deepSeekApiKey": "DeepSeek API 密钥",
 		"getDeepSeekApiKey": "获取 DeepSeek API 密钥",
 		"doubaoApiKey": "豆包 API 密钥",

+ 2 - 0
webview-ui/src/i18n/locales/zh-TW/settings.json

@@ -261,6 +261,8 @@
 		"getCerebrasApiKey": "取得 Cerebras API 金鑰",
 		"chutesApiKey": "Chutes API 金鑰",
 		"getChutesApiKey": "取得 Chutes API 金鑰",
+		"fireworksApiKey": "Fireworks API 金鑰",
+		"getFireworksApiKey": "取得 Fireworks API 金鑰",
 		"deepSeekApiKey": "DeepSeek API 金鑰",
 		"getDeepSeekApiKey": "取得 DeepSeek API 金鑰",
 		"doubaoApiKey": "豆包 API 金鑰",

+ 5 - 0
webview-ui/src/utils/validate.ts

@@ -115,6 +115,11 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri
 				return i18next.t("settings:validation.apiKey")
 			}
 			break
+		case "fireworks":
+			if (!apiConfiguration.fireworksApiKey) {
+				return i18next.t("settings:validation.apiKey")
+			}
+			break
 	}
 
 	return undefined