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
46 changes: 44 additions & 2 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<any>;
if (modelId.includes("@")) {
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
113 changes: 113 additions & 0 deletions test/loader-atlascloud.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | undefined>,
fn: () => Promise<void>,
): Promise<void> {
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<string> {
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");
});
});