pi-provider-kimi-code 0.4.0 → 0.5.0

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
@@ -166,9 +166,9 @@ The full env list, including base URL overrides, `kimi-cli` path overrides, uplo
166
166
 
167
167
  ### Cache behavior
168
168
 
169
- Kimi's cache is content-based. It fires automatically when your prompt prefix matches an earlier request. `prompt_cache_key` and Anthropic `cache_control` markers do not control cache hits on the Coding endpoint.
169
+ Kimi's cache is content-based. It fires automatically when your prompt prefix matches an earlier request. `prompt_cache_key`, Anthropic `cache_control` markers, and identifying headers like `X-Msh-Device-Id` are all ignored for cache decisions — cache is keyed purely by prompt content and is shared across machines, agent forks, and device-id rotations on the same Kimi account.
170
170
 
171
- See [docs/caching.md](docs/caching.md) for the measured behavior: TTL, cross-protocol cache reuse, 256-token prefix alignment, and the cases that invalidate cache.
171
+ See [docs/caching.md](docs/caching.md) for the measured behavior: TTL, cross-protocol cache reuse, 256-token prefix alignment, device-id sharing, and the cases that invalidate cache.
172
172
 
173
173
  ### Protocol modes
174
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-kimi-code",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Use Kimi Code in Pi without losing the Kimi-specific bits",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/constants.ts CHANGED
@@ -24,11 +24,17 @@ export const DEFAULT_BASE_URL = IS_OPENAI_PROTOCOL
24
24
  ? "https://api.kimi.com/coding/v1"
25
25
  : "https://api.kimi.com/coding";
26
26
 
27
- export const KIMI_CLI_VERSION = "1.44.0";
28
- export const KIMI_CLI_USER_AGENT = `KimiCLI/${KIMI_CLI_VERSION}`;
29
- export const KIMI_PLATFORM = "kimi_cli";
27
+ export const KIMI_CODE_VERSION = "0.1.1";
28
+ export const KIMI_CODE_USER_AGENT_PRODUCT = "kimi-code-cli";
29
+ export const KIMI_CODE_USER_AGENT = `${KIMI_CODE_USER_AGENT_PRODUCT}/${KIMI_CODE_VERSION}`;
30
+ export const KIMI_PLATFORM = "kimi-code-cli";
31
+
32
+ export function getKimiCodeHome(): string {
33
+ const value = process.env.KIMI_CODE_HOME?.trim();
34
+ return value || join(os.homedir(), ".kimi-code");
35
+ }
30
36
 
31
- export const DEVICE_ID_PATH = join(os.homedir(), ".pi", "providers", "kimi-coding", "device_id");
37
+ export const DEVICE_ID_PATH = join(getKimiCodeHome(), "device_id");
32
38
 
33
39
  export const DEFAULT_KIMI_MODEL_INPUT = ["text", "image"] as const;
34
40
 
package/src/device.ts CHANGED
@@ -1,8 +1,7 @@
1
- // Device identification: device-id persistence, kimi-cli-compatible request
1
+ // Device identification: device-id persistence, Kimi Code-compatible request
2
2
  // headers, and the cross-platform device-model string. Pure helpers live near
3
3
  // the side-effecting one-shots (file IO, exec) they support.
4
4
 
5
- import { execSync } from "node:child_process";
6
5
  import { randomBytes } from "node:crypto";
7
6
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
7
  import os from "node:os";
@@ -10,8 +9,8 @@ import { dirname } from "node:path";
10
9
 
11
10
  import {
12
11
  DEVICE_ID_PATH,
13
- KIMI_CLI_USER_AGENT,
14
- KIMI_CLI_VERSION,
12
+ KIMI_CODE_USER_AGENT,
13
+ KIMI_CODE_VERSION,
15
14
  KIMI_PLATFORM,
16
15
  } from "./constants.ts";
17
16
 
@@ -47,16 +46,8 @@ function persistDeviceId(deviceId: string): void {
47
46
  }
48
47
  }
49
48
 
50
- function getMacOSVersion(): string {
51
- try {
52
- return execSync("sw_vers -productVersion", { encoding: "utf8" }).trim();
53
- } catch {
54
- return os.release();
55
- }
56
- }
57
-
58
49
  // Normalize Node's lower-case `process.platform` (`linux`, `freebsd`,
