pi-provider-kimi-code 0.1.4 → 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 +87 -1
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
13
|
import os from "node:os";
|
|
14
|
-
import type { OAuthCredentials, OAuthLoginCallbacks } from "@mariozechner/pi-ai";
|
|
14
|
+
import type { OAuthCredentials, OAuthLoginCallbacks, AssistantMessageEvent, Context, Model, SimpleStreamOptions } from "@mariozechner/pi-ai";
|
|
15
|
+
import { streamSimpleAnthropic, AssistantMessageEventStream } from "@mariozechner/pi-ai";
|
|
15
16
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
16
17
|
|
|
17
18
|
// =============================================================================
|
|
@@ -266,6 +267,90 @@ async function refreshKimiCodeToken(credentials: OAuthCredentials): Promise<OAut
|
|
|
266
267
|
};
|
|
267
268
|
}
|
|
268
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
|
+
|
|
269
354
|
// =============================================================================
|
|
270
355
|
// Extension Entry Point
|
|
271
356
|
// =============================================================================
|
|
@@ -275,6 +360,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
275
360
|
baseUrl: "https://api.kimi.com/coding",
|
|
276
361
|
apiKey: "KIMI_API_KEY",
|
|
277
362
|
api: "anthropic-messages",
|
|
363
|
+
streamSimple: streamSimpleKimi,
|
|
278
364
|
|
|
279
365
|
headers: getCommonHeaders(),
|
|
280
366
|
|