Files
OmniRoute/open-sse/config/glmProvider.ts
diegosouzapw bbecbccb0a fix(cli): harden setup, doctor, and backup workflows
Hide admin password entry during setup, make doctor degrade to warnings
when source-only runtime checks are unavailable, and improve stop
behavior by attempting graceful shutdown before force killing ports.

Also use SQLite's backup API for safer snapshots under WAL, align CLI
key writes with the current provider_connections schema, and include
follow-on compatibility fixes for GLM provider detection, stream error
sanitization, and auth-aware test coverage.
2026-05-11 09:13:49 -03:00

392 lines
12 KiB
TypeScript

import { ANTHROPIC_VERSION_HEADER } from "./anthropicHeaders.ts";
type JsonRecord = Record<string, unknown>;
export type GlmApiRegion = "international" | "china";
export type GlmTransport = "openai" | "anthropic";
export const GLM_DEFAULT_BASE_URLS = Object.freeze({
international: "https://api.z.ai/api/coding/paas/v4/chat/completions",
china: "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
});
export const GLM_ANTHROPIC_DEFAULT_BASE_URLS = Object.freeze({
international: "https://api.z.ai/api/anthropic/v1/messages",
china: "https://open.bigmodel.cn/api/anthropic/v1/messages",
});
export const GLM_SHARED_MODELS = Object.freeze([
{
id: "glm-5.1",
name: "GLM 5.1",
contextLength: 204800,
maxOutputTokens: 131072,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-5",
name: "GLM 5",
contextLength: 200000,
maxOutputTokens: 131072,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-5-turbo",
name: "GLM 5 Turbo",
contextLength: 200000,
maxOutputTokens: 131072,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-4.7-flash",
name: "GLM 4.7 Flash",
contextLength: 200000,
maxOutputTokens: 131072,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-4.7",
name: "GLM 4.7",
contextLength: 200000,
maxOutputTokens: 131072,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-4.6v",
name: "GLM 4.6V (Vision)",
contextLength: 128000,
maxOutputTokens: 32768,
toolCalling: true,
supportsReasoning: true,
supportsVision: true,
},
{
id: "glm-4.6",
name: "GLM 4.6",
contextLength: 200000,
maxOutputTokens: 32768,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-4.5v",
name: "GLM 4.5V (Vision)",
contextLength: 16000,
maxOutputTokens: 32768,
toolCalling: true,
supportsReasoning: true,
supportsVision: true,
},
{
id: "glm-4.5",
name: "GLM 4.5",
contextLength: 128000,
maxOutputTokens: 32768,
toolCalling: true,
supportsReasoning: true,
},
{
id: "glm-4.5-air",
name: "GLM 4.5 Air",
contextLength: 128000,
maxOutputTokens: 32768,
toolCalling: true,
supportsReasoning: true,
},
]);
export const GLM_MODELS_URLS = Object.freeze({
international: "https://api.z.ai/api/coding/paas/v4/models",
china: "https://open.bigmodel.cn/api/coding/paas/v4/models",
});
export const GLM_QUOTA_URLS = Object.freeze({
international: "https://api.z.ai/api/monitor/usage/quota/limit",
china: "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
});
export const GLMT_TIMEOUT_MS = 900_000;
export const GLM_TIMEOUT_MS = 900_000;
export const GLM_REQUEST_DEFAULTS = Object.freeze({
maxTokens: 16_384,
});
export const GLMT_REQUEST_DEFAULTS = Object.freeze({
maxTokens: 65_536,
temperature: 0.2,
thinkingBudgetTokens: 24_576,
thinkingType: "adaptive" as const,
});
export const GLM_COUNT_TOKENS_TIMEOUT_MS = 3_000;
export const GLM_CLAUDE_CODE_USER_AGENT = "claude-cli/2.1.137 (external, sdk-cli)";
export const GLM_ANTHROPIC_BETA = [
"claude-code-20250219",
"interleaved-thinking-2025-05-14",
"context-management-2025-06-27",
"prompt-caching-scope-2026-01-05",
"advisor-tool-2026-03-01",
"effort-2025-11-24",
].join(",");
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function asString(value: unknown): string | null {
return typeof value === "string" && value.trim() ? value.trim() : null;
}
function splitUrlQueryAndHash(url: string): { base: string; suffix: string } {
const idx = url.search(/[?#]/);
if (idx === -1) return { base: url, suffix: "" };
return { base: url.substring(0, idx), suffix: url.substring(idx) };
}
export function getGlmApiRegion(providerSpecificData: unknown): GlmApiRegion {
const data = asRecord(providerSpecificData);
return data.apiRegion === "china" ? "china" : "international";
}
export function buildGlmModelsUrl(
providerSpecificData: unknown,
transport: GlmTransport = "openai",
fallbackBaseUrl?: string | null
): string {
const data = asRecord(providerSpecificData);
const customModelsUrl = asString(data.modelsUrl);
if (customModelsUrl) return customModelsUrl;
if (transport === "anthropic") {
return joinGlmBaseAndPath(
getGlmAnthropicBaseUrl(providerSpecificData, fallbackBaseUrl),
"/v1/models"
);
}
const configuredBaseUrl = asString(data.baseUrl);
if (configuredBaseUrl) {
if (isAnthropicGlmBaseUrl(configuredBaseUrl)) {
return GLM_MODELS_URLS[getGlmApiRegion(providerSpecificData)];
}
return joinGlmBaseAndPath(configuredBaseUrl, "/models");
}
return GLM_MODELS_URLS[getGlmApiRegion(providerSpecificData)];
}
export function getGlmQuotaUrl(providerSpecificData: unknown): string {
return GLM_QUOTA_URLS[getGlmApiRegion(providerSpecificData)];
}
function stripKnownGlmEndpointSuffix(baseUrl: string): { base: string; suffix: string } {
const parts = splitUrlQueryAndHash(baseUrl);
let base = parts.base;
while (base.endsWith("/")) {
base = base.slice(0, -1);
}
const countTokensMatch = base.match(/\/(?:v\d+\/)?messages\/count_tokens$/i);
if (countTokensMatch) {
base = base.substring(0, base.length - countTokensMatch[0].length);
} else {
const messagesMatch = base.match(/\/(?:v\d+\/)?messages$/i);
if (messagesMatch) {
base = base.substring(0, base.length - messagesMatch[0].length);
} else if (base.toLowerCase().endsWith("/chat/completions")) {
base = base.substring(0, base.length - "/chat/completions".length);
} else if (base.toLowerCase().endsWith("/models")) {
base = base.substring(0, base.length - "/models".length);
}
}
return { base, suffix: parts.suffix };
}
function joinGlmBaseAndPath(baseUrl: string, path: string): string {
const { base, suffix } = stripKnownGlmEndpointSuffix(baseUrl);
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
const versionMatch = base.match(/\/v\d+$/i);
if (
versionMatch &&
normalizedPath.toLowerCase().startsWith(`${versionMatch[0].toLowerCase()}/`)
) {
return `${base}${normalizedPath.slice(versionMatch[0].length)}${suffix}`;
}
return `${base}${normalizedPath}${suffix}`;
}
function stripQueryAndTrailingSlash(baseUrl: string): string {
let base = splitUrlQueryAndHash(baseUrl).base;
while (base.endsWith("/")) {
base = base.slice(0, -1);
}
return base;
}
function addBetaQuery(url: string): string {
const parsed = new URL(url);
parsed.searchParams.set("beta", "true");
return parsed.toString();
}
export function isAnthropicGlmBaseUrl(baseUrl: string): boolean {
const base = stripQueryAndTrailingSlash(baseUrl).toLowerCase();
return base.includes("/api/anthropic/") || base.endsWith("/api/anthropic");
}
export function isCodingGlmBaseUrl(baseUrl: string): boolean {
const base = stripQueryAndTrailingSlash(baseUrl).toLowerCase();
const idx = base.indexOf("/api/coding/paas/v");
if (idx === -1) return false;
const afterV = base.charCodeAt(idx + "/api/coding/paas/v".length);
return afterV >= 48 && afterV <= 57; // first char after 'v' must be a digit
}
export function getGlmBaseUrl(
providerSpecificData: unknown,
fallbackBaseUrl?: string | null
): string {
const data = asRecord(providerSpecificData);
const configuredBaseUrl = asString(data.baseUrl);
if (configuredBaseUrl) return configuredBaseUrl;
const regionalBaseUrl =
typeof fallbackBaseUrl === "string" &&
fallbackBaseUrl.trim() &&
isCodingGlmBaseUrl(fallbackBaseUrl)
? fallbackBaseUrl.trim()
: GLM_DEFAULT_BASE_URLS[getGlmApiRegion(providerSpecificData)];
if (regionalBaseUrl) return regionalBaseUrl;
return typeof fallbackBaseUrl === "string" && fallbackBaseUrl.trim()
? fallbackBaseUrl.trim()
: GLM_DEFAULT_BASE_URLS.international;
}
export function getGlmAnthropicBaseUrl(
providerSpecificData: unknown,
fallbackBaseUrl?: string | null
): string {
const data = asRecord(providerSpecificData);
const anthropicBaseUrl = asString(data.anthropicBaseUrl);
if (anthropicBaseUrl) return anthropicBaseUrl;
const configuredBaseUrl = asString(data.baseUrl);
if (configuredBaseUrl) {
if (isCodingGlmBaseUrl(configuredBaseUrl)) {
return GLM_ANTHROPIC_DEFAULT_BASE_URLS[getGlmApiRegion(providerSpecificData)];
}
return configuredBaseUrl;
}
if (
typeof fallbackBaseUrl === "string" &&
fallbackBaseUrl.trim() &&
isCodingGlmBaseUrl(fallbackBaseUrl)
) {
return GLM_ANTHROPIC_DEFAULT_BASE_URLS[
fallbackBaseUrl.includes("open.bigmodel.cn") ? "china" : getGlmApiRegion(providerSpecificData)
];
}
if (
typeof fallbackBaseUrl === "string" &&
fallbackBaseUrl.trim() &&
!isCodingGlmBaseUrl(fallbackBaseUrl)
) {
return fallbackBaseUrl.trim();
}
return GLM_ANTHROPIC_DEFAULT_BASE_URLS[getGlmApiRegion(providerSpecificData)];
}
export function getGlmPrimaryTransport(
providerSpecificData: unknown,
fallbackBaseUrl?: string | null
): GlmTransport {
const data = asRecord(providerSpecificData);
const configuredTransport = asString(data.primaryTransport);
if (configuredTransport === "anthropic") return "anthropic";
if (configuredTransport === "openai") return "openai";
return isAnthropicGlmBaseUrl(getGlmBaseUrl(providerSpecificData, fallbackBaseUrl))
? "anthropic"
: "openai";
}
export function getGlmTransport(providerSpecificData: unknown, fallbackBaseUrl?: string | null) {
return getGlmPrimaryTransport(providerSpecificData, fallbackBaseUrl);
}
export function buildGlmChatUrl(
providerSpecificData: unknown,
transport: GlmTransport = "openai",
fallbackBaseUrl?: string | null
): string {
if (transport === "anthropic") {
return buildGlmAnthropicMessagesUrl(providerSpecificData, fallbackBaseUrl);
}
return buildGlmOpenAIChatUrl(providerSpecificData, fallbackBaseUrl);
}
export function buildGlmOpenAIChatUrl(
providerSpecificData: unknown,
fallbackBaseUrl?: string | null
): string {
const configuredBaseUrl = getGlmBaseUrl(providerSpecificData, fallbackBaseUrl);
const baseUrl = isAnthropicGlmBaseUrl(configuredBaseUrl)
? GLM_DEFAULT_BASE_URLS[getGlmApiRegion(providerSpecificData)]
: configuredBaseUrl;
return joinGlmBaseAndPath(baseUrl, "/chat/completions");
}
export function buildGlmAnthropicMessagesUrl(
providerSpecificData: unknown,
fallbackBaseUrl?: string | null
): string {
return addBetaQuery(
joinGlmBaseAndPath(
getGlmAnthropicBaseUrl(providerSpecificData, fallbackBaseUrl),
"/v1/messages"
)
);
}
export function buildGlmCountTokensUrl(
providerSpecificData: unknown,
fallbackBaseUrl?: string | null
): string {
return addBetaQuery(
joinGlmBaseAndPath(
getGlmAnthropicBaseUrl(providerSpecificData, fallbackBaseUrl),
"/v1/messages/count_tokens"
)
);
}
export function buildGlmCodingHeaders(apiKey: string, stream = true): Record<string, string> {
return {
"Content-Type": "application/json",
Accept: stream ? "text/event-stream" : "application/json",
Authorization: `Bearer ${apiKey}`,
};
}
export function buildGlmBaseHeaders(apiKey: string, stream = true): Record<string, string> {
return {
"Content-Type": "application/json",
Accept: stream ? "text/event-stream" : "application/json",
"x-api-key": apiKey,
"anthropic-version": ANTHROPIC_VERSION_HEADER,
"anthropic-beta": GLM_ANTHROPIC_BETA,
"anthropic-dangerous-direct-browser-access": "true",
"User-Agent": GLM_CLAUDE_CODE_USER_AGENT,
"X-Stainless-Lang": "js",
"X-Stainless-Runtime": "node",
"X-Stainless-Retry-Count": "0",
"accept-language": "*",
"accept-encoding": "gzip, deflate, br, zstd",
connection: "keep-alive",
};
}