diff --git a/open-sse/providers/registry/alicode-intl.js b/open-sse/providers/registry/alicode-intl.js index b93d1d89..45d4f21d 100644 --- a/open-sse/providers/registry/alicode-intl.js +++ b/open-sse/providers/registry/alicode-intl.js @@ -3,18 +3,18 @@ export default { priority: 10, alias: "alicode-intl", display: { - name: "Alibaba Intl", + name: "Alibaba Coding", icon: "cloud", color: "#FF6A00", textIcon: "ALi", - website: "https://modelstudio.console.alibabacloud.com", + website: "https://www.alibabacloud.com/product/coding", notice: { - apiKeyUrl: "https://modelstudio.console.alibabacloud.com/?apiKey=1", + apiKeyUrl: "https://www.alibabacloud.com/product/coding", }, }, category: "apikey", transport: { - baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions", + baseUrl: "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions", headers: {}, quirks: { preserveCacheControl: true }, }, diff --git a/open-sse/providers/registry/alims-intl.js b/open-sse/providers/registry/alims-intl.js new file mode 100644 index 00000000..66e045fc --- /dev/null +++ b/open-sse/providers/registry/alims-intl.js @@ -0,0 +1,32 @@ +// Model Studio Intl — standard DashScope API keys (sk-...), NOT Coding Plan keys. +// Sibling of alicode-intl (Coding Plan). Two key types use two different hosts. +export default { + id: "alims-intl", + priority: 11, + alias: "alims-intl", + display: { + name: "Alibaba Studio", + icon: "cloud", + color: "#FF6A00", + textIcon: "ALi", + website: "https://modelstudio.console.alibabacloud.com", + notice: { + apiKeyUrl: "https://modelstudio.console.alibabacloud.com/?apiKey=1", + }, + }, + category: "apikey", + transport: { + baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions", + headers: {}, + quirks: { preserveCacheControl: true }, + }, + models: [ + { id: "qwen3.5-plus", name: "Qwen3.5 Plus" }, + { id: "kimi-k2.5", name: "Kimi K2.5" }, + { id: "glm-5", name: "GLM 5" }, + { id: "MiniMax-M2.5", name: "MiniMax M2.5" }, + { id: "qwen3-coder-next", name: "Qwen3 Coder Next" }, + { id: "qwen3-coder-plus", name: "Qwen3 Coder Plus" }, + { id: "glm-4.7", name: "GLM 4.7" }, + ], +}; diff --git a/open-sse/providers/registry/index.js b/open-sse/providers/registry/index.js index 7b565b7d..cc1025a1 100644 --- a/open-sse/providers/registry/index.js +++ b/open-sse/providers/registry/index.js @@ -98,6 +98,7 @@ import p95 from "./xai.js"; import p96 from "./xiaomi-mimo.js"; import p97 from "./xiaomi-tokenplan.js"; import p98 from "./youcom.js"; +import p99 from "./alims-intl.js"; export default [ p0, @@ -199,4 +200,5 @@ export default [ p96, p97, p98, + p99, ]; diff --git a/src/app/api/providers/[id]/models/route.js b/src/app/api/providers/[id]/models/route.js index 89b1ca4b..7418d826 100644 --- a/src/app/api/providers/[id]/models/route.js +++ b/src/app/api/providers/[id]/models/route.js @@ -229,6 +229,14 @@ const PROVIDER_MODELS_CONFIG = { authPrefix: "Bearer ", parseResponse: (data) => data.data || [] }, + "alims-intl": { + url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models", + method: "GET", + headers: { "Content-Type": "application/json" }, + authHeader: "Authorization", + authPrefix: "Bearer ", + parseResponse: (data) => data.data || [] + }, "volcengine-ark": createOpenAIModelsConfig("https://ark.cn-beijing.volces.com/api/coding/v3/models"), byteplus: createOpenAIModelsConfig("https://ark.ap-southeast.bytepluses.com/api/coding/v3/models"), diff --git a/src/app/api/providers/[id]/test/testUtils.js b/src/app/api/providers/[id]/test/testUtils.js index 8afdbf93..d69191b5 100644 --- a/src/app/api/providers/[id]/test/testUtils.js +++ b/src/app/api/providers/[id]/test/testUtils.js @@ -620,10 +620,13 @@ async function testApiKeyConnection(connection, effectiveProxy = null) { return { valid, error: valid ? null : "Invalid API key" }; } case "alicode": - case "alicode-intl": { - // Aliyun Coding Plan uses OpenAI-compatible API + case "alicode-intl": + case "alims-intl": { + // Aliyun Coding Plan uses OpenAI-compatible API; alims-intl uses Model Studio compatible-mode const aliBaseUrl = connection.provider === "alicode-intl" ? "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions" + : connection.provider === "alims-intl" + ? "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions" : "https://coding.dashscope.aliyuncs.com/v1/chat/completions"; const res = await fetchWithConnectionProxy(aliBaseUrl, { method: "POST", diff --git a/src/app/api/providers/validate/route.js b/src/app/api/providers/validate/route.js index d5684091..0be11b1e 100644 --- a/src/app/api/providers/validate/route.js +++ b/src/app/api/providers/validate/route.js @@ -301,11 +301,12 @@ export async function POST(request) { case "minimax": case "minimax-cn": case "alicode-intl": + case "alims-intl": case "alicode": case "agentrouter": { // Use baseUrl from PROVIDERS (DRY); separate openai-format vs claude-format flow const cfg = PROVIDERS[provider]; - const isOpenAiFormat = provider === "glm-cn" || provider === "alicode" || provider === "alicode-intl"; + const isOpenAiFormat = provider === "glm-cn" || provider === "alicode" || provider === "alicode-intl" || provider === "alims-intl"; if (isOpenAiFormat) { const testModel = getDefaultModel(provider); diff --git a/tests/__baseline__/alias-baseline.json b/tests/__baseline__/alias-baseline.json index 866b75c7..6e189878 100644 --- a/tests/__baseline__/alias-baseline.json +++ b/tests/__baseline__/alias-baseline.json @@ -98,6 +98,7 @@ "idToAlias": { "alicode": "alicode", "alicode-intl": "alicode-intl", + "alims-intl": "alims-intl", "anthropic": "anthropic", "antigravity": "ag", "assemblyai": "assemblyai", @@ -168,6 +169,7 @@ "ag", "alicode", "alicode-intl", + "alims-intl", "anthropic", "assemblyai", "black-forest-labs", diff --git a/tests/__baseline__/providers-baseline.json b/tests/__baseline__/providers-baseline.json index 22f4e74f..0c11eda0 100644 --- a/tests/__baseline__/providers-baseline.json +++ b/tests/__baseline__/providers-baseline.json @@ -1,6 +1,6 @@ { "alicode-intl": { - "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions", + "baseUrl": "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions", "headers": {}, "quirks": { "preserveCacheControl": true @@ -872,5 +872,13 @@ } } ] + }, + "alims-intl": { + "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions", + "headers": {}, + "quirks": { + "preserveCacheControl": true + }, + "format": "openai" } } \ No newline at end of file diff --git a/tests/unit/alicode-intl-endpoint-2591.test.js b/tests/unit/alicode-intl-endpoint-2591.test.js index 7779328f..4b0c5213 100644 --- a/tests/unit/alicode-intl-endpoint-2591.test.js +++ b/tests/unit/alicode-intl-endpoint-2591.test.js @@ -1,24 +1,35 @@ -// #2591 — Alibaba Intl (alicode-intl) must use the OpenAI-compatible-mode -// DashScope endpoint so standard DashScope API keys work. The previous -// coding-intl host only accepted Alibaba Coding Plan keys and rejected -// ordinary DashScope keys with "Invalid API key". +// #2591 — Alibaba Intl key types split across two hosts: +// - alicode-intl: Coding Plan keys (sk-sp-...) → coding-intl.dashscope.aliyuncs.com +// - alims-intl: standard DashScope API keys (sk-...) → dashscope-intl.aliyuncs.com/compatible-mode +// The two key types are NOT interchangeable across hosts. Split into two providers +// so each key type reaches its own host. import { describe, it, expect } from "vitest"; import alicodeIntl from "../../open-sse/providers/registry/alicode-intl.js"; +import alimsIntl from "../../open-sse/providers/registry/alims-intl.js"; -describe("alicode-intl endpoint (issue #2591)", () => { - it("routes to the compatible-mode DashScope endpoint", () => { +describe("alicode-intl endpoint (Coding Plan keys)", () => { + it("routes to the coding-intl host for Coding Plan keys", () => { expect(alicodeIntl.id).toBe("alicode-intl"); expect(alicodeIntl.transport.baseUrl).toBe( - "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions" + "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions" ); }); - it("does not use the coding-intl host that rejects standard keys", () => { - expect(alicodeIntl.transport.baseUrl).not.toContain("coding-intl.dashscope.aliyuncs.com"); - }); - it("keeps the chat/completions path and preserveCacheControl quirk", () => { expect(alicodeIntl.transport.baseUrl).toContain("/v1/chat/completions"); expect(alicodeIntl.transport.quirks.preserveCacheControl).toBe(true); }); }); + +describe("alims-intl endpoint (standard DashScope keys)", () => { + it("routes to the compatible-mode DashScope endpoint for standard keys", () => { + expect(alimsIntl.id).toBe("alims-intl"); + expect(alimsIntl.transport.baseUrl).toBe( + "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions" + ); + }); + + it("does not use the coding-intl host that rejects standard keys", () => { + expect(alimsIntl.transport.baseUrl).not.toContain("coding-intl.dashscope.aliyuncs.com"); + }); +});