pi-provider-kimi-code 0.6.3 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  Pi already has a basic `kimi-coding` provider. This extension is for the parts that start to matter once Kimi Code becomes part of your real Pi workflow: account login reuse, file uploads, tool schema compatibility, live model metadata, measured cache behavior, and API-tested parameter handling.
9
9
 
10
- > **Kimi K2.7 supported.** The provider automatically discovers the current model from Kimi's `/v1/models` endpoint at login, refresh, and `/kimi-settings` time, so new rollouts (including K2.7) are picked up immediately without a package update.
10
+ > **Kimi K3 supported.** The provider combines Kimi's live `/v1/models` catalog with your membership level from `/usages`, exposing only eligible models and advertising the correct K3 context limit. It refreshes this metadata at startup and through `/kimi-settings`; OAuth login and token refresh also update the model catalog.
11
11
 
12
12
  ## Why this exists
13
13
 
@@ -19,20 +19,20 @@ Kimi's API surface goes beyond the LLM itself. It includes file uploads, web sea
19
19
  - **Send files the Kimi way.** Large inline images go through Kimi's Files API and become `ms://` references instead of huge base64 payloads.
20
20
  - **Know what the cache is doing.** Kimi caches by content prefix. This repo measures that behavior instead of pretending `prompt_cache_key` controls it.
21
21
  - **Keep Pi's tools working.** Moonshot's API rejects tool schemas over 15 KB — a limit that Pi's extension ecosystem regularly hits ([#16](https://github.com/Leechael/pi-provider-kimi-code/issues/16), [#21](https://github.com/Leechael/pi-provider-kimi-code/issues/21)). This extension automatically deduplicates schemas with `$ref`/`$defs` before sending, so subagents and other extensions don't break.
22
- - **Tested against the live API.** Thinking config, parameter constraints, protocol compatibility, and streaming behavior are all verified against Kimi's Coding endpoint — not assumed from docs. When the API rejects something (wrong `temperature`, unsupported `tool_choice`), the provider strips it before you see a 400.
22
+ - **Tested against the live API.** Thinking config, parameter constraints, protocol compatibility, and streaming behavior are all verified against Kimi's Coding endpoint — not assumed from docs. When the API rejects something (wrong `temperature`, unsupported `tool_choice`), the provider normalizes the request before you see a 400.
23
23
  - **Turn on Kimi-native tools when you want them.** `moonshot_search`, `moonshot_fetch`, and `kimi_datasource` are opt-in, configurable per user or per project.
24
24
  - **Embed in your own build.** `KimiCode()` factory lets you ship Kimi Code support inside a custom Pi agent with programmatic config overrides — no file-based extension path needed.
25
25
 
26
26
  ## What this package adds
27
27
 
28
28
  - Kimi account login in Pi, plus `KIMI_API_KEY` for CI or pay-per-token use.
29
- - `kimi-code` credential reuse from `~/.kimi/credentials/kimi-code.json`.
29
+ - `kimi-code` credential reuse from `~/.kimi-code/credentials/kimi-code.json`, with read-only support for the legacy `~/.kimi` path.
30
30
  - Kimi Files API uploads for large inline images.
31
- - Live model metadata from Kimi at login, refresh, and `/kimi-settings` time.
31
+ - Live model metadata combined with membership-aware K3 and HighSpeed availability.
32
32
  - OpenAI-compatible mode by default, Anthropic-compatible mode on request.
33
- - Kimi K2.7 reasoning level mapping for Pi's reasoning controls.
33
+ - K2.7 and K3 reasoning level mapping for Pi's reasoning controls.
34
34
  - Tool schema dedup to stay under Moonshot's 15 KB per-tool-schema limit.
35
- - K2.7 parameter guard: strips `temperature`, `top_p`, and `tool_choice` values that the API rejects.
35
+ - K2.7 parameter guard: removes rejected `temperature` and `top_p` values and rewrites unsupported `tool_choice` values to `auto`.
36
36
  - Stream cleanup for Kimi's thinking-only placeholder text.
