Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down Expand Up @@ -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 |

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public void saveConfiguration(Map<String, Object> session, ConfigurationManager
Map<String, Object> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ public interface AgentOnboardingProvider {

String defaultModel();

default String chatModelId() {
return getId().replace(".", "-");
}

default Map<String, Object> additionalProperties() {
return Map.of();
}

default Optional<SystemWideToken> systemWideToken() {
return Optional.empty();
}
Expand Down
12 changes: 12 additions & 0 deletions providers/atlas/build.gradle
Original file line number Diff line number Diff line change
@@ -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'))
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> additionalProperties() {
return Map.of("spring.ai.openai.base-url", "https://api.atlascloud.ai");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ai.javaclaw.providers.atlas.AtlasAgentAutoConfiguration
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import ai.javaclaw.onboarding.AgentOnboardingProvider;

import java.util.Map;

public class OpenAIAgentOnboardingProvider implements AgentOnboardingProvider {

@Override
Expand All @@ -28,4 +30,9 @@ public boolean requiresApiKey() {
public String defaultModel() {
return "gpt-5.4";
}

@Override
public Map<String, Object> additionalProperties() {
return Map.of("spring.ai.openai.base-url", "https://api.openai.com");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
}

Expand All @@ -28,4 +30,4 @@ void autoConfigurationIsRegisteredViaImportsFile() throws IOException {
assertThat(importedAutoConfigurations(OpenAIAgentAutoConfigurationTest.class))
.contains(OpenAIAgentAutoConfiguration.class.getName());
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ include 'providers:anthropic'
include 'providers:google'
include 'providers:ollama'
include 'providers:openai'
include 'providers:atlas'
include 'app'