pi-provider-kimi-code 0.1.3 → 0.1.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.
Files changed (2) hide show
  1. package/index.ts +99 -10
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -10,7 +10,9 @@
10
10
  */
11
11
 
12
12
  import { randomBytes } from "node:crypto";
13
- import type { OAuthCredentials, OAuthLoginCallbacks } from "@mariozechner/pi-ai";
13
+ import os from "node:os";
14
+ import type { OAuthCredentials, OAuthLoginCallbacks, AssistantMessageEvent, Context, Model, SimpleStreamOptions } from "@mariozechner/pi-ai";
15
+ import { streamSimpleAnthropic, AssistantMessageEventStream } from "@mariozechner/pi-ai";
14
16
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
15
17
 
16
18
  // =============================================================================
@@ -19,8 +21,9 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
19
21
 
20
22
  const CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
21
23
  const OAUTH_HOST = "https://auth.kimi.com";
22
- const KIMI_CLI_USER_AGENT = "kimi-cli/1.0.0 (external, cli)";
24
+ const KIMI_CLI_USER_AGENT = "KimiCLI/1.0.0";
23
25
  const KIMI_PLATFORM = "kimi_cli";
26
+ const KIMI_CLI_VERSION = "1.0.0";
24
27
 
25
28
  // =============================================================================
26
29
  // Device identification
@@ -32,14 +35,15 @@ function createDeviceId(): string {
32
35
 
33
36
  function getDeviceModel(): string {
34
37
  const platform = process.platform;
35
- const arch = process.arch;
38
+ const arch = os.machine() || process.arch;
39
+ const release = os.release();
36
40
  if (platform === "darwin") {
37
- return `macOS ${arch}`;
41
+ return release && arch ? `macOS ${release} ${arch}` : `macOS ${arch}`;
38
42
  }
39
43
  if (platform === "win32") {
40
- return `Windows ${arch}`;
44
+ return release && arch ? `Windows ${release} ${arch}` : `Windows ${arch}`;
41
45
  }
42
- return `${platform} ${arch}`;
46
+ return release && arch ? `${platform} ${release} ${arch}` : `${platform} ${arch}`;
43
47
  }
44
48
 
45
49
  const DEVICE_MODEL = getDeviceModel();
@@ -56,7 +60,10 @@ function getCommonHeaders(): Record<string, string> {
56
60
  return {
57
61
  "User-Agent": KIMI_CLI_USER_AGENT,
58
62
  "X-Msh-Platform": KIMI_PLATFORM,
63
+ "X-Msh-Version": KIMI_CLI_VERSION,
64
+ "X-Msh-Device-Name": os.hostname(),
59
65
  "X-Msh-Device-Model": DEVICE_MODEL,
66
+ "X-Msh-Os-Version": os.release(),
60
67
  "X-Msh-Device-Id": getStableDeviceId(),
61
68
  };
62
69
  }
@@ -260,6 +267,90 @@ async function refreshKimiCodeToken(credentials: OAuthCredentials): Promise<OAut
260
267
  };
261
268
  }
262
269
 
270
+ // =============================================================================
271
+ // Stream wrapper: strip "(Empty response: ...)" text blocks from Kimi API
272
+ // =============================================================================
273
+ // The Kimi API wraps thinking-only responses (no text content) into a text
274
+ // block like: (Empty response: {'content': [{'type': 'thinking', ...}]})
275
+ // This leaks internal state to the user. We detect and suppress such blocks.
276
+
277
+ const EMPTY_RESPONSE_PREFIX = "(Empty response:";
278
+
279
+ function streamSimpleKimi(
280
+ model: Model<"anthropic-messages">,
281
+ context: Context,
282
+ options?: SimpleStreamOptions,
283
+ ): AssistantMessageEventStream {
284
+ const upstream = streamSimpleAnthropic(model, context, options);
285
+ const filtered = new AssistantMessageEventStream();
286
+
287
+ // Buffer text block events so we can suppress the entire block if it
288
+ // turns out to be a Kimi "(Empty response: ...)" wrapper.
289
+ const suppressedIndices = new Set<number>();
290
+ let textBuffer: AssistantMessageEvent[] = [];
291
+ let bufferingIndex: number | null = null;
292
+
293
+ void (async () => {
294
+ try {
295
+ for await (const event of upstream) {
296
+ // Start buffering when a new text block begins
297
+ if (event.type === "text_start") {
298
+ bufferingIndex = event.contentIndex;
299
+ textBuffer = [event];
300
+ continue;
301
+ }
302
+
303
+ // Accumulate text deltas while buffering
304
+ if (bufferingIndex !== null && "contentIndex" in event && event.contentIndex === bufferingIndex) {
305
+ if (event.type === "text_delta") {
306
+ textBuffer.push(event);
307
+ continue;
308
+ }
309
+ if (event.type === "text_end") {
310
+ if (event.content.startsWith(EMPTY_RESPONSE_PREFIX)) {
311
+ // Suppress entire text block — discard buffered events
312
+ suppressedIndices.add(bufferingIndex);
313
+ const content = event.partial.content;
314
+ if (content[bufferingIndex]?.type === "text") {
315
+ content.splice(bufferingIndex, 1);
316
+ }
317
+ } else {
318
+ // Legitimate text block — flush buffer + end event
319
+ for (const buffered of textBuffer) filtered.push(buffered);
320
+ filtered.push(event);
321
+ }
322
+ textBuffer = [];
323
+ bufferingIndex = null;
324
+ continue;
325
+ }
326
+ }
327
+
328
+ // Skip events for already-suppressed indices
329
+ if ("contentIndex" in event && suppressedIndices.has(event.contentIndex)) {
330
+ continue;
331
+ }
332
+
333
+ // Clean suppressed blocks from the final message
334
+ if (event.type === "done" && suppressedIndices.size > 0) {
335
+ event.message.content = event.message.content.filter(
336
+ (block) => !(block.type === "text" && typeof block.text === "string" && block.text.startsWith(EMPTY_RESPONSE_PREFIX)),
337
+ );
338
+ }
339
+
340
+ filtered.push(event);
341
+ }
342
+ } catch (err) {
343
+ filtered.push({
344
+ type: "error",
345
+ reason: "error",
346
+ error: { content: [], stopReason: "error", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 } },
347
+ } as AssistantMessageEvent & { type: "error" });
348
+ }
349
+ })();
350
+
351
+ return filtered;
352
+ }
353
+
263
354
  // =============================================================================
264
355
  // Extension Entry Point
265
356
  // =============================================================================
@@ -269,11 +360,9 @@ export default function (pi: ExtensionAPI) {
269
360
  baseUrl: "https://api.kimi.com/coding",
270
361
  apiKey: "KIMI_API_KEY",
271
362
  api: "anthropic-messages",
363
+ streamSimple: streamSimpleKimi,
272
364
 
273
- headers: {
274
- "User-Agent": KIMI_CLI_USER_AGENT,
275
- "X-Msh-Platform": KIMI_PLATFORM,
276
- },
365
+ headers: getCommonHeaders(),
277
366
 
278
367
  models: [
279
368
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-kimi-code",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "pi custom provider extension for Kimi Code models with OAuth device-code login",
5
5
  "type": "module",
6
6
  "license": "MIT",