diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index 682395ebde..5d74a7aedc 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -422,6 +422,226 @@ const CHAT_OPENAI_COMPAT_MODELS: Record = { { id: "mimo-v2-omni", name: "MiMo-V2-Omni", contextLength: 262144, maxOutputTokens: 131072 }, { id: "mimo-v2-flash", name: "MiMo-V2-Flash", contextLength: 262144, maxOutputTokens: 65536 }, ], + gitlawb: [ + { id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", contextLength: 1048576, maxOutputTokens: 131072 }, + { id: "mimo-v2.5", name: "MiMo-V2.5", contextLength: 1048576, maxOutputTokens: 131072 }, + { id: "mimo-v2-pro", name: "MiMo-V2-Pro", contextLength: 262144, maxOutputTokens: 131072 }, + { id: "mimo-v2-omni", name: "MiMo-V2-Omni", contextLength: 262144, maxOutputTokens: 131072 }, + { id: "mimo-v2-flash", name: "MiMo-V2-Flash", contextLength: 262144, maxOutputTokens: 65536 }, + ], + "gitlawb-gmi": [ + { + id: "XiaomiMiMo/MiMo-V2.5-Pro", + name: "MiMo-V2.5-Pro (GMI)", + contextLength: 1050000, + maxOutputTokens: 131072, + }, + { + id: "XiaomiMiMo/MiMo-V2.5", + name: "MiMo-V2.5 (GMI)", + contextLength: 1050000, + maxOutputTokens: 131072, + }, + { id: "openai/gpt-5.5", name: "GPT-5.5", contextLength: 1050000, maxOutputTokens: 131072 }, + { + id: "openai/gpt-5.4-pro", + name: "GPT-5.4 Pro", + contextLength: 409600, + maxOutputTokens: 131072, + }, + { id: "openai/gpt-5.4", name: "GPT-5.4", contextLength: 409600, maxOutputTokens: 131072 }, + { + id: "openai/gpt-5.4-mini", + name: "GPT-5.4 Mini", + contextLength: 409600, + maxOutputTokens: 131072, + }, + { + id: "openai/gpt-5.4-nano", + name: "GPT-5.4 Nano", + contextLength: 409600, + maxOutputTokens: 131072, + }, + { + id: "openai/gpt-5.3-codex", + name: "GPT-5.3 Codex", + contextLength: 409600, + maxOutputTokens: 131072, + }, + { + id: "openai/gpt-5.2-codex", + name: "GPT-5.2 Codex", + contextLength: 409600, + maxOutputTokens: 131072, + }, + { id: "openai/gpt-5.2", name: "GPT-5.2", contextLength: 409600, maxOutputTokens: 131072 }, + { id: "openai/gpt-5.1", name: "GPT-5.1", contextLength: 409600, maxOutputTokens: 131072 }, + { id: "openai/gpt-5", name: "GPT-5", contextLength: 409600, maxOutputTokens: 131072 }, + { id: "openai/gpt-4o", name: "GPT-4o", contextLength: 131072, maxOutputTokens: 16384 }, + { + id: "openai/gpt-4o-mini", + name: "GPT-4o Mini", + contextLength: 131072, + maxOutputTokens: 16384, + }, + { + id: "anthropic/claude-opus-4.7", + name: "Claude Opus 4.7", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-opus-4.6", + name: "Claude Opus 4.6", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-opus-4.5", + name: "Claude Opus 4.5", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-opus-4.1", + name: "Claude Opus 4.1", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-sonnet-4.6", + name: "Claude Sonnet 4.6", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-sonnet-4.5", + name: "Claude Sonnet 4.5", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-sonnet-4", + name: "Claude Sonnet 4", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "anthropic/claude-haiku-4.5", + name: "Claude Haiku 4.5", + contextLength: 409600, + maxOutputTokens: 131072, + targetFormat: "claude", + }, + { + id: "deepseek-ai/DeepSeek-V4-Pro", + name: "DeepSeek V4 Pro", + contextLength: 1048576, + maxOutputTokens: 131072, + supportsReasoning: true, + }, + { + id: "deepseek-ai/DeepSeek-V4-Flash", + name: "DeepSeek V4 Flash", + contextLength: 1048575, + maxOutputTokens: 131072, + supportsReasoning: true, + }, + { + id: "deepseek-ai/DeepSeek-R1-0528", + name: "DeepSeek R1", + contextLength: 163840, + maxOutputTokens: 131072, + supportsReasoning: true, + }, + { + id: "deepseek-ai/DeepSeek-V3.2", + name: "DeepSeek V3.2", + contextLength: 163840, + maxOutputTokens: 131072, + }, + { + id: "google/gemini-3.1-pro-preview", + name: "Gemini 3.1 Pro", + contextLength: 1048576, + maxOutputTokens: 131072, + }, + { + id: "google/gemini-3.1-flash-lite-preview", + name: "Gemini 3.1 Flash Lite", + contextLength: 1048576, + maxOutputTokens: 131072, + }, + { + id: "google/gemini-3-flash-preview", + name: "Gemini 3 Flash", + contextLength: 1048576, + maxOutputTokens: 131072, + }, + { id: "zai-org/GLM-5.1-FP8", name: "GLM-5.1", contextLength: 202752, maxOutputTokens: 131072 }, + { id: "zai-org/GLM-5-FP8", name: "GLM-5", contextLength: 202752, maxOutputTokens: 131072 }, + { + id: "moonshotai/Kimi-K2.6", + name: "Kimi K2.6", + contextLength: 65536, + maxOutputTokens: 131072, + }, + { + id: "moonshotai/Kimi-K2.5", + name: "Kimi K2.5", + contextLength: 262144, + maxOutputTokens: 131072, + }, + { + id: "MiniMaxAI/MiniMax-M2.7", + name: "MiniMax M2.7", + contextLength: 196608, + maxOutputTokens: 131072, + }, + { + id: "MiniMaxAI/MiniMax-M2.5", + name: "MiniMax M2.5", + contextLength: 196608, + maxOutputTokens: 131072, + }, + { + id: "Qwen/Qwen3.6-Max-Preview", + name: "Qwen3.6 Max", + contextLength: 262144, + maxOutputTokens: 131072, + }, + { + id: "Qwen/Qwen3.6-Plus", + name: "Qwen3.6 Plus", + contextLength: 262144, + maxOutputTokens: 131072, + }, + { + id: "Qwen/Qwen3.5-397B-A17B", + name: "Qwen3.5 397B", + contextLength: 262144, + maxOutputTokens: 131072, + }, + { + id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", + name: "Qwen3 Coder 480B", + contextLength: 262128, + maxOutputTokens: 131072, + }, + { + id: "nvidia/NVIDIA-Nemotron-3-Nano-Omni", + name: "Nemotron 3 Nano", + contextLength: 262144, + maxOutputTokens: 131072, + }, + ], "inference-net": buildModels([ "meta-llama/Llama-3.3-70B-Instruct", "deepseek-ai/DeepSeek-R1", @@ -2785,6 +3005,39 @@ export const REGISTRY: Record = { models: CHAT_OPENAI_COMPAT_MODELS["xiaomi-mimo"], }, + gitlawb: { + id: "gitlawb", + alias: "glb", + format: "openai", + executor: "default", + baseUrl: "https://opengateway.gitlawb.com/v1/xiaomi-mimo", + authType: "apikey", + authHeader: "bearer", + headers: { + "User-Agent": "OpenClaude/1.0 (linux; x86_64)", + "X-Title": "OpenClaude CLI", + "HTTP-Referer": "https://github.com/Gitlawb/openclaude", + }, + models: CHAT_OPENAI_COMPAT_MODELS["gitlawb"], + }, + + "gitlawb-gmi": { + id: "gitlawb-gmi", + alias: "glb-gmi", + format: "openai", + executor: "default", + baseUrl: "https://opengateway.gitlawb.com/v1/gmi-cloud", + authType: "apikey", + authHeader: "bearer", + headers: { + "User-Agent": "OpenClaude/1.0 (linux; x86_64)", + "X-Title": "OpenClaude CLI", + "HTTP-Referer": "https://github.com/Gitlawb/openclaude", + }, + passthroughModels: true, + models: CHAT_OPENAI_COMPAT_MODELS["gitlawb-gmi"], + }, + "inference-net": { id: "inference-net", alias: "inet", diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index 2f091d04c5..088dfa9eaf 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -1228,6 +1228,30 @@ export const APIKEY_PROVIDERS = { textIcon: "MM", website: "https://mimo.mi.com", }, + gitlawb: { + id: "gitlawb", + alias: "glb", + name: "Gitlawb Opengateway (MiMo)", + icon: "hub", + color: "#10B981", + textIcon: "GLB", + website: "https://opengateway.gitlawb.com", + hasFree: true, + freeNote: "Free tier available — no credit card required", + apiHint: "Get your API key from Gitlawb Opengateway dashboard.", + }, + "gitlawb-gmi": { + id: "gitlawb-gmi", + alias: "glb-gmi", + name: "Gitlawb Opengateway (GMI Cloud)", + icon: "hub", + color: "#10B981", + textIcon: "GMI", + website: "https://opengateway.gitlawb.com", + hasFree: true, + freeNote: "Free tier available — no credit card required", + apiHint: "Get your API key from Gitlawb Opengateway dashboard.", + }, "inference-net": { id: "inference-net", alias: "inet", diff --git a/tests/unit/gitlawb-provider.test.ts b/tests/unit/gitlawb-provider.test.ts new file mode 100644 index 0000000000..f8b15a5051 --- /dev/null +++ b/tests/unit/gitlawb-provider.test.ts @@ -0,0 +1,101 @@ +import { describe, it } from "node:test"; +import assert from "node:assert"; + +describe("Gitlawb Opengateway providers", () => { + describe("gitlawb (xiaomi-mimo)", () => { + it("should be registered in APIKEY_PROVIDERS", async () => { + const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); + const provider = APIKEY_PROVIDERS["gitlawb"]; + assert.ok(provider, "gitlawb should exist in APIKEY_PROVIDERS"); + assert.strictEqual(provider.id, "gitlawb"); + assert.strictEqual(provider.alias, "glb"); + assert.ok(provider.name.includes("Gitlawb")); + assert.strictEqual(provider.hasFree, true); + }); + + it("should have registry entry with correct baseUrl", async () => { + const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); + const entry = REGISTRY["gitlawb"]; + assert.ok(entry, "gitlawb should exist in REGISTRY"); + assert.strictEqual(entry.baseUrl, "https://opengateway.gitlawb.com/v1/xiaomi-mimo"); + assert.strictEqual(entry.format, "openai"); + assert.strictEqual(entry.executor, "default"); + assert.strictEqual(entry.authType, "apikey"); + }); + + it("should have CLI-mimicking headers", async () => { + const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); + const entry = REGISTRY["gitlawb"]; + assert.ok(entry.headers, "should have headers"); + assert.ok(entry.headers["User-Agent"].includes("OpenClaude")); + assert.strictEqual(entry.headers["X-Title"], "OpenClaude CLI"); + assert.ok(entry.headers["HTTP-Referer"].includes("Gitlawb/openclaude")); + }); + + it("should list MiMo models", async () => { + const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); + const entry = REGISTRY["gitlawb"]; + assert.ok( + entry.models.length >= 5, + `should have at least 5 models, got ${entry.models.length}` + ); + const pro = entry.models.find((m: any) => m.id === "mimo-v2.5-pro"); + assert.ok(pro, "mimo-v2.5-pro should be listed"); + assert.strictEqual(pro.contextLength, 1048576); + }); + }); + + describe("gitlawb-gmi (gmi-cloud)", () => { + it("should be registered in APIKEY_PROVIDERS", async () => { + const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); + const provider = APIKEY_PROVIDERS["gitlawb-gmi"]; + assert.ok(provider, "gitlawb-gmi should exist in APIKEY_PROVIDERS"); + assert.strictEqual(provider.id, "gitlawb-gmi"); + assert.strictEqual(provider.alias, "glb-gmi"); + assert.strictEqual(provider.hasFree, true); + }); + + it("should have registry entry with gmi-cloud baseUrl", async () => { + const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); + const entry = REGISTRY["gitlawb-gmi"]; + assert.ok(entry, "gitlawb-gmi should exist in REGISTRY"); + assert.strictEqual(entry.baseUrl, "https://opengateway.gitlawb.com/v1/gmi-cloud"); + assert.strictEqual(entry.format, "openai"); + assert.strictEqual(entry.authType, "apikey"); + }); + + it("should list GPT, Claude, DeepSeek, Gemini models", async () => { + const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); + const entry = REGISTRY["gitlawb-gmi"]; + assert.ok(entry.models.length >= 30, `should have 30+ models, got ${entry.models.length}`); + + const modelIds = entry.models.map((m: any) => m.id); + assert.ok(modelIds.includes("openai/gpt-5.5"), "should have GPT-5.5"); + assert.ok( + modelIds.some((id: string) => id.startsWith("anthropic/claude")), + "should have Claude models" + ); + assert.ok( + modelIds.some((id: string) => id.startsWith("deepseek-ai/")), + "should have DeepSeek models" + ); + assert.ok( + modelIds.some((id: string) => id.startsWith("google/gemini")), + "should have Gemini models" + ); + }); + + it("should have CLI-mimicking headers", async () => { + const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); + const entry = REGISTRY["gitlawb-gmi"]; + assert.ok(entry.headers, "should have headers"); + assert.ok(entry.headers["User-Agent"].includes("OpenClaude")); + }); + }); + + it("both providers should pass schema validation", async () => { + const { AI_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); + assert.ok(AI_PROVIDERS["gitlawb"], "gitlawb should be in AI_PROVIDERS"); + assert.ok(AI_PROVIDERS["gitlawb-gmi"], "gitlawb-gmi should be in AI_PROVIDERS"); + }); +});