From 3c5ca41bd42563edddb8d27e6979eaa6ae9f19f8 Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:45:34 +0800 Subject: [PATCH] Add Atlas Cloud model shortcut --- src/loader.ts | 46 +++++++++++++- test/loader-atlascloud.test.ts | 113 +++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 test/loader-atlascloud.test.ts diff --git a/src/loader.ts b/src/loader.ts index b8d193e..29cc0d6 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -78,6 +78,44 @@ function parseModelString(modelStr: string): { provider: string; modelId: string }; } +const ATLASCLOUD_PROVIDER_ALIASES = new Set(["atlascloud", "atlas-cloud", "atlas"]); +const ATLASCLOUD_DEFAULT_BASE_URL = "https://api.atlascloud.ai/v1"; +const ATLASCLOUD_API_KEY_ENV_VARS = ["ATLASCLOUD_API_KEY", "ATLAS_CLOUD_API_KEY"]; +const ATLASCLOUD_BASE_URL_ENV_VARS = [ + "ATLASCLOUD_API_BASE", + "ATLASCLOUD_BASE_URL", + "ATLAS_CLOUD_API_BASE", + "ATLAS_CLOUD_BASE_URL", +]; + +function firstEnv(names: string[]): string | undefined { + for (const name of names) { + const value = process.env[name]; + if (value) return value; + } + return undefined; +} + +function isAtlasCloudProvider(provider: string): boolean { + return ATLASCLOUD_PROVIDER_ALIASES.has(provider.toLowerCase()); +} + +function getProviderApiKey(provider: string): string | undefined { + if (isAtlasCloudProvider(provider)) { + return firstEnv(ATLASCLOUD_API_KEY_ENV_VARS); + } + + const envName = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`; + return process.env[envName] || process.env.LYZR_API_KEY; +} + +function getProviderBaseUrl(provider: string): string | undefined { + if (isAtlasCloudProvider(provider)) { + return firstEnv(ATLASCLOUD_BASE_URL_ENV_VARS) || ATLASCLOUD_DEFAULT_BASE_URL; + } + return undefined; +} + /** * Create a custom Model for any OpenAI-compatible endpoint. * Used when model string contains @baseUrl or GITAGENT_MODEL_BASE_URL is set. @@ -389,6 +427,7 @@ Do NOT track trivial single-command tasks (e.g. "what time is it"). But DO check const { provider, modelId } = parseModelString(modelStr); const envBaseUrl = process.env.GITAGENT_MODEL_BASE_URL; + const providerBaseUrl = getProviderBaseUrl(provider); let model: Model; if (modelId.includes("@")) { @@ -398,6 +437,9 @@ Do NOT track trivial single-command tasks (e.g. "what time is it"). But DO check } else if (envBaseUrl) { // Environment-specified base URL overrides all providers model = createCustomModel(provider, modelId, envBaseUrl); + } else if (providerBaseUrl) { + // Provider shortcut for known OpenAI-compatible endpoints. + model = createCustomModel(provider, modelId, providerBaseUrl); } else { // Standard registered model model = getModel(provider as any, modelId as any); @@ -409,8 +451,8 @@ Do NOT track trivial single-command tasks (e.g. "what time is it"). But DO check // pi-ai finds OPENAI_API_KEY. The actual auth happens via custom headers on the model. const knownProviders = new Set(["openai", "anthropic", "google", "google-vertex", "groq", "cerebras", "xai", "openrouter", "mistral", "amazon-bedrock", "azure-openai-responses", "huggingface", "opencode", "kimi-coding", "github-copilot"]); if (model.baseUrl && !knownProviders.has(provider)) { - // Use provider-specific key if available, otherwise use LYZR key or dummy - const providerKey = process.env[`${provider.toUpperCase()}_API_KEY`] || process.env.LYZR_API_KEY; + // Use provider-specific key aliases when available. + const providerKey = getProviderApiKey(provider); if (providerKey && !process.env.OPENAI_API_KEY) { process.env.OPENAI_API_KEY = providerKey; } diff --git a/test/loader-atlascloud.test.ts b/test/loader-atlascloud.test.ts new file mode 100644 index 0000000..d221603 --- /dev/null +++ b/test/loader-atlascloud.test.ts @@ -0,0 +1,113 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; + +import { loadAgent } from "../dist/loader.js"; + +const ENV_NAMES = [ + "OPENAI_API_KEY", + "ATLASCLOUD_API_KEY", + "ATLAS_CLOUD_API_KEY", + "ATLASCLOUD_API_BASE", + "ATLASCLOUD_BASE_URL", + "ATLAS_CLOUD_API_BASE", + "ATLAS_CLOUD_BASE_URL", + "GITAGENT_MODEL_BASE_URL", +]; + +async function withEnv( + values: Record, + fn: () => Promise, +): Promise { + const previous = new Map(ENV_NAMES.map((name) => [name, process.env[name]])); + for (const name of ENV_NAMES) { + delete process.env[name]; + } + for (const [name, value] of Object.entries(values)) { + if (value === undefined) { + delete process.env[name]; + } else { + process.env[name] = value; + } + } + + try { + await fn(); + } finally { + for (const name of ENV_NAMES) { + const value = previous.get(name); + if (value === undefined) { + delete process.env[name]; + } else { + process.env[name] = value; + } + } + } +} + +async function makeAgentDir(model: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "gitagent-atlascloud-")); + await writeFile( + join(dir, "agent.yaml"), + [ + 'spec_version: "0.1.0"', + "name: atlascloud-test", + "version: 0.1.0", + "description: Test agent", + "model:", + ` preferred: "${model}"`, + " fallback: []", + "tools: []", + "runtime:", + " max_turns: 1", + "", + ].join("\n"), + "utf-8", + ); + return dir; +} + +test("atlascloud model shortcut creates an OpenAI-compatible Atlas Cloud model", async () => { + await withEnv({ ATLASCLOUD_API_KEY: "atlas-key" }, async () => { + const dir = await makeAgentDir("atlascloud:qwen/qwen3.5-flash"); + const loaded = await loadAgent(dir); + + assert.equal(loaded.model.id, "qwen/qwen3.5-flash"); + assert.equal(loaded.model.baseUrl, "https://api.atlascloud.ai/v1"); + assert.equal(loaded.model.api, "openai-completions"); + assert.equal(loaded.model.provider, "openai"); + assert.equal(process.env.OPENAI_API_KEY, "atlas-key"); + }); +}); + +test("atlas-cloud alias supports key and base URL environment aliases", async () => { + await withEnv( + { + ATLAS_CLOUD_API_KEY: "alias-key", + ATLASCLOUD_BASE_URL: "https://atlas.example.test/v1", + }, + async () => { + const dir = await makeAgentDir("atlas-cloud:deepseek-ai/deepseek-v4-pro"); + const loaded = await loadAgent(dir); + + assert.equal(loaded.model.id, "deepseek-ai/deepseek-v4-pro"); + assert.equal(loaded.model.baseUrl, "https://atlas.example.test/v1"); + assert.equal(loaded.model.provider, "openai"); + assert.equal(process.env.OPENAI_API_KEY, "alias-key"); + }, + ); +}); + +test("explicit atlas endpoint keeps the inline base URL and uses Atlas key aliases", async () => { + await withEnv({ ATLAS_CLOUD_API_KEY: "inline-key" }, async () => { + const dir = await makeAgentDir("atlas:qwen/qwen3.5-flash@https://proxy.example.test/v1"); + const loaded = await loadAgent(dir); + + assert.equal(loaded.model.id, "qwen/qwen3.5-flash"); + assert.equal(loaded.model.baseUrl, "https://proxy.example.test/v1"); + assert.equal(loaded.model.provider, "openai"); + assert.equal(process.env.OPENAI_API_KEY, "inline-key"); + }); +});