From e21a315cd3af545bebfbd597a33346dfece4ca0a Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Tue, 11 Aug 2026 00:54:44 +0800 Subject: [PATCH] feat: add Atlas Cloud provider Signed-off-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> --- AGENTS.md | 2 + README.md | 6 +-- app/build.gradle | 1 + .../onboarding/steps/S2_ProviderStep.java | 3 +- .../onboarding/AgentOnboardingProvider.java | 8 ++++ providers/atlas/build.gradle | 12 +++++ .../atlas/AtlasAgentAutoConfiguration.java | 16 +++++++ .../atlas/AtlasAgentOnboardingProvider.java | 48 +++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../AtlasAgentAutoConfigurationTest.java | 37 ++++++++++++++ .../openai/OpenAIAgentOnboardingProvider.java | 7 +++ .../OpenAIAgentAutoConfigurationTest.java | 4 +- settings.gradle | 1 + 13 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 providers/atlas/build.gradle create mode 100644 providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfiguration.java create mode 100644 providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentOnboardingProvider.java create mode 100644 providers/atlas/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 providers/atlas/src/test/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfigurationTest.java diff --git a/AGENTS.md b/AGENTS.md index 81dd6d9e..9b455bea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,7 @@ root ├── providers/ │ ├── anthropic/ ← Anthropic (Claude) provider + Claude Code OAuth support │ ├── openai/ ← OpenAI (GPT) provider +│ ├── atlas/ ← Atlas Cloud OpenAI-compatible provider │ ├── ollama/ ← Ollama local provider (no API key required) │ └── google/ ← Google Gen AI (Gemini) provider └── plugins/ @@ -113,6 +114,7 @@ User/Agent → TaskManager.create() |---|---|---|---| | `anthropic` | `providers/anthropic` | `claude-sonnet-4-6` | Required (or Claude Code OAuth) | | `openai` | `providers/openai` | `gpt-5.4` | Required | +| `atlas` | `providers/atlas` | `qwen/qwen3.8-max` | Required | | `ollama` | `providers/ollama` | `qwen3.5:27b` | Not required (local) | | `google.genai` | `providers/google` | `gemini-3-flash-preview` | Required | diff --git a/README.md b/README.md index b2cd645b..8802d57f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ JavaClaw is a Java-based personal AI assistant that runs on your own devices. It - **Multi-Channel Support** — Chat UI (WebSocket), Telegram, Discord, and an extensible plugin-based channel architecture - **Task Management** — Create, schedule (one-off, delayed, or recurring via cron), and track tasks as human-readable Markdown files - **Extensible Skills** — Load skills from `workspace/skills/` and from `skills jar` packages on the classpath -- **LLM Provider Choice** — Plug in OpenAI, Anthropic, or Ollama (local); switchable during onboarding +- **LLM Provider Choice** — Plug in OpenAI, Atlas Cloud, Anthropic, or Ollama (local); switchable during onboarding - **MCP Support** — Model Context Protocol client for connecting external tool servers - **Shell & File Access** — Agent can read/write files and run bash commands on your machine - **Smart Web Tools** — Brave web search and intelligent web scraping @@ -51,7 +51,7 @@ JavaClaw/ - Java 25 - Gradle (or use the included `./gradlew` wrapper) -- An LLM API key (OpenAI / Anthropic) **or** a running [Ollama](https://ollama.com) instance +- An LLM API key (OpenAI / Atlas Cloud / Anthropic) **or** a running [Ollama](https://ollama.com) instance ### Run @@ -70,7 +70,7 @@ Then open [http://localhost:8080/onboarding](http://localhost:8080/onboarding) t ### Onboarding 1. **Welcome** — Introduction screen -2. **Provider** — Choose Ollama, OpenAI, or Anthropic +2. **Provider** — Choose Ollama, OpenAI, Atlas Cloud, or Anthropic 3. **Credentials** — Enter your API key and model name 4. **Agent Prompt** — Customize `workspace/AGENT.md` with your personal info (name, email, role, etc.) 5. **MCP Servers** — Optionally configure Model Context Protocol servers diff --git a/app/build.gradle b/app/build.gradle index b05b3d6e..2eacc38d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,7 @@ dependencies { implementation project(':providers:google') implementation project(':providers:ollama') implementation project(':providers:openai') + implementation project(':providers:atlas') implementation project(':plugins:discord') implementation project(':plugins:telegram') diff --git a/app/src/main/java/ai/javaclaw/onboarding/steps/S2_ProviderStep.java b/app/src/main/java/ai/javaclaw/onboarding/steps/S2_ProviderStep.java index 7f978891..576d52f6 100644 --- a/app/src/main/java/ai/javaclaw/onboarding/steps/S2_ProviderStep.java +++ b/app/src/main/java/ai/javaclaw/onboarding/steps/S2_ProviderStep.java @@ -70,7 +70,8 @@ public void saveConfiguration(Map session, ConfigurationManager Map props = new LinkedHashMap<>(); agentOnboardingProvider.saveProperty(props, "chat.options.model", model); agentOnboardingProvider.saveProperty(props, "api-key", apiKey); - props.put("spring.ai.model.chat", agentOnboardingProvider.getId().replace(".", "-")); + props.putAll(agentOnboardingProvider.additionalProperties()); + props.put("spring.ai.model.chat", agentOnboardingProvider.chatModelId()); configurationManager.updateProperties(props); } } diff --git a/base/src/main/java/ai/javaclaw/onboarding/AgentOnboardingProvider.java b/base/src/main/java/ai/javaclaw/onboarding/AgentOnboardingProvider.java index e4919d67..85dff8bc 100644 --- a/base/src/main/java/ai/javaclaw/onboarding/AgentOnboardingProvider.java +++ b/base/src/main/java/ai/javaclaw/onboarding/AgentOnboardingProvider.java @@ -15,6 +15,14 @@ public interface AgentOnboardingProvider { String defaultModel(); + default String chatModelId() { + return getId().replace(".", "-"); + } + + default Map additionalProperties() { + return Map.of(); + } + default Optional systemWideToken() { return Optional.empty(); } diff --git a/providers/atlas/build.gradle b/providers/atlas/build.gradle new file mode 100644 index 00000000..7e11f2ed --- /dev/null +++ b/providers/atlas/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java-library' +} + +dependencies { + implementation project(':base') + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.ai:spring-ai-starter-model-openai' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation testFixtures(project(':base')) +} diff --git a/providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfiguration.java b/providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfiguration.java new file mode 100644 index 00000000..10d43c47 --- /dev/null +++ b/providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfiguration.java @@ -0,0 +1,16 @@ +package ai.javaclaw.providers.atlas; + +import ai.javaclaw.onboarding.AgentOnboardingProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +public class AtlasAgentAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(AtlasAgentOnboardingProvider.class) + public AgentOnboardingProvider atlasAgentOnboardingProvider() { + return new AtlasAgentOnboardingProvider(); + } +} diff --git a/providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentOnboardingProvider.java b/providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentOnboardingProvider.java new file mode 100644 index 00000000..58957b42 --- /dev/null +++ b/providers/atlas/src/main/java/ai/javaclaw/providers/atlas/AtlasAgentOnboardingProvider.java @@ -0,0 +1,48 @@ +package ai.javaclaw.providers.atlas; + +import ai.javaclaw.onboarding.AgentOnboardingProvider; + +import java.util.Map; + +public class AtlasAgentOnboardingProvider implements AgentOnboardingProvider { + + @Override + public String getId() { + return "atlas"; + } + + @Override + public String getLabel() { + return "Atlas Cloud"; + } + + @Override + public String slogan() { + return "Use Atlas Cloud's OpenAI-compatible API as an agent."; + } + + @Override + public boolean requiresApiKey() { + return true; + } + + @Override + public String defaultModel() { + return "qwen/qwen3.8-max"; + } + + @Override + public String chatModelId() { + return "openai"; + } + + @Override + public String createPropertyKey(String propertySuffix) { + return "spring.ai.openai." + propertySuffix; + } + + @Override + public Map additionalProperties() { + return Map.of("spring.ai.openai.base-url", "https://api.atlascloud.ai"); + } +} diff --git a/providers/atlas/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/providers/atlas/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..2744f71e --- /dev/null +++ b/providers/atlas/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +ai.javaclaw.providers.atlas.AtlasAgentAutoConfiguration diff --git a/providers/atlas/src/test/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfigurationTest.java b/providers/atlas/src/test/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfigurationTest.java new file mode 100644 index 00000000..aad15fb7 --- /dev/null +++ b/providers/atlas/src/test/java/ai/javaclaw/providers/atlas/AtlasAgentAutoConfigurationTest.java @@ -0,0 +1,37 @@ +package ai.javaclaw.providers.atlas; + +import ai.javaclaw.onboarding.AgentOnboardingProvider; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import java.io.IOException; + +import static ai.javaclaw.testsupport.AutoConfigurationImportsTestSupport.importedAutoConfigurations; +import static org.assertj.core.api.Assertions.assertThat; + +class AtlasAgentAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(AtlasAgentAutoConfiguration.class)); + + @Test + void registersAtlasOnboardingProvider() { + contextRunner.run(context -> { + assertThat(context).hasSingleBean(AgentOnboardingProvider.class); + AgentOnboardingProvider provider = context.getBean(AgentOnboardingProvider.class); + assertThat(provider.getId()).isEqualTo("atlas"); + assertThat(provider.chatModelId()).isEqualTo("openai"); + assertThat(provider.defaultModel()).isEqualTo("qwen/qwen3.8-max"); + assertThat(provider.createPropertyKey("api-key")).isEqualTo("spring.ai.openai.api-key"); + assertThat(provider.additionalProperties()) + .containsEntry("spring.ai.openai.base-url", "https://api.atlascloud.ai"); + }); + } + + @Test + void autoConfigurationIsRegisteredViaImportsFile() throws IOException { + assertThat(importedAutoConfigurations(AtlasAgentAutoConfigurationTest.class)) + .contains(AtlasAgentAutoConfiguration.class.getName()); + } +} diff --git a/providers/openai/src/main/java/ai/javaclaw/providers/openai/OpenAIAgentOnboardingProvider.java b/providers/openai/src/main/java/ai/javaclaw/providers/openai/OpenAIAgentOnboardingProvider.java index 412821a0..1c387ca6 100644 --- a/providers/openai/src/main/java/ai/javaclaw/providers/openai/OpenAIAgentOnboardingProvider.java +++ b/providers/openai/src/main/java/ai/javaclaw/providers/openai/OpenAIAgentOnboardingProvider.java @@ -2,6 +2,8 @@ import ai.javaclaw.onboarding.AgentOnboardingProvider; +import java.util.Map; + public class OpenAIAgentOnboardingProvider implements AgentOnboardingProvider { @Override @@ -28,4 +30,9 @@ public boolean requiresApiKey() { public String defaultModel() { return "gpt-5.4"; } + + @Override + public Map additionalProperties() { + return Map.of("spring.ai.openai.base-url", "https://api.openai.com"); + } } diff --git a/providers/openai/src/test/java/ai/javaclaw/providers/openai/OpenAIAgentAutoConfigurationTest.java b/providers/openai/src/test/java/ai/javaclaw/providers/openai/OpenAIAgentAutoConfigurationTest.java index 42588ae6..cb10239d 100644 --- a/providers/openai/src/test/java/ai/javaclaw/providers/openai/OpenAIAgentAutoConfigurationTest.java +++ b/providers/openai/src/test/java/ai/javaclaw/providers/openai/OpenAIAgentAutoConfigurationTest.java @@ -20,6 +20,8 @@ void registersOnboardingProvider() { contextRunner.run(context -> { assertThat(context).hasSingleBean(AgentOnboardingProvider.class); assertThat(context.getBean(AgentOnboardingProvider.class).getId()).isEqualTo("openai"); + assertThat(context.getBean(AgentOnboardingProvider.class).additionalProperties()) + .containsEntry("spring.ai.openai.base-url", "https://api.openai.com"); }); } @@ -28,4 +30,4 @@ void autoConfigurationIsRegisteredViaImportsFile() throws IOException { assertThat(importedAutoConfigurations(OpenAIAgentAutoConfigurationTest.class)) .contains(OpenAIAgentAutoConfiguration.class.getName()); } -} \ No newline at end of file +} diff --git a/settings.gradle b/settings.gradle index 8a54b0c1..fb4ce6c3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,4 +11,5 @@ include 'providers:anthropic' include 'providers:google' include 'providers:ollama' include 'providers:openai' +include 'providers:atlas' include 'app'