37
37
  - Optional `moonshot_search`, `moonshot_fetch`, and unified datasource `kimi_datasource` tools via `/kimi-settings`.
38
38
  - Programmatic `KimiCode()` factory for embedding in custom Pi builds.
@@ -84,15 +84,21 @@ Inside Pi, run:
84
84
  /login kimi-coding
85
85
  ```
86
86
 
87
- A browser tab opens, you sign into your Kimi account, and Pi stores the credential at `~/.pi/agent/auth.json`. Tokens refresh automatically.
87
+ A browser tab opens, you sign into your Kimi account, and Pi stores the credential at `~/.pi/agent/auth.json`. Tokens refresh automatically. The extension also syncs refreshed credentials to the current `kimi-code` credential file:
88
88
 
89
- If you already use `kimi-code`, this extension can reuse its credential file:
89
+ ```text
90
+ ~/.kimi-code/credentials/kimi-code.json
91
+ ```
92
+
93
+ If you already use `kimi-code`, the extension can reuse that session. Set `KIMI_CODE_HOME` if its home directory lives somewhere else.
94
+
95
+ The legacy credential path is also supported:
90
96
 
91
97
  ```text
92
98
  ~/.kimi/credentials/kimi-code.json
93
99
  ```
94
100
 
95
- The file is read-only from this extension's side. Set `KIMI_SHARE_DIR` if your `kimi-code` share directory lives somewhere else.
101
+ Legacy credentials are read-only. Set `KIMI_SHARE_DIR` to override the legacy `~/.kimi` directory.
96
102
 
97
103
  For CI or pay-per-token use, set `KIMI_API_KEY` instead:
98
104
 
@@ -100,18 +106,24 @@ For CI or pay-per-token use, set `KIMI_API_KEY` instead:
100
106
  KIMI_API_KEY=sk-... pi
101
107
  ```
102
108
 
103
- ## Model
109
+ ## Models
104
110
 
105
- This provider publishes one Pi model ID:
111
+ This provider publishes three Pi model IDs:
106
112
 
107
- ```text
108
- kimi-coding/kimi-for-coding
109
- ```
113
+ | Pi model ID | Upstream model | Access |
114
+ | --------------------------------------- | ------------------------ | ------------------------------------------------------ |
115
+ | `kimi-coding/kimi-for-coding` | Kimi K2.7 Code | All Kimi Code members |
116
+ | `kimi-coding/kimi-for-coding-highspeed` | Kimi K2.7 Code HighSpeed | Allegretto and above |
117
+ | `kimi-coding/k3` | Kimi K3 | Moderato: 256K context; Allegretto and above: up to 1M |
110
118
 
111
- Select it inside Pi:
119
+ Plans below Moderato cannot use K3. The provider reads the current membership level from `/usages` and combines it with `/models`: server catalog availability remains authoritative, while known plan limits can only hide unavailable selections or lower K3's advertised context window. Unknown or unavailable membership data falls back to the server catalog instead of guessing.
120
+
121
+ Select a model inside Pi:
112
122
 
113
123
  ```text
114
124
  /model kimi-coding/kimi-for-coding
125
+ /model kimi-coding/kimi-for-coding-highspeed
126
+ /model kimi-coding/k3
115
127
  ```
116
128
 
117
129
  Kimi keeps Coding models behind aliases. Rather than hardcoding a stale model list, this extension asks Kimi for the current model info when you log in or refresh. If your account is on a newer rollout (e.g., Kimi K2.7) or internal test, Pi can pick up the latest model name and context size without waiting for a package release.
@@ -123,7 +135,9 @@ Fallback values:
123
135
  - Input: text and image
124
136
  - Reasoning: enabled
125
137
 
