import { ANTHROPIC_VERSION_HEADER } from "./anthropicHeaders.ts"; type JsonRecord = Record; 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 { return { "Content-Type": "application/json", Accept: stream ? "text/event-stream" : "application/json", Authorization: `Bearer ${apiKey}`, }; } export function buildGlmBaseHeaders(apiKey: string, stream = true): Record { 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", }; }