From 846378f240daa5e9ae4977912efcb3c76496657c Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:03:32 +0800 Subject: [PATCH 1/2] feat: add Atlas Cloud provider --- CITATION.cff | 2 +- README.md | 6 +- docs/architecture/ARCHITECTURE.md | 2 +- docs/features/LLM_PROVIDERS.md | 48 ++++-- package.json | 2 +- scripts/build-knowledge-corpus.mjs | 4 +- src/api/model.ts | 2 + .../__tests__/provider-defaults.test.ts | 20 ++- .../responseFormatForProvider.test.ts | 9 ++ src/api/runtime/provider-defaults.ts | 5 + src/api/runtime/responseFormatForProvider.ts | 6 +- .../llm/providers/AIModelProviderManager.ts | 7 +- .../__tests__/OpenAICompatProviders.test.ts | 72 ++++++++- .../implementations/AtlasCloudProvider.ts | 150 ++++++++++++++++++ .../__tests__/platform-knowledge.test.ts | 2 +- .../planning/ProviderAssignmentEngine.ts | 5 +- tests/api/model.spec.ts | 10 ++ tests/e2e/platform-knowledge.e2e.spec.ts | 2 +- 18 files changed, 323 insertions(+), 31 deletions(-) create mode 100644 src/core/llm/providers/implementations/AtlasCloudProvider.ts diff --git a/CITATION.cff b/CITATION.cff index ec2e9bbc261..e7b23ca950b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,7 +1,7 @@ cff-version: 1.2.0 message: "If you use AgentOS in your work, please cite it using this metadata." title: AgentOS -abstract: "An Apache-2.0 TypeScript AI agent runtime: persistent cognitive memory, optional HEXACO personality, multi-agent orchestration, runtime tool forging, multimodal RAG, and one dispatch interface across 11 LLM providers." +abstract: "An Apache-2.0 TypeScript AI agent runtime: persistent cognitive memory, optional HEXACO personality, multi-agent orchestration, runtime tool forging, multimodal RAG, and one dispatch interface across 12 LLM providers." type: software authors: - given-names: Johnny diff --git a/README.md b/README.md index d6a776fc93d..6fe655334ad 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ # **AgentOS** · TypeScript AI Agent Framework -**Agents that remember, forge their own tools, and survive long-running sessions.** Persistent cognitive memory, optional HEXACO personality, multi-agent orchestration, and one dispatch interface across 11 LLM providers. Apache-2.0. +**Agents that remember, forge their own tools, and survive long-running sessions.** Persistent cognitive memory, optional HEXACO personality, multi-agent orchestration, and one dispatch interface across 12 LLM providers. Apache-2.0. [![npm](https://img.shields.io/npm/v/@framers/agentos?style=flat-square&logo=npm&color=cb3837)](https://www.npmjs.com/package/@framers/agentos) [![CI](https://img.shields.io/github/actions/workflow/status/framerslab/agentos/ci.yml?branch=master&style=flat-square&logo=github&label=CI)](https://github.com/framerslab/agentos/actions/workflows/ci.yml) @@ -33,7 +33,7 @@ AgentOS is an open-source TypeScript framework for AI agents that **remember, ad - **Top open-source memory benchmarks:** [85.6% on LongMemEval-S](https://github.com/framerslab/agentos-bench/blob/master/results/LEADERBOARD.md) at $0.0090/correct (gpt-4o), and 70.2% on LongMemEval-M, the only open-source library above 65% on M with reproducible methodology. - **Runtime tool forging.** An agent writes a TypeScript function with a Zod schema, an LLM judge approves it, and it runs in a hardened `node:vm` sandbox before joining the catalog for the rest of the session. - **Persistent [cognitive memory](https://docs.agentos.sh/features/cognitive-memory)** with 8 neuroscience-backed mechanisms: Ebbinghaus decay, retrieval-induced forgetting, reconsolidation, source-confidence decay. -- **Optional [HEXACO personality](https://docs.agentos.sh/features/hexaco-personality)**, [6 orchestration strategies](https://docs.agentos.sh/features/agency-collaboration), [guardrails](https://docs.agentos.sh/features/guardrails-architecture), and [voice](https://docs.agentos.sh/features/voice-pipeline) across **11 LLM providers**; 100+ extensions and 88 skills auto-load at startup. +- **Optional [HEXACO personality](https://docs.agentos.sh/features/hexaco-personality)**, [6 orchestration strategies](https://docs.agentos.sh/features/agency-collaboration), [guardrails](https://docs.agentos.sh/features/guardrails-architecture), and [voice](https://docs.agentos.sh/features/voice-pipeline) across **12 LLM providers**; 100+ extensions and 88 skills auto-load at startup. --- @@ -164,7 +164,7 @@ Strategies: `sequential`, `parallel`, `debate`, `review-loop`, `hierarchical`, ` | Package | Role | |---|---| -| [`@framers/agentos`](https://www.npmjs.com/package/@framers/agentos) | Core runtime: agents, cognitive memory, orchestration, guardrails, voice, 11 LLM providers. Apache-2.0. | +| [`@framers/agentos`](https://www.npmjs.com/package/@framers/agentos) | Core runtime: agents, cognitive memory, orchestration, guardrails, voice, 12 LLM providers. Apache-2.0. | | [`@framers/agentos-extensions`](https://www.npmjs.com/package/@framers/agentos-extensions) | 100+ first-party extensions: channel adapters, tool packs, integrations, guardrail packs. | | [`@framers/agentos-extensions-registry`](https://www.npmjs.com/package/@framers/agentos-extensions-registry) | Discovery + auto-loader for the extensions catalog. | | [`@framers/agentos-skills`](https://www.npmjs.com/package/@framers/agentos-skills) | 88 curated `SKILL.md` skills. | diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index a354f672fe8..640ac9c6402 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -14,7 +14,7 @@ This page is the system map. For the *what* of each subsystem — components, li For specific subsystem deep-dives, see: - [Provenance & Immutability](../features/provenance-immutability.md) -![AgentOS layered architecture: seven cooperating layers from caller-facing API (generateText, streamText, agent, agency, mission) through cognitive substrate (GMI coordinator, PersonaOverlayManager, SentimentTracker, MetapromptExecutor), memory and RAG (4-tier memory, 8 cognitive mechanisms, HyDE, GraphRAG, 7 vector backends), tools and capabilities (100+ extension packs, 88 SKILL.md modules, runtime tool forging), guardrails and HITL (PII redaction, ML classifiers, NLI grounding, 5 approval triggers), orchestration (workflow, mission, AgentGraph, checkpointing), down to I/O and providers (voice pipeline, channels, media generation, 11 LLM providers, OpenRouter fanout).](/img/diagrams/system-architecture.svg) +![AgentOS layered architecture: seven cooperating layers from caller-facing API (generateText, streamText, agent, agency, mission) through cognitive substrate (GMI coordinator, PersonaOverlayManager, SentimentTracker, MetapromptExecutor), memory and RAG (4-tier memory, 8 cognitive mechanisms, HyDE, GraphRAG, 7 vector backends), tools and capabilities (100+ extension packs, 88 SKILL.md modules, runtime tool forging), guardrails and HITL (PII redaction, ML classifiers, NLI grounding, 5 approval triggers), orchestration (workflow, mission, AgentGraph, checkpointing), down to I/O and providers (voice pipeline, channels, media generation, 12 LLM providers, OpenRouter fanout).](/img/diagrams/system-architecture.svg) Each layer above corresponds to a section below. The mapping is one-to-one: layer 1 → [API Surface Contract](#api-surface-contract), layer 2 → [GMI](#gmi-generalized-mind-instance), layer 3 → [Memory System](#memory-system), layer 4 → [Tools, Skills, Extensions](#tools-skills--extensions), layer 5 → [Safety & Guardrails](#safety--guardrails), layer 6 → [Orchestration](#orchestration), layer 7 → [Perception & Channels](#perception--channels). The component pills inside each layer in the diagram are the same class and function names you'll see in the subsystem write-ups. diff --git a/docs/features/LLM_PROVIDERS.md b/docs/features/LLM_PROVIDERS.md index 66646db6c8a..0c28dc73def 100644 --- a/docs/features/LLM_PROVIDERS.md +++ b/docs/features/LLM_PROVIDERS.md @@ -1,6 +1,6 @@ # LLM Providers — multi-provider configuration & routing -AgentOS abstracts every LLM behind a single [`IProvider`](https://github.com/framerslab/agentos/blob/master/src/core/llm/providers/IProvider.ts) interface. Eleven providers are wired in directly — nine via API key, two via local CLI bridges that ride an existing Claude Max or Google account subscription. OpenRouter, included in the eleven, fans out to 200+ additional models from the same set of vendors. Every provider speaks the same streaming protocol, supports the same tool-call shape (with the documented exceptions below), and participates in the same cost ledger. The fallback chain is auto-built from whichever keys are set in the environment and is overridable per agent. +AgentOS abstracts every LLM behind a single [`IProvider`](https://github.com/framerslab/agentos/blob/master/src/core/llm/providers/IProvider.ts) interface. Twelve providers are wired in directly — ten via API key, two via local CLI bridges that ride an existing Claude Max or Google account subscription. OpenRouter, included in the twelve, fans out to 200+ additional models from the same set of vendors. Every provider speaks the same streaming protocol, supports the same tool-call shape (with the documented exceptions below), and participates in the same cost ledger. The fallback chain is auto-built from whichever keys are set in the environment and is overridable per agent. --- @@ -22,6 +22,7 @@ AgentOS abstracts every LLM behind a single [`IProvider`](https://github.com/fra - [Mistral AI](#mistral-ai) - [xAI (Grok)](#xai-grok) - [OpenRouter](#openrouter) + - [Atlas Cloud](#atlas-cloud) - [Ollama](#ollama) 9. [Programmatic Configuration](#programmatic-configuration) 10. [Adding a Custom Provider](#adding-a-custom-provider) @@ -36,7 +37,7 @@ AgentOS abstracts LLM access behind a unified [`IProvider`](https://github.com/f **Key features:** -- **11 providers** supported out of the box (9 API-key + 2 CLI-based) +- **12 providers** supported out of the box (10 API-key + 2 CLI-based) - **CLI providers**: Use your Claude Max or Google account subscription via local CLI — no API key needed - **Auto-detection**: Set an API key or install a CLI and the provider is available - **Fallback**: Automatic retry with alternate providers on failure (`fallbackProviders`) @@ -58,6 +59,7 @@ AgentOS abstracts LLM access behind a unified [`IProvider`](https://github.com/f | **Mistral** | `MISTRAL_API_KEY` | `mistral-large-latest` | Yes | Yes | No | Yes | $$ | | **xAI** | `XAI_API_KEY` | `grok-2` | Yes | Yes | Yes | No | $$ | | **OpenRouter** | `OPENROUTER_API_KEY` | `openai/gpt-4o` | Yes | Yes | Yes* | Yes* | Varies | +| **Atlas Cloud** | `ATLASCLOUD_API_KEY` | `deepseek-ai/deepseek-v4-pro` | Yes | Yes | No | No | Varies | | **Ollama** | `OLLAMA_BASE_URL` | `llama3.2` | Yes | Partial | Model-dep. | Yes | Free | | **Claude Code CLI** | _(PATH detection)_ | `claude-sonnet-4-5-20250929` | Yes | Yes | Yes | No | Free* | | **Gemini CLI** | _(PATH detection)_ | `gemini-2.5-flash` | Yes | Partial** | Yes | No | Free* | @@ -111,15 +113,16 @@ order and uses the first one found: 1. `OPENROUTER_API_KEY` → OpenRouter 2. `OPENAI_API_KEY` → OpenAI -3. `ANTHROPIC_API_KEY` → Anthropic -4. `GEMINI_API_KEY` → Google Gemini -5. `GROQ_API_KEY` → Groq -6. `TOGETHER_API_KEY` → Together AI -7. `MISTRAL_API_KEY` → Mistral -8. `XAI_API_KEY` → xAI -9. `which claude` → Claude Code CLI (PATH detection — no API key, uses Max subscription) -10. `which gemini` → Gemini CLI (PATH detection — no API key, uses Google account) -11. `OLLAMA_BASE_URL` → Ollama +3. `ATLASCLOUD_API_KEY` → Atlas Cloud +4. `ANTHROPIC_API_KEY` → Anthropic +5. `GEMINI_API_KEY` → Google Gemini +6. `GROQ_API_KEY` → Groq +7. `TOGETHER_API_KEY` → Together AI +8. `MISTRAL_API_KEY` → Mistral +9. `XAI_API_KEY` → xAI +10. `which claude` → Claude Code CLI (PATH detection — no API key, uses Max subscription) +11. `which gemini` → Gemini CLI (PATH detection — no API key, uses Google account) +12. `OLLAMA_BASE_URL` → Ollama You can override auto-detection in four ways, highest priority first: @@ -161,6 +164,9 @@ OPENAI_API_KEY=sk-... # Fallback provider OPENROUTER_API_KEY=sk-or-... +# OpenAI-compatible provider +ATLASCLOUD_API_KEY=... + # Local provider (no API key needed) OLLAMA_BASE_URL=http://localhost:11434 ``` @@ -240,7 +246,7 @@ AgentOS tracks token usage and cost across all providers: | Tier | Providers | Approximate Cost (1M tokens) | |------|-----------|------------------------------| | **$** (Budget) | Groq, Together, Ollama (free) | $0.00–$0.60 | -| **$$** (Standard) | Gemini, Mistral, xAI, OpenRouter (varies) | $0.50–$3.00 | +| **$$** (Standard) | Gemini, Mistral, xAI, OpenRouter (varies), Atlas Cloud (varies) | $0.50–$3.00 | | **$$$** (Premium) | OpenAI, Anthropic | $3.00–$15.00 | ### Cost-Aware Caps @@ -385,6 +391,23 @@ Popular OpenRouter models: - `google/gemini-2.5-flash` - `meta-llama/llama-3.3-70b-instruct` +### Atlas Cloud + +```bash +export ATLASCLOUD_API_KEY=... +# Optional endpoint override for proxies or private deployments: +export ATLASCLOUD_BASE_URL=https://api.atlascloud.ai/v1 +``` + +Atlas Cloud uses an OpenAI-compatible `/v1/chat/completions` endpoint, so it +shares AgentOS's standard OpenAI-compatible transport, streaming, tool-call, and +structured-output handling. + +| Model | Context | Vision | Tool Calling | Notes | +|-------|---------|--------|-------------|-------| +| `deepseek-ai/deepseek-v4-pro` | 1M | No | Yes | Default long-context reasoning model | +| `qwen/qwen3.5-flash` | 1M | No | Partial | Fast chat model | + ### Ollama ```bash @@ -491,6 +514,7 @@ exercised paths. | Mistral | Yes | No | `auto/none/any` | Good support | | xAI | Yes | No | `auto/none` | Basic tool use | | OpenRouter | Model-dependent | Model-dependent | Model-dependent | Pass-through | +| Atlas Cloud | Yes | Yes | `auto/none` | OpenAI-compatible JSON schema mode | | Ollama | Partial | No | `auto/none` | Model-dependent | ### Embedding Support diff --git a/package.json b/package.json index 9a62ee54a1d..09fc5ac3859 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@framers/agentos", "version": "0.9.140", - "description": "AgentOS: open-source TypeScript framework for autonomous AI agents. Unified graph orchestration, cognitive memory, runtime tool forging, multi-tier guardrails, voice pipeline, and 11 LLM providers.", + "description": "AgentOS: open-source TypeScript framework for autonomous AI agents. Unified graph orchestration, cognitive memory, runtime tool forging, multi-tier guardrails, voice pipeline, and 12 LLM providers.", "homepage": "https://agentos.sh", "repository": { "type": "git", diff --git a/scripts/build-knowledge-corpus.mjs b/scripts/build-knowledge-corpus.mjs index f7837f93f39..44dc16b4bbc 100644 --- a/scripts/build-knowledge-corpus.mjs +++ b/scripts/build-knowledge-corpus.mjs @@ -210,7 +210,7 @@ function getFaqEntries(skillCount = 0) { id: 'faq:supported-models', heading: 'What LLM models are supported?', content: - 'AgentOS supports 11 LLM providers: OpenAI (gpt-4o, gpt-4o-mini), Anthropic (claude-sonnet, claude-haiku, claude-opus), Google Gemini (gemini-2.5-pro, gemini-2.5-flash), Groq, Together AI, Mistral, xAI, OpenRouter (fans out to 200+ models), and Ollama (any local model), plus CLI bridges for Claude Code and Gemini CLI.', + 'AgentOS supports 12 LLM providers: OpenAI (gpt-4o, gpt-4o-mini), Anthropic (claude-sonnet, claude-haiku, claude-opus), Google Gemini (gemini-2.5-pro, gemini-2.5-flash), Groq, Together AI, Mistral, xAI, Atlas Cloud, OpenRouter (fans out to 200+ models), and Ollama (any local model), plus CLI bridges for Claude Code and Gemini CLI.', category: 'faq', }, { @@ -489,7 +489,7 @@ function getApiReferenceEntries() { id: 'api:generateText', heading: 'generateText() API', content: - "import { generateText } from '@framers/agentos'. generateText({ model: 'openai:gpt-4o', prompt: 'Hello', system?: '...', tools?: [...], maxSteps?: 5, temperature?: 0.7 }) -> Promise<{ text, provider, model, usage, toolCalls }>. The core text generation function supporting all 11 LLM providers.", + "import { generateText } from '@framers/agentos'. generateText({ model: 'openai:gpt-4o', prompt: 'Hello', system?: '...', tools?: [...], maxSteps?: 5, temperature?: 0.7 }) -> Promise<{ text, provider, model, usage, toolCalls }>. The core text generation function supporting all 12 LLM providers.", category: 'api', }, { diff --git a/src/api/model.ts b/src/api/model.ts index dee2edc6bb7..4e5667f9233 100644 --- a/src/api/model.ts +++ b/src/api/model.ts @@ -39,6 +39,7 @@ export interface ResolvedProvider { const ENV_KEY_MAP: Record = { openai: 'OPENAI_API_KEY', + atlascloud: 'ATLASCLOUD_API_KEY', anthropic: 'ANTHROPIC_API_KEY', openrouter: 'OPENROUTER_API_KEY', gemini: 'GEMINI_API_KEY', @@ -52,6 +53,7 @@ const ENV_KEY_MAP: Record = { const ENV_URL_MAP: Record = { openai: 'OPENAI_BASE_URL', + atlascloud: 'ATLASCLOUD_BASE_URL', openrouter: 'OPENROUTER_BASE_URL', stability: 'STABILITY_BASE_URL', replicate: 'REPLICATE_BASE_URL', diff --git a/src/api/runtime/__tests__/provider-defaults.test.ts b/src/api/runtime/__tests__/provider-defaults.test.ts index 39449d1ccdb..8b40c43a39b 100644 --- a/src/api/runtime/__tests__/provider-defaults.test.ts +++ b/src/api/runtime/__tests__/provider-defaults.test.ts @@ -13,7 +13,7 @@ import { resolveModelOption, resolveProvider } from '../model.js'; describe('PROVIDER_DEFAULTS', () => { it('has text model for all major providers', () => { - for (const id of ['openai', 'anthropic', 'ollama', 'openrouter', 'gemini']) { + for (const id of ['openai', 'anthropic', 'ollama', 'openrouter', 'atlascloud', 'gemini']) { expect(PROVIDER_DEFAULTS[id]?.text).toBeDefined(); } }); @@ -40,6 +40,7 @@ describe('autoDetectProvider', () => { // Restore env after each test for (const key of [ 'OPENROUTER_API_KEY', + 'ATLASCLOUD_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GEMINI_API_KEY', @@ -62,6 +63,7 @@ describe('autoDetectProvider', () => { it('detects openai from OPENAI_API_KEY', () => { delete process.env.ANTHROPIC_API_KEY; delete process.env.OPENROUTER_API_KEY; + delete process.env.ATLASCLOUD_API_KEY; process.env.OPENAI_API_KEY = 'test'; expect(autoDetectProvider()).toBe('openai'); }); @@ -75,6 +77,7 @@ describe('autoDetectProvider', () => { it('detects anthropic from ANTHROPIC_API_KEY', () => { delete process.env.OPENAI_API_KEY; delete process.env.OPENROUTER_API_KEY; + delete process.env.ATLASCLOUD_API_KEY; process.env.ANTHROPIC_API_KEY = 'test'; expect(autoDetectProvider()).toBe('anthropic'); }); @@ -82,6 +85,7 @@ describe('autoDetectProvider', () => { it('skips providers without image defaults when detecting for image tasks', () => { delete process.env.OPENAI_API_KEY; delete process.env.OPENROUTER_API_KEY; + delete process.env.ATLASCLOUD_API_KEY; process.env.ANTHROPIC_API_KEY = 'anthropic-test'; process.env.STABILITY_API_KEY = 'stability-test'; @@ -92,6 +96,7 @@ describe('autoDetectProvider', () => { delete process.env.OPENAI_API_KEY; delete process.env.ANTHROPIC_API_KEY; delete process.env.OPENROUTER_API_KEY; + delete process.env.ATLASCLOUD_API_KEY; delete process.env.GEMINI_API_KEY; delete process.env.GROQ_API_KEY; delete process.env.TOGETHER_API_KEY; @@ -107,6 +112,7 @@ describe('autoDetectProvider', () => { delete process.env.OPENAI_API_KEY; delete process.env.ANTHROPIC_API_KEY; delete process.env.OPENROUTER_API_KEY; + delete process.env.ATLASCLOUD_API_KEY; delete process.env.GEMINI_API_KEY; delete process.env.GROQ_API_KEY; delete process.env.TOGETHER_API_KEY; @@ -120,6 +126,13 @@ describe('autoDetectProvider', () => { expect(autoDetectProvider()).toBe('claude-code-cli'); }); + + it('detects atlascloud from ATLASCLOUD_API_KEY', () => { + delete process.env.OPENROUTER_API_KEY; + delete process.env.OPENAI_API_KEY; + process.env.ATLASCLOUD_API_KEY = 'atlas-test'; + expect(autoDetectProvider()).toBe('atlascloud'); + }); }); describe('resolveModelOption', () => { @@ -128,6 +141,11 @@ describe('resolveModelOption', () => { expect(result).toEqual({ providerId: 'openai', modelId: 'gpt-4o' }); }); + it('resolves atlascloud provider-only to default text model', () => { + const result = resolveModelOption({ provider: 'atlascloud' }, 'text'); + expect(result).toEqual({ providerId: 'atlascloud', modelId: 'deepseek-ai/deepseek-v4-pro' }); + }); + it('resolves provider + explicit model override', () => { const result = resolveModelOption({ provider: 'openai', model: 'gpt-4o-mini' }, 'text'); expect(result).toEqual({ providerId: 'openai', modelId: 'gpt-4o-mini' }); diff --git a/src/api/runtime/__tests__/responseFormatForProvider.test.ts b/src/api/runtime/__tests__/responseFormatForProvider.test.ts index 98a4d204edf..57e50bed3ee 100644 --- a/src/api/runtime/__tests__/responseFormatForProvider.test.ts +++ b/src/api/runtime/__tests__/responseFormatForProvider.test.ts @@ -81,6 +81,15 @@ describe('buildResponseFormatForProvider', () => { ).toEqual({ type: 'json_object' }); }); + it('atlascloud + plain schema -> strict json_schema; record -> json_object', () => { + expect( + buildResponseFormatForProvider(inputs('atlascloud', 'deepseek-ai/deepseek-v4-pro', plainSchema)), + ).toMatchObject({ type: 'json_schema' }); + expect( + buildResponseFormatForProvider(inputs('atlascloud', 'deepseek-ai/deepseek-v4-pro', recordSchema)), + ).toEqual({ type: 'json_object' }); + }); + it('unknown provider -> undefined (schema rides the system prompt)', () => { expect( buildResponseFormatForProvider(inputs('ollama', 'llama3', plainSchema)), diff --git a/src/api/runtime/provider-defaults.ts b/src/api/runtime/provider-defaults.ts index a97356542ad..9b697ab3754 100644 --- a/src/api/runtime/provider-defaults.ts +++ b/src/api/runtime/provider-defaults.ts @@ -55,6 +55,10 @@ export const PROVIDER_DEFAULTS: Record = { text: 'openai/gpt-4o', cheap: 'openai/gpt-4o-mini', }, + atlascloud: { + text: 'deepseek-ai/deepseek-v4-pro', + cheap: 'qwen/qwen3.5-flash', + }, gemini: { text: 'gemini-2.5-flash', cheap: 'gemini-2.0-flash', @@ -125,6 +129,7 @@ const AUTO_DETECT_ORDER: AutoDetectProbe[] = [ // are set". { envKey: 'OPENROUTER_API_KEY', provider: 'openrouter' }, { envKey: 'OPENAI_API_KEY', provider: 'openai' }, + { envKey: 'ATLASCLOUD_API_KEY', provider: 'atlascloud' }, { envKey: 'ANTHROPIC_API_KEY', provider: 'anthropic' }, { envKey: 'GEMINI_API_KEY', provider: 'gemini' }, { envKey: 'GROQ_API_KEY', provider: 'groq' }, diff --git a/src/api/runtime/responseFormatForProvider.ts b/src/api/runtime/responseFormatForProvider.ts index b85d04e8156..229f352edea 100644 --- a/src/api/runtime/responseFormatForProvider.ts +++ b/src/api/runtime/responseFormatForProvider.ts @@ -23,7 +23,7 @@ import { modelSupportsForcedToolChoice } from '../../core/llm/providers/model-fo * as a loose JSON mode. (Moved from generateObject.ts so the provider-branch * logic lives exactly once.) */ -export const JSON_MODE_PROVIDERS = new Set(['openai', 'openrouter']); +export const JSON_MODE_PROVIDERS = new Set(['openai', 'openrouter', 'atlascloud']); /** Inputs for {@link buildResponseFormatForProvider}. */ export interface BuildResponseFormatForProviderInputs { @@ -61,7 +61,7 @@ export interface BuildResponseFormatForProviderInputs { * `options.responseFormat` (bridge options + text return only), so * emitting the Gemini marker there is dead weight; the leg is prompt-only * in practice and this builder says so honestly. - * - `openrouter` — OpenAI-shaped strict `json_schema` (forwarded upstream + * - `openrouter` / `atlascloud` — OpenAI-shaped strict `json_schema` (forwarded upstream * with `require_parameters` routing) when strict-compatible, else * `json_object`. * - any other {@link JSON_MODE_PROVIDERS} member — `json_object`. @@ -101,7 +101,7 @@ export function buildResponseFormatForProvider( // The leg is prompt-only in practice; say so honestly. return undefined; } - if (providerId === 'openrouter') { + if (providerId === 'openrouter' || providerId === 'atlascloud') { return canUseStrictJsonSchema(jsonSchema) ? buildOpenAIJsonSchemaResponseFormat(jsonSchema, schemaName) : { type: 'json_object' as const }; diff --git a/src/core/llm/providers/AIModelProviderManager.ts b/src/core/llm/providers/AIModelProviderManager.ts index c3e7aa1b1e2..9362a981ecb 100644 --- a/src/core/llm/providers/AIModelProviderManager.ts +++ b/src/core/llm/providers/AIModelProviderManager.ts @@ -27,6 +27,7 @@ import { GroqProvider, GroqProviderConfig } from './implementations/GroqProvider import { TogetherProvider, TogetherProviderConfig } from './implementations/TogetherProvider'; import { MistralProvider, MistralProviderConfig } from './implementations/MistralProvider'; import { XAIProvider, XAIProviderConfig } from './implementations/XAIProvider'; +import { AtlasCloudProvider, AtlasCloudProviderConfig } from './implementations/AtlasCloudProvider'; import { GeminiProvider, GeminiProviderConfig } from './implementations/GeminiProvider'; import { ClaudeCodeProvider, ClaudeCodeProviderConfig } from './implementations/ClaudeCodeProvider'; import { GeminiCLIProvider, GeminiCLIProviderConfig } from './implementations/GeminiCLIProvider'; @@ -39,7 +40,7 @@ import { GMIError, GMIErrorCode, createGMIErrorFromError } from '../../utils/err export interface ProviderConfigEntry { providerId: string; enabled: boolean; - config: Partial>; + config: Partial>; isDefault?: boolean; } @@ -144,6 +145,9 @@ export class AIModelProviderManager { case 'xai': providerInstance = new XAIProvider(); break; + case 'atlascloud': + providerInstance = new AtlasCloudProvider(); + break; case 'gemini': providerInstance = new GeminiProvider(); break; @@ -376,4 +380,3 @@ export class AIModelProviderManager { console.log("AIModelProviderManager: Shutdown complete. All providers processed."); } } - diff --git a/src/core/llm/providers/__tests__/OpenAICompatProviders.test.ts b/src/core/llm/providers/__tests__/OpenAICompatProviders.test.ts index ee7732a1862..c438432a0c8 100644 --- a/src/core/llm/providers/__tests__/OpenAICompatProviders.test.ts +++ b/src/core/llm/providers/__tests__/OpenAICompatProviders.test.ts @@ -1,7 +1,7 @@ /** * @fileoverview Unit tests for the OpenAI-compatible provider wrappers. * - * Validates that Groq, Together, Mistral, and xAI providers: + * Validates that Groq, Together, Mistral, xAI, and Atlas Cloud providers: * - Initialize correctly with proper base URLs * - Delegate to the underlying OpenAI provider * - Have correct model defaults in their catalogs @@ -41,6 +41,7 @@ import { GroqProvider } from '../implementations/GroqProvider'; import { TogetherProvider } from '../implementations/TogetherProvider'; import { MistralProvider } from '../implementations/MistralProvider'; import { XAIProvider } from '../implementations/XAIProvider'; +import { AtlasCloudProvider } from '../implementations/AtlasCloudProvider'; import { PROVIDER_DEFAULTS, autoDetectProvider } from '../../../../api/runtime/provider-defaults'; // --------------------------------------------------------------------------- @@ -247,6 +248,63 @@ describe('OpenAI-compatible provider wrappers', () => { }); }); + // ========================================================================= + // AtlasCloudProvider + // ========================================================================= + + describe('AtlasCloudProvider', () => { + it('has providerId "atlascloud"', () => { + const provider = new AtlasCloudProvider(); + expect(provider.providerId).toBe('atlascloud'); + }); + + it('initializes with Atlas Cloud base URL', async () => { + const provider = new AtlasCloudProvider(); + await provider.initialize({ apiKey: 'atlas-test-key' }); + + expect(initializeMock).toHaveBeenCalledTimes(1); + const config = initializeMock.mock.calls[0][0]; + expect(config.apiKey).toBe('atlas-test-key'); + expect(config.baseURL).toBe('https://api.atlascloud.ai/v1'); + }); + + it('throws when API key is missing', async () => { + const provider = new AtlasCloudProvider(); + await expect(provider.initialize({ apiKey: '' })).rejects.toThrow('API key is required'); + }); + + it('defaults to deepseek-ai/deepseek-v4-pro model', async () => { + const provider = new AtlasCloudProvider(); + await provider.initialize({ apiKey: 'atlas-test' }); + expect(provider.defaultModelId).toBe('deepseek-ai/deepseek-v4-pro'); + }); + + it('lists known Atlas Cloud models', async () => { + const provider = new AtlasCloudProvider(); + await provider.initialize({ apiKey: 'atlas-test' }); + const models = await provider.listAvailableModels(); + const ids = models.map(m => m.modelId); + expect(ids).toContain('deepseek-ai/deepseek-v4-pro'); + expect(ids).toContain('qwen/qwen3.5-flash'); + for (const m of models) { + expect(m.providerId).toBe('atlascloud'); + } + }); + + it('delegates generateCompletion to OpenAI provider', async () => { + const provider = new AtlasCloudProvider(); + await provider.initialize({ apiKey: 'atlas-test' }); + await provider.generateCompletion('test-model', [], {}); + expect(generateCompletionMock).toHaveBeenCalledTimes(1); + }); + + it('rejects embeddings (not supported)', async () => { + const provider = new AtlasCloudProvider(); + await provider.initialize({ apiKey: 'atlas-test' }); + await expect(provider.generateEmbeddings('model', ['text'])).rejects.toThrow('embeddings'); + }); + }); + // ========================================================================= // PROVIDER_DEFAULTS registration // ========================================================================= @@ -276,6 +334,12 @@ describe('OpenAI-compatible provider wrappers', () => { expect(PROVIDER_DEFAULTS.xai.cheap).toBe('grok-2-mini'); }); + it('includes atlascloud with correct defaults', () => { + expect(PROVIDER_DEFAULTS.atlascloud).toBeDefined(); + expect(PROVIDER_DEFAULTS.atlascloud.text).toBe('deepseek-ai/deepseek-v4-pro'); + expect(PROVIDER_DEFAULTS.atlascloud.cheap).toBe('qwen/qwen3.5-flash'); + }); + it('still has anthropic defaults', () => { expect(PROVIDER_DEFAULTS.anthropic).toBeDefined(); expect(PROVIDER_DEFAULTS.anthropic.text).toBe('claude-sonnet-4-6'); @@ -294,6 +358,7 @@ describe('OpenAI-compatible provider wrappers', () => { // Clear all provider env vars so auto-detect starts clean process.env = { ...originalEnv }; delete process.env.OPENAI_API_KEY; + delete process.env.ATLASCLOUD_API_KEY; delete process.env.ANTHROPIC_API_KEY; delete process.env.OPENROUTER_API_KEY; delete process.env.GEMINI_API_KEY; @@ -331,6 +396,11 @@ describe('OpenAI-compatible provider wrappers', () => { expect(autoDetectProvider()).toBe('xai'); }); + it('detects atlascloud when ATLASCLOUD_API_KEY is set', () => { + process.env.ATLASCLOUD_API_KEY = 'atlas-test'; + expect(autoDetectProvider()).toBe('atlascloud'); + }); + it('prefers openai over groq when both are set', () => { process.env.OPENAI_API_KEY = 'sk-test'; process.env.GROQ_API_KEY = 'gsk-test'; diff --git a/src/core/llm/providers/implementations/AtlasCloudProvider.ts b/src/core/llm/providers/implementations/AtlasCloudProvider.ts new file mode 100644 index 00000000000..f0c09a996b6 --- /dev/null +++ b/src/core/llm/providers/implementations/AtlasCloudProvider.ts @@ -0,0 +1,150 @@ +// File: backend/agentos/core/llm/providers/implementations/AtlasCloudProvider.ts + +/** + * @fileoverview Implements the IProvider interface for Atlas Cloud's + * OpenAI-compatible LLM endpoint. + * + * Atlas Cloud exposes `/v1/chat/completions` with OpenAI-shaped request and + * response payloads, so this provider reuses the OpenAIProvider transport while + * keeping Atlas Cloud credentials, endpoint defaults, and model catalog separate + * from OpenAI. + * + * @module backend/agentos/core/llm/providers/implementations/AtlasCloudProvider + * @implements {IProvider} + */ + +import { + IProvider, + ChatMessage, + ModelCompletionOptions, + ModelCompletionResponse, + ModelInfo, + ProviderEmbeddingOptions, + ProviderEmbeddingResponse, +} from '../IProvider'; +import { OpenAIProvider } from './OpenAIProvider'; + +export interface AtlasCloudProviderConfig { + /** Atlas Cloud API key. Sourced from `ATLASCLOUD_API_KEY`. */ + apiKey: string; + /** + * Base URL override. + * @default "https://api.atlascloud.ai/v1" + */ + baseURL?: string; + /** + * Default model to use when none is specified. + * @default "deepseek-ai/deepseek-v4-pro" + */ + defaultModelId?: string; + /** Request timeout in milliseconds. @default 60000 */ + requestTimeout?: number; +} + +const ATLAS_CLOUD_MODELS: ModelInfo[] = [ + { + modelId: 'deepseek-ai/deepseek-v4-pro', + providerId: 'atlascloud', + displayName: 'DeepSeek V4 Pro', + description: 'Long-context reasoning model served through Atlas Cloud.', + capabilities: ['chat', 'tool_use', 'json_mode', 'structured_outputs'], + contextWindowSize: 1048576, + supportsStreaming: true, + status: 'active', + }, + { + modelId: 'qwen/qwen3.5-flash', + providerId: 'atlascloud', + displayName: 'Qwen3.5 Flash', + description: 'Fast long-context chat model served through Atlas Cloud.', + capabilities: ['chat'], + contextWindowSize: 1000000, + supportsStreaming: true, + status: 'active', + }, +]; + +/** + * Thin wrapper around {@link OpenAIProvider} for Atlas Cloud's + * OpenAI-compatible LLM API. + */ +export class AtlasCloudProvider implements IProvider { + /** @inheritdoc */ + public readonly providerId: string = 'atlascloud'; + /** @inheritdoc */ + public isInitialized: boolean = false; + /** @inheritdoc */ + public defaultModelId?: string; + + private delegate = new OpenAIProvider(); + + public async initialize(config: AtlasCloudProviderConfig): Promise { + if (!config.apiKey) { + throw new Error('API key is required for AtlasCloudProvider. Set ATLASCLOUD_API_KEY.'); + } + + this.defaultModelId = config.defaultModelId ?? 'deepseek-ai/deepseek-v4-pro'; + + await this.delegate.initialize({ + apiKey: config.apiKey, + baseURL: config.baseURL ?? 'https://api.atlascloud.ai/v1', + defaultModelId: this.defaultModelId, + requestTimeout: config.requestTimeout ?? 60000, + }); + + this.isInitialized = true; + console.log(`AtlasCloudProvider initialized. Default model: ${this.defaultModelId}.`); + } + + /** @inheritdoc */ + public async generateCompletion( + modelId: string, + messages: ChatMessage[], + options: ModelCompletionOptions, + ): Promise { + return this.delegate.generateCompletion(modelId, messages, options); + } + + /** @inheritdoc */ + public async *generateCompletionStream( + modelId: string, + messages: ChatMessage[], + options: ModelCompletionOptions, + ): AsyncGenerator { + yield* this.delegate.generateCompletionStream(modelId, messages, options); + } + + public async generateEmbeddings( + _modelId: string, + _texts: string[], + _options?: ProviderEmbeddingOptions, + ): Promise { + throw new Error( + 'Atlas Cloud does not currently expose embeddings in AgentOS. Use a dedicated embedding provider.', + ); + } + + public async listAvailableModels(filter?: { capability?: string }): Promise { + if (filter?.capability) { + return ATLAS_CLOUD_MODELS.filter(m => m.capabilities.includes(filter.capability!)); + } + return [...ATLAS_CLOUD_MODELS]; + } + + /** @inheritdoc */ + public async getModelInfo(modelId: string): Promise { + return ATLAS_CLOUD_MODELS.find(m => m.modelId === modelId); + } + + /** @inheritdoc */ + public async checkHealth(): Promise<{ isHealthy: boolean; details?: unknown }> { + return this.delegate.checkHealth(); + } + + /** @inheritdoc */ + public async shutdown(): Promise { + await this.delegate.shutdown(); + this.isInitialized = false; + console.log('AtlasCloudProvider shutdown complete.'); + } +} diff --git a/src/orchestration/pipeline/query/__tests__/platform-knowledge.test.ts b/src/orchestration/pipeline/query/__tests__/platform-knowledge.test.ts index 242195b188d..c68aed60e7d 100644 --- a/src/orchestration/pipeline/query/__tests__/platform-knowledge.test.ts +++ b/src/orchestration/pipeline/query/__tests__/platform-knowledge.test.ts @@ -34,7 +34,7 @@ const PLATFORM_CORPUS_FIXTURE = JSON.stringify([ id: 'api:generateText', heading: 'generateText() API', content: - "import { generateText } from '@framers/agentos'. Core text generation function supporting all 11 LLM providers.", + "import { generateText } from '@framers/agentos'. Core text generation function supporting all 12 LLM providers.", category: 'api', }, { diff --git a/src/orchestration/planning/ProviderAssignmentEngine.ts b/src/orchestration/planning/ProviderAssignmentEngine.ts index 7574730ef9d..f2e88af664c 100644 --- a/src/orchestration/planning/ProviderAssignmentEngine.ts +++ b/src/orchestration/planning/ProviderAssignmentEngine.ts @@ -23,6 +23,7 @@ const DEFAULTS: Record = { gemini: { text: 'gemini-2.5-flash', cheap: 'gemini-2.0-flash' }, ollama: { text: 'llama3.2', cheap: 'llama3.2' }, openrouter: { text: 'openai/gpt-4o', cheap: 'openai/gpt-4o-mini' }, + atlascloud: { text: 'deepseek-ai/deepseek-v4-pro', cheap: 'qwen/qwen3.5-flash' }, groq: { text: 'llama-3.3-70b-versatile', cheap: 'gemma2-9b-it' }, together: { text: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo', @@ -90,7 +91,7 @@ export class ProviderAssignmentEngine { private assignBest(node: AnnotatedNode): NodeProviderAssignment { const provider = this.pickProvider( 'best', - ['anthropic', 'openai', 'openrouter', 'gemini', 'groq', 'xai', 'mistral', 'together', 'ollama'], + ['anthropic', 'openai', 'openrouter', 'atlascloud', 'gemini', 'groq', 'xai', 'mistral', 'together', 'ollama'], ); const defaults = DEFAULTS[provider]; return { @@ -105,7 +106,7 @@ export class ProviderAssignmentEngine { private assignCheapest(node: AnnotatedNode): NodeProviderAssignment { const provider = this.pickProvider( 'cheapest', - ['groq', 'gemini', 'openai', 'openrouter', 'together', 'mistral', 'xai', 'anthropic', 'ollama'], + ['groq', 'gemini', 'atlascloud', 'openai', 'openrouter', 'together', 'mistral', 'xai', 'anthropic', 'ollama'], ); const defaults = DEFAULTS[provider]; return { diff --git a/tests/api/model.spec.ts b/tests/api/model.spec.ts index f69c6d5b65c..b36effb9c8a 100644 --- a/tests/api/model.spec.ts +++ b/tests/api/model.spec.ts @@ -57,6 +57,16 @@ describe('resolveProvider', () => { expect(config.baseUrl).toBe('http://localhost:11434'); }); + it('resolves atlascloud from ATLASCLOUD_API_KEY and ATLASCLOUD_BASE_URL', () => { + process.env.ATLASCLOUD_API_KEY = 'atlas-test'; + process.env.ATLASCLOUD_BASE_URL = 'https://proxy.example.com/v1'; + const config = resolveProvider('atlascloud', 'deepseek-ai/deepseek-v4-pro'); + expect(config.providerId).toBe('atlascloud'); + expect(config.apiKey).toBe('atlas-test'); + expect(config.baseUrl).toBe('https://proxy.example.com/v1'); + expect(config.modelId).toBe('deepseek-ai/deepseek-v4-pro'); + }); + it('throws when no API key found', () => { delete process.env.OPENAI_API_KEY; expect(() => resolveProvider('openai', 'gpt-4o')).toThrow('No API key'); diff --git a/tests/e2e/platform-knowledge.e2e.spec.ts b/tests/e2e/platform-knowledge.e2e.spec.ts index 969c79d7289..0f91bcd4b71 100644 --- a/tests/e2e/platform-knowledge.e2e.spec.ts +++ b/tests/e2e/platform-knowledge.e2e.spec.ts @@ -182,7 +182,7 @@ describe('Platform Knowledge — e2e', () => { // Generator mock mockGenerateText.mockResolvedValueOnce({ - text: 'AgentOS supports 11 LLM providers including OpenAI, Anthropic, Google, and Ollama.', + text: 'AgentOS supports 12 LLM providers including OpenAI, Anthropic, Google, Atlas Cloud, and Ollama.', provider: 'openai', model: 'gpt-4o-mini', usage: { promptTokens: 150, completionTokens: 40, totalTokens: 190 }, From 1be41ac36e96f6463eab4e7a029f020530cb09f7 Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:08:10 +0800 Subject: [PATCH 2/2] chore: address atlascloud provider review nit --- src/core/llm/providers/implementations/AtlasCloudProvider.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/llm/providers/implementations/AtlasCloudProvider.ts b/src/core/llm/providers/implementations/AtlasCloudProvider.ts index f0c09a996b6..1edd5e0541f 100644 --- a/src/core/llm/providers/implementations/AtlasCloudProvider.ts +++ b/src/core/llm/providers/implementations/AtlasCloudProvider.ts @@ -126,7 +126,8 @@ export class AtlasCloudProvider implements IProvider { public async listAvailableModels(filter?: { capability?: string }): Promise { if (filter?.capability) { - return ATLAS_CLOUD_MODELS.filter(m => m.capabilities.includes(filter.capability!)); + const { capability } = filter; + return ATLAS_CLOUD_MODELS.filter(m => m.capabilities.includes(capability)); } return [...ATLAS_CLOUD_MODELS]; }