import crypto from "node:crypto"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; const qoderRSAPublicKey = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8iMH5c02LilrsERw9t6Pv5Nc 4k6Pz1EaDicBMpdpxKduSZu5OANqUq8er4GM95omAGIOPOh+Nx0spthYA2BqGz+l 6HRkPJ7S236FZz73In/KVuLnwI8JJ2CbuJap8kvheCCZpmAWpb/cPx/3Vr/J6I17 XcW+ML9FoCI6AOvOzwIDAQAB -----END PUBLIC KEY-----`; const QoderIDEVersion = "1.0.0"; const QoderClientType = "5"; const QoderDataPolicy = "disagree"; const QoderLoginVersion = "v2"; const QoderMachineOS = "x86_64_windows"; const QoderMachineTypeMagic = "5"; const QoderModeEnv = process.env.QODER_REGION || process.env.QODER_BACKEND || process.env.QODER_MODE || ""; export type QoderMode = "global" | "cn"; interface UserInfo { uid: string; security_oauth_token: string; name: string; aid: string; email: string; } interface CosyPayload { version: string; requestId: string; info: string; cosyVersion: string; ideVersion: string; } export interface CosyCredentials { userID: string; authToken: string; name: string; email: string; machineID?: string; } export function getQoderMode(modeOverride?: string): QoderMode { const mode = (modeOverride || QoderModeEnv).toLowerCase(); if (["cn", "china", "qodercn", "qoder-cn"].includes(mode)) return "cn"; if (["global", "intl", "international", "qoder"].includes(mode)) return "global"; if ( (process.env.QODERCN_PERSONAL_ACCESS_TOKEN || process.env.QODERCN_PAT) && !(process.env.QODER_PERSONAL_ACCESS_TOKEN || process.env.QODER_PAT) ) { return "cn"; } return "global"; } export function isQoderCNMode(modeOverride?: string): boolean { return getQoderMode(modeOverride) === "cn"; } /** True when value looks like a Qoder PAT (pt-...), not a job token / opaque key. */ export function isQoderPatValue(value?: string): boolean { return Boolean(value?.trim().startsWith("pt-")); } /** * Resolve a Qoder CN PAT from the environment. * Accepts QODER_API_KEY only when it is a PAT (`pt-...`) — never treat jt-/opaque keys as PATs. */ export function getQoderCNPat(): string { const dedicated = process.env.QODERCN_PERSONAL_ACCESS_TOKEN || process.env.QODERCN_PAT || ""; if (dedicated.trim()) return dedicated.trim(); const apiKey = process.env.QODER_API_KEY || ""; return isQoderPatValue(apiKey) ? apiKey.trim() : ""; } const QoderVPCDomain = "vpc.qoder.com.cn"; function parseQoderVPCInstance(value?: string): string | undefined { if (!value?.trim()) return undefined; let candidate = value.trim().toLowerCase(); try { candidate = new URL(candidate.includes("://") ? candidate : `https://${candidate}`).hostname; } catch { return undefined; } const suffix = `.${QoderVPCDomain}`; if (candidate.endsWith(suffix)) { candidate = candidate.slice(0, -suffix.length); if (candidate.endsWith("-gateway") || candidate.endsWith("-openapi")) { candidate = candidate.slice(0, -8); } } else if (candidate.includes(".")) { return undefined; } return /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(candidate) ? candidate : undefined; } function getQoderVPCInstance(endpointOverride?: string): string | undefined { return parseQoderVPCInstance( endpointOverride || process.env.QODER_VPC_INSTANCE || process.env.QODER_VPC_ENDPOINT || process.env.QODERCN_VPC_ENDPOINT || process.env.QODERCN_CLI_VPC_ENDPOINT || process.env.QODER_CN_BASE_URL || process.env.QODER_CN_OPENAPI_URL || process.env.QODER_CN_CENTER_URL, ); } function getQoderVPCServiceUrl(service: "gateway" | "openapi", endpointOverride?: string): string | undefined { const instance = getQoderVPCInstance(endpointOverride); return instance ? `https://${instance}-${service}.${QoderVPCDomain}` : undefined; } export function getQoderBaseUrl(mode?: string): string { if (isQoderCNMode(mode)) { const override = process.env.QODER_CN_BASE_URL; const url = getQoderVPCServiceUrl("gateway", override) || override || "https://gateway.qoder.com.cn/"; return url.endsWith("/") ? url : `${url}/`; } return "https://api3.qoder.sh/"; } export function getQoderOpenApiUrl(mode?: string): string { if (isQoderCNMode(mode)) { const override = process.env.QODER_CN_OPENAPI_URL; const url = getQoderVPCServiceUrl("openapi", override) || override || "https://openapi.qoder.com.cn"; return url.replace(/\/+$/, ""); } return "https://openapi.qoder.sh"; } export function getQoderCenterUrl(mode?: string): string { if (isQoderCNMode(mode)) { const override = process.env.QODER_CN_CENTER_URL; const url = getQoderVPCServiceUrl("gateway", override) || override || "https://gateway.qoder.com.cn"; return url.replace(/\/+$/, ""); } return "https://center.qoder.sh"; } export function getQoderModelListURL(mode?: string): string { return `${getQoderBaseUrl(mode)}algo/api/v2/model/list`; } export function getQoderChatURL(mode?: string): string { return `${getQoderBaseUrl(mode)}algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1`; } export function getQoderExchangeURL(mode?: string): string { return `${getQoderOpenApiUrl(mode)}/api/v1/jobToken/exchange`; } export function getQoderJobTokenRefreshURL(mode?: string): string { return `${getQoderOpenApiUrl(mode)}/api/v1/jobToken/refresh`; } export function getQoderUserInfoURL(mode?: string): string { return `${getQoderOpenApiUrl(mode)}/api/v1/userinfo`; } export function getQoderUsageURL(mode?: string): string { return `${getQoderOpenApiUrl(mode)}/api/v2/quota/usage`; } export function getQoderRefreshURL(mode?: string): string { return `${getQoderCenterUrl(mode)}/algo/api/v3/user/refresh_token`; } export function getQoderCNDirectModel(modelID?: string): string { return ( { "qoder-cn": "auto", "qwen3.7-max": "qmodel_latest", "qwen3.7-plus": "qmodel", "qwen3.6-plus": "qmodel", "qwen3.6-flash": "q36fmodel", "deepseek-v4-pro": "dmodel", "deepseek-v4-flash": "dfmodel", "glm-5.2": "gm51model", "glm-5.1": "gm51model", "kimi-k2.6": "kmodel", "minimax-m2.7": "mmodel", "minimax-m3": "mmodel", }[modelID || ""] || modelID || "auto" ); } const qoderCNFriendlyModels: Record = { auto: { id: "auto", name: "Auto · Qoder CN" }, "qoder-cn": { id: "qoder-cn", name: "Auto · Qoder CN" }, qmodel_latest: { id: "qwen3.7-max", name: "Qwen 3.7 Max · Qoder CN" }, qmodel: { id: "qwen3.7-plus", name: "Qwen 3.7 Plus · Qoder CN" }, q36fmodel: { id: "qwen3.6-flash", name: "Qwen 3.6 Flash · Qoder CN" }, qfmodel: { id: "qwen3.6-flash", name: "Qwen 3.6 Flash · Qoder CN" }, dmodel: { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro · Qoder CN" }, dfmodel: { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash · Qoder CN" }, gm51model: { id: "glm-5.2", name: "GLM 5.2 · Qoder CN" }, kmodel: { id: "kimi-k2.6", name: "Kimi K2.6 · Qoder CN" }, mmodel: { id: "minimax-m2.7", name: "MiniMax M2.7 · Qoder CN" }, }; function prettifyQoderCNModelName(name: string): string { const pretty = (name || "Qoder CN Model") .replace(/Qwen(\d)/g, "Qwen $1") .replace(/Qwen([\d.]+)-/g, "Qwen $1 ") .replace(/DeepSeek\s*V(\d)-/g, "DeepSeek V$1 ") .replace(/\s+/g, " ") .trim(); return pretty.includes("Qoder CN") ? pretty : `${pretty} · Qoder CN`; } export function getQoderCNFriendlyModelInfo(key: string, display?: string): { id: string; name: string } { return qoderCNFriendlyModels[key] || { id: key, name: prettifyQoderCNModelName(display || key) }; } export function toQoderCNFriendlyModel(model: T): T { const info = getQoderCNFriendlyModelInfo(model.id, model.name); return { ...model, id: info.id, name: info.name, }; } /** Public or VPC tenant dashboard origin (no trailing slash). */ export function getQoderManageUrl(mode?: string): string { if (isQoderCNMode(mode)) { const instance = getQoderVPCInstance(); if (instance) return `https://${instance}.${QoderVPCDomain}`; return "https://qoder.com.cn"; } return "https://qoder.com"; } /** Where users create/copy a PAT (public CN or VPC tenant integrations page). */ export function getQoderIntegrationsUrl(mode?: string): string { return `${getQoderManageUrl(mode)}/account/integrations`; } export function getQoderUserEmailFallback(mode?: string): string { return isQoderCNMode(mode) ? "user@qoder.com.cn" : "user@qoder.com"; } function rsaEncryptBase64(data: Buffer | string): string { const key = { key: qoderRSAPublicKey, padding: crypto.constants.RSA_PKCS1_PADDING, }; const encrypted = crypto.publicEncrypt(key, typeof data === "string" ? Buffer.from(data) : data); return encrypted.toString("base64"); } function aesEncryptCBCBase64(plaintext: string, keyStr: string): string { const cipher = crypto.createCipheriv("aes-128-cbc", Buffer.from(keyStr), Buffer.from(keyStr)); let encrypted = cipher.update(plaintext, "utf8", "base64"); encrypted += cipher.final("base64"); return encrypted; } function computeSigPath(urlStr: string): string { const parsed = new URL(urlStr); let sigPath = parsed.pathname; if (sigPath.startsWith("/algo")) { sigPath = sigPath.substring("/algo".length); } return sigPath; } export function getMachineId(): string { const paths = [join(homedir(), ".qoder", ".auth", "machine_id"), join(homedir(), ".pi", "agent", "qoder-machine-id")]; for (const p of paths) { if (existsSync(p)) { try { const val = readFileSync(p, "utf8").trim(); if (val) return val; } catch {} } } const newId = crypto.randomUUID(); try { const savePath = paths[1]; mkdirSync(dirname(savePath), { recursive: true }); writeFileSync(savePath, newId, "utf8"); } catch {} return newId; } export function buildAuthHeaders( body: Buffer | string | null, requestURL: string, creds: CosyCredentials, ): Record { if (!creds.userID) { throw new Error("cosy: user id is empty"); } if (!creds.authToken) { throw new Error("cosy: auth token is empty"); } const aesKey = crypto.randomUUID().replace(/-/g, "").slice(0, 16); const userInfo: UserInfo = { uid: creds.userID, security_oauth_token: creds.authToken, name: creds.name || "", aid: "", email: creds.email || "", }; const infoB64 = aesEncryptCBCBase64(JSON.stringify(userInfo), aesKey); const cosyKey = rsaEncryptBase64(aesKey); const timestamp = Math.floor(Date.now() / 1000).toString(); const requestId = crypto.randomUUID(); const cosyPayload: CosyPayload = { version: "v1", requestId, info: infoB64, cosyVersion: QoderIDEVersion, ideVersion: "", }; const payloadB64 = Buffer.from(JSON.stringify(cosyPayload)).toString("base64"); const sigPath = computeSigPath(requestURL); const bodyStr = body ? (Buffer.isBuffer(body) ? body.toString("utf8") : body) : ""; const sigInput = `${payloadB64}\n${cosyKey}\n${timestamp}\n${bodyStr}\n${sigPath}`; const sig = crypto.createHash("md5").update(sigInput).digest("hex"); const bodyHash = crypto .createHash("md5") .update(body || "") .digest("hex"); const bodyLen = body ? (Buffer.isBuffer(body) ? body.length : Buffer.from(body).length).toString() : "0"; const machineID = creds.machineID || getMachineId(); return { Authorization: `Bearer COSY.${payloadB64}.${sig}`, "Cosy-Key": cosyKey, "Cosy-User": creds.userID, "Cosy-Date": timestamp, "Cosy-Version": QoderIDEVersion, "Cosy-Machineid": machineID, "Cosy-Machinetoken": machineID, "Cosy-Machinetype": QoderMachineTypeMagic, "Cosy-Machineos": QoderMachineOS, "Cosy-Clienttype": QoderClientType, "Cosy-Clientip": "127.0.0.1", "Cosy-Bodyhash": bodyHash, "Cosy-Bodylength": bodyLen, "Cosy-Sigpath": sigPath, "Cosy-Data-Policy": QoderDataPolicy, "Cosy-Organization-Id": "", "Cosy-Organization-Tags": "", "Login-Version": QoderLoginVersion, "X-Request-Id": crypto.randomUUID(), }; } function isCosyDebugEnabled(): boolean { return ["1", "true", "yes", "on"].includes((process.env.QODER_COSY_DEBUG || "").toLowerCase()); } export function logCosyRequest(method: string, requestURL: string, headers: Record): void { if (!isCosyDebugEnabled()) return; console.error( `[qoder:cosy] request ${JSON.stringify({ method, url: requestURL, cosyDate: headers["Cosy-Date"], cosySigpath: headers["Cosy-Sigpath"], cosyBodyhash: headers["Cosy-Bodyhash"], cosyBodylength: headers["Cosy-Bodylength"], cosyOrganizationId: headers["Cosy-Organization-Id"], cosyOrganizationTags: headers["Cosy-Organization-Tags"], })}`, ); } export async function logCosyResponse(requestURL: string, response: Response): Promise { if (!isCosyDebugEnabled()) return; let bodyPreview: string | undefined; if (!response.ok) { try { bodyPreview = (await response.clone().text()).slice(0, 200); } catch { bodyPreview = ""; } } const responseHeaders: Record = {}; for (const name of [ "date", "server", "via", "x-request-id", "x-cosy-request-id", "x-trace-id", "trace-id", "cf-ray", "x-cache", "x-envoy-upstream-service-time", ]) { const value = response.headers.get(name); if (value !== null) responseHeaders[name] = value; } console.error( `[qoder:cosy] response ${JSON.stringify({ url: requestURL, status: response.status, statusText: response.statusText, headers: responseHeaders, ...(bodyPreview === undefined ? {} : { bodyPreview }), })}`, ); } function isQoderTenantDashboardHost(hostname: string): boolean { const host = hostname.toLowerCase(); const suffix = `.${QoderVPCDomain}`; if (!host.endsWith(suffix)) return false; const prefix = host.slice(0, -suffix.length); return Boolean(prefix) && !prefix.includes(".") && !prefix.endsWith("-gateway") && !prefix.endsWith("-openapi"); } /** Enrich Qoder HTTP failures with VPC/CSRF guidance without leaking secrets. */ export function formatQoderHttpError( kind: "api" | "pat-exchange", status: number, statusText: string, bodyText: string, requestURL?: string, ): string { const preview = bodyText.replace(/\s+/g, " ").trim().slice(0, 200); const prefix = kind === "pat-exchange" ? `Qoder PAT exchange failed: ${status} ${statusText}` : `Qoder API request failed: ${status} ${statusText}`; const base = preview ? `${prefix}. Response: ${preview}` : prefix; let host = ""; if (requestURL) { try { host = new URL(requestURL).hostname.toLowerCase(); } catch { host = ""; } } const hints: string[] = []; if (/CSRFInvalid/i.test(bodyText)) { if (host && isQoderTenantDashboardHost(host)) { hints.push( `Request hit the tenant dashboard host (${host}). Use the derived API hosts instead: https://-gateway.vpc.qoder.com.cn and https://-openapi.vpc.qoder.com.cn (set QODER_VPC_INSTANCE=).`, ); } else { hints.push( "CSRFInvalid usually means the request reached web/session middleware instead of the VPC gateway/OpenAPI service. Set QODER_VPC_INSTANCE (or QODERCN_VPC_ENDPOINT) and retry with QODER_COSY_DEBUG=1.", ); } } if (/open_access_token not found/i.test(bodyText)) { hints.push( "The VPC OpenAPI host could not resolve a tenant-side access record for this PAT. Create/use a PAT from the VPC tenant dashboard (https://.vpc.qoder.com.cn/account/integrations).", ); } return hints.length > 0 ? `${base} Hint: ${hints.join(" ")}` : base; }