126
- The provider maps Pi's reasoning levels to Kimi's `reasoning_effort` and top-level `thinking` parameters. The mapping refreshes automatically on credential refresh. Opening `/kimi-settings` also re-discovers the latest model metadata.
138
+ The provider maps Pi's reasoning levels to Kimi's top-level `thinking` parameter. K3 currently advertises only `max`; `max` and `xhigh` map to `max`, `high` and `medium` map to `high`, and `low` and `minimal` map to `low`. It sends `thinking.effort` only when `/models` advertises the mapped value. The mapping refreshes automatically on credential refresh. Opening `/kimi-settings` also re-discovers the latest model and membership metadata.
139
+
140
+ Switching models or thinking effort invalidates Kimi's existing context cache. Start a new session when switching to avoid re-prefilling a long conversation and consuming extra quota.
127
141
 
128
142
  ## Optional tools
129
143
 
@@ -135,14 +149,14 @@ Inside Pi, run:
135
149
  /kimi-settings
136
150
  ```
137
151
 
138
- That command shows the current server-side model name (e.g. "K2.7 Code High Speed"), your Kimi usage summary, and lets you edit the home or project config. Changes apply to the active session tool set.
152
+ That command shows the current server-side model name (e.g. "K2.7 Code High Speed"), your Kimi quota and Extra Usage balance, and lets you edit the home or project config. Changes apply to the active session tool set.
139
153
 
140
154
  Configurable settings include protocol mode, upload threshold, and per-tool enable/collapse.
141
155
 
142
156
  Config files are JSON:
143
157
 
144
- - Home: `~/.pi/pi-provider-kimi-code.json`
145
- - Project override: `<cwd>/.pi/pi-provider-kimi-code.json`
158
+ - Home: `~/.pi/providers/kimi-coding/config.json`
159
+ - Project override: `<cwd>/.pi/providers/kimi-coding/config.json`
146
160
 
147
161
  Project config overrides home config with a deep merge. Missing files or missing keys mean all tools stay off.
148
162
 
@@ -277,7 +291,7 @@ Kimi's cache is **content-based**: it fires automatically when your prompt prefi
277
291
  - A timestamp, request ID, or randomized header is being interpolated into the prompt.
278
292
  - The first ~256 tokens of the prompt differ between turns.
279
293
 
280
- `PI_CACHE_RETENTION=none` skips `prompt_cache_key` injection but **does not** disable Kimi's caching (the cache is unconditional). For deterministic measurement, run `scripts/test_e2e.sh` with `KIMI_E2E_ONLY_CACHE=1`. See [docs/caching.md](docs/caching.md) for full mechanics.
294
+ `PI_CACHE_RETENTION=none` skips `prompt_cache_key` injection but **does not** disable Kimi's caching (the cache is unconditional). For deterministic measurement, run a focused suite under `scripts/e2e/cache/`, starting with `scripts/e2e/cache/ttl.sh`. See [docs/caching.md](docs/caching.md) for full mechanics.
281
295
 
282
296
  ### OpenAI-compatible tools complain about a `developer` role
283
297
 
@@ -294,7 +308,7 @@ In OpenAI mode this extension maps the `developer` role to `system` (Kimi's Codi
294
308
 
295
309
  ## Credits
296
310
 
297
- Based on the login implementation from Kimi Code by Moonshot AI. Built as a Pi extension by [@badlogic](https://github.com/badlogic).
311
+ Based on the login implementation from Kimi Code by Moonshot AI. Pi was originally created by [@badlogic](https://github.com/badlogic).
298
312
 
299
313
  ## License
300
314
 
package/index.ts CHANGED
@@ -42,12 +42,19 @@ import { PROVIDER_ID, PROVIDER_VERSION, getBaseUrl, getKimiApiType } from "./src
42
42
  import {
43
43
  type KimiOAuthCredentials,
44
44
  type KimiOAuthExtras,
45
+ applyKimiMembershipLimitsToModel,
45
46
  buildKimiModelFromConfig,
46
47
  applyKimiOAuthExtrasToModel,
48
+ KIMI_CODING_HIGHSPEED_MODEL_ID,
49
+ KIMI_CODING_MODEL_ID,
50
+ KIMI_K3_MODEL_ID,
51
+ KIMI_MODEL_CATALOG_VERSION,
47
52
  discoverKimiModelMetadata,
53
+ getKimiModelMetadata,
54
+ isKimiModelAvailableForMembership,
48
55
  resolveKimiModelConfig,
49
56
  } from "./src/models.ts";
50
- import { loginKimiCode, refreshKimiCodeToken } from "./src/oauth.ts";
57
+ import { getKimiApiKey, loginKimiCode, refreshKimiCodeToken } from "./src/oauth.ts";
51
58
  import { isKimiProjectConfigApproved } from "./src/project-trust.ts";
52
59
  import {
53
60
  type KimiConfigScope,
@@ -57,13 +64,14 @@ import {
57
64
  parseByteSizeInput,
58
65
  } from "./src/settings-ui.ts";
59
66
  import { setStoreResolvedKimiConfig, streamSimpleKimi } from "./src/stream.ts";
60
- import { fetchKimiUsageSummary, getKimiUsageToken } from "./src/usage.ts";
67
+ import { fetchKimiUsageSnapshot, getKimiUsageToken } from "./src/usage.ts";
61
68
  import { buildMoonshotFetchTool, buildMoonshotSearchTool } from "./src/tools/moonshot.ts";
62
69
  import { buildKimiDatasourceTool } from "./src/tools/datasource.ts";
63
70
  interface KimiRuntimeState {
64
71
  cwd: string;
65
72
  config: KimiCodeConfig;
66
73
  modelExtras: KimiOAuthExtras;
74
+ membershipLevel: string | null;
67
75
  projectTrusted: boolean;
68
76
  overrides?: KimiCodeConfigPatch;
69
77
  }
@@ -114,7 +122,10 @@ function reloadEffectiveKimiRuntimeConfig(
114
122
  state.config = config;
115
123
  state.projectTrusted = projectTrusted;
116
124
  setStoreResolvedKimiConfig({
117
- model: resolveKimiModelConfig(config.model, state.modelExtras),
125
+ model: resolveKimiModelConfig(
126
+ config.model,
127
+ getKimiModelMetadata(state.modelExtras, KIMI_CODING_MODEL_ID),
128
+ ),
118
129
  protocol: config.protocol,
119
130
  uploads: config.uploads,
120
131
  });
@@ -132,13 +143,13 @@ function applyEffectiveKimiRuntimeConfig(
132
143
  return config;
133
144
  }
134
145
 
135
- async function refreshModelExtras(state: KimiRuntimeState): Promise<void> {
146
+ async function refreshModelExtras(state: KimiRuntimeState): Promise<boolean> {
136
147
  const token = getKimiUsageToken();
137
- if (!token) return;
148
+ if (!token) return false;
138
149
  const extras = await discoverKimiModelMetadata(token, state.config.protocol);
139
- if (Object.keys(extras).length > 0) {
140
- Object.assign(state.modelExtras, extras);
141
- }
150
+ if (Object.keys(extras).length === 0) return false;
151
+ Object.assign(state.modelExtras, extras);
152
+ return true;
142
153
  }
143
154
 
144
155
  async function openSettingsMenu(
@@ -146,7 +157,21 @@ async function openSettingsMenu(
146
157
  ctx: ExtensionCommandContext,
147
158
  state: KimiRuntimeState,
148
159
  ): Promise<void> {
149
- const [usage] = await Promise.all([fetchKimiUsageSummary(), refreshModelExtras(state)]);
160
+ const modelDiscoveryToken = getKimiUsageToken();
161
+ const [usageSnapshot, initialModelsRefreshed] = await Promise.all([
162
+ fetchKimiUsageSnapshot(),
163
+ refreshModelExtras(state),
164
+ ]);
165
+ let modelsRefreshed = initialModelsRefreshed;
166
+ const refreshedToken = getKimiUsageToken();
167
+ if (!modelsRefreshed && refreshedToken && refreshedToken !== modelDiscoveryToken) {
168
+ modelsRefreshed = await refreshModelExtras(state);
169
+ }
170
+ const refreshedMembershipLevel = usageSnapshot.membershipLevel ?? state.membershipLevel;
171
+ const membershipChanged = state.membershipLevel !== refreshedMembershipLevel;
172
+ state.membershipLevel = refreshedMembershipLevel;
173
+ if (modelsRefreshed || membershipChanged) registerKimiProvider(pi, state);
174
+ const usage = usageSnapshot.summary;
150
175
 
151
176
  const projectTrusted = await isKimiProjectConfigApproved(ctx, ctx.cwd);
152
177
  const homeDraft = loadHomeKimiCodeConfig(os.homedir());
@@ -354,10 +379,43 @@ function saveScopeKimiCodeConfig(
354
379
  }
355
380
  }
356
381
 
382
+ function filterAvailableKimiModels<T extends { id: string }>(
383
+ models: T[],
384
+ extras: KimiOAuthExtras,
385
+ membershipLevel: string | null,
386
+ ): T[] {
387
+ const available =
388
+ extras.modelCatalogVersion === KIMI_MODEL_CATALOG_VERSION && extras.modelCatalog
389
+ ? new Set(Object.keys(extras.modelCatalog))
390
+ : null;
391
+ return models.filter(
392
+ (model) =>
393
+ (!available || available.has(model.id)) &&
394
+ isKimiModelAvailableForMembership(model.id, membershipLevel) !== false,
395
+ );
396
+ }
397
+
357
398
  function registerKimiProvider(pi: ExtensionAPI, state: KimiRuntimeState): void {
358
- const model = applyKimiOAuthExtrasToModel(
359
- buildKimiModelFromConfig(state.config.model),
360
- state.modelExtras,
399
+ const standardModel = applyKimiMembershipLimitsToModel(
400
+ applyKimiOAuthExtrasToModel(
401
+ buildKimiModelFromConfig(state.config.model),
402
+ getKimiModelMetadata(state.modelExtras, KIMI_CODING_MODEL_ID),
403
+ ),
404
+ state.membershipLevel,
405
+ );
406
+ const highSpeedModel = applyKimiMembershipLimitsToModel(
407
+ applyKimiOAuthExtrasToModel(
408
+ buildKimiModelFromConfig(state.config.model, KIMI_CODING_HIGHSPEED_MODEL_ID),
409
+ getKimiModelMetadata(state.modelExtras, KIMI_CODING_HIGHSPEED_MODEL_ID),
410
+ ),
411
+ state.membershipLevel,
412
+ );
413
+ const k3Model = applyKimiMembershipLimitsToModel(
414
+ applyKimiOAuthExtrasToModel(
415
+ buildKimiModelFromConfig(state.config.model, KIMI_K3_MODEL_ID),
416
+ getKimiModelMetadata(state.modelExtras, KIMI_K3_MODEL_ID),
417
+ ),
418
+ state.membershipLevel,
361
419
  );
362
420
 
363
421
  pi.registerProvider(PROVIDER_ID, {
@@ -366,13 +424,35 @@ function registerKimiProvider(pi: ExtensionAPI, state: KimiRuntimeState): void {
366
424
  api: getKimiApiType(state.config.protocol),
367
425
  streamSimple: streamSimpleKimi,
368
426
 
369
- models: [model],
427
+ models: filterAvailableKimiModels(
428
+ [standardModel, highSpeedModel, k3Model],
429
+ state.modelExtras,
430
+ state.membershipLevel,
431
+ ),
370
432
 
371
433
  oauth: {
372
434
  name: "Kimi Code (OAuth)",
373
- login: loginKimiCode,
374
- refreshToken: refreshKimiCodeToken,
375
- getApiKey: (cred) => cred.access,
435
+ login: async (callbacks) => {
436
+ const credentials = await loginKimiCode(callbacks);
437
+ const usage = await fetchKimiUsageSnapshot({
438
+ timeoutMs: 2500,
439
+ token: credentials.access,
440
+ refreshOnUnauthorized: false,
441
+ });
442
+ state.membershipLevel = usage.membershipLevel;
443
+ return credentials;
444
+ },
445
+ refreshToken: async (credentials) => {
446
+ const refreshed = await refreshKimiCodeToken(credentials);
447
+ const usage = await fetchKimiUsageSnapshot({
448
+ timeoutMs: 2500,
449
+ token: refreshed.access,
450
+ refreshOnUnauthorized: false,
451
+ });
452
+ state.membershipLevel = usage.membershipLevel;
453
+ return refreshed;
454
+ },
455
+ getApiKey: getKimiApiKey,
376
456
  // Reflect server-side model identity on the registered model after login
377
457
  // / refresh. We never rewrite the model id (pi-side `/model` selections
378
458
  // and persisted sessions reference it); only the human-facing name, the
@@ -382,10 +462,25 @@ function registerKimiProvider(pi: ExtensionAPI, state: KimiRuntimeState): void {
382
462
  const extras = cred as KimiOAuthCredentials;
383
463
  state.modelExtras = extras;
384
464
  reloadEffectiveKimiRuntimeConfig(state, state.cwd, state.projectTrusted);
385
- return models.map((model) => {
386
- if (model.id !== "kimi-for-coding") return model;
387
- return applyKimiOAuthExtrasToModel(model, extras);
388
- });
465
+ const available =
466
+ extras.modelCatalogVersion === KIMI_MODEL_CATALOG_VERSION && extras.modelCatalog
467
+ ? new Set(Object.keys(extras.modelCatalog))
468
+ : null;
469
+ return models
470
+ .filter(
471
+ (model) =>
472
+ model.provider !== PROVIDER_ID ||
473
+ ((!available || available.has(model.id)) &&
474
+ isKimiModelAvailableForMembership(model.id, state.membershipLevel) !== false),
475
+ )
476
+ .map((model) =>
477
+ model.provider === PROVIDER_ID
478
+ ? applyKimiMembershipLimitsToModel(
479
+ applyKimiOAuthExtrasToModel(model, getKimiModelMetadata(extras, model.id)),
480
+ state.membershipLevel,
481
+ )
482
+ : model,
483
+ );
389
484
  },
390
485
  },
391
486
  });
@@ -399,13 +494,24 @@ export function KimiCode(overrides?: KimiCodeConfigPatch): ExtensionFactory {
399
494
  overrides,
400
495
  );
401
496
  const discoveryToken = getKimiUsageToken();
402
- const discovered = discoveryToken
403
- ? await discoverKimiModelMetadata(discoveryToken, config.protocol)
404
- : {};
497
+ const [initialDiscovery, usageSnapshot] = await Promise.all([
498
+ discoveryToken ? discoverKimiModelMetadata(discoveryToken, config.protocol) : {},
499
+ fetchKimiUsageSnapshot({ timeoutMs: 2500 }),
500
+ ]);
501
+ let discovered = initialDiscovery;
502
+ const refreshedToken = getKimiUsageToken();
503
+ if (
504
+ Object.keys(discovered).length === 0 &&
505
+ refreshedToken &&
506
+ refreshedToken !== discoveryToken
507
+ ) {
508
+ discovered = await discoverKimiModelMetadata(refreshedToken, config.protocol);
509
+ }
405
510
  const state: KimiRuntimeState = {
406
511
  cwd,
407
512
  config,
408
513
  modelExtras: discovered,
514
+ membershipLevel: usageSnapshot.membershipLevel,
409
515
  projectTrusted: false,
410
516
  overrides,
411
517
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-kimi-code",
3
- "version": "0.6.3",
3
+ "version": "0.6.6",
4
4
  "description": "Use Kimi Code in Pi without losing the Kimi-specific bits",
5
5
  "keywords": [
6
6
  "ai-provider",
package/src/config.ts CHANGED
@@ -38,6 +38,8 @@ export interface ModelConfig {
38
38
 
39
39
  export interface KimiResolvedModelConfig extends ModelConfig {
40
40
  supportsThinkingType?: "only" | "no" | "both";
41
+ supportEfforts?: string[];
42
+ defaultEffort?: string;
41
43
  }
42
44
 
43
45
  export interface KimiCodeConfig {
@@ -119,9 +121,10 @@ export const DEFAULT_KIMI_CODE_CONFIG: KimiCodeConfig = {
119
121
  off: { effort: null, enabled: false },
120
122
  minimal: { effort: "low", enabled: true },
121
123
  low: { effort: "low", enabled: true },
122
- medium: { effort: "medium", enabled: true },
124
+ medium: { effort: "high", enabled: true },
123
125
  high: { effort: "high", enabled: true },
124
- xhigh: { effort: "high", enabled: true },
126
+ xhigh: { effort: "max", enabled: true },
127
+ max: { effort: "max", enabled: true },
125
128
  },
126
129
  thinkingKeep: "all",
127
130
  generation: {},
package/src/constants.ts CHANGED
@@ -41,12 +41,12 @@ export function getDefaultBaseUrl(protocol: KimiWireProtocol): string {
41
41
 
42
42
  export const DEFAULT_BASE_URL = getDefaultBaseUrl(ENV_KIMI_CODE_PROTOCOL);
43
43
 
44
- export const PROVIDER_VERSION = "0.6.3";
44
+ export const PROVIDER_VERSION = "0.6.6";
45
45
 
46
46
  // Upstream kimi-code CLI version — used in User-Agent and X-Msh-Version
47
47
  // headers to match the official client's identity. Update this when
48
- // syncing with https://github.com/anthropics/kimi-code.
49
- export const KIMI_UPSTREAM_VERSION = "0.18.0";
48
+ // syncing with https://github.com/MoonshotAI/kimi-code.
49
+ export const KIMI_UPSTREAM_VERSION = "0.26.0";
50
50
  export const KIMI_CODE_USER_AGENT = `kimi-code-cli/${KIMI_UPSTREAM_VERSION}`;
51
51
  export const KIMI_PLATFORM = "kimi_code_cli";
52
52
 
package/src/device.ts CHANGED
@@ -152,6 +152,22 @@ function getStableDeviceId(): string {
152
152
  return DEVICE_ID;
153
153
  }
154
154
 
155
+ export function parseKimiCodeCustomHeaders(
156
+ env: NodeJS.ProcessEnv = process.env,
157
+ ): Record<string, string> {
158
+ const raw = env.KIMI_CODE_CUSTOM_HEADERS?.trim();
159
+ if (!raw) return {};
160
+ const headers: Record<string, string> = {};
161
+ for (const line of raw.split("\n")) {
162
+ const colon = line.indexOf(":");
163
+ if (colon < 0) continue;
164
+ const name = line.slice(0, colon).trim();
165
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) continue;
166
+ headers[name] = asciiHeaderValue(line.slice(colon + 1));
167
+ }
168
+ return headers;
169
+ }
170
+
155
171
  export function getCommonHeaders(): Record<string, string> {
156
172
  const headers = {
157
173
  "User-Agent": KIMI_CODE_USER_AGENT,
@@ -166,3 +182,28 @@ export function getCommonHeaders(): Record<string, string> {
166
182
  Object.entries(headers).map(([key, value]) => [key, asciiHeaderValue(value)]),
167
183
  ) as Record<string, string>;
168
184
  }
185
+
186
+ const RESERVED_PROVIDER_HEADERS = new Set([
187
+ "accept",
188
+ "anthropic-version",
189
+ "authorization",
190
+ "content-type",
191
+ "user-agent",
192
+ "x-api-key",
193
+ ]);
194
+
195
+ function isReservedProviderHeader(name: string): boolean {
196
+ const normalized = name.toLowerCase();
197
+ return RESERVED_PROVIDER_HEADERS.has(normalized) || normalized.startsWith("x-msh-");
198
+ }
199
+
200
+ export function getKimiProviderHeaders(
201
+ env: NodeJS.ProcessEnv = process.env,
202
+ ): Record<string, string> {
203
+ const customHeaders = Object.fromEntries(
204
+ Object.entries(parseKimiCodeCustomHeaders(env)).filter(
205
+ ([name]) => !isReservedProviderHeader(name),
206
+ ),
207
+ );
208
+ return { ...customHeaders, ...getCommonHeaders() };
209
+ }