refactor(gitlawb): extract shared opengateway validator factory, fix docs path in test

- Extract gitlawb/gitlawb-gmi validators into buildOpengatewayValidator factory
- Fix dockerignore-docs-coverage test: update stale docs/AUTO-COMBO.md -> docs/routing/AUTO-COMBO.md
This commit is contained in:
oyi77
2026-05-28 19:21:08 +07:00
parent bbf8d4ccb9
commit 471df45bcb
2 changed files with 56 additions and 58 deletions

View File

@@ -3492,6 +3492,55 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
}
}
/**
* Build Opengateway-style validators (xiaomi-mimo compatible).
* These providers share a POST /chat/completions auth check pattern and differ
* only in default baseUrl and test model name.
*/
function buildOpengatewayValidator(
defaultBaseUrl: string,
model: string
) {
return async ({ apiKey, providerSpecificData }: any) => {
try {
const baseUrl = normalizeBaseUrl(
providerSpecificData?.baseUrl || defaultBaseUrl
);
const chatUrl = `${baseUrl.replace(/\/chat\/completions$/, "")}/chat/completions`;
const res = await validationWrite(
chatUrl,
{
method: "POST",
headers: buildBearerHeaders(apiKey, providerSpecificData),
body: JSON.stringify({
model,
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
},
isLocal
);
if (res.status === 401 || res.status === 403) {
return { valid: false, error: "Invalid API key" };
}
// Any non-auth response (200, 400, 422, 429) means auth passed
return { valid: true, error: null };
} catch (error: any) {
return toValidationErrorResult(error);
}
};
}
// Same as buildOpengatewayValidator but returns an object spreadable into SPECIALTY_VALIDATORS.
// isLocal is captured via closure from the outer function scope.
function buildGitlawbValidators(
configs: [string, string, string][]
): Record<string, ReturnType<typeof buildOpengatewayValidator>> {
return Object.fromEntries(
configs.map(([id, baseUrl, model]) => [id, buildOpengatewayValidator(baseUrl, model)])
);
}
// ── Specialty provider validation ──
const SPECIALTY_VALIDATORS = {
jules: validateJulesProvider,
@@ -3745,62 +3794,11 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
},
// Gitlawb Opengateway — Xiaomi MiMo compatible, same /models endpoint limitation.
// Bypass /models probe in favor of chat/completions, matching xiaomi-mimo's pattern.
gitlawb: async ({ apiKey, providerSpecificData }: any) => {
try {
const baseUrl = normalizeBaseUrl(
providerSpecificData?.baseUrl || "https://opengateway.gitlawb.com/v1/xiaomi-mimo"
);
const chatUrl = `${baseUrl.replace(/\/chat\/completions$/, "")}/chat/completions`;
const res = await validationWrite(
chatUrl,
{
method: "POST",
headers: buildBearerHeaders(apiKey, providerSpecificData),
body: JSON.stringify({
model: "mimo-v2.5-pro",
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
},
isLocal
);
if (res.status === 401 || res.status === 403) {
return { valid: false, error: "Invalid API key" };
}
// Any non-auth response (200, 400, 422, 429) means auth passed
return { valid: true, error: null };
} catch (error: any) {
return toValidationErrorResult(error);
}
},
"gitlawb-gmi": async ({ apiKey, providerSpecificData }: any) => {
try {
const baseUrl = normalizeBaseUrl(
providerSpecificData?.baseUrl || "https://opengateway.gitlawb.com/v1/gmi-cloud"
);
const chatUrl = `${baseUrl.replace(/\/chat\/completions$/, "")}/chat/completions`;
const res = await validationWrite(
chatUrl,
{
method: "POST",
headers: buildBearerHeaders(apiKey, providerSpecificData),
body: JSON.stringify({
model: "XiaomiMiMo/MiMo-V2.5-Pro",
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
},
isLocal
);
if (res.status === 401 || res.status === 403) {
return { valid: false, error: "Invalid API key" };
}
// Any non-auth response (200, 400, 422, 429) means auth passed
return { valid: true, error: null };
} catch (error: any) {
return toValidationErrorResult(error);
}
},
// Uses a factory to share validation logic across Opengateway provider variants.
...buildGitlawbValidators([
["gitlawb", "https://opengateway.gitlawb.com/v1/xiaomi-mimo", "mimo-v2.5-pro"],
["gitlawb-gmi", "https://opengateway.gitlawb.com/v1/gmi-cloud", "XiaomiMiMo/MiMo-V2.5-Pro"],
]),
// Search providers — use factored validator
...Object.fromEntries(
Object.entries(SEARCH_VALIDATOR_CONFIGS).map(([id, configFn]) => [

View File

@@ -25,7 +25,7 @@ const DOCKERIGNORE = path.resolve(REPO_ROOT, ".dockerignore");
const REQUIRED_DOCS = [
"docs/README.md",
"docs/PROVIDERS.md",
"docs/AUTO-COMBO.md",
"docs/routing/AUTO-COMBO.md",
"docs/guides/SETUP_GUIDE.md",
"docs/guides/TROUBLESHOOTING.md",
"docs/reference/API_REFERENCE.md",
@@ -154,7 +154,7 @@ test("#2348 .dockerignore keeps every doc the in-product viewer needs", () => {
test("#2348 .dockerignore still excludes the heavy i18n tree", () => {
const parsed = parseDockerignore(fs.readFileSync(DOCKERIGNORE, "utf8"));
const heavy = "docs/i18n/pt-BR/docs/AUTO-COMBO.md";
const heavy = "docs/i18n/pt-BR/docs/routing/AUTO-COMBO.md";
assert.ok(
isIgnored(heavy, parsed),
`${heavy} should be excluded from Docker context but is not — image size will balloon`