From a472c440156fc549c500c649ea0033220fb97e61 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:16:50 -0400 Subject: [PATCH] feat(api): add Fireworks AI provider preset (#1543) Promotes Fireworks AI to a first-class discoverable preset over the existing Anthropic-compatible Bearer path. Closes #890. --- README.md | 2 +- src/shared/provider-preset-catalog.ts | 14 +++++ .../api/provider-presets-fireworks.test.ts | 52 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/unit/api/provider-presets-fireworks.test.ts diff --git a/README.md b/README.md index 1d1c24cb..dd086993 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ CCS gives you one stable command surface while letting you switch between: - OAuth providers like Codex, Kiro, Claude, Qwen, Kimi, and more, with legacy Copilot compatibility for existing setups - API and local-model profiles like GLM, Kimi, OpenRouter, Ollama, llama.cpp, - Novita, and Alibaba Coding Plan + Novita, Fireworks AI, and Alibaba Coding Plan The goal is simple: stop rewriting config files, stop breaking active sessions, and move between providers in seconds. diff --git a/src/shared/provider-preset-catalog.ts b/src/shared/provider-preset-catalog.ts index 5d934789..bdc9e88c 100644 --- a/src/shared/provider-preset-catalog.ts +++ b/src/shared/provider-preset-catalog.ts @@ -23,6 +23,7 @@ export const PROVIDER_PRESET_IDS = [ 'qwen', 'ollama-cloud', 'novita', + 'fireworks', ] as const; export type ProviderPresetId = (typeof PROVIDER_PRESET_IDS)[number]; @@ -266,6 +267,19 @@ const RAW_PROVIDER_PRESET_DEFINITIONS: readonly ProviderPresetDefinition[] = [ badge: 'Anthropic-compatible', icon: '/icons/novita.svg', }, + { + id: 'fireworks', + name: 'Fireworks AI', + description: 'Anthropic-compatible inference (Kimi, Qwen, Llama) via api.fireworks.ai', + baseUrl: 'https://api.fireworks.ai/inference', + defaultProfileName: 'fireworks', + defaultModel: 'accounts/fireworks/models/kimi-k2p5', + apiKeyPlaceholder: 'fw_...', + apiKeyHint: 'Get your API key at fireworks.ai/api-keys', + category: 'alternative', + requiresApiKey: true, + badge: 'Anthropic-compatible', + }, ]; function clonePresetDefinition(preset: ProviderPresetDefinition): ProviderPresetDefinition { diff --git a/tests/unit/api/provider-presets-fireworks.test.ts b/tests/unit/api/provider-presets-fireworks.test.ts new file mode 100644 index 00000000..155f341d --- /dev/null +++ b/tests/unit/api/provider-presets-fireworks.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'bun:test'; +import { getPresetById, isValidPresetId } from '../../../src/api/services/provider-presets'; +import { PROVIDER_PRESET_IDS } from '../../../src/shared/provider-preset-catalog'; + +describe('provider-presets-fireworks', () => { + it('resolves fireworks preset id', () => { + const preset = getPresetById('fireworks'); + expect(preset?.id).toBe('fireworks'); + expect(preset?.baseUrl).toBe('https://api.fireworks.ai/inference'); + expect(preset?.defaultProfileName).toBe('fireworks'); + }); + + it('registers fireworks in PROVIDER_PRESET_IDS', () => { + expect(PROVIDER_PRESET_IDS).toContain('fireworks'); + }); + + it('uses the Anthropic-compatible base URL without a /v1 suffix', () => { + const preset = getPresetById('fireworks'); + expect(preset?.baseUrl).toBe('https://api.fireworks.ai/inference'); + expect(preset?.baseUrl.endsWith('/v1')).toBe(false); + }); + + it('pins a fully-qualified Fireworks model id as default', () => { + const preset = getPresetById('fireworks'); + expect(preset?.defaultModel).toMatch(/^accounts\/fireworks\/(models|routers)\//); + }); + + it('validates fireworks preset requires API key with the fw_ placeholder', () => { + const preset = getPresetById('fireworks'); + expect(preset?.requiresApiKey).toBe(true); + expect(preset?.apiKeyPlaceholder).toBe('fw_...'); + }); + + it('treats fireworks as a valid preset id', () => { + expect(isValidPresetId('fireworks')).toBe(true); + }); + + it('handles whitespace in fireworks preset id', () => { + const preset = getPresetById(' fireworks '); + expect(preset?.id).toBe('fireworks'); + }); + + it('handles uppercase fireworks preset id', () => { + const preset = getPresetById('FIREWORKS'); + expect(preset?.id).toBe('fireworks'); + }); + + it('does not resolve partial or invalid fireworks ids', () => { + expect(getPresetById('fireworks-invalid')).toBeUndefined(); + expect(isValidPresetId('fireworks-invalid')).toBe(false); + }); +});