feat(provider): add Gitlawb Opengateway provider (xiaomi-mimo + gmi-cloud)

Add two OpenAI-compatible API-key providers via the Gitlawb Opengateway
gateway at opengateway.gitlawb.com:

- gitlawb (alias glb): xiaomi-mimo endpoint with 5 MiMo models
- gitlawb-gmi (alias glb-gmi): gmi-cloud endpoint with 40+ models
  including GPT-5.x, Claude 4.x, DeepSeek, Gemini, Qwen, GLM, Kimi

Both providers include CLI-mimicking headers (User-Agent, X-Title,
HTTP-Referer) to avoid upstream rate limiting. GMI Cloud provider
has passthroughModels enabled since model access varies per API key.
This commit is contained in:
oyi77
2026-05-17 04:49:39 +07:00
parent 08f03a0fa6
commit b83d1a0fc8
3 changed files with 378 additions and 0 deletions

View File

@@ -422,6 +422,226 @@ const CHAT_OPENAI_COMPAT_MODELS: Record<string, RegistryModel[]> = {
{ 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<string, RegistryEntry> = {
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",

View File

@@ -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",

View File

@@ -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");
});
});