Add Atlas Cloud provider support (#896)
* Add Atlas Cloud provider support * fix: restore files removed by Atlas provider PR --------- Co-authored-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com>
This commit is contained in:
@@ -372,12 +372,13 @@ export async function POST(req: Request) {
|
||||
break
|
||||
}
|
||||
|
||||
// GLM, Qwen, Kimi, Qiniu, Novita, MiMo - OpenAI compatible
|
||||
// GLM, Qwen, Kimi, Qiniu, Novita, MiMo, Atlas Cloud - OpenAI compatible
|
||||
case "glm":
|
||||
case "qwen":
|
||||
case "kimi":
|
||||
case "qiniu":
|
||||
case "novita":
|
||||
case "atlascloud":
|
||||
case "mimo": {
|
||||
const baseURL =
|
||||
baseUrl ||
|
||||
|
||||
@@ -194,3 +194,8 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
||||
# Get your API key from: https://platform.xiaomimimo.com/
|
||||
# MIMO_API_KEY=your_mimo_api_key
|
||||
# MIMO_BASE_URL=https://api.xiaomimimo.com/v1 # Optional, default. Token Plan users: https://token-plan-cn.xiaomimimo.com/v1
|
||||
|
||||
# Atlas Cloud Configuration (Optional)
|
||||
# Get your API key from: https://www.atlascloud.ai/console/api-keys
|
||||
# ATLASCLOUD_API_KEY=your_atlascloud_api_key
|
||||
# ATLASCLOUD_BASE_URL=https://api.atlascloud.ai/v1 # Optional, default. LLM chat endpoint; media generation uses a separate API.
|
||||
|
||||
+6
-2
@@ -118,6 +118,7 @@ const ALLOWED_CLIENT_PROVIDERS: ProviderName[] = [
|
||||
"minimax",
|
||||
"novita",
|
||||
"mimo",
|
||||
"atlascloud",
|
||||
]
|
||||
|
||||
// Bedrock provider options for Anthropic beta features
|
||||
@@ -543,6 +544,7 @@ function buildProviderOptions(
|
||||
case "kimi":
|
||||
case "qiniu":
|
||||
case "novita":
|
||||
case "atlascloud":
|
||||
case "mimo": {
|
||||
// These providers don't have reasoning configs in AI SDK yet
|
||||
// Gateway passes through to underlying providers which handle their own configs
|
||||
@@ -581,6 +583,7 @@ export const PROVIDER_ENV_VARS: Record<ProviderName, string | null> = {
|
||||
minimax: "MINIMAX_API_KEY",
|
||||
novita: "NOVITA_API_KEY",
|
||||
mimo: "MIMO_API_KEY",
|
||||
atlascloud: "ATLASCLOUD_API_KEY",
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1370,7 +1373,8 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
||||
case "glm":
|
||||
case "qwen":
|
||||
case "qiniu":
|
||||
case "novita": {
|
||||
case "novita":
|
||||
case "atlascloud": {
|
||||
const envVar = PROVIDER_ENV_VARS[provider]
|
||||
if (!envVar) {
|
||||
throw new Error(
|
||||
@@ -1414,7 +1418,7 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, aihubmix, deepseek, siliconflow, sglang, gateway, edgeone, doubao, modelscope, glm, qwen, qiniu, kimi, minimax, novita, mimo`,
|
||||
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, aihubmix, deepseek, siliconflow, sglang, gateway, edgeone, doubao, modelscope, glm, qwen, qiniu, kimi, minimax, novita, mimo, atlascloud`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export type ProviderName =
|
||||
| "minimax"
|
||||
| "novita"
|
||||
| "mimo"
|
||||
| "atlascloud"
|
||||
|
||||
// Individual model configuration
|
||||
export interface ModelConfig {
|
||||
@@ -116,6 +117,7 @@ export const PROVIDER_LOGO_MAP: Record<string, string> = {
|
||||
minimax: "minimax",
|
||||
novita: "novita",
|
||||
mimo: "xiaomi",
|
||||
atlascloud: "openai",
|
||||
}
|
||||
|
||||
// Provider metadata
|
||||
@@ -206,6 +208,10 @@ export const PROVIDER_INFO: Record<
|
||||
label: "MiMo (Xiaomi)",
|
||||
defaultBaseUrl: "https://api.xiaomimimo.com/v1",
|
||||
},
|
||||
atlascloud: {
|
||||
label: "Atlas Cloud",
|
||||
defaultBaseUrl: "https://api.atlascloud.ai/v1",
|
||||
},
|
||||
}
|
||||
|
||||
// Suggested models per provider for quick add
|
||||
@@ -444,6 +450,7 @@ export const SUGGESTED_MODELS: Partial<Record<ProviderName, string[]>> = {
|
||||
"deepseek/deepseek-v4-flash",
|
||||
],
|
||||
mimo: ["mimo-v2.5-pro", "mimo-v2.5"],
|
||||
atlascloud: ["qwen/qwen3.5-flash", "deepseek-ai/deepseek-v4-pro"],
|
||||
}
|
||||
|
||||
// Helper to generate UUID
|
||||
|
||||
@@ -206,6 +206,16 @@ vi.mock("@aihubmix/ai-sdk-provider", () => {
|
||||
return { aihubmix: mockAihubmix, createAihubmix: mockCreateAihubmix }
|
||||
})
|
||||
|
||||
vi.mock("@ai-sdk/openai", () => {
|
||||
const mockModel = { modelId: "test-model" }
|
||||
const mockChat = vi.fn(() => mockModel)
|
||||
const mockProviderFn = vi.fn(() => mockModel) as any
|
||||
mockProviderFn.chat = mockChat
|
||||
const mockCreateOpenAI = vi.fn(() => mockProviderFn)
|
||||
const mockOpenai = vi.fn(() => mockModel)
|
||||
return { createOpenAI: mockCreateOpenAI, openai: mockOpenai }
|
||||
})
|
||||
|
||||
describe("AIHubMix provider", () => {
|
||||
let createAihubmixMock: ReturnType<typeof vi.fn>
|
||||
const savedEnv: Record<string, string | undefined> = {}
|
||||
@@ -262,6 +272,54 @@ describe("AIHubMix provider", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("Atlas Cloud provider", () => {
|
||||
let createOpenAIMock: ReturnType<typeof vi.fn>
|
||||
const savedEnv: Record<string, string | undefined> = {}
|
||||
|
||||
beforeEach(async () => {
|
||||
savedEnv.ATLASCLOUD_API_KEY = process.env.ATLASCLOUD_API_KEY
|
||||
savedEnv.ATLASCLOUD_BASE_URL = process.env.ATLASCLOUD_BASE_URL
|
||||
delete process.env.ATLASCLOUD_BASE_URL
|
||||
|
||||
const mod = await import("@ai-sdk/openai")
|
||||
createOpenAIMock = mod.createOpenAI as ReturnType<typeof vi.fn>
|
||||
createOpenAIMock.mockClear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env.ATLASCLOUD_API_KEY = savedEnv.ATLASCLOUD_API_KEY
|
||||
process.env.ATLASCLOUD_BASE_URL = savedEnv.ATLASCLOUD_BASE_URL
|
||||
})
|
||||
|
||||
it("uses Atlas Cloud default endpoint with ATLASCLOUD_API_KEY", () => {
|
||||
process.env.ATLASCLOUD_API_KEY = "server-atlas-key"
|
||||
|
||||
getAIModel({
|
||||
provider: "atlascloud",
|
||||
modelId: "qwen/qwen3.5-flash",
|
||||
})
|
||||
|
||||
expect(createOpenAIMock).toHaveBeenCalledWith({
|
||||
apiKey: "server-atlas-key",
|
||||
baseURL: "https://api.atlascloud.ai/v1",
|
||||
})
|
||||
})
|
||||
|
||||
it("uses custom Atlas Cloud base URL when provided", () => {
|
||||
getAIModel({
|
||||
provider: "atlascloud",
|
||||
apiKey: "client-atlas-key",
|
||||
baseUrl: "https://proxy.example.com/v1",
|
||||
modelId: "deepseek-ai/deepseek-v4-pro",
|
||||
})
|
||||
|
||||
expect(createOpenAIMock).toHaveBeenCalledWith({
|
||||
apiKey: "client-atlas-key",
|
||||
baseURL: "https://proxy.example.com/v1",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Kimi provider uses createDeepSeek for reasoning_content support", () => {
|
||||
let createDeepSeekMock: ReturnType<typeof vi.fn>
|
||||
const savedEnv: Record<string, string | undefined> = {}
|
||||
|
||||
@@ -39,6 +39,22 @@ describe("ServerModelsConfigSchema", () => {
|
||||
expect(() => ServerModelsConfigSchema.parse(config)).not.toThrow()
|
||||
})
|
||||
|
||||
it("accepts Atlas Cloud provider names", () => {
|
||||
const config: ServerModelsConfig = {
|
||||
providers: [
|
||||
{
|
||||
name: "Atlas Cloud Server",
|
||||
provider: "atlascloud",
|
||||
models: ["qwen/qwen3.5-flash"],
|
||||
apiKeyEnv: "ATLASCLOUD_API_KEY",
|
||||
baseUrlEnv: "ATLASCLOUD_BASE_URL",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
expect(() => ServerModelsConfigSchema.parse(config)).not.toThrow()
|
||||
})
|
||||
|
||||
it("rejects invalid provider names", () => {
|
||||
const invalidConfig = {
|
||||
providers: [
|
||||
|
||||
Reference in New Issue
Block a user