Add Fireworks AI Provider Support (#4374) * add fireworks provider * fix * add context length * Fireworks fixes * Add logo image --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

e9be0f1c64814135274f443f03429fb8583fad92

ershang-fireworks <ershang-oss@fireworks.ai>

Signed
10 files changed, +145 -6Showing whitespace changes
public/img/fireworks.svg+1 -0
@@ -0,0 +1 @@
1+<svg preserveAspectRatio="xMinYMid meet" class="h-5" viewBox="0 0 638 315" xmlns="http://www.w3.org/2000/svg"><path d="M318.563 221.755C300.863 221.755 284.979 211.247 278.206 194.978L196.549 0H244.342L318.842 178.361L393.273 0H441.066L358.92 195.048C352.112 211.247 336.263 221.755 318.563 221.755Z"></path><path d="M425.111 314.933C407.481 314.933 391.667 304.494 384.824 288.366C377.947 272.097 381.507 253.524 393.936 240.921L542.657 90.2803L561.229 134.094L425.076 271.748L619.147 270.666L637.72 314.479L425.146 315.003L425.076 314.933H425.111Z"></path><path d="M0 314.408L18.5727 270.595L212.643 271.677L76.525 133.988L95.0977 90.1748L243.819 240.816C256.247 253.384 259.843 272.026 252.93 288.26C246.088 304.424 230.203 314.827 212.643 314.827L0.0698221 314.339L0 314.408Z"></path></svg>
public/index.html+23 -6
@@ -691,7 +691,7 @@
691691 </span>
692692 </div>
693693 </div>
694694 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,nanogpt,deepseek,xai,pollinations,moonshot,fireworks">
695695 <div class="range-block-title" data-i18n="Temperature">
696696 Temperature
697697 </div>
@@ -704,7 +704,7 @@
704704 </div>
705705 </div>
706706 </div>
707707 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations,moonshot,fireworks">
708708 <div class="range-block-title" data-i18n="Frequency Penalty">
709709 Frequency Penalty
710710 </div>
@@ -717,7 +717,7 @@
717717 </div>
718718 </div>
719719 </div>
720720 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai,pollinations,moonshot,fireworks">
721721 <div class="range-block-title" data-i18n="Presence Penalty">
722722 Presence Penalty
723723 </div>
@@ -743,7 +743,7 @@
743743 </div>
744744 </div>
745745 </div>
746746 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,nanogpt,deepseek,xai,pollinations,moonshot,fireworks">
747747 <div class="range-block-title" data-i18n="Top P">
748748 Top P
749749 </div>
@@ -1984,7 +1984,7 @@
19841984 </b>
19851985 </div>
19861986 </div>
19871987 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks">
19881988 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
19891989 <input id="openai_function_calling" type="checkbox" />
19901990 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -2065,7 +2065,7 @@
20652065 </span>
20662066 </div>
20672067 </div>
20682068 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks">
20692069 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
20702070 <input id="openai_show_thoughts" type="checkbox" />
20712071 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2793,6 +2793,7 @@
27932793 <option value="claude">Claude</option>
27942794 <option value="cohere">Cohere</option>
27952795 <option value="deepseek">DeepSeek</option>
2796+ <option value="fireworks">Fireworks AI</option>
27962797 <option value="groq">Groq</option>
27972798 <option value="makersuite">Google AI Studio</option>
27982799 <option value="vertexai">Google Vertex AI</option>
@@ -3484,6 +3485,22 @@
34843485 </select>
34853486 </div>
34863487 </div>
3488+ <div id="fireworks_form" data-source="fireworks">
3489+ <h4 data-i18n="Fireworks AI API Key">Fireworks AI API Key</h4>
3490+ <div class="flex-container">
3491+ <input id="api_key_fireworks" name="api_key_fireworks" class="text_pole flex1" value="" type="text" autocomplete="off">
3492+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_fireworks"></div>
3493+ </div>
3494+ <div data-for="api_key_fireworks" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3495+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3496+ </div>
3497+ <div>
3498+ <h4 data-i18n="Fireworks AI Model">Fireworks AI Model</h4>
3499+ <select id="model_fireworks_select">
3500+ <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3501+ </select>
3502+ </div>
3503+ </div>
34873504 <div id="perplexity_form" data-source="perplexity">
34883505 <h4 data-i18n="Perplexity API Key">Perplexity API Key</h4>
34893506 <div class="flex-container">
public/scripts/RossAscends-mods.js+1 -0
@@ -407,6 +407,7 @@ function RA_autoconnect(PrevApi) {
407407 || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
408408 || (secret_state[SECRET_KEYS.AIMLAPI] && oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI)
409409 || (secret_state[SECRET_KEYS.MOONSHOT] && oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT)
410+ || (secret_state[SECRET_KEYS.FIREWORKS] && oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS)
410411 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
411412 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
412413 ) {
public/scripts/openai.js+85 -0
@@ -185,6 +185,7 @@ export const chat_completion_sources = {
185185 XAI: 'xai',
186186 POLLINATIONS: 'pollinations',
187187 MOONSHOT: 'moonshot',
188+ FIREWORKS: 'fireworks',
188189};
189190
190191const character_names_behavior = {
@@ -458,6 +459,7 @@ const oai_settings = {
458459 xai_model: 'grok-3-beta',
459460 pollinations_model: 'openai',
460461 moonshot_model: 'kimi-latest',
462+ fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
461463 custom_model: '',
462464 custom_url: '',
463465 custom_include_body: '',
@@ -1620,6 +1622,8 @@ export function getChatCompletionModel(source = null) {
16201622 return oai_settings.pollinations_model;
16211623 case chat_completion_sources.MOONSHOT:
16221624 return oai_settings.moonshot_model;
1625+ case chat_completion_sources.FIREWORKS:
1626+ return oai_settings.fireworks_model;
16231627 default:
16241628 console.error(`Unknown chat completion source: ${activeSource}`);
16251629 return '';
@@ -1872,6 +1876,27 @@ function saveModelList(data) {
18721876
18731877 $('#model_groq_select').val(oai_settings.groq_model).trigger('change');
18741878 }
1879+
1880+ if (oai_settings.chat_completion_source === chat_completion_sources.FIREWORKS) {
1881+ $('#model_fireworks_select').empty();
1882+ model_list.forEach((model) => {
1883+ if (!model?.supports_chat) {
1884+ return;
1885+ }
1886+ $('#model_fireworks_select').append(
1887+ $('<option>', {
1888+ value: model.id,
1889+ text: model.id,
1890+ }));
1891+ });
1892+
1893+ const selectedModel = model_list.find(model => model.id === oai_settings.fireworks_model);
1894+ if (model_list.length > 0 && (!selectedModel || !oai_settings.fireworks_model)) {
1895+ oai_settings.fireworks_model = model_list[0].id;
1896+ }
1897+
1898+ $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change');
1899+ }
18751900}
18761901
18771902function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@@ -4487,6 +4512,31 @@ function getMoonshotMaxContext(model, isUnlocked) {
44874512 return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || max_32k;
44884513}
44894514
4515+/**
4516+ * Get the maximum context size for the Fireworks model
4517+ * @param {string} model Model identifier
4518+ * @param {boolean} isUnlocked Whether context limits are unlocked
4519+ * @returns {number} Maximum context size in tokens
4520+ */
4521+function getFireworksMaxContext(model, isUnlocked) {
4522+ if (isUnlocked) {
4523+ return unlocked_max;
4524+ }
4525+
4526+ // First check if model info is available from model_list
4527+ if (Array.isArray(model_list) && model_list.length > 0) {
4528+ const modelInfo = model_list.find((record) => record.id === model);
4529+ if (modelInfo?.context_length) {
4530+ return modelInfo.context_length;
4531+ }
4532+ if (modelInfo?.context_window) {
4533+ return modelInfo.context_window;
4534+ }
4535+ }
4536+
4537+ return max_32k;
4538+}
4539+
44904540async function onModelChange() {
44914541 biasCache = undefined;
44924542 let value = String($(this).val() || '');
@@ -4627,6 +4677,15 @@ async function onModelChange() {
46274677 oai_settings.moonshot_model = value;
46284678 }
46294679
4680+ if ($(this).is('#model_fireworks_select')) {
4681+ if (!value) {
4682+ console.debug('Null Fireworks model selected. Ignoring.');
4683+ return;
4684+ }
4685+ console.log('Fireworks model changed to', value);
4686+ oai_settings.fireworks_model = value;
4687+ }
4688+
46304689 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
46314690 if (oai_settings.max_context_unlocked) {
46324691 $('#openai_max_context').attr('max', max_2mil);
@@ -4899,6 +4958,15 @@ async function onModelChange() {
48994958 $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
49004959 }
49014960
4961+ if (oai_settings.chat_completion_source === chat_completion_sources.FIREWORKS) {
4962+ const maxContext = getFireworksMaxContext(oai_settings.fireworks_model, oai_settings.max_context_unlocked);
4963+ $('#openai_max_context').attr('max', maxContext);
4964+ oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
4965+ $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4966+ oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
4967+ $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4968+ }
4969+
49024970 $('#openai_max_context_counter').attr('max', Number($('#openai_max_context').attr('max')));
49034971
49044972 saveSettingsDebounced();
@@ -5143,6 +5211,19 @@ async function onConnectButtonClick(e) {
51435211 }
51445212 }
51455213
5214+ if (oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS) {
5215+ const api_key_fireworks = String($('#api_key_fireworks').val()).trim();
5216+
5217+ if (api_key_fireworks.length) {
5218+ await writeSecret(SECRET_KEYS.FIREWORKS, api_key_fireworks);
5219+ }
5220+
5221+ if (!secret_state[SECRET_KEYS.FIREWORKS]) {
5222+ console.log('No secret key saved for Fireworks');
5223+ return;
5224+ }
5225+ }
5226+
51465227 startStatusLoading();
51475228 saveSettingsDebounced();
51485229 await getStatusOpen();
@@ -5207,6 +5288,9 @@ function toggleChatCompletionForms() {
52075288 else if (oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT) {
52085289 $('#model_moonshot_select').trigger('change');
52095290 }
5291+ else if (oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS) {
5292+ $('#model_fireworks_select').trigger('change');
5293+ }
52105294 $('[data-source]').each(function () {
52115295 const validSources = $(this).data('source').split(',');
52125296 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
@@ -6139,6 +6223,7 @@ export function initOpenAI() {
61396223 $('#model_xai_select').on('change', onModelChange);
61406224 $('#model_pollinations_select').on('change', onModelChange);
61416225 $('#model_moonshot_select').on('change', onModelChange);
6226+ $('#model_fireworks_select').on('change', onModelChange);
61426227 $('#settings_preset_openai').on('change', onSettingsPresetChange);
61436228 $('#new_oai_preset').on('click', onNewPresetClick);
61446229 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/secrets.js+3 -0
@@ -60,6 +60,7 @@ export const SECRET_KEYS = {
6060 AIMLAPI: 'api_key_aimlapi',
6161 FALAI: 'api_key_falai',
6262 XAI: 'api_key_xai',
63+ FIREWORKS: 'api_key_fireworks',
6364 VERTEXAI_SERVICE_ACCOUNT: 'vertexai_service_account_json',
6465 MINIMAX: 'api_key_minimax',
6566 MINIMAX_GROUP_ID: 'minimax_group_id',
@@ -107,6 +108,7 @@ const FRIENDLY_NAMES = {
107108 [SECRET_KEYS.FALAI]: 'FAL.AI',
108109 [SECRET_KEYS.AZURE_TTS]: 'Azure TTS',
109110 [SECRET_KEYS.AIMLAPI]: 'AI/ML API',
111+ [SECRET_KEYS.FIREWORKS]: 'Fireworks AI',
110112 [SECRET_KEYS.DEEPL]: 'DeepL',
111113 [SECRET_KEYS.LIBRE]: 'LibreTranslate',
112114 [SECRET_KEYS.LIBRE_URL]: 'LibreTranslate Endpoint (e.g. http://127.0.0.1:5000/translate)',
@@ -151,6 +153,7 @@ const INPUT_MAP = {
151153 [SECRET_KEYS.XAI]: '#api_key_xai',
152154 [SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]: '#vertexai_service_account_json',
153155 [SECRET_KEYS.MOONSHOT]: '#api_key_moonshot',
156+ [SECRET_KEYS.FIREWORKS]: '#api_key_fireworks',
154157};
155158
156159const getLabel = () => moment().format('L LT');
public/scripts/slash-commands.js+1 -0
@@ -4855,6 +4855,7 @@ function getModelOptions(quiet) {
48554855 { id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI },
48564856 { id: 'model_pollinations_select', api: 'openai', type: chat_completion_sources.POLLINATIONS },
48574857 { id: 'model_moonshot_select', api: 'openai', type: chat_completion_sources.MOONSHOT },
4858+ { id: 'model_fireworks_select', api: 'openai', type: chat_completion_sources.FIREWORKS },
48584859 { id: 'model_novel_select', api: 'novel', type: null },
48594860 { id: 'horde_model', api: 'koboldhorde', type: null },
48604861 ];
public/scripts/tool-calling.js+8 -0
@@ -605,6 +605,13 @@ export class ToolManager {
605605 }
606606 }
607607
608+ if (oai_settings.chat_completion_source === chat_completion_sources.FIREWORKS && Array.isArray(model_list)) {
609+ const currentModel = model_list.find(model => model.id === oai_settings.fireworks_model);
610+ if (currentModel) {
611+ return currentModel.supports_tools;
612+ }
613+ }
614+
608615 const supportedSources = [
609616 chat_completion_sources.OPENAI,
610617 chat_completion_sources.CUSTOM,
@@ -621,6 +628,7 @@ export class ToolManager {
621628 chat_completion_sources.XAI,
622629 chat_completion_sources.POLLINATIONS,
623630 chat_completion_sources.MOONSHOT,
631+ chat_completion_sources.FIREWORKS,
624632 ];
625633 return supportedSources.includes(oai_settings.chat_completion_source);
626634 }
src/constants.js+1 -0
@@ -179,6 +179,7 @@ export const CHAT_COMPLETION_SOURCES = {
179179 XAI: 'xai',
180180 POLLINATIONS: 'pollinations',
181181 MOONSHOT: 'moonshot',
182+ FIREWORKS: 'fireworks',
182183};
183184
184185/**
src/endpoints/backends/chat-completions.js+21 -0
@@ -66,6 +66,7 @@ const API_XAI = 'https://api.x.ai/v1';
6666const API_AIMLAPI = 'https://api.aimlapi.com/v1';
6767const API_POLLINATIONS = 'https://text.pollinations.ai/openai';
6868const API_MOONSHOT = 'https://api.moonshot.ai/v1';
69+const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1';
6970
7071/**
7172 * Gets OpenRouter transforms based on the request.
@@ -1251,6 +1252,10 @@ router.post('/status', async function (request, statusResponse) {
12511252 apiUrl = API_MOONSHOT;
12521253 apiKey = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
12531254 headers = {};
1255+ } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) {
1256+ apiUrl = API_FIREWORKS;
1257+ apiKey = readSecret(request.user.directories, SECRET_KEYS.FIREWORKS);
1258+ headers = {};
12541259 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MAKERSUITE) {
12551260 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
12561261 apiUrl = trimTrailingSlash(request.body.reverse_proxy || API_MAKERSUITE);
@@ -1609,6 +1614,22 @@ router.post('/generate', function (request, response) {
16091614 },
16101615 };
16111616 }
1617+ } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) {
1618+ apiUrl = API_FIREWORKS;
1619+ apiKey = readSecret(request.user.directories, SECRET_KEYS.FIREWORKS);
1620+ headers = {};
1621+ bodyParams = {};
1622+ if (request.body.json_schema) {
1623+ bodyParams['response_format'] = {
1624+ type: 'json_schema',
1625+ json_schema: {
1626+ name: request.body.json_schema.name,
1627+ description: request.body.json_schema.description,
1628+ schema: request.body.json_schema.value,
1629+ strict: request.body.json_schema.strict ?? true,
1630+ },
1631+ };
1632+ }
16121633 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.NANOGPT) {
16131634 apiUrl = API_NANOGPT;
16141635 apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
src/endpoints/secrets.js+1 -0
@@ -53,6 +53,7 @@ export const SECRET_KEYS = {
5353 SERPER: 'api_key_serper',
5454 AIMLAPI: 'api_key_aimlapi',
5555 XAI: 'api_key_xai',
56+ FIREWORKS: 'api_key_fireworks',
5657 VERTEXAI_SERVICE_ACCOUNT: 'vertexai_service_account_json',
5758 MINIMAX: 'api_key_minimax',
5859 MINIMAX_GROUP_ID: 'minimax_group_id',