From d41b7b4e3cc3f54d368e19b9e01bb42bb286497d Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:10:58 +0800 Subject: [PATCH] Add Atlas Cloud provider --- README.md | 7 +-- docs/providers/README.md | 1 + docs/providers/atlascloud.md | 48 +++++++++++++++++++ packages/runtime/src/model-routes.ts | 2 +- packages/runtime/src/provider-capabilities.ts | 7 +++ .../runtime/src/provider.integration.test.ts | 22 +++++++++ packages/runtime/src/provider.test.ts | 28 +++++++++-- packages/runtime/src/provider.ts | 27 +++++++++++ .../runtime/src/runtime/setup-api.test.ts | 2 +- packages/runtime/src/runtime/setup-api.ts | 9 ++-- packages/runtime/src/types.ts | 2 +- packages/web/src/lib/providers.ts | 1 + 12 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 docs/providers/atlascloud.md diff --git a/README.md b/README.md index 85562fa44..6c0a42471 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Gini's **runtime is the gateway**: a single Bun process per instance owns state - Authenticated localhost gateway and a Next.js + Tailwind + shadcn/ui control plane - Persistent chat, runs, tasks, approvals, traces, audit events, jobs, memories, and skills - Approval-gated file, terminal, and code tools -- Provider support: Codex OAuth; OpenAI / Azure OpenAI / DeepSeek / OpenRouter API keys; the first-party Anthropic Claude API; Amazon Bedrock (model-agnostic Converse, AWS SigV4 — Claude, Nova, Llama, Mistral, DeepSeek); and any OpenAI-compatible local server +- Provider support: Codex OAuth; OpenAI / Azure OpenAI / DeepSeek / Atlas Cloud / OpenRouter API keys; the first-party Anthropic Claude API; Amazon Bedrock (model-agnostic Converse, AWS SigV4 — Claude, Nova, Llama, Mistral, DeepSeek); and any OpenAI-compatible local server - Local embeddings, reranking, and voice-message speech-to-text by default - Parallel instances with isolated state, ports, and logs - In-chat actionable controls — secure credential/secret fields, sign-in and sensitive-step handoff into the agent's browser, choice prompts, and confirm-before-send — so the agent unblocks itself in context instead of stranding you @@ -129,6 +129,7 @@ Run `gini setup` for an interactive picker, or configure directly: gini provider set codex gpt-5.5 # Codex OAuth (reads ~/.codex/auth.json) gini provider set openai gpt-5.4-mini # uses $OPENAI_API_KEY gini provider set openrouter # uses $OPENROUTER_API_KEY +gini provider set atlascloud qwen/qwen3.5-flash # uses $ATLASCLOUD_API_KEY gini provider set local --base-url http://127.0.0.1:8000/v1 gini provider set anthropic claude-opus-4-8 # first-party Claude API, uses $ANTHROPIC_API_KEY # Amazon Bedrock: model-agnostic Converse, SigV4-signed with AWS keys you enter via the web form or `gini setup` @@ -139,11 +140,11 @@ gini provider set azure gpt-4o \ --deployment --api-version 2024-10-21 --auth-scheme api-key # uses $AZURE_OPENAI_API_KEY ``` -For step-by-step setup of each provider — getting credentials, installing any prerequisite tooling (Ollama, …), and configuring it in the CLI or web — see the per-provider guides: [OpenAI](docs/providers/openai.md), [Anthropic](docs/providers/anthropic.md), [Amazon Bedrock](docs/providers/bedrock.md), [Azure OpenAI](docs/providers/azure.md), [OpenRouter](docs/providers/openrouter.md), [DeepSeek](docs/providers/deepseek.md), [Codex](docs/providers/codex.md), and [Local](docs/providers/local.md). The [providers index](docs/providers/README.md) lists them all with their auth model at a glance. +For step-by-step setup of each provider — getting credentials, installing any prerequisite tooling (Ollama, …), and configuring it in the CLI or web — see the per-provider guides: [OpenAI](docs/providers/openai.md), [Anthropic](docs/providers/anthropic.md), [Amazon Bedrock](docs/providers/bedrock.md), [Azure OpenAI](docs/providers/azure.md), [OpenRouter](docs/providers/openrouter.md), [DeepSeek](docs/providers/deepseek.md), [Atlas Cloud](docs/providers/atlascloud.md), [Codex](docs/providers/codex.md), and [Local](docs/providers/local.md). The [providers index](docs/providers/README.md) lists them all with their auth model at a glance. The `local` provider works with any OpenAI-compatible server (oMLX, vLLM, LM Studio, llama.cpp). The `azure` provider targets an Azure OpenAI resource: set `--base-url` to `https://.openai.azure.com` and pick a deployment; `--api-version` defaults to a GA value and `--auth-scheme` defaults to `api-key` (a resource key), with `bearer` available for an Entra token. API keys are read from environment variables, and Codex OAuth is read from `~/.codex/auth.json` (or `CODEX_AUTH_JSON`) — nothing is written to Gini config. Run `gini --help` for the full flag set, or see [provider-extra-body.md](docs/adr/provider-extra-body.md) for the `--extra-body` contract and [Azure OpenAI As A First-Class Provider](docs/adr/azure-provider.md) for the Azure routing contract. When a credential fails mid-chat, see [Codex re-authentication](docs/providers/codex.md#re-authentication) and [Provider Re-Authentication Guidance](docs/adr/provider-reauth-guidance.md). -`gini setup`'s interactive picker covers every provider — OpenAI, Codex, Anthropic, Amazon Bedrock, Azure OpenAI, OpenRouter, DeepSeek, and Local — prompting for whatever each one needs (an API key, the AWS access key + secret for Bedrock, the resource endpoint and deployment for Azure, the base URL for Local). `gini provider set …` (above) and the web **Settings → Add provider** form remain available for scripted or non-interactive configuration. +`gini setup`'s interactive picker covers every provider — OpenAI, Codex, Anthropic, Amazon Bedrock, Azure OpenAI, OpenRouter, DeepSeek, Atlas Cloud, and Local — prompting for whatever each one needs (an API key, the AWS access key + secret for Bedrock, the resource endpoint and deployment for Azure, the base URL for Local). `gini provider set …` (above) and the web **Settings → Add provider** form remain available for scripted or non-interactive configuration. ## Parallel Instances diff --git a/docs/providers/README.md b/docs/providers/README.md index 7ec917201..ba31bc9a2 100644 --- a/docs/providers/README.md +++ b/docs/providers/README.md @@ -12,6 +12,7 @@ provider in Gini (both the CLI and the web Add Provider form). | Azure OpenAI | API key / Entra | `AZURE_OPENAI_API_KEY` | [azure.md](azure.md) | | OpenRouter | API key | `OPENROUTER_API_KEY` | [openrouter.md](openrouter.md) | | DeepSeek | API key | `DEEPSEEK_API_KEY` | [deepseek.md](deepseek.md) | +| Atlas Cloud | API key | `ATLASCLOUD_API_KEY` | [atlascloud.md](atlascloud.md) | | Codex (OpenAI OAuth) | OAuth / CLI | `~/.codex/auth.json` (no key) | [codex.md](codex.md) | | Local (OpenAI-compatible) | none / optional key | `GINI_LOCAL_API_KEY` (optional) | [local.md](local.md) | diff --git a/docs/providers/atlascloud.md b/docs/providers/atlascloud.md new file mode 100644 index 000000000..b2194a190 --- /dev/null +++ b/docs/providers/atlascloud.md @@ -0,0 +1,48 @@ +# Atlas Cloud + +Atlas Cloud is an OpenAI-compatible API-key provider. Gini talks to +`https://api.atlascloud.ai/v1` and authenticates with a Bearer key. + +## Step 1 - Get an API key + +1. Sign in to the [Atlas Cloud console](https://www.atlascloud.ai/console/api-keys). +2. Create an API key and copy it. +3. Check the live model catalog in Atlas Cloud before pinning a model for + production use. + +## Step 2 - Set the key + +Gini reads the key from the `ATLASCLOUD_API_KEY` environment variable. Set it in +your shell or in `~/.gini/secrets.env` for persistence: + +```bash +# ~/.gini/secrets.env (created mode 0600) +ATLASCLOUD_API_KEY=ak-... +``` + +The web Add Provider form writes this for you. + +## Step 3 - Configure the provider in Gini + +### CLI + +```bash +gini provider set atlascloud qwen/qwen3.5-flash +``` + +The default model is `qwen/qwen3.5-flash`. You can also pin another Atlas Cloud +chat model, such as `deepseek-ai/deepseek-v4-pro`. The base URL defaults to +`https://api.atlascloud.ai/v1`; override it only for a compatible proxy with +`--base-url`. + +### Web + +Open **Settings -> Add provider -> Atlas Cloud**, paste the key, and pick or type +an Atlas Cloud model ID. + +## Re-authentication + +Atlas Cloud is an API-key provider, so a credential failure surfaces the +provider error and links to **Settings -> Providers** to paste a new key. Rotate +keys from the Atlas Cloud console when needed. See ADR +[provider-reauth-guidance.md](../adr/provider-reauth-guidance.md). diff --git a/packages/runtime/src/model-routes.ts b/packages/runtime/src/model-routes.ts index e56bf3077..8751ec266 100644 --- a/packages/runtime/src/model-routes.ts +++ b/packages/runtime/src/model-routes.ts @@ -53,7 +53,7 @@ export function anthropicMirrorModelId(bedrockModelId: string): string | undefin // revisions and features first), codex's OAuth bundle beats the metered // clouds, and openrouter/local are deliberate opt-ins that should never // win a tie. Unknown providers sort last in catalog order. -const ROUTE_PRIORITY = ["openai", "anthropic", "deepseek", "codex", "azure", "bedrock", "openrouter", "local"] as const; +const ROUTE_PRIORITY = ["openai", "anthropic", "deepseek", "atlascloud", "codex", "azure", "bedrock", "openrouter", "local"] as const; function routePriority(provider: ProviderCatalogItem["name"]): number { const index = (ROUTE_PRIORITY as readonly string[]).indexOf(provider); diff --git a/packages/runtime/src/provider-capabilities.ts b/packages/runtime/src/provider-capabilities.ts index 0352e5b96..9718a8783 100644 --- a/packages/runtime/src/provider-capabilities.ts +++ b/packages/runtime/src/provider-capabilities.ts @@ -149,6 +149,8 @@ export function resolveProviderContextWindowTokens(provider: ProviderConfig): nu return openrouterContextWindowTokens(model); case "deepseek": return deepseekContextWindowTokens(model); + case "atlascloud": + return FALLBACK_CONTEXT_WINDOW_TOKENS; case "anthropic": return anthropicContextWindowTokens(model); case "bedrock": @@ -292,6 +294,11 @@ export function resolveProviderModality(provider: ProviderConfig): ProviderModal case "deepseek": // Confirmed text-only API — no image/file content part. return { vision: false, nativeDocs: false }; + case "atlascloud": + // Atlas Cloud's OpenAI-compatible LLM endpoint is used here for text chat + // models. Keep attachments conservative until the runtime has per-model + // modality discovery for Atlas model ids. + return { vision: false, nativeDocs: false }; case "anthropic": // Claude Opus/Sonnet/Haiku accept image input and ingest documents // natively via the Messages API (image + document content blocks). The diff --git a/packages/runtime/src/provider.integration.test.ts b/packages/runtime/src/provider.integration.test.ts index ea4686c77..0b2b0bd62 100644 --- a/packages/runtime/src/provider.integration.test.ts +++ b/packages/runtime/src/provider.integration.test.ts @@ -199,6 +199,28 @@ describe("provider integration (against in-process mock server)", () => { }); }); + test("atlascloud generateTaskSummary uses the OpenAI-compatible endpoint and bearer key", async () => { + await withMockServer(async ({ server, setEnv, uniqueEnvName }) => { + const keyEnv = uniqueEnvName(); + setEnv(keyEnv, "test-atlas-key"); + const provider = normalizeProvider({ + name: "atlascloud", + model: "qwen/qwen3.5-flash", + baseUrl: server.url, + apiKeyEnv: keyEnv + }); + const result = await generateTaskSummary(cfg(provider), "summarize via atlas"); + expect(result.text).toBe("mock-echo: summarize via atlas"); + expect(server.received).toHaveLength(1); + const captured = server.received[0]!; + expect(captured.method).toBe("POST"); + expect(captured.url.endsWith("/v1/chat/completions")).toBe(true); + expect(captured.headers.authorization).toBe("[REDACTED]"); + const sent = captured.body as Record; + expect(sent.model).toBe("qwen/qwen3.5-flash"); + }); + }); + test("local provider sends no Authorization header when api key env is unset", async () => { await withMockServer(async ({ server, uniqueEnvName }) => { const keyEnv = uniqueEnvName(); diff --git a/packages/runtime/src/provider.test.ts b/packages/runtime/src/provider.test.ts index 8b0b5cbb6..352f60250 100644 --- a/packages/runtime/src/provider.test.ts +++ b/packages/runtime/src/provider.test.ts @@ -880,17 +880,18 @@ describe("provider", () => { // ---------------- extraBody plumbing (oMLX-style chat_template_kwargs) ---------------- - // The openai/openrouter/local providers all forward `extraBody` into the + // The openai/openrouter/atlascloud/local providers all forward `extraBody` into the // request body of every chat-completions call (tool-calling, structured, // vision, generateTaskSummary). Codex uses /responses with its own shape // and is not expected to honor extraBody — verified separately by the // /responses tests above which already assert the request body keys. - test("normalizeProvider preserves extraBody for local/openai/openrouter and drops it for echo/codex", () => { + test("normalizeProvider preserves extraBody for local/openai/openrouter/atlascloud and drops it for echo/codex", () => { const extraBody = { chat_template_kwargs: { enable_thinking: true } }; expect(normalizeProvider({ name: "local", model: "m", extraBody }).extraBody).toEqual(extraBody); expect(normalizeProvider({ name: "openai", model: "m", extraBody }).extraBody).toEqual(extraBody); expect(normalizeProvider({ name: "openrouter", model: "m", extraBody }).extraBody).toEqual(extraBody); + expect(normalizeProvider({ name: "atlascloud", model: "m", extraBody }).extraBody).toEqual(extraBody); expect(normalizeProvider({ name: "azure", model: "m", baseUrl: "https://r.openai.azure.com", extraBody }).extraBody).toEqual(extraBody); // echo and codex don't carry extraBody through. Echo is deterministic and // bypasses the HTTP path; codex uses /responses (different wire shape). @@ -898,6 +899,15 @@ describe("provider", () => { expect(normalizeProvider({ name: "codex", model: "m", extraBody }).extraBody).toBeUndefined(); }); + test("normalizeProvider applies Atlas Cloud OpenAI-compatible defaults", () => { + expect(normalizeProvider({ name: "atlascloud", model: "" })).toEqual({ + name: "atlascloud", + model: "qwen/qwen3.5-flash", + baseUrl: "https://api.atlascloud.ai/v1", + apiKeyEnv: "ATLASCLOUD_API_KEY" + }); + }); + test("normalizeProvider applies azure defaults: api-key auth, GA api-version, AZURE_OPENAI_API_KEY env", () => { const provider = normalizeProvider({ name: "azure", model: "gpt-4o", baseUrl: "https://r.openai.azure.com" }); expect(provider.name).toBe("azure"); @@ -1047,15 +1057,20 @@ describe("provider", () => { }); test("non-azure providers stay configured from their env var alone", () => { - const prev = process.env.OPENAI_API_KEY; + const prevOpenAI = process.env.OPENAI_API_KEY; + const prevAtlas = process.env.ATLASCLOUD_API_KEY; process.env.OPENAI_API_KEY = "sk-x"; + process.env.ATLASCLOUD_API_KEY = "atlas-key"; try { // openai has a working default endpoint, so the env key is enough even // when it isn't the active provider. expect(isProviderConfigured("openai", "azure", undefined, undefined)).toBe(true); + expect(isProviderConfigured("atlascloud", "azure", undefined, undefined)).toBe(true); } finally { - if (prev === undefined) delete process.env.OPENAI_API_KEY; - else process.env.OPENAI_API_KEY = prev; + if (prevOpenAI === undefined) delete process.env.OPENAI_API_KEY; + else process.env.OPENAI_API_KEY = prevOpenAI; + if (prevAtlas === undefined) delete process.env.ATLASCLOUD_API_KEY; + else process.env.ATLASCLOUD_API_KEY = prevAtlas; } }); @@ -3585,6 +3600,7 @@ describe("resolveDispatchProvider (transient fallback)", () => { "OPENAI_API_KEY", "OPENROUTER_API_KEY", "DEEPSEEK_API_KEY", + "ATLASCLOUD_API_KEY", "GINI_LOCAL_API_KEY", "ANTHROPIC_API_KEY", "AZURE_OPENAI_API_KEY", @@ -3824,6 +3840,7 @@ describe("auth-error classification", () => { expect(providerDisplayLabel("openai")).toBe("OpenAI"); expect(providerDisplayLabel("openrouter")).toBe("OpenRouter"); expect(providerDisplayLabel("deepseek")).toBe("DeepSeek"); + expect(providerDisplayLabel("atlascloud")).toBe("Atlas Cloud"); expect(providerDisplayLabel("azure")).toBe("Azure OpenAI"); expect(providerDisplayLabel("local")).toBe("Local"); expect(providerDisplayLabel("echo")).toBe("Gini Echo"); @@ -3839,6 +3856,7 @@ describe("auth-error classification", () => { expect(providerReauth("bedrock")).toEqual({ kind: "aws", url: "/settings" }); expect(providerReauth("openai")).toEqual({ kind: "settings", url: "/settings" }); expect(providerReauth("deepseek")).toEqual({ kind: "settings", url: "/settings" }); + expect(providerReauth("atlascloud")).toEqual({ kind: "settings", url: "/settings" }); expect(providerReauth("openrouter")).toEqual({ kind: "settings", url: "/settings" }); expect(providerReauth("azure")).toEqual({ kind: "settings", url: "/settings" }); expect(providerReauth("local")).toEqual({ kind: "settings", url: "/settings" }); diff --git a/packages/runtime/src/provider.ts b/packages/runtime/src/provider.ts index 0a73c95ea..5244716a5 100644 --- a/packages/runtime/src/provider.ts +++ b/packages/runtime/src/provider.ts @@ -18,6 +18,8 @@ const DEFAULT_CODEX_MODEL = "gpt-5.5"; const DEFAULT_CODEX_AUTH_PATH = "~/.codex/auth.json"; const DEFAULT_DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"; const DEFAULT_DEEPSEEK_MODEL = "deepseek-v4-flash"; +const DEFAULT_ATLASCLOUD_BASE_URL = "https://api.atlascloud.ai/v1"; +const DEFAULT_ATLASCLOUD_MODEL = "qwen/qwen3.5-flash"; // Anthropic Messages API (first-party Claude). The default baseUrl is the bare // host (no /v1) — callAnthropicMessages appends /v1/messages. Auth is the // ANTHROPIC_API_KEY (sk-ant…) in the x-api-key header. Amazon Bedrock is a @@ -156,6 +158,7 @@ const PROVIDER_API_KEY_ENV: Record = { openai: "OPENAI_API_KEY", openrouter: "OPENROUTER_API_KEY", deepseek: "DEEPSEEK_API_KEY", + atlascloud: "ATLASCLOUD_API_KEY", local: "GINI_LOCAL_API_KEY", anthropic: "ANTHROPIC_API_KEY", azure: "AZURE_OPENAI_API_KEY" @@ -405,6 +408,16 @@ export function providerCatalog(): ProviderCatalogItem[] { capabilities: ["chat-completions", "tool-calling"], costHint: "external" }, + { + id: "atlascloud", + name: "atlascloud", + displayName: "Atlas Cloud", + baseUrl: DEFAULT_ATLASCLOUD_BASE_URL, + auth: "env", + models: [DEFAULT_ATLASCLOUD_MODEL, "deepseek-ai/deepseek-v4-pro"], + capabilities: ["chat-completions", "tool-calling", "openai-compatible"], + costHint: "external" + }, { id: "azure", name: "azure", @@ -450,6 +463,8 @@ export function providerDisplayLabel(name: ProviderName): string { return "OpenRouter"; case "deepseek": return "DeepSeek"; + case "atlascloud": + return "Atlas Cloud"; case "anthropic": return "Anthropic"; case "bedrock": @@ -3521,6 +3536,7 @@ export async function generateTaskSummary( provider.name === "openrouter" || provider.name === "local" || provider.name === "deepseek" || + provider.name === "atlascloud" || // Azure OpenAI exposes deployment-scoped chat/completions, not the flat // /responses surface this path uses for standard OpenAI — route it through // the chat-completions builder so the URL + api-key header come out right. @@ -3665,6 +3681,7 @@ export async function generateStructured( provider.name === "local" || provider.name === "openai" || provider.name === "deepseek" || + provider.name === "atlascloud" || provider.name === "azure" ) { return callStructuredChatCompletions(provider, request); @@ -3893,6 +3910,15 @@ export function normalizeProvider(provider: ProviderConfig): ProviderConfig { ...(extraBody ? { extraBody } : {}) }; } + if (provider.name === "atlascloud") { + return { + name: "atlascloud", + model: provider.model || DEFAULT_ATLASCLOUD_MODEL, + baseUrl: pickBaseUrl(provider.baseUrl, DEFAULT_ATLASCLOUD_BASE_URL), + apiKeyEnv: provider.apiKeyEnv ?? "ATLASCLOUD_API_KEY", + ...(provider.extraBody ? { extraBody: provider.extraBody } : {}) + }; + } if (provider.name === "codex") { return { name: "codex", @@ -4839,6 +4865,7 @@ function defaultBaseUrl(provider: ProviderConfig): string { if (provider.name === "openrouter") return "https://openrouter.ai/api/v1"; if (provider.name === "local") return "http://127.0.0.1:11434/v1"; if (provider.name === "deepseek") return DEFAULT_DEEPSEEK_BASE_URL; + if (provider.name === "atlascloud") return DEFAULT_ATLASCLOUD_BASE_URL; if (provider.name === "anthropic") return DEFAULT_ANTHROPIC_BASE_URL; if (provider.name === "bedrock") return bedrockRuntimeBaseUrl(provider.awsRegion ?? DEFAULT_BEDROCK_REGION); // Azure has no universal default — it is per-resource. Return empty so a diff --git a/packages/runtime/src/runtime/setup-api.test.ts b/packages/runtime/src/runtime/setup-api.test.ts index b2ea546a4..7ff146756 100644 --- a/packages/runtime/src/runtime/setup-api.test.ts +++ b/packages/runtime/src/runtime/setup-api.test.ts @@ -118,7 +118,7 @@ describe("setup-api", () => { test("status: providerConfigured reflects the codex platform default on a fresh instance", () => { const status = getSetupStatus(config); expect(status.ok).toBe(true); - expect(status.providers).toEqual(["openai", "codex", "openrouter", "deepseek", "local", "anthropic", "bedrock", "azure"]); + expect(status.providers).toEqual(["openai", "codex", "openrouter", "deepseek", "atlascloud", "local", "anthropic", "bedrock", "azure"]); // Platform default is "codex". providerHealth treats codex as // configured when the runtime can find an auth.json; in this // scratch env there is none (CODEX_AUTH_JSON is scrubbed in diff --git a/packages/runtime/src/runtime/setup-api.ts b/packages/runtime/src/runtime/setup-api.ts index 45ff6e8aa..94ba9fb98 100644 --- a/packages/runtime/src/runtime/setup-api.ts +++ b/packages/runtime/src/runtime/setup-api.ts @@ -8,13 +8,13 @@ // Behavior: // - GET /api/setup/status reflects the live provider config plus the // available picker options (SUPPORTED_PROVIDERS: openai, codex, openrouter, -// deepseek, local, anthropic, bedrock, azure). `current` is the active +// deepseek, atlascloud, local, anthropic, bedrock, azure). `current` is the active // provider name when configured; null otherwise. `providerConfigured` is // true when the active provider has valid creds — same definition // `providerHealth` uses. // - POST /api/setup/provider accepts {provider, apiKey?, model?, baseUrl?} -// for the env-keyed providers (openai/openrouter/deepseek/local/anthropic/ -// azure) — plus apiVersion/deployment/authScheme for azure's +// for the env-keyed providers (openai/openrouter/deepseek/atlascloud/local/ +// anthropic/azure) — plus apiVersion/deployment/authScheme for azure's // deployment-scoped routing; {provider: "bedrock", model?, awsRegion?, // awsAccessKeyId?, awsSecretAccessKey?}, which SigV4-signs with the entered // AWS keys (written to secrets.env under the AWS_* names); or @@ -56,7 +56,7 @@ import { isValidEnvVarName, removeKeyFromSecretsEnv, writeKeyToSecretsEnv } from import { requestAutostartRefresh } from "./autostart-refresh"; import type { ProviderConfig, ProviderName, RuntimeConfig } from "../types"; -const SUPPORTED_PROVIDERS = ["openai", "codex", "openrouter", "deepseek", "local", "anthropic", "bedrock", "azure"] as const; +const SUPPORTED_PROVIDERS = ["openai", "codex", "openrouter", "deepseek", "atlascloud", "local", "anthropic", "bedrock", "azure"] as const; type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number]; // Env-keyed providers that authenticate via an env var written to @@ -72,6 +72,7 @@ const ENV_KEY_PROVIDERS: Record