From f1028da15b0ba83c1a4da597863f038c02292157 Mon Sep 17 00:00:00 2001 From: yanalialiuk Date: Mon, 18 May 2026 14:56:57 +0300 Subject: [PATCH 1/4] Add Atomic Chat as a local OpenAI-compatible model provider --- core/llm/autodetect.ts | 1 + core/llm/llms/AtomicChat.ts | 12 ++++ core/llm/llms/OpenAI-compatible.vitest.ts | 6 ++ core/llm/llms/index.ts | 2 + .../model-providers/more/atomic-chat.mdx | 71 +++++++++++++++++++ .../pages/AddNewModel/configs/providers.ts | 32 +++++++++ packages/openai-adapters/src/index.ts | 2 + packages/openai-adapters/src/types.ts | 1 + 8 files changed, 127 insertions(+) create mode 100644 core/llm/llms/AtomicChat.ts create mode 100644 docs/customize/model-providers/more/atomic-chat.mdx diff --git a/core/llm/autodetect.ts b/core/llm/autodetect.ts index 736caebcc6e..82df165d4b6 100644 --- a/core/llm/autodetect.ts +++ b/core/llm/autodetect.ts @@ -44,6 +44,7 @@ import { } from "./templates/edit.js"; const PROVIDER_HANDLES_TEMPLATING: string[] = [ + "atomic-chat", "lmstudio", "lemonade", "openai", diff --git a/core/llm/llms/AtomicChat.ts b/core/llm/llms/AtomicChat.ts new file mode 100644 index 00000000000..363e13fd710 --- /dev/null +++ b/core/llm/llms/AtomicChat.ts @@ -0,0 +1,12 @@ +import { LLMOptions } from "../../index.js"; + +import OpenAI from "./OpenAI.js"; + +class AtomicChat extends OpenAI { + static providerName = "atomic-chat"; + static defaultOptions: Partial = { + apiBase: "http://127.0.0.1:1337/v1/", + }; +} + +export default AtomicChat; diff --git a/core/llm/llms/OpenAI-compatible.vitest.ts b/core/llm/llms/OpenAI-compatible.vitest.ts index 402fb7e7585..6b7ee2e4668 100644 --- a/core/llm/llms/OpenAI-compatible.vitest.ts +++ b/core/llm/llms/OpenAI-compatible.vitest.ts @@ -10,6 +10,7 @@ import OpenRouter from "./OpenRouter.js"; import xAI from "./xAI.js"; import Mistral from "./Mistral.js"; import Mimo from "./Mimo.js"; +import AtomicChat from "./AtomicChat.js"; import LMStudio from "./LMStudio.js"; import Cerebras from "./Cerebras.js"; import DeepInfra from "./DeepInfra.js"; @@ -306,6 +307,11 @@ createOpenAISubclassTests(Mimo, { defaultApiBase: "https://api.xiaomimimo.com/v1/", }); +createOpenAISubclassTests(AtomicChat, { + providerName: "atomic-chat", + defaultApiBase: "http://127.0.0.1:1337/v1/", +}); + createOpenAISubclassTests(LMStudio, { providerName: "lmstudio", defaultApiBase: "http://localhost:1234/v1/", diff --git a/core/llm/llms/index.ts b/core/llm/llms/index.ts index 453b2d90cd8..373c535f457 100644 --- a/core/llm/llms/index.ts +++ b/core/llm/llms/index.ts @@ -10,6 +10,7 @@ import { import { renderTemplatedString } from "../../util/handlebars/renderTemplatedString"; import { BaseLLM } from "../index"; import Anthropic from "./Anthropic"; +import AtomicChat from "./AtomicChat"; import Asksage from "./Asksage"; import Azure from "./Azure"; import Bedrock from "./Bedrock"; @@ -73,6 +74,7 @@ import xAI from "./xAI"; import zAI from "./zAI"; export const LLMClasses = [ Anthropic, + AtomicChat, Cohere, CometAPI, FunctionNetwork, diff --git a/docs/customize/model-providers/more/atomic-chat.mdx b/docs/customize/model-providers/more/atomic-chat.mdx new file mode 100644 index 00000000000..58032af3b71 --- /dev/null +++ b/docs/customize/model-providers/more/atomic-chat.mdx @@ -0,0 +1,71 @@ +--- +title: "Atomic Chat" +description: "Configure Atomic Chat with Continue for local LLM inference through an OpenAI-compatible API" +--- + + + Get started with [Atomic Chat](https://atomic.chat/) — a desktop app that serves local models at `http://127.0.0.1:1337/v1` + + +## Overview + +Atomic Chat runs open-source models on your machine and exposes an OpenAI-compatible HTTP API. Continue connects to that endpoint for chat, edit, and agent workflows. + +## Configuration + +### Option 1: Using the Continue UI (Recommended) + +1. Open the model selector in Continue +2. Choose **Add Model** +3. Select **Atomic Chat** from the provider list +4. Pick a model from the autodetected list (requires Atomic Chat to be running) + +### Option 2: Manual configuration + + + + ```yaml title="config.yaml" + name: My Config + version: 0.0.1 + schema: v1 + + models: + - name: Atomic Chat + provider: atomic-chat + model: + apiBase: http://127.0.0.1:1337/v1/ + ``` + + + ```json title="config.json" + { + "models": [ + { + "title": "Atomic Chat", + "provider": "atomic-chat", + "model": "", + "apiBase": "http://127.0.0.1:1337/v1/" + } + ] + } + ``` + + + +Replace `` with an id from Atomic Chat. List available models: + +```bash +curl -s http://127.0.0.1:1337/v1/models | jq '.data[].id' +``` + +## Getting started + +1. Install and open [Atomic Chat](https://atomic.chat/) +2. Load a model in the app so the API is listening on port **1337** (or **1338** if you changed the port) +3. Add **Atomic Chat** as a provider in Continue and select your model + +## Tool calling + +For agent mode and tools, prefer models with strong tool-calling support (for example Qwen-Coder or DeepSeek-Coder variants). + +[View the source](https://github.com/continuedev/continue/blob/main/core/llm/llms/AtomicChat.ts) diff --git a/gui/src/pages/AddNewModel/configs/providers.ts b/gui/src/pages/AddNewModel/configs/providers.ts index 82f1e15c7a0..821393ffa52 100644 --- a/gui/src/pages/AddNewModel/configs/providers.ts +++ b/gui/src/pages/AddNewModel/configs/providers.ts @@ -796,6 +796,38 @@ Select the \`GPT-4o\` model below to complete your provider configuration, but n ], apiKeyUrl: "https://console.x.ai/", }, + atomicChat: { + title: "Atomic Chat", + provider: "atomic-chat", + description: "Local LLMs via the Atomic Chat desktop app", + longDescription: + "Atomic Chat runs models on your machine and serves them through an OpenAI-compatible API (default `http://127.0.0.1:1337/v1`).\n\n1. Download from [atomic.chat](https://atomic.chat/) and open the app\n2. Load a model so the local API is running\n3. Add Atomic Chat in Continue and choose a model from the list\n\nModel ids must match those returned by `GET /v1/models`.", + icon: "ollama.png", + tags: [ModelProviderTags.Local, ModelProviderTags.OpenSource], + params: { + apiBase: "http://127.0.0.1:1337/v1/", + }, + packages: [ + { + ...models.AUTODETECT, + params: { + ...models.AUTODETECT.params, + title: "Atomic Chat", + }, + }, + ...openSourceModels, + ], + collectInputFor: [ + ...completionParamsInputsConfigs, + { + ...apiBaseInput, + defaultValue: "http://127.0.0.1:1337/v1/", + required: true, + }, + ], + downloadUrl: "https://atomic.chat/", + refPage: "atomic-chat", + }, lemonade: { title: "Lemonade", provider: "lemonade", diff --git a/packages/openai-adapters/src/index.ts b/packages/openai-adapters/src/index.ts index c9eb4da00fa..432a5adef4a 100644 --- a/packages/openai-adapters/src/index.ts +++ b/packages/openai-adapters/src/index.ts @@ -185,6 +185,8 @@ export function constructLlmApi(config: LLMConfig): BaseLlmApi | undefined { case "llama.cpp": case "llamafile": return openAICompatible("http://localhost:8000/", config); + case "atomic-chat": + return openAICompatible("http://127.0.0.1:1337/v1/", config); case "lmstudio": return openAICompatible("http://localhost:1234/", config); case "ollama": diff --git a/packages/openai-adapters/src/types.ts b/packages/openai-adapters/src/types.ts index 3b324b0ac6b..ed4e65a9565 100644 --- a/packages/openai-adapters/src/types.ts +++ b/packages/openai-adapters/src/types.ts @@ -46,6 +46,7 @@ export const OpenAIConfigSchema = BasePlusConfig.extend({ z.literal("function-network"), z.literal("llama.cpp"), z.literal("llamafile"), + z.literal("atomic-chat"), z.literal("lmstudio"), z.literal("ollama"), z.literal("cerebras"), From 74bb664112962ad5c1a5fe7c1660ca4f2ce63444 Mon Sep 17 00:00:00 2001 From: yanalialiuk Date: Tue, 19 May 2026 13:12:29 +0300 Subject: [PATCH 2/4] Use Atomic Chat icon for atomic-chat provider --- gui/public/logos/atomic-chat.png | Bin 0 -> 31495 bytes gui/src/pages/AddNewModel/configs/providers.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 gui/public/logos/atomic-chat.png diff --git a/gui/public/logos/atomic-chat.png b/gui/public/logos/atomic-chat.png new file mode 100644 index 0000000000000000000000000000000000000000..5938b12376f35395fd7d045044c1f1cef7d6c6ea GIT binary patch literal 31495 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 M`1YM_xwKKhe_I6vd;kCd literal 0 HcmV?d00001 diff --git a/gui/src/pages/AddNewModel/configs/providers.ts b/gui/src/pages/AddNewModel/configs/providers.ts index 821393ffa52..be800a776e9 100644 --- a/gui/src/pages/AddNewModel/configs/providers.ts +++ b/gui/src/pages/AddNewModel/configs/providers.ts @@ -802,7 +802,7 @@ Select the \`GPT-4o\` model below to complete your provider configuration, but n description: "Local LLMs via the Atomic Chat desktop app", longDescription: "Atomic Chat runs models on your machine and serves them through an OpenAI-compatible API (default `http://127.0.0.1:1337/v1`).\n\n1. Download from [atomic.chat](https://atomic.chat/) and open the app\n2. Load a model so the local API is running\n3. Add Atomic Chat in Continue and choose a model from the list\n\nModel ids must match those returned by `GET /v1/models`.", - icon: "ollama.png", + icon: "atomic-chat.png", tags: [ModelProviderTags.Local, ModelProviderTags.OpenSource], params: { apiBase: "http://127.0.0.1:1337/v1/", From 22f3d13045fff0f53c3bd0a68c14863e09cde44e Mon Sep 17 00:00:00 2001 From: yanalialiuk Date: Tue, 19 May 2026 13:19:45 +0300 Subject: [PATCH 3/4] Fix Atomic Chat model selection in Add Model UI Fetch models from the local API on load and provider select, remove unrelated presets, and block Connect until a real model id is chosen. Co-authored-by: Cursor --- gui/src/forms/AddModelForm.tsx | 48 ++++++++++++++----- .../configs/fetchProviderModels.ts | 46 +++++++++++++++++- .../pages/AddNewModel/configs/providers.ts | 1 - 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/gui/src/forms/AddModelForm.tsx b/gui/src/forms/AddModelForm.tsx index 7041d2c0814..d7be7642d7c 100644 --- a/gui/src/forms/AddModelForm.tsx +++ b/gui/src/forms/AddModelForm.tsx @@ -30,6 +30,7 @@ const MODEL_PROVIDERS_URL = "https://docs.continue.dev/customize/model-providers"; const CODESTRAL_URL = "https://console.mistral.ai/codestral"; const CONTINUE_SETUP_URL = "https://docs.continue.dev/setup/overview"; +const LOCAL_DYNAMIC_MODEL_PROVIDERS = ["atomic-chat"]; export function AddModelForm({ onDone, @@ -61,7 +62,10 @@ export function AddModelForm({ const handleFetchModels = useCallback(async () => { const apiKey = formMethods.watch("apiKey"); const apiBase = formMethods.watch("apiBase"); - if (!apiKey) return; + const isLocalDynamic = LOCAL_DYNAMIC_MODEL_PROVIDERS.includes( + selectedProvider.provider, + ); + if (!apiKey && !isLocalDynamic) return; const providerAtFetchTime = selectedProvider.provider; setIsFetchingModels(true); @@ -69,12 +73,17 @@ export function AddModelForm({ const models = await fetchProviderModels( ideMessenger, providerAtFetchTime, - apiKey, - apiBase, - ); - setFetchedModelsList((prev) => - selectedProvider.provider === providerAtFetchTime ? models : prev, + apiKey || undefined, + apiBase || selectedProvider.params?.apiBase, ); + if (selectedProvider.provider === providerAtFetchTime) { + setFetchedModelsList(models); + if (isLocalDynamic && models.length > 0) { + setSelectedModel((current) => + current.params.model === "AUTODETECT" ? models[0] : current, + ); + } + } } catch (error) { console.error("Failed to fetch models:", error); } finally { @@ -82,6 +91,12 @@ export function AddModelForm({ } }, [ideMessenger, selectedProvider, formMethods]); + useEffect(() => { + if (LOCAL_DYNAMIC_MODEL_PROVIDERS.includes(selectedProvider.provider)) { + void handleFetchModels(); + } + }, [selectedProvider.provider, handleFetchModels]); + const popularProviderTitles = [ providers["openai"]?.title || "", providers["anthropic"]?.title || "", @@ -112,6 +127,10 @@ export function AddModelForm({ : selectedProvider.apiKeyUrl; function isDisabled() { + if (selectedProvider.provider === "atomic-chat") { + return selectedModel.params.model === "AUTODETECT"; + } + if (selectedProvider.downloadUrl) { return false; } @@ -127,7 +146,11 @@ export function AddModelForm({ } useEffect(() => { - setSelectedModel(selectedProvider.packages[0]); + const defaultModel = + selectedProvider.packages.find( + (pkg) => pkg.params.model !== "AUTODETECT", + ) ?? selectedProvider.packages[0]; + setSelectedModel(defaultModel); if (!selectedProvider.tags?.includes(ModelProviderTags.RequiresApiKey)) { formMethods.setValue("apiKey", ""); } @@ -245,10 +268,13 @@ export function AddModelForm({ type="button" title="Use entered API key to fetch available models" className={`cursor-pointer border-none bg-transparent p-0 ${ - apiKeyValue && - apiKeyValue.length > 0 && - selectedProvider.provider !== "ollama" && - selectedProvider.provider !== "openrouter" + (apiKeyValue && + apiKeyValue.length > 0 && + selectedProvider.provider !== "ollama" && + selectedProvider.provider !== "openrouter") || + LOCAL_DYNAMIC_MODEL_PROVIDERS.includes( + selectedProvider.provider, + ) ? `text-description-muted hover:text-foreground` : "invisible" }`} diff --git a/gui/src/pages/AddNewModel/configs/fetchProviderModels.ts b/gui/src/pages/AddNewModel/configs/fetchProviderModels.ts index 6e0dce45a16..980ce1f1cf2 100644 --- a/gui/src/pages/AddNewModel/configs/fetchProviderModels.ts +++ b/gui/src/pages/AddNewModel/configs/fetchProviderModels.ts @@ -61,6 +61,23 @@ function toOpenRouterPackage(model: FetchedModel): ModelPackage { }; } +function toAtomicChatPackage(model: FetchedModel): ModelPackage { + const id = model.modelId ?? model.name; + return { + title: model.name, + description: model.description ?? model.name, + params: { + title: model.name, + model: id, + ...modelParams(model), + }, + isOpenSource: true, + tags: [ModelProviderTags.Local, ModelProviderTags.OpenSource], + providerOptions: ["atomic-chat"], + icon: model.icon ?? "atomic-chat.png", + }; +} + function toGenericPackage(model: FetchedModel, provider: string): ModelPackage { const id = model.modelId ?? model.name; return { @@ -96,10 +113,13 @@ async function fetchModels( export async function fetchProviderModels( ideMessenger: IIdeMessenger, provider: string, - apiKey: string, + apiKey?: string, apiBase?: string, ): Promise { const models = await fetchModels(ideMessenger, provider, apiKey, apiBase); + if (provider === "atomic-chat") { + return models.map(toAtomicChatPackage); + } return models.map((m) => toGenericPackage(m, provider)); } @@ -141,4 +161,28 @@ export async function initializeDynamicModels(ideMessenger: IIdeMessenger) { } catch (error) { console.error("Failed to initialize OpenRouter models:", error); } + + if (providers.atomicChat) { + const autodetect = { + ...models.AUTODETECT, + params: { ...models.AUTODETECT.params, title: "Atomic Chat" }, + }; + + try { + const fetched = await fetchModels( + ideMessenger, + "atomic-chat", + undefined, + providers.atomicChat.params.apiBase, + ); + if (fetched.length > 0) { + providers.atomicChat.packages = fetched.map(toAtomicChatPackage); + } else { + providers.atomicChat.packages = [autodetect]; + } + } catch (error) { + console.error("Failed to initialize Atomic Chat models:", error); + providers.atomicChat.packages = [autodetect]; + } + } } diff --git a/gui/src/pages/AddNewModel/configs/providers.ts b/gui/src/pages/AddNewModel/configs/providers.ts index be800a776e9..d00815f41bd 100644 --- a/gui/src/pages/AddNewModel/configs/providers.ts +++ b/gui/src/pages/AddNewModel/configs/providers.ts @@ -815,7 +815,6 @@ Select the \`GPT-4o\` model below to complete your provider configuration, but n title: "Atomic Chat", }, }, - ...openSourceModels, ], collectInputFor: [ ...completionParamsInputsConfigs, From 6196458c26e739c22686ee14a1b9848f29842fae Mon Sep 17 00:00:00 2001 From: yanalialiuk Date: Tue, 19 May 2026 13:29:32 +0300 Subject: [PATCH 4/4] Fix stale closure race in Add Model model fetch Invalidate in-flight fetches when the provider changes and compare against a ref so late responses cannot overwrite another provider's model list. Co-authored-by: Cursor --- gui/src/forms/AddModelForm.tsx | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/gui/src/forms/AddModelForm.tsx b/gui/src/forms/AddModelForm.tsx index d7be7642d7c..465b3eb714b 100644 --- a/gui/src/forms/AddModelForm.tsx +++ b/gui/src/forms/AddModelForm.tsx @@ -2,7 +2,7 @@ import { ArrowPathIcon, ArrowTopRightOnSquareIcon, } from "@heroicons/react/24/outline"; -import { useCallback, useContext, useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { Button, Input, StyledActionButton } from "../components"; import Alert from "../components/gui/Alert"; @@ -50,6 +50,10 @@ export function AddModelForm({ [], ); const [isFetchingModels, setIsFetchingModels] = useState(false); + const selectedProviderRef = useRef(selectedProvider.provider); + const fetchGenerationRef = useRef(0); + + selectedProviderRef.current = selectedProvider.provider; useEffect(() => { void initializeDynamicModels(ideMessenger); @@ -57,17 +61,18 @@ export function AddModelForm({ useEffect(() => { setFetchedModelsList([]); + fetchGenerationRef.current += 1; }, [selectedProvider]); const handleFetchModels = useCallback(async () => { const apiKey = formMethods.watch("apiKey"); const apiBase = formMethods.watch("apiBase"); - const isLocalDynamic = LOCAL_DYNAMIC_MODEL_PROVIDERS.includes( - selectedProvider.provider, - ); + const providerAtFetchTime = selectedProviderRef.current; + const isLocalDynamic = + LOCAL_DYNAMIC_MODEL_PROVIDERS.includes(providerAtFetchTime); if (!apiKey && !isLocalDynamic) return; - const providerAtFetchTime = selectedProvider.provider; + const fetchGeneration = fetchGenerationRef.current; setIsFetchingModels(true); try { const models = await fetchProviderModels( @@ -76,13 +81,17 @@ export function AddModelForm({ apiKey || undefined, apiBase || selectedProvider.params?.apiBase, ); - if (selectedProvider.provider === providerAtFetchTime) { - setFetchedModelsList(models); - if (isLocalDynamic && models.length > 0) { - setSelectedModel((current) => - current.params.model === "AUTODETECT" ? models[0] : current, - ); - } + if ( + fetchGenerationRef.current !== fetchGeneration || + selectedProviderRef.current !== providerAtFetchTime + ) { + return; + } + setFetchedModelsList(models); + if (isLocalDynamic && models.length > 0) { + setSelectedModel((current) => + current.params.model === "AUTODETECT" ? models[0] : current, + ); } } catch (error) { console.error("Failed to fetch models:", error);