File size: 7,670 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | /**
* Region profiles for the mainland-China (.com) and global (.ai)
* Kimi Code deployments, plus the resolver that decides which region a
* client belongs to.
*
* A region is a bundle of endpoints (OAuth host, managed API base URL, CDN,
* site, telemetry). The OAuth client_id is shared across regions and stays
* in `./constants`.
*
* Resolution order (first match wins):
* 1. env override (`KIMI_CODE_OAUTH_HOST` / `KIMI_OAUTH_HOST`)
* 2. persisted login (the `oauthHost` stored in config.toml's oauth ref)
* 3. persisted default-slot login (the oauth ref's key equals
* `KIMI_CODE_OAUTH_KEY` — a mainland-China login persists no
* `oauthHost`, so the default slot's presence is an explicit-mainland-cn
* signal that outranks the marker)
* 4. install-channel marker file (`<home>/region`, written by install
* scripts; consultable only before the first login)
* 5. default 'mainland-cn'
*/
import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { z } from 'zod';
import { DEFAULT_KIMI_CODE_OAUTH_HOST } from './constants';
import { DEFAULT_KIMI_CODE_BASE_URL } from './managed-usage';
import { kimiCodeEnvBaseUrl, kimiCodeEnvOAuthHost, KIMI_CODE_OAUTH_KEY } from './managed-kimi-code';
export type KimiRegion = 'mainland-cn' | 'global';
/** Zod schema for the wire/domain contract; parses to {@link KimiRegion}. */
export const kimiRegionSchema = z.enum(['mainland-cn', 'global']);
export interface KimiRegionProfile {
/** OAuth host the device flow talks to (authorize/token derive from it). */
readonly oauthHost: string;
/** Managed API base (`/coding/v1`): usages, userinfo, models, feedback... */
readonly baseUrl: string;
/** Update/install/plugin-marketplace root. */
readonly cdnBase: string;
/** Official site root (docs, console, signup, upgrade pages). */
readonly siteBase: string;
readonly telemetryEndpoint: string;
}
export const KIMI_REGION_PROFILES: Record<KimiRegion, KimiRegionProfile> = {
'mainland-cn': {
oauthHost: DEFAULT_KIMI_CODE_OAUTH_HOST,
baseUrl: DEFAULT_KIMI_CODE_BASE_URL,
cdnBase: 'https://code.kimi.com/kimi-code',
siteBase: 'https://www.kimi.com',
telemetryEndpoint: 'https://telemetry-logs.kimi.com/v1/event',
},
global: {
oauthHost: 'https://auth.kimi.ai',
baseUrl: 'https://api.kimi.ai/coding/v1',
cdnBase: 'https://code.kimi.ai/kimi-code',
siteBase: 'https://www.kimi.ai',
telemetryEndpoint: 'https://telemetry-logs.kimi.ai/v1/event',
},
};
export function kimiRegionProfile(region: KimiRegion): KimiRegionProfile {
return KIMI_REGION_PROFILES[region];
}
/**
* Content-CDN URL builder (tips banner, WebBridge / Computer-Use binaries).
* International mirror coverage of cdn.kimi.ai for these payloads is still
* being confirmed, so both regions currently share the .com host — funnel
* every content URL through here so flipping later touches one function.
*/
export function kimiCdnContentUrl(path: string): string {
return `https://cdn.kimi.com/${path.replace(/^\/+/, '')}`;
}
/**
* Login hosts for an explicit region choice, or `undefined` when an env
* override (`KIMI_CODE_OAUTH_HOST` / `KIMI_OAUTH_HOST` / `KIMI_CODE_BASE_URL`)
* is in play — env keeps full control of endpoints, so a region pick must not
* smuggle profile hosts past it (requested hosts outrank env in
* `resolveKimiCodeLoginAuth`).
*
* When returned, both hosts are always set — including for 'mainland-cn',
* whose values equal the defaults. Passing them explicitly is what lets
* "switch back to mainland China" override a previously persisted global
* login in config.toml.
*/
export function kimiRegionLoginHosts(
region: KimiRegion,
env: NodeJS.ProcessEnv = process.env,
): { readonly oauthHost: string; readonly baseUrl: string } | undefined {
if (kimiCodeEnvOAuthHost(env) !== undefined || kimiCodeEnvBaseUrl(env) !== undefined) {
return undefined;
}
const profile = kimiRegionProfile(region);
return { oauthHost: profile.oauthHost, baseUrl: profile.baseUrl };
}
/**
* Marker file name under the Kimi home dir. Install scripts write a single
* line (`mainland-cn` or `global`) here so a fresh client can default to the
* region matching the channel it was installed from. It is only consulted
* while the user has never logged in; a persisted login (config.toml) always
* wins.
*/
export const KIMI_REGION_MARKER_FILENAME = 'region';
export interface ResolveKimiRegionOptions {
/** Defaults to `process.env`. */
readonly env?: NodeJS.ProcessEnv;
/** The `oauthHost` persisted in config.toml's oauth ref, if any. */
readonly configuredOAuthHost?: string;
/**
* The credential key persisted in config.toml's oauth ref, if any. The
* default slot ({@link KIMI_CODE_OAUTH_KEY}) only ever holds a
* mainland-China login — mainland-cn persists no `oauthHost` — so its
* presence is an explicit-mainland-cn signal that outranks the
* install-channel marker.
*/
readonly configuredOAuthKey?: string;
/** Kimi home dir; defaults to `KIMI_CODE_HOME` or `~/.kimi-code`. */
readonly homeDir?: string;
/**
* Set false to skip the install-channel marker (e.g. the desktop app's
* embedded server, which is not installed through a channel script and
* leaves the region choice entirely to the login UI).
*/
readonly readMarker?: boolean;
}
function normalizeHost(value: string): string {
return value.trim().replace(/\/+$/, '');
}
function regionForOAuthHost(oauthHost: string): KimiRegion | undefined {
const normalized = normalizeHost(oauthHost);
for (const region of Object.keys(KIMI_REGION_PROFILES) as KimiRegion[]) {
if (normalizeHost(KIMI_REGION_PROFILES[region].oauthHost) === normalized) return region;
}
return undefined;
}
function readRegionMarker(homeDir: string): KimiRegion | undefined {
let raw: string;
try {
raw = readFileSync(join(homeDir, KIMI_REGION_MARKER_FILENAME), 'utf-8');
} catch {
return undefined;
}
const value = raw.trim();
return value === 'mainland-cn' || value === 'global' ? value : undefined;
}
// Mirrors `defaultKimiHome` in ./toolkit; keep the two in sync so the marker
// always lands next to the credentials dir it describes.
function defaultHomeDir(env: NodeJS.ProcessEnv): string {
const override = env['KIMI_CODE_HOME'];
if (override !== undefined && override.length > 0) return override;
return join(homedir(), '.kimi-code');
}
export function resolveKimiRegion(options: ResolveKimiRegionOptions = {}): KimiRegion {
const env = options.env ?? process.env;
// An env host that matches a profile pins the region. An unknown env host
// means a custom/internal environment: the per-endpoint env overrides keep
// doing their job regardless of region, so skip straight to the default
// instead of letting a stale config/marker point CDN links somewhere odd.
const envHost = env['KIMI_CODE_OAUTH_HOST'] ?? env['KIMI_OAUTH_HOST'];
if (envHost !== undefined && envHost.length > 0) {
return regionForOAuthHost(envHost) ?? 'mainland-cn';
}
const configured = options.configuredOAuthHost;
if (configured !== undefined && configured.length > 0) {
const configuredRegion = regionForOAuthHost(configured);
if (configuredRegion !== undefined) return configuredRegion;
}
if (options.configuredOAuthKey === KIMI_CODE_OAUTH_KEY) return 'mainland-cn';
if (options.readMarker !== false) {
const markerRegion = readRegionMarker(options.homeDir ?? defaultHomeDir(env));
if (markerRegion !== undefined) return markerRegion;
}
return 'mainland-cn';
}
|