59
- // `sunos`...) to Python `platform.system()` casing used by upstream kimi-cli.
50
+ // `sunos`...) to the canonical OS names used by Kimi Code identity headers.
60
51
  const SYSTEM_NAME: Record<string, string> = {
61
52
  aix: "AIX",
62
53
  freebsd: "FreeBSD",
@@ -73,8 +64,8 @@ export interface DeviceModelInput {
73
64
  macVersion?: string;
74
65
  }
75
66
 
76
- // Pure function exposed for tests. Mirror of upstream kimi-cli's
77
- // `_device_model()` in `src/kimi_cli/auth/oauth.py`.
67
+ // Pure function exposed for tests. Mirrors upstream Kimi Code identity
68
+ // formatting in `packages/oauth/src/identity.ts`.
78
69
  export function computeDeviceModel(input: DeviceModelInput): string {
79
70
  const { platform, release, arch, macVersion } = input;
80
71
  if (platform === "darwin") {
@@ -110,12 +101,11 @@ function getDeviceModel(): string {
110
101
  platform: process.platform,
111
102
  release: os.release(),
112
103
  arch: os.machine() || process.arch,
113
- macVersion: process.platform === "darwin" ? getMacOSVersion() : undefined,
114
104
  });
115
105
  }
116
106
 
117
107
  export function getOsVersion(): string {
118
- return os.version();
108
+ return os.release();
119
109
  }
120
110
 
121
111
  export function asciiHeaderValue(value: string, fallback = "unknown"): string {
@@ -150,9 +140,9 @@ function getStableDeviceId(): string {
150
140
 
151
141
  export function getCommonHeaders(): Record<string, string> {
152
142
  const headers = {
153
- "User-Agent": KIMI_CLI_USER_AGENT,
143
+ "User-Agent": KIMI_CODE_USER_AGENT,
154
144
  "X-Msh-Platform": KIMI_PLATFORM,
155
- "X-Msh-Version": KIMI_CLI_VERSION,
145
+ "X-Msh-Version": KIMI_CODE_VERSION,
156
146
  "X-Msh-Device-Name": os.hostname(),
157
147
  "X-Msh-Device-Model": DEVICE_MODEL,
158
148
  "X-Msh-Os-Version": getOsVersion(),
package/src/oauth.ts CHANGED
@@ -1,4 +1,4 @@
1
- // OAuth subsystem: device flow, refresh-with-retry, kimi-cli credential
1
+ // OAuth subsystem: device flow, refresh-with-retry, Kimi credential
2
2
  // reuse, the login / refresh handlers wired into pi's OAuth interface, and
3
3
  // the stream-level auth refresh used by the streaming handler.
4
4
 
@@ -183,13 +183,13 @@ export async function refreshAccessToken(
183
183
  }
184
184
 
185
185
  // =============================================================================
186
- // Reuse existing kimi-cli credentials
186
+ // Reuse existing Kimi credentials
187
187
  //
188
- // Users who already ran the upstream `kimi-cli` and signed in have a valid
189
- // OAuth token sitting at `$KIMI_SHARE_DIR/credentials/kimi-code.json`
190
- // (defaults to `~/.kimi/...`). Loading it lets users skip the device-flow
191
- // dance entirely. Read-only: we never overwrite kimi-cli's file, only seed
192
- // pi's auth.json via the value returned to pi's OAuth callback.
188
+ // Users who already ran the upstream Kimi Code CLI and signed in have a valid
189
+ // OAuth token under `$KIMI_CODE_HOME/credentials/kimi-code.json` (defaults to
190
+ // `~/.kimi-code/...`). We also keep read-only support for legacy kimi-cli
191
+ // credentials under `$KIMI_SHARE_DIR/credentials/kimi-code.json` (defaults to
192
+ // `~/.kimi/...`). Loading either lets users skip the device-flow dance.
193
193
  // =============================================================================
194
194
 
195
195
  interface KimiCliCredentialsFile {
@@ -198,21 +198,27 @@ interface KimiCliCredentialsFile {
198
198
  expires_at?: number; // Unix seconds (upstream convention)
199
199
  }
200
200
 
201
- function getKimiCliCredentialsPath(): string {
201
+ function getKimiCredentialPaths(): string[] {
202
+ const kimiCodeHome = process.env.KIMI_CODE_HOME || join(os.homedir(), ".kimi-code");
202
203
  const shareDir = process.env.KIMI_SHARE_DIR || join(os.homedir(), ".kimi");
203
- return join(shareDir, "credentials", "kimi-code.json");
204
+ return [
205
+ join(kimiCodeHome, "credentials", "kimi-code.json"),
206
+ join(shareDir, "credentials", "kimi-code.json"),
207
+ ];
204
208
  }
205
209
 
206
210
  function readKimiCliCredentials(): KimiCliCredentialsFile | null {
207
- const path = getKimiCliCredentialsPath();
208
- try {
209
- if (!existsSync(path)) return null;
210
- const data = JSON.parse(readFileSync(path, "utf-8")) as KimiCliCredentialsFile;
211
- if (!data.access_token || !data.refresh_token) return null;
212
- return data;
213
- } catch {
214
- return null;
211
+ for (const path of getKimiCredentialPaths()) {
212
+ try {
213
+ if (!existsSync(path)) continue;
214
+ const data = JSON.parse(readFileSync(path, "utf-8")) as KimiCliCredentialsFile;
215
+ if (!data.access_token || !data.refresh_token) continue;
216
+ return data;
217
+ } catch {
218
+ continue;
219
+ }
215
220
  }
221
+ return null;
216
222
  }
217
223
 
218
224
  async function tryReuseKimiCliCredentials(
@@ -226,7 +232,7 @@ async function tryReuseKimiCliCredentials(
226
232
  // between this returning and the first API call.
227
233
  const stillFresh = expiresAtMs > Date.now() + 60_000;
228
234
 
229
- callbacks.onProgress?.("Found existing kimi-cli credentials, reusing them.");
235
+ callbacks.onProgress?.("Found existing Kimi credentials, reusing them.");
230
236
 
231
237
  if (stillFresh) {
232
238
  const extras = await discoverKimiModelMetadata(data.access_token!);
@@ -238,7 +244,7 @@ async function tryReuseKimiCliCredentials(
238
244
  };
239
245
  }
240
246
 
241
- callbacks.onProgress?.("kimi-cli access token expired, refreshing.");
247
+ callbacks.onProgress?.("Kimi access token expired, refreshing.");
242
248
  try {
243
249
  const token = await refreshAccessToken(data.refresh_token!);
244
250
  const extras = await discoverKimiModelMetadata(token.access_token);
@@ -249,7 +255,7 @@ async function tryReuseKimiCliCredentials(
249
255
  ...extras,
250
256
  };
251
257
  } catch {
252
- callbacks.onProgress?.("Refresh of kimi-cli token failed, falling back to device flow.");
258
+ callbacks.onProgress?.("Refresh of Kimi token failed, falling back to device flow.");
253
259
  return null;
254
260
  }
255
261
  }
package/src/payload.ts CHANGED
@@ -19,7 +19,7 @@ export type Uploader = (mimeType: string, data: string) => Promise<string | null
19
19
  export interface KimiEnvOverrides {
20
20
  temperature?: number;
21
21
  topP?: number;
22
- maxTokens?: number;
22
+ maxCompletionTokens?: number;
23
23
  }
24
24
 
25
25
  export function resolveCacheRetention(value?: CacheRetention): CacheRetention {
@@ -87,8 +87,9 @@ export function readEnvOverrides(): KimiEnvOverrides {
87
87
  if (temp) out.temperature = parseFloat(temp);
88
88
  const topP = process.env.KIMI_MODEL_TOP_P;
89
89
  if (topP) out.topP = parseFloat(topP);
90
- const maxTokens = process.env.KIMI_MODEL_MAX_TOKENS;
91
- if (maxTokens) out.maxTokens = parseInt(maxTokens, 10);
90
+ const maxCompletionTokens =
91
+ process.env.KIMI_MODEL_MAX_COMPLETION_TOKENS ?? process.env.KIMI_MODEL_MAX_TOKENS;
92
+ if (maxCompletionTokens) out.maxCompletionTokens = parseInt(maxCompletionTokens, 10);
92
93
  return out;
93
94
  }
94
95
 
@@ -421,10 +422,13 @@ export async function applyKimiPayloadMutations(
421
422
  }
422
423
 
423
424
  // 4. Env-level hyperparameter overrides (pre-parsed into numbers by caller).
424
- const { temperature, topP, maxTokens } = ctx.envOverrides;
425
+ const { temperature, topP, maxCompletionTokens } = ctx.envOverrides;
425
426
  if (temperature !== undefined) payload.temperature = temperature;
426
427
  if (topP !== undefined) payload.top_p = topP;
427
- if (maxTokens !== undefined) payload.max_tokens = maxTokens;
428
+ if (maxCompletionTokens !== undefined) {
429
+ payload.max_completion_tokens = maxCompletionTokens;
430
+ delete payload.max_tokens;
431
+ }
428
432
 
429
433
  // 5. Reasoning effort mapping.
430
434
  if (ctx.reasoning) {