feat: add Cloudflare Workers AI provider (#5385) * feat: add Cloudflare Workers AI provider Adds support for Cloudflare Workers AI using its OpenAI-compatible API. Workers AI-specific stuff includes: - Model list fetching and capabilities detection - Tokenizer auto-detection for typical hosted model families - Streaming not supported when using structured output Closes #5305 * Make the entire header clickable * Add missing samplers * Fix non-streaming reasoning parsing --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -697,7 +697,7 @@ | |||
| 697 | </span> | 697 | </span> |
| 698 | </div> | 698 | </div> |
| 699 | </div> | 699 | </div> |
| 700 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai"> | 700 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai,workers_ai"> |
| 701 | <div class="range-block-title" data-i18n="Temperature"> | 701 | <div class="range-block-title" data-i18n="Temperature"> |
| 702 | Temperature | 702 | Temperature |
| 703 | </div> | 703 | </div> |
| @@ -710,7 +710,7 @@ | |||
| 710 | </div> | 710 | </div> |
| 711 | </div> | 711 | </div> |
| 712 | </div> | 712 | </div> |
| 713 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes"> | 713 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes,workers_ai"> |
| 714 | <div class="range-block-title" data-i18n="Frequency Penalty"> | 714 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 715 | Frequency Penalty | 715 | Frequency Penalty |
| 716 | </div> | 716 | </div> |
| @@ -723,7 +723,7 @@ | |||
| 723 | </div> | 723 | </div> |
| 724 | </div> | 724 | </div> |
| 725 | </div> | 725 | </div> |
| 726 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes"> | 726 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes,workers_ai"> |
| 727 | <div class="range-block-title" data-i18n="Presence Penalty"> | 727 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 728 | Presence Penalty | 728 | Presence Penalty |
| 729 | </div> | 729 | </div> |
| @@ -736,7 +736,7 @@ | |||
| 736 | </div> | 736 | </div> |
| 737 | </div> | 737 | </div> |
| 738 | </div> | 738 | </div> |
| 739 | <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt"> | 739 | <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt,workers_ai"> |
| 740 | <div class="range-block-title" data-i18n="Top K"> | 740 | <div class="range-block-title" data-i18n="Top K"> |
| 741 | Top K | 741 | Top K |
| 742 | </div> | 742 | </div> |
| @@ -749,7 +749,7 @@ | |||
| 749 | </div> | 749 | </div> |
| 750 | </div> | 750 | </div> |
| 751 | </div> | 751 | </div> |
| 752 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai"> | 752 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai,workers_ai"> |
| 753 | <div class="range-block-title" data-i18n="Top P"> | 753 | <div class="range-block-title" data-i18n="Top P"> |
| 754 | Top P | 754 | Top P |
| 755 | </div> | 755 | </div> |
| @@ -762,7 +762,7 @@ | |||
| 762 | </div> | 762 | </div> |
| 763 | </div> | 763 | </div> |
| 764 | </div> | 764 | </div> |
| 765 | <div class="range-block" data-source="openrouter,nanogpt,chutes"> | 765 | <div class="range-block" data-source="openrouter,nanogpt,chutes,workers_ai"> |
| 766 | <div class="range-block-title" data-i18n="Repetition Penalty"> | 766 | <div class="range-block-title" data-i18n="Repetition Penalty"> |
| 767 | Repetition Penalty | 767 | Repetition Penalty |
| 768 | </div> | 768 | </div> |
| @@ -986,7 +986,7 @@ | |||
| 986 | </div> | 986 | </div> |
| 987 | </div> | 987 | </div> |
| 988 | </div> | 988 | </div> |
| 989 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai"> | 989 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai,workers_ai"> |
| 990 | <div class="range-block-title justifyLeft" data-i18n="Seed"> | 990 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 991 | Seed | 991 | Seed |
| 992 | </div> | 992 | </div> |
| @@ -1996,7 +1996,7 @@ | |||
| 1996 | </b> | 1996 | </b> |
| 1997 | </div> | 1997 | </div> |
| 1998 | </div> | 1998 | </div> |
| 1999 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai,nanogpt"> | 1999 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai,nanogpt,workers_ai"> |
| 2000 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> | 2000 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 2001 | <input id="openai_function_calling" type="checkbox" /> | 2001 | <input id="openai_function_calling" type="checkbox" /> |
| 2002 | <span data-i18n="Enable function calling">Enable function calling</span> | 2002 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -2035,7 +2035,7 @@ | |||
| 2035 | </em> | 2035 | </em> |
| 2036 | </div> | 2036 | </div> |
| 2037 | </div> | 2037 | </div> |
| 2038 | <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow,chutes"> | 2038 | <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow,chutes,workers_ai"> |
| 2039 | <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand"> | 2039 | <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 2040 | <input id="openai_media_inlining" type="checkbox" /> | 2040 | <input id="openai_media_inlining" type="checkbox" /> |
| 2041 | <span data-i18n="Send inline media">Send inline media</span> | 2041 | <span data-i18n="Send inline media">Send inline media</span> |
| @@ -2059,7 +2059,7 @@ | |||
| 2059 | <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong> | 2059 | <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong> |
| 2060 | <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong> | 2060 | <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong> |
| 2061 | </div> | 2061 | </div> |
| 2062 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes,makersuite,vertexai"> | 2062 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes,makersuite,vertexai,workers_ai"> |
| 2063 | <div class="flex-container oneline-dropdown"> | 2063 | <div class="flex-container oneline-dropdown"> |
| 2064 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> | 2064 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> |
| 2065 | Inline Image Quality | 2065 | Inline Image Quality |
| @@ -2120,7 +2120,7 @@ | |||
| 2120 | </div> | 2120 | </div> |
| 2121 | </div> | 2121 | </div> |
| 2122 | </div> | 2122 | </div> |
| 2123 | <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,chutes,azure_openai,nanogpt,zai"> | 2123 | <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,chutes,azure_openai,nanogpt,zai,workers_ai"> |
| 2124 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> | 2124 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2125 | <input id="openai_show_thoughts" type="checkbox" /> | 2125 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2126 | <span data-i18n="Request model reasoning">Request model reasoning</span> | 2126 | <span data-i18n="Request model reasoning">Request model reasoning</span> |
| @@ -2894,6 +2894,7 @@ | |||
| 2894 | <option value="azure_openai">Azure OpenAI</option> | 2894 | <option value="azure_openai">Azure OpenAI</option> |
| 2895 | <option value="chutes">Chutes</option> | 2895 | <option value="chutes">Chutes</option> |
| 2896 | <option value="claude">Claude</option> | 2896 | <option value="claude">Claude</option> |
| 2897 | <option value="workers_ai">Cloudflare Workers AI</option> | ||
| 2897 | <option value="cohere">Cohere</option> | 2898 | <option value="cohere">Cohere</option> |
| 2898 | <!-- Temporarily disabled. --> | 2899 | <!-- Temporarily disabled. --> |
| 2899 | <!-- <option value="cometapi">CometAPI</option> --> | 2900 | <!-- <option value="cometapi">CometAPI</option> --> |
| @@ -3671,6 +3672,26 @@ | |||
| 3671 | </select> | 3672 | </select> |
| 3672 | </div> | 3673 | </div> |
| 3673 | </div> | 3674 | </div> |
| 3675 | <div id="workers_ai_form" data-source="workers_ai"> | ||
| 3676 | <h4><a href="https://dash.cloudflare.com/?to=/:account/ai/workers-ai/api-quick-start" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Workers AI API Key">Cloudflare Workers AI API Key</a></h4> | ||
| 3677 | <div class="flex-container"> | ||
| 3678 | <input id="api_key_workers_ai" name="api_key_workers_ai" class="text_pole flex1" value="" type="text" autocomplete="off"> | ||
| 3679 | <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_workers_ai"></div> | ||
| 3680 | </div> | ||
| 3681 | <div data-for="api_key_workers_ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> | ||
| 3682 | For privacy reasons, your API key will be hidden after you click 'Connect'. | ||
| 3683 | </div> | ||
| 3684 | <h4><a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Account ID">Cloudflare Account ID</a></h4> | ||
| 3685 | <div class="flex-container"> | ||
| 3686 | <input id="workers_ai_account_id" class="text_pole flex1" value="" type="text" autocomplete="off" placeholder="e.g. 023e105f4ecef8ad9ca31a8372d0c353" data-i18n="[placeholder]e.g. 023e105f4ecef8ad9ca31a8372d0c353"> | ||
| 3687 | </div> | ||
| 3688 | <div> | ||
| 3689 | <h4 data-i18n="Workers AI Model">Workers AI Model</h4> | ||
| 3690 | <select id="model_workers_ai_select"> | ||
| 3691 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | ||
| 3692 | </select> | ||
| 3693 | </div> | ||
| 3694 | </div> | ||
| 3674 | <div id="deepseek_form" data-source="deepseek"> | 3695 | <div id="deepseek_form" data-source="deepseek"> |
| 3675 | <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4> | 3696 | <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4> |
| 3676 | <div class="flex-container"> | 3697 | <div class="flex-container"> |
| @@ -407,6 +407,7 @@ function RA_autoconnect(PrevApi) { | |||
| 407 | || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) | 407 | || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) |
| 408 | || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI) | 408 | || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI) |
| 409 | || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) | 409 | || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) |
| 410 | || (secret_state[SECRET_KEYS.WORKERS_AI] && oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI) | ||
| 410 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) | 411 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 411 | || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) | 412 | || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) |
| 412 | ) { | 413 | ) { |
| @@ -196,6 +196,7 @@ export const chat_completion_sources = { | |||
| 196 | AZURE_OPENAI: 'azure_openai', | 196 | AZURE_OPENAI: 'azure_openai', |
| 197 | ZAI: 'zai', | 197 | ZAI: 'zai', |
| 198 | SILICONFLOW: 'siliconflow', | 198 | SILICONFLOW: 'siliconflow', |
| 199 | WORKERS_AI: 'workers_ai', | ||
| 199 | }; | 200 | }; |
| 200 | 201 | ||
| 201 | const character_names_behavior = { | 202 | const character_names_behavior = { |
| @@ -279,6 +280,7 @@ const sensitiveFields = [ | |||
| 279 | 'vertexai_express_project_id', | 280 | 'vertexai_express_project_id', |
| 280 | 'azure_base_url', | 281 | 'azure_base_url', |
| 281 | 'azure_deployment_name', | 282 | 'azure_deployment_name', |
| 283 | 'workers_ai_account_id', | ||
| 282 | ]; | 284 | ]; |
| 283 | 285 | ||
| 284 | /** | 286 | /** |
| @@ -337,6 +339,8 @@ export const settingsToUpdate = { | |||
| 337 | vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], | 339 | vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], |
| 338 | zai_model: ['#model_zai_select', 'zai_model', false, true], | 340 | zai_model: ['#model_zai_select', 'zai_model', false, true], |
| 339 | zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true], | 341 | zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true], |
| 342 | workers_ai_model: ['#model_workers_ai_select', 'workers_ai_model', false, true], | ||
| 343 | workers_ai_account_id: ['#workers_ai_account_id', 'workers_ai_account_id', false, true], | ||
| 340 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], | 344 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], |
| 341 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], | 345 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], |
| 342 | names_behavior: ['#names_behavior', 'names_behavior', false, false], | 346 | names_behavior: ['#names_behavior', 'names_behavior', false, false], |
| @@ -439,6 +443,8 @@ const default_settings = { | |||
| 439 | fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct', | 443 | fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct', |
| 440 | zai_model: 'glm-4.6', | 444 | zai_model: 'glm-4.6', |
| 441 | zai_endpoint: ZAI_ENDPOINT.COMMON, | 445 | zai_endpoint: ZAI_ENDPOINT.COMMON, |
| 446 | workers_ai_model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast', | ||
| 447 | workers_ai_account_id: '', | ||
| 442 | azure_base_url: '', | 448 | azure_base_url: '', |
| 443 | azure_deployment_name: '', | 449 | azure_deployment_name: '', |
| 444 | azure_api_version: '2024-02-15-preview', | 450 | azure_api_version: '2024-02-15-preview', |
| @@ -1727,6 +1733,8 @@ export function getChatCompletionModel(settings = null) { | |||
| 1727 | return settings.azure_openai_model; | 1733 | return settings.azure_openai_model; |
| 1728 | case chat_completion_sources.ZAI: | 1734 | case chat_completion_sources.ZAI: |
| 1729 | return settings.zai_model; | 1735 | return settings.zai_model; |
| 1736 | case chat_completion_sources.WORKERS_AI: | ||
| 1737 | return settings.workers_ai_model; | ||
| 1730 | default: | 1738 | default: |
| 1731 | console.error(`Unknown chat completion source: ${source}`); | 1739 | console.error(`Unknown chat completion source: ${source}`); |
| 1732 | return ''; | 1740 | return ''; |
| @@ -2193,6 +2201,24 @@ function saveModelList(data) { | |||
| 2193 | $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change'); | 2201 | $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change'); |
| 2194 | } | 2202 | } |
| 2195 | 2203 | ||
| 2204 | if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) { | ||
| 2205 | $('#model_workers_ai_select').empty(); | ||
| 2206 | model_list.forEach((model) => { | ||
| 2207 | $('#model_workers_ai_select').append( | ||
| 2208 | $('<option>', { | ||
| 2209 | value: model.id, | ||
| 2210 | text: model.id, | ||
| 2211 | })); | ||
| 2212 | }); | ||
| 2213 | |||
| 2214 | const selectedModel = model_list.find(model => model.id === oai_settings.workers_ai_model); | ||
| 2215 | if (model_list.length > 0 && (!selectedModel || !oai_settings.workers_ai_model)) { | ||
| 2216 | oai_settings.workers_ai_model = model_list[0].id; | ||
| 2217 | } | ||
| 2218 | |||
| 2219 | $('#model_workers_ai_select').val(oai_settings.workers_ai_model).trigger('change'); | ||
| 2220 | } | ||
| 2221 | |||
| 2196 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { | 2222 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { |
| 2197 | $('#model_cometapi_select').empty(); | 2223 | $('#model_cometapi_select').empty(); |
| 2198 | 2224 | ||
| @@ -2604,7 +2630,8 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2604 | ]; | 2630 | ]; |
| 2605 | 2631 | ||
| 2606 | const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model); | 2632 | const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model); |
| 2607 | const stream = settings.stream_openai && type !== 'quiet' && !isO1; | 2633 | const isWorkersAIJsonMode = settings.chat_completion_source === chat_completion_sources.WORKERS_AI && jsonSchema; |
| 2634 | const stream = settings.stream_openai && type !== 'quiet' && !isO1 && !isWorkersAIJsonMode; | ||
| 2608 | 2635 | ||
| 2609 | const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue']; | 2636 | const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue']; |
| 2610 | const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source); | 2637 | const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source); |
| @@ -2815,6 +2842,16 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2815 | generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL; | 2842 | generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL; |
| 2816 | } | 2843 | } |
| 2817 | 2844 | ||
| 2845 | if (settings.chat_completion_source === chat_completion_sources.WORKERS_AI) { | ||
| 2846 | generate_data.workers_ai_account_id = settings.workers_ai_account_id; | ||
| 2847 | generate_data.top_k = settings.top_k_openai > 0 ? Math.min(Number(settings.top_k_openai), 50) : undefined; | ||
| 2848 | generate_data.repetition_penalty = Number(settings.repetition_penalty_openai); | ||
| 2849 | generate_data.seed = settings.seed >= 1 ? Number(settings.seed) : undefined; | ||
| 2850 | generate_data.top_p = Math.max(Number(settings.top_p_openai), 0.001); | ||
| 2851 | delete generate_data.n; | ||
| 2852 | delete generate_data.logit_bias; | ||
| 2853 | } | ||
| 2854 | |||
| 2818 | // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus | 2855 | // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus |
| 2819 | if (settings.chat_completion_source === chat_completion_sources.NANOGPT) { | 2856 | if (settings.chat_completion_source === chat_completion_sources.NANOGPT) { |
| 2820 | generate_data.top_k = Number(settings.top_k_openai); | 2857 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -3052,7 +3089,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov | |||
| 3052 | } | 3089 | } |
| 3053 | }); | 3090 | }); |
| 3054 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | 3091 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 3055 | } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW, chat_completion_sources.CHUTES].includes(chat_completion_source)) { | 3092 | } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW, chat_completion_sources.CHUTES, chat_completion_sources.WORKERS_AI].includes(chat_completion_source)) { |
| 3056 | if (show_thoughts) { | 3093 | if (show_thoughts) { |
| 3057 | state.reasoning += | 3094 | state.reasoning += |
| 3058 | data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ?? | 3095 | data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ?? |
| @@ -4272,6 +4309,10 @@ async function getStatusOpen() { | |||
| 4272 | data.siliconflow_endpoint = oai_settings.siliconflow_endpoint; | 4309 | data.siliconflow_endpoint = oai_settings.siliconflow_endpoint; |
| 4273 | } | 4310 | } |
| 4274 | 4311 | ||
| 4312 | if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) { | ||
| 4313 | data.workers_ai_account_id = oai_settings.workers_ai_account_id; | ||
| 4314 | } | ||
| 4315 | |||
| 4275 | const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM; | 4316 | const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM; |
| 4276 | if (canBypass) { | 4317 | if (canBypass) { |
| 4277 | setOnlineStatus(t`Status check bypassed`); | 4318 | setOnlineStatus(t`Status check bypassed`); |
| @@ -5329,6 +5370,15 @@ async function onModelChange() { | |||
| 5329 | oai_settings.zai_model = value; | 5370 | oai_settings.zai_model = value; |
| 5330 | } | 5371 | } |
| 5331 | 5372 | ||
| 5373 | if ($(this).is('#model_workers_ai_select')) { | ||
| 5374 | if (!value || !hasModelsLoaded) { | ||
| 5375 | console.debug('Null Workers AI model selected. Ignoring.'); | ||
| 5376 | return; | ||
| 5377 | } | ||
| 5378 | console.log('Workers AI model changed to', value); | ||
| 5379 | oai_settings.workers_ai_model = value; | ||
| 5380 | } | ||
| 5381 | |||
| 5332 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) { | 5382 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) { |
| 5333 | if (oai_settings.max_context_unlocked) { | 5383 | if (oai_settings.max_context_unlocked) { |
| 5334 | $('#openai_max_context').attr('max', max_2mil); | 5384 | $('#openai_max_context').attr('max', max_2mil); |
| @@ -5549,6 +5599,22 @@ async function onModelChange() { | |||
| 5549 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); | 5599 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 5550 | } | 5600 | } |
| 5551 | 5601 | ||
| 5602 | if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) { | ||
| 5603 | if (oai_settings.max_context_unlocked) { | ||
| 5604 | $('#openai_max_context').attr('max', unlocked_max); | ||
| 5605 | } else { | ||
| 5606 | const model = model_list.find(m => m.id === oai_settings.workers_ai_model); | ||
| 5607 | const ctxProp = Array.isArray(model?.properties) && model.properties.find(p => p.property_id === 'context_window'); | ||
| 5608 | const contextLength = ctxProp ? Number(ctxProp.value) : max_8k; | ||
| 5609 | $('#openai_max_context').attr('max', contextLength || max_8k); | ||
| 5610 | } | ||
| 5611 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | ||
| 5612 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | ||
| 5613 | const workersAiMaxTemp = 5.0; | ||
| 5614 | oai_settings.temp_openai = Math.min(workersAiMaxTemp, oai_settings.temp_openai); | ||
| 5615 | $('#temp_openai').attr('max', workersAiMaxTemp).val(oai_settings.temp_openai).trigger('input'); | ||
| 5616 | } | ||
| 5617 | |||
| 5552 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { | 5618 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { |
| 5553 | $('#openai_max_context').attr('max', oai_settings.max_context_unlocked ? unlocked_max : max_128k); | 5619 | $('#openai_max_context').attr('max', oai_settings.max_context_unlocked ? unlocked_max : max_128k); |
| 5554 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | 5620 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| @@ -5710,6 +5776,7 @@ async function onConnectButtonClick(e) { | |||
| 5710 | [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true }, | 5776 | [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true }, |
| 5711 | [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false }, | 5777 | [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false }, |
| 5712 | [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false }, | 5778 | [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false }, |
| 5779 | [chat_completion_sources.WORKERS_AI]: { key: SECRET_KEYS.WORKERS_AI, selector: '#api_key_workers_ai', proxy: false }, | ||
| 5713 | }; | 5780 | }; |
| 5714 | 5781 | ||
| 5715 | // Vertex AI Express version - use API key | 5782 | // Vertex AI Express version - use API key |
| @@ -5799,6 +5866,8 @@ function toggleChatCompletionForms() { | |||
| 5799 | $('#azure_openai_model').trigger('change'); | 5866 | $('#azure_openai_model').trigger('change'); |
| 5800 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { | 5867 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { |
| 5801 | $('#model_zai_select').trigger('change'); | 5868 | $('#model_zai_select').trigger('change'); |
| 5869 | } else if (oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI) { | ||
| 5870 | $('#model_workers_ai_select').trigger('change'); | ||
| 5802 | } | 5871 | } |
| 5803 | 5872 | ||
| 5804 | $('[data-source]').each(function () { | 5873 | $('[data-source]').each(function () { |
| @@ -5980,6 +6049,10 @@ export function isImageInliningSupported() { | |||
| 5980 | return visionSupportedModels.some(model => oai_settings.zai_model.includes(model)); | 6049 | return visionSupportedModels.some(model => oai_settings.zai_model.includes(model)); |
| 5981 | case chat_completion_sources.SILICONFLOW: | 6050 | case chat_completion_sources.SILICONFLOW: |
| 5982 | return visionSupportedModels.some(model => oai_settings.siliconflow_model.includes(model)); | 6051 | return visionSupportedModels.some(model => oai_settings.siliconflow_model.includes(model)); |
| 6052 | case chat_completion_sources.WORKERS_AI: { | ||
| 6053 | const waiModel = Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.workers_ai_model); | ||
| 6054 | return Boolean(waiModel && Array.isArray(waiModel.properties) && waiModel.properties.some(p => p.property_id === 'vision' && p.value === 'true')); | ||
| 6055 | } | ||
| 5983 | default: | 6056 | default: |
| 5984 | return false; | 6057 | return false; |
| 5985 | } | 6058 | } |
| @@ -6952,6 +7025,10 @@ export function initOpenAI() { | |||
| 6952 | oai_settings.siliconflow_endpoint = String($(this).val()); | 7025 | oai_settings.siliconflow_endpoint = String($(this).val()); |
| 6953 | saveSettingsDebounced(); | 7026 | saveSettingsDebounced(); |
| 6954 | }); | 7027 | }); |
| 7028 | $('#workers_ai_account_id').on('input', function () { | ||
| 7029 | oai_settings.workers_ai_account_id = String($(this).val()); | ||
| 7030 | saveSettingsDebounced(); | ||
| 7031 | }); | ||
| 6955 | $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange); | 7032 | $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange); |
| 6956 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); | 7033 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); |
| 6957 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); | 7034 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); |
| @@ -6980,6 +7057,7 @@ export function initOpenAI() { | |||
| 6980 | $('#model_fireworks_select').on('change', onModelChange); | 7057 | $('#model_fireworks_select').on('change', onModelChange); |
| 6981 | $('#azure_openai_model').on('change', onModelChange); | 7058 | $('#azure_openai_model').on('change', onModelChange); |
| 6982 | $('#model_zai_select').on('change', onModelChange); | 7059 | $('#model_zai_select').on('change', onModelChange); |
| 7060 | $('#model_workers_ai_select').on('change', onModelChange); | ||
| 6983 | $('#settings_preset_openai').on('change', onSettingsPresetChange); | 7061 | $('#settings_preset_openai').on('change', onSettingsPresetChange); |
| 6984 | $('#new_oai_preset').on('click', onNewPresetClick); | 7062 | $('#new_oai_preset').on('click', onNewPresetClick); |
| 6985 | $('#delete_oai_preset').on('click', onDeletePresetClick); | 7063 | $('#delete_oai_preset').on('click', onDeletePresetClick); |
| @@ -134,6 +134,7 @@ export function extractReasoningFromData(data, { | |||
| 134 | case chat_completion_sources.NANOGPT: | 134 | case chat_completion_sources.NANOGPT: |
| 135 | case chat_completion_sources.SILICONFLOW: | 135 | case chat_completion_sources.SILICONFLOW: |
| 136 | case chat_completion_sources.ZAI: | 136 | case chat_completion_sources.ZAI: |
| 137 | case chat_completion_sources.WORKERS_AI: | ||
| 137 | case chat_completion_sources.CUSTOM: { | 138 | case chat_completion_sources.CUSTOM: { |
| 138 | return data?.choices?.[0]?.message?.reasoning_content | 139 | return data?.choices?.[0]?.message?.reasoning_content |
| 139 | ?? data?.choices?.[0]?.message?.reasoning | 140 | ?? data?.choices?.[0]?.message?.reasoning |
| @@ -1082,7 +1083,7 @@ function registerReasoningSlashCommands() { | |||
| 1082 | helpString: t`Collapse the reasoning block of a message or range of messages.`, | 1083 | helpString: t`Collapse the reasoning block of a message or range of messages.`, |
| 1083 | unnamedArgumentList: reasoningVisibilityArgs, | 1084 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1084 | callback: (_args, value) => { | 1085 | callback: (_args, value) => { |
| 1085 | const details = getReasoningDetailsElements(value); | 1086 | const details = getReasoningDetailsElements(value.toString()); |
| 1086 | if (details) details.removeAttr('open'); | 1087 | if (details) details.removeAttr('open'); |
| 1087 | return ''; | 1088 | return ''; |
| 1088 | }, | 1089 | }, |
| @@ -1094,7 +1095,7 @@ function registerReasoningSlashCommands() { | |||
| 1094 | helpString: t`Expand the reasoning block of a message or range of messages.`, | 1095 | helpString: t`Expand the reasoning block of a message or range of messages.`, |
| 1095 | unnamedArgumentList: reasoningVisibilityArgs, | 1096 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1096 | callback: (_args, value) => { | 1097 | callback: (_args, value) => { |
| 1097 | const details = getReasoningDetailsElements(value); | 1098 | const details = getReasoningDetailsElements(value.toString()); |
| 1098 | if (details) details.attr('open', ''); | 1099 | if (details) details.attr('open', ''); |
| 1099 | return ''; | 1100 | return ''; |
| 1100 | }, | 1101 | }, |
| @@ -1106,7 +1107,7 @@ function registerReasoningSlashCommands() { | |||
| 1106 | helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`, | 1107 | helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`, |
| 1107 | unnamedArgumentList: reasoningVisibilityArgs, | 1108 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1108 | callback: (_args, value) => { | 1109 | callback: (_args, value) => { |
| 1109 | const details = getReasoningDetailsElements(value); | 1110 | const details = getReasoningDetailsElements(value.toString()); |
| 1110 | if (!details) return ''; | 1111 | if (!details) return ''; |
| 1111 | details.each(function () { | 1112 | details.each(function () { |
| 1112 | const $el = $(this); | 1113 | const $el = $(this); |
| @@ -76,6 +76,7 @@ export const SECRET_KEYS = { | |||
| 76 | POLLINATIONS: 'api_key_pollinations', | 76 | POLLINATIONS: 'api_key_pollinations', |
| 77 | VOLCENGINE_APP_ID: 'volcengine_app_id', | 77 | VOLCENGINE_APP_ID: 'volcengine_app_id', |
| 78 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', | 78 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', |
| 79 | WORKERS_AI: 'api_key_workers_ai', | ||
| 79 | }; | 80 | }; |
| 80 | 81 | ||
| 81 | const FRIENDLY_NAMES = { | 82 | const FRIENDLY_NAMES = { |
| @@ -140,6 +141,7 @@ const FRIENDLY_NAMES = { | |||
| 140 | [SECRET_KEYS.POLLINATIONS]: 'Pollinations', | 141 | [SECRET_KEYS.POLLINATIONS]: 'Pollinations', |
| 141 | [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID', | 142 | [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID', |
| 142 | [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key', | 143 | [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key', |
| 144 | [SECRET_KEYS.WORKERS_AI]: 'Cloudflare Workers AI', | ||
| 143 | }; | 145 | }; |
| 144 | 146 | ||
| 145 | const INPUT_MAP = { | 147 | const INPUT_MAP = { |
| @@ -184,6 +186,7 @@ const INPUT_MAP = { | |||
| 184 | [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow', | 186 | [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow', |
| 185 | [SECRET_KEYS.COMFY_RUNPOD]: '#api_key_comfy_runpod', | 187 | [SECRET_KEYS.COMFY_RUNPOD]: '#api_key_comfy_runpod', |
| 186 | [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations', | 188 | [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations', |
| 189 | [SECRET_KEYS.WORKERS_AI]: '#api_key_workers_ai', | ||
| 187 | }; | 190 | }; |
| 188 | 191 | ||
| 189 | const getLabel = () => moment().format('L LT'); | 192 | const getLabel = () => moment().format('L LT'); |
| @@ -6386,6 +6386,7 @@ function getModelOptions(quiet) { | |||
| 6386 | { id: 'model_fireworks_select', api: 'openai', type: chat_completion_sources.FIREWORKS }, | 6386 | { id: 'model_fireworks_select', api: 'openai', type: chat_completion_sources.FIREWORKS }, |
| 6387 | { id: 'model_cometapi_select', api: 'openai', type: chat_completion_sources.COMETAPI }, | 6387 | { id: 'model_cometapi_select', api: 'openai', type: chat_completion_sources.COMETAPI }, |
| 6388 | { id: 'model_zai_select', api: 'openai', type: chat_completion_sources.ZAI }, | 6388 | { id: 'model_zai_select', api: 'openai', type: chat_completion_sources.ZAI }, |
| 6389 | { id: 'model_workers_ai_select', api: 'openai', type: chat_completion_sources.WORKERS_AI }, | ||
| 6389 | { id: 'model_novel_select', api: 'novel', type: null }, | 6390 | { id: 'model_novel_select', api: 'novel', type: null }, |
| 6390 | { id: 'horde_model', api: 'koboldhorde', type: null }, | 6391 | { id: 'horde_model', api: 'koboldhorde', type: null }, |
| 6391 | ]; | 6392 | ]; |
| @@ -694,6 +694,28 @@ export function getTokenizerModel() { | |||
| 694 | } | 694 | } |
| 695 | } | 695 | } |
| 696 | 696 | ||
| 697 | if (oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI && oai_settings.workers_ai_model) { | ||
| 698 | const model = oai_settings.workers_ai_model.toLowerCase(); | ||
| 699 | |||
| 700 | if (model.includes('deepseek')) { | ||
| 701 | return deepseekTokenizer; | ||
| 702 | } else if (model.includes('qwen') || model.includes('qwq') || model.includes('kimi')) { | ||
| 703 | return qwen2Tokenizer; | ||
| 704 | } else if (model.includes('llama-3') || model.includes('llama-4')) { | ||
| 705 | return llama3Tokenizer; | ||
| 706 | } else if (model.includes('llama')) { | ||
| 707 | return llamaTokenizer; | ||
| 708 | } else if (model.includes('gemma')) { | ||
| 709 | return gemmaTokenizer; | ||
| 710 | } else if (model.includes('mistral')) { | ||
| 711 | return mistralTokenizer; | ||
| 712 | } else if (model.includes('phi')) { | ||
| 713 | return turboTokenizer; | ||
| 714 | } else if (model.includes('gpt-oss')) { | ||
| 715 | return gpt4oTokenizer; | ||
| 716 | } | ||
| 717 | } | ||
| 718 | |||
| 697 | if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) { | 719 | if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) { |
| 698 | if (oai_settings.cohere_model.includes('command-a')) { | 720 | if (oai_settings.cohere_model.includes('command-a')) { |
| 699 | return commandATokenizer; | 721 | return commandATokenizer; |
| @@ -637,6 +637,8 @@ export class ToolManager { | |||
| 637 | return currentModel.supported_features?.includes('tools'); | 637 | return currentModel.supported_features?.includes('tools'); |
| 638 | case chat_completion_sources.ELECTRONHUB: | 638 | case chat_completion_sources.ELECTRONHUB: |
| 639 | return currentModel.metadata?.function_call; | 639 | return currentModel.metadata?.function_call; |
| 640 | case chat_completion_sources.WORKERS_AI: | ||
| 641 | return Array.isArray(currentModel.properties) && currentModel.properties.some(p => p.property_id === 'function_calling' && p.value === 'true'); | ||
| 640 | } | 642 | } |
| 641 | } | 643 | } |
| 642 | 644 | ||
| @@ -664,6 +666,7 @@ export class ToolManager { | |||
| 664 | chat_completion_sources.ZAI, | 666 | chat_completion_sources.ZAI, |
| 665 | chat_completion_sources.SILICONFLOW, | 667 | chat_completion_sources.SILICONFLOW, |
| 666 | chat_completion_sources.NANOGPT, | 668 | chat_completion_sources.NANOGPT, |
| 669 | chat_completion_sources.WORKERS_AI, | ||
| 667 | ]; | 670 | ]; |
| 668 | return supportedSources.includes(settings.chat_completion_source); | 671 | return supportedSources.includes(settings.chat_completion_source); |
| 669 | } | 672 | } |
| @@ -209,6 +209,7 @@ export const CHAT_COMPLETION_SOURCES = { | |||
| 209 | AZURE_OPENAI: 'azure_openai', | 209 | AZURE_OPENAI: 'azure_openai', |
| 210 | ZAI: 'zai', | 210 | ZAI: 'zai', |
| 211 | SILICONFLOW: 'siliconflow', | 211 | SILICONFLOW: 'siliconflow', |
| 212 | WORKERS_AI: 'workers_ai', | ||
| 212 | }; | 213 | }; |
| 213 | 214 | ||
| 214 | /** | 215 | /** |
| @@ -90,6 +90,7 @@ const API_ZAI_CODING = 'https://api.z.ai/api/coding/paas/v4'; | |||
| 90 | const API_SILICONFLOW = 'https://api.siliconflow.com/v1'; | 90 | const API_SILICONFLOW = 'https://api.siliconflow.com/v1'; |
| 91 | const API_SILICONFLOW_CN = 'https://api.siliconflow.cn/v1'; | 91 | const API_SILICONFLOW_CN = 'https://api.siliconflow.cn/v1'; |
| 92 | const API_OPENROUTER = 'https://openrouter.ai/api/v1'; | 92 | const API_OPENROUTER = 'https://openrouter.ai/api/v1'; |
| 93 | const API_WORKERS_AI = 'https://api.cloudflare.com/client/v4/accounts'; | ||
| 93 | 94 | ||
| 94 | /** | 95 | /** |
| 95 | * Module-scoped Claude caching configuration values. | 96 | * Module-scoped Claude caching configuration values. |
| @@ -1833,6 +1834,49 @@ router.post('/status', async function (request, statusResponse) { | |||
| 1833 | apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW, request.body.secret_id); | 1834 | apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW, request.body.secret_id); |
| 1834 | headers = {}; | 1835 | headers = {}; |
| 1835 | queryParams = { type: 'text', sub_type: 'chat' }; | 1836 | queryParams = { type: 'text', sub_type: 'chat' }; |
| 1837 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.WORKERS_AI) { | ||
| 1838 | apiKey = readSecret(request.user.directories, SECRET_KEYS.WORKERS_AI, request.body.secret_id); | ||
| 1839 | |||
| 1840 | if (!apiKey) { | ||
| 1841 | console.warn('Cloudflare Workers AI API key is missing.'); | ||
| 1842 | return statusResponse.status(400).send({ error: true }); | ||
| 1843 | } | ||
| 1844 | |||
| 1845 | try { | ||
| 1846 | const accountId = String(request.body.workers_ai_account_id || '').trim(); | ||
| 1847 | if (!accountId) { | ||
| 1848 | console.warn('Cloudflare Workers AI Account ID is missing.'); | ||
| 1849 | return statusResponse.status(400).send({ error: true }); | ||
| 1850 | } | ||
| 1851 | |||
| 1852 | const modelsUrl = new URL(`${API_WORKERS_AI}/${encodeURIComponent(accountId)}/ai/models/search`); | ||
| 1853 | modelsUrl.searchParams.set('task', 'Text Generation'); | ||
| 1854 | modelsUrl.searchParams.set('per_page', '1000'); | ||
| 1855 | |||
| 1856 | const response = await fetch(modelsUrl, { | ||
| 1857 | method: 'GET', | ||
| 1858 | headers: { | ||
| 1859 | 'Authorization': 'Bearer ' + apiKey, | ||
| 1860 | }, | ||
| 1861 | }); | ||
| 1862 | |||
| 1863 | if (response.ok) { | ||
| 1864 | /** @type {any} */ | ||
| 1865 | const data = await response.json(); | ||
| 1866 | const models = Array.isArray(data?.result) | ||
| 1867 | ? data.result.map(model => ({ ...model, id: model.name })) | ||
| 1868 | : []; | ||
| 1869 | |||
| 1870 | console.debug('Available Cloudflare Workers AI models:', models.map(m => m.id)); | ||
| 1871 | return statusResponse.send({ data: models }); | ||
| 1872 | } else { | ||
| 1873 | console.warn('Cloudflare Workers AI models endpoint failed:', response.status, response.statusText); | ||
| 1874 | return statusResponse.status(response.status).send({ error: true }); | ||
| 1875 | } | ||
| 1876 | } catch (error) { | ||
| 1877 | console.error('Error fetching Cloudflare Workers AI models:', error); | ||
| 1878 | return statusResponse.status(500).send({ error: true }); | ||
| 1879 | } | ||
| 1836 | } else { | 1880 | } else { |
| 1837 | console.warn('This chat completion source is not supported yet.'); | 1881 | console.warn('This chat completion source is not supported yet.'); |
| 1838 | return statusResponse.status(400).send({ error: true }); | 1882 | return statusResponse.status(400).send({ error: true }); |
| @@ -2315,6 +2359,24 @@ router.post('/generate', async function (request, response) { | |||
| 2315 | if (request.body.json_schema) { | 2359 | if (request.body.json_schema) { |
| 2316 | setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema); | 2360 | setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema); |
| 2317 | } | 2361 | } |
| 2362 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.WORKERS_AI) { | ||
| 2363 | apiKey = readSecret(request.user.directories, SECRET_KEYS.WORKERS_AI, request.body.secret_id); | ||
| 2364 | const accountId = String(request.body.workers_ai_account_id || '').trim(); | ||
| 2365 | if (!accountId) { | ||
| 2366 | console.warn('Cloudflare Workers AI Account ID is missing.'); | ||
| 2367 | return response.status(400).send({ error: true }); | ||
| 2368 | } | ||
| 2369 | apiUrl = `${API_WORKERS_AI}/${encodeURIComponent(accountId)}/ai/v1`; | ||
| 2370 | headers = {}; | ||
| 2371 | bodyParams = { | ||
| 2372 | repetition_penalty: request.body.repetition_penalty, | ||
| 2373 | }; | ||
| 2374 | if (request.body.json_schema) { | ||
| 2375 | bodyParams['response_format'] = { | ||
| 2376 | type: 'json_schema', | ||
| 2377 | json_schema: request.body.json_schema.value, | ||
| 2378 | }; | ||
| 2379 | } | ||
| 2318 | } else { | 2380 | } else { |
| 2319 | console.warn('This chat completion source is not supported yet.'); | 2381 | console.warn('This chat completion source is not supported yet.'); |
| 2320 | return response.status(400).send({ error: true }); | 2382 | return response.status(400).send({ error: true }); |
| @@ -69,6 +69,7 @@ export const SECRET_KEYS = { | |||
| 69 | POLLINATIONS: 'api_key_pollinations', | 69 | POLLINATIONS: 'api_key_pollinations', |
| 70 | VOLCENGINE_APP_ID: 'volcengine_app_id', | 70 | VOLCENGINE_APP_ID: 'volcengine_app_id', |
| 71 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', | 71 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', |
| 72 | WORKERS_AI: 'api_key_workers_ai', | ||
| 72 | }; | 73 | }; |
| 73 | 74 | ||
| 74 | /** | 75 | /** |