diff --git a/docs/configuration.md b/docs/configuration.md index 9d4c0c491..338991a33 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -80,6 +80,7 @@ IMAP_PASSWORD=your-password-here | `longcat` | LLM (LongCat) | [longcat.chat](https://longcat.chat/platform/docs/zh/) | | `ollama` | LLM (local, Ollama) | — | | `lm_studio` | LLM (local, LM Studio) | — | +| `atomic_chat` | LLM (local, [Atomic Chat](https://atomic.chat/)) | — | | `mistral` | LLM | [docs.mistral.ai](https://docs.mistral.ai/) | | `stepfun` | LLM (Step Fun/阶跃星辰) | [platform.stepfun.com](https://platform.stepfun.com) | | `ovms` | LLM (local, OpenVINO Model Server) | [docs.openvino.ai](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) | @@ -502,6 +503,36 @@ ollama run llama3.2 +
+Atomic Chat (local) + +[Atomic Chat](https://atomic.chat/) is a local-first desktop app that exposes an **OpenAI-compatible** HTTP API (default `http://localhost:1337/v1`). Start Atomic Chat and enable the local API server, then point nanobot at it. + +**1. Add to config** (partial — merge into `~/.nanobot/config.json`): + +```json +{ + "providers": { + "atomic_chat": { + "apiKey": null, + "apiBase": "http://localhost:1337/v1" + } + }, + "agents": { + "defaults": { + "provider": "atomic_chat", + "model": "your-model-id-from-atomic-chat" + } + } +} +``` + +> **Note:** Set `apiKey` to `null` if your Atomic Chat server does not require a key. If it does, set `apiKey` (or the `ATOMIC_CHAT_API_KEY` environment variable) to the value Atomic Chat expects. The `model` string must match the model id Atomic Chat exposes on its OpenAI-compatible endpoint. + +> `provider: "auto"` also works when `providers.atomic_chat.apiBase` is configured, but setting `"provider": "atomic_chat"` is the clearest option. + +
+
OpenVINO Model Server (local / OpenAI-compatible) diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index c8556ec9f..96f9014a9 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -197,6 +197,7 @@ class ProvidersConfig(Base): vllm: ProviderConfig = Field(default_factory=ProviderConfig) ollama: ProviderConfig = Field(default_factory=ProviderConfig) # Ollama local models lm_studio: ProviderConfig = Field(default_factory=ProviderConfig) # LM Studio local models + atomic_chat: ProviderConfig = Field(default_factory=ProviderConfig) # Atomic Chat local models ovms: ProviderConfig = Field(default_factory=ProviderConfig) # OpenVINO Model Server (OVMS) gemini: ProviderConfig = Field(default_factory=ProviderConfig) moonshot: ProviderConfig = Field(default_factory=ProviderConfig) diff --git a/nanobot/providers/registry.py b/nanobot/providers/registry.py index 3eda6c5a4..4dba0c46d 100644 --- a/nanobot/providers/registry.py +++ b/nanobot/providers/registry.py @@ -422,6 +422,17 @@ PROVIDERS: tuple[ProviderSpec, ...] = ( detect_by_base_keyword="1234", default_api_base="http://localhost:1234/v1", ), + # Atomic Chat (local, OpenAI-compatible) — https://atomic.chat/ + ProviderSpec( + name="atomic_chat", + keywords=("atomic-chat", "atomic_chat", "atomicchat"), + env_key="ATOMIC_CHAT_API_KEY", + display_name="Atomic Chat", + backend="openai_compat", + is_local=True, + detect_by_base_keyword="1337", + default_api_base="http://localhost:1337/v1", + ), # === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) === ProviderSpec( name="ovms", diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py index b0c3c43ee..90c2ce877 100644 --- a/tests/cli/test_commands.py +++ b/tests/cli/test_commands.py @@ -371,6 +371,28 @@ def test_config_accepts_lm_studio_without_api_key_and_uses_default_localhost_api assert config.get_api_base() == "http://localhost:1234/v1" +def test_config_accepts_atomic_chat_without_api_key_and_uses_default_localhost_api_base(): + config = Config.model_validate( + { + "agents": { + "defaults": { + "provider": "atomic_chat", + "model": "local-model", + } + }, + "providers": { + "atomicChat": { + "apiKey": None, + } + }, + } + ) + + assert config.get_provider_name() == "atomic_chat" + assert config.get_api_key() is None + assert config.get_api_base() == "http://localhost:1337/v1" + + def test_find_by_name_accepts_camel_case_and_hyphen_aliases(): assert find_by_name("volcengineCodingPlan") is not None assert find_by_name("volcengineCodingPlan").name == "volcengine_coding_plan" @@ -378,6 +400,8 @@ def test_find_by_name_accepts_camel_case_and_hyphen_aliases(): assert find_by_name("github-copilot").name == "github_copilot" assert find_by_name("longcat") is not None assert find_by_name("longcat").name == "longcat" + assert find_by_name("atomic-chat") is not None + assert find_by_name("atomic-chat").name == "atomic_chat" def test_config_explicit_longcat_provider_resolves_provider_name():