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.
- package/index.ts +99 -10
- 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
|
|
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 = "
|
|
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
|
{
|