pi-provider-kimi-code 0.4.1 → 0.5.1
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 +0 -15
- package/package.json +23 -21
- package/src/constants.ts +9 -4
- package/src/device.ts +9 -19
- package/src/oauth.ts +27 -20
- package/src/payload.ts +14 -6
- package/src/stream.ts +34 -7
- package/src/tools/moonshot.ts +39 -19
package/README.md
CHANGED
|
@@ -3,21 +3,6 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/pi-provider-kimi-code)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
> [!CAUTION]
|
|
9
|
-
> ## THIS PLUGIN IS NO LONGER MAINTAINED — DO NOT USE
|
|
10
|
-
>
|
|
11
|
-
> **Why this project was abandoned:**
|
|
12
|
-
>
|
|
13
|
-
> - **Account suspended by mistake.** Moonshot suspended the maintainer's account citing a policy violation that never occurred.
|
|
14
|
-
> - **No explanation after reinstatement.** The account was eventually unblocked, but Moonshot provided no reason, no apology, and no clarification of what triggered the ban.
|
|
15
|
-
> - **Discriminatory treatment of Chinese users.** Moonshot's platform shows a consistent pattern of treating Chinese-language users differently — stricter enforcement, opaque policies, and no recourse.
|
|
16
|
-
>
|
|
17
|
-
> Building on top of a vendor that bans first and refuses to explain afterwards is not a foundation worth maintaining. **Use this code at your own risk. No issues will be triaged. No PRs will be merged. No releases will be cut.**
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
6
|
**Use Kimi Code in Pi, with the Kimi parts handled.**
|
|
22
7
|
|
|
23
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, measured cache behavior, live model metadata, and optional Moonshot search/fetch tools.
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-kimi-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Use Kimi Code in Pi without losing the Kimi-specific bits",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"pi-package",
|
|
7
|
-
"pi-extension",
|
|
8
|
-
"pi-provider",
|
|
9
|
-
"pi",
|
|
10
|
-
"pi-coding-agent",
|
|
11
|
-
"pi-mono",
|
|
12
|
-
"coding-agent",
|
|
13
6
|
"ai-provider",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
7
|
+
"coding-agent",
|
|
8
|
+
"device-code",
|
|
16
9
|
"kimi",
|
|
17
|
-
"kimi-for-coding",
|
|
18
10
|
"kimi-coder",
|
|
11
|
+
"kimi-for-coding",
|
|
19
12
|
"kimi-k2",
|
|
20
|
-
"kimi-k2.6",
|
|
21
13
|
"kimi-k2.5",
|
|
14
|
+
"kimi-k2.6",
|
|
15
|
+
"llm",
|
|
16
|
+
"llm-provider",
|
|
22
17
|
"moonshot",
|
|
23
18
|
"moonshotai",
|
|
24
19
|
"oauth",
|
|
25
|
-
"
|
|
20
|
+
"pi",
|
|
21
|
+
"pi-coding-agent",
|
|
22
|
+
"pi-extension",
|
|
23
|
+
"pi-mono",
|
|
24
|
+
"pi-package",
|
|
25
|
+
"pi-provider",
|
|
26
26
|
"subscription"
|
|
27
27
|
],
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/Leechael/pi-provider-kimi-code/issues"
|
|
30
|
+
},
|
|
28
31
|
"license": "MIT",
|
|
32
|
+
"author": "Leechael",
|
|
29
33
|
"repository": {
|
|
30
34
|
"type": "git",
|
|
31
35
|
"url": "https://github.com/Leechael/pi-provider-kimi-code"
|
|
32
36
|
},
|
|
33
|
-
"bugs": {
|
|
34
|
-
"url": "https://github.com/Leechael/pi-provider-kimi-code/issues"
|
|
35
|
-
},
|
|
36
|
-
"author": "Leechael",
|
|
37
37
|
"files": [
|
|
38
38
|
"index.ts",
|
|
39
39
|
"src",
|
|
@@ -51,17 +51,19 @@
|
|
|
51
51
|
"format:check": "oxfmt . --check",
|
|
52
52
|
"pre-commit": "prek run"
|
|
53
53
|
},
|
|
54
|
-
"peerDependencies": {
|
|
55
|
-
"@earendil-works/pi-coding-agent": "*",
|
|
56
|
-
"@earendil-works/pi-ai": "*"
|
|
57
|
-
},
|
|
58
54
|
"devDependencies": {
|
|
55
|
+
"@earendil-works/pi-ai": "^0.78.0",
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.78.0",
|
|
59
57
|
"@j178/prek": "^0.3.8",
|
|
60
58
|
"@types/node": "^24.0.0",
|
|
61
59
|
"oxfmt": "^0.44.0",
|
|
62
60
|
"oxlint": "^1.59.0",
|
|
63
61
|
"typescript": "^5.9.0"
|
|
64
62
|
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@earendil-works/pi-ai": "*",
|
|
65
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
66
|
+
},
|
|
65
67
|
"pi": {
|
|
66
68
|
"extensions": [
|
|
67
69
|
"./index.ts"
|
package/src/constants.ts
CHANGED
|
@@ -24,11 +24,16 @@ 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
|
|
28
|
-
export const
|
|
29
|
-
export const KIMI_PLATFORM = "
|
|
27
|
+
export const KIMI_CODE_VERSION = "0.6.0";
|
|
28
|
+
export const KIMI_CODE_USER_AGENT = `kimi-code-cli/${KIMI_CODE_VERSION}`;
|
|
29
|
+
export const KIMI_PLATFORM = "kimi_code_cli";
|
|
30
30
|
|
|
31
|
-
export
|
|
31
|
+
export function getKimiCodeHome(): string {
|
|
32
|
+
const value = process.env.KIMI_CODE_HOME?.trim();
|
|
33
|
+
return value || join(os.homedir(), ".kimi-code");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DEVICE_ID_PATH = join(getKimiCodeHome(), "device_id");
|
|
32
37
|
|
|
33
38
|
export const DEFAULT_KIMI_MODEL_INPUT = ["text", "image"] as const;
|
|
34
39
|
|
package/src/device.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// Device identification: device-id persistence,
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
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.
|
|
77
|
-
//
|
|
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.
|
|
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":
|
|
143
|
+
"User-Agent": KIMI_CODE_USER_AGENT,
|
|
154
144
|
"X-Msh-Platform": KIMI_PLATFORM,
|
|
155
|
-
"X-Msh-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,
|
|
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
|
|
186
|
+
// Reuse existing Kimi credentials
|
|
187
187
|
//
|
|
188
|
-
// Users who already ran the upstream
|
|
189
|
-
// OAuth token
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
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
|
|
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
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
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?.("
|
|
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
|
|
258
|
+
callbacks.onProgress?.("Refresh of Kimi token failed, falling back to device flow.");
|
|
253
259
|
return null;
|
|
254
260
|
}
|
|
255
261
|
}
|
|
@@ -382,6 +388,7 @@ export async function refreshKimiAuthToken(currentKey: string): Promise<string |
|
|
|
382
388
|
console.error("[kimi-coding] auth refresh: requesting new access token");
|
|
383
389
|
const refreshed = await refreshAccessToken(cred.refresh);
|
|
384
390
|
const newCred: OAuthCredential = {
|
|
391
|
+
...cred,
|
|
385
392
|
type: "oauth",
|
|
386
393
|
access: refreshed.access_token,
|
|
387
394
|
refresh: refreshed.refresh_token,
|
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
|
-
|
|
22
|
+
maxCompletionTokens?: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function resolveCacheRetention(value?: CacheRetention): CacheRetention {
|
|
@@ -87,8 +87,8 @@ 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
|
|
91
|
-
if (
|
|
90
|
+
const maxCompletionTokens = process.env.KIMI_MODEL_MAX_COMPLETION_TOKENS;
|
|
91
|
+
if (maxCompletionTokens) out.maxCompletionTokens = parseInt(maxCompletionTokens, 10);
|
|
92
92
|
return out;
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -420,11 +420,19 @@ export async function applyKimiPayloadMutations(
|
|
|
420
420
|
if (resolved) payload.prompt_cache_key = resolved;
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
// 4.
|
|
424
|
-
|
|
423
|
+
// 4. Normalize deprecated max_tokens and apply env-level hyperparameter
|
|
424
|
+
// overrides (pre-parsed into numbers by caller).
|
|
425
|
+
if (payload.max_completion_tokens === undefined && typeof payload.max_tokens === "number") {
|
|
426
|
+
payload.max_completion_tokens = payload.max_tokens;
|
|
427
|
+
}
|
|
428
|
+
delete payload.max_tokens;
|
|
429
|
+
|
|
430
|
+
const { temperature, topP, maxCompletionTokens } = ctx.envOverrides;
|
|
425
431
|
if (temperature !== undefined) payload.temperature = temperature;
|
|
426
432
|
if (topP !== undefined) payload.top_p = topP;
|
|
427
|
-
if (
|
|
433
|
+
if (maxCompletionTokens !== undefined) {
|
|
434
|
+
payload.max_completion_tokens = maxCompletionTokens;
|
|
435
|
+
}
|
|
428
436
|
|
|
429
437
|
// 5. Reasoning effort mapping.
|
|
430
438
|
if (ctx.reasoning) {
|
package/src/stream.ts
CHANGED
|
@@ -46,24 +46,50 @@ export async function* filterEmptyResponseStream(
|
|
|
46
46
|
): AsyncIterable<AssistantMessageEvent> {
|
|
47
47
|
const suppressedIndices = new Set<number>();
|
|
48
48
|
let textBuffer: AssistantMessageEvent[] = [];
|
|
49
|
+
let bufferedText = "";
|
|
49
50
|
let bufferingIndex: number | null = null;
|
|
50
51
|
|
|
52
|
+
const suppressBufferedTextBlock = (): void => {
|
|
53
|
+
if (bufferingIndex !== null) suppressedIndices.add(bufferingIndex);
|
|
54
|
+
textBuffer = [];
|
|
55
|
+
bufferedText = "";
|
|
56
|
+
bufferingIndex = null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const flushBufferedTextBlock = async function* (): AsyncIterable<AssistantMessageEvent> {
|
|
60
|
+
for (const buffered of textBuffer) yield buffered;
|
|
61
|
+
textBuffer = [];
|
|
62
|
+
bufferedText = "";
|
|
63
|
+
bufferingIndex = null;
|
|
64
|
+
};
|
|
65
|
+
|
|
51
66
|
for await (const event of upstream) {
|
|
52
|
-
// Start buffering
|
|
67
|
+
// Start buffering only long enough to decide whether this text block is
|
|
68
|
+
// Kimi's synthetic "(Empty response: ...)" wrapper. Normal text is flushed
|
|
69
|
+
// as soon as it diverges from the marker so output still streams.
|
|
53
70
|
if (event.type === "text_start") {
|
|
54
71
|
bufferingIndex = event.contentIndex;
|
|
55
72
|
textBuffer = [event];
|
|
73
|
+
bufferedText = "";
|
|
56
74
|
continue;
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
// Accumulate text deltas + detect the empty-response marker on text_end.
|
|
60
77
|
if (
|
|
61
78
|
bufferingIndex !== null &&
|
|
62
79
|
"contentIndex" in event &&
|
|
63
80
|
event.contentIndex === bufferingIndex
|
|
64
81
|
) {
|
|
65
82
|
if (event.type === "text_delta") {
|
|
83
|
+
bufferedText += event.delta;
|
|
66
84
|
textBuffer.push(event);
|
|
85
|
+
if (bufferedText.startsWith(EMPTY_RESPONSE_PREFIX)) {
|
|
86
|
+
suppressBufferedTextBlock();
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (EMPTY_RESPONSE_PREFIX.startsWith(bufferedText)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
yield* flushBufferedTextBlock();
|
|
67
93
|
continue;
|
|
68
94
|
}
|
|
69
95
|
if (event.type === "text_end") {
|
|
@@ -72,14 +98,11 @@ export async function* filterEmptyResponseStream(
|
|
|
72
98
|
// array: it is a shared reference into session state, and mutating
|
|
73
99
|
// it would shift subsequent contentIndex values, corrupting the
|
|
74
100
|
// stream.
|
|
75
|
-
|
|
101
|
+
suppressBufferedTextBlock();
|
|
76
102
|
} else {
|
|
77
|
-
|
|
78
|
-
for (const buffered of textBuffer) yield buffered;
|
|
103
|
+
yield* flushBufferedTextBlock();
|
|
79
104
|
yield event;
|
|
80
105
|
}
|
|
81
|
-
textBuffer = [];
|
|
82
|
-
bufferingIndex = null;
|
|
83
106
|
continue;
|
|
84
107
|
}
|
|
85
108
|
}
|
|
@@ -103,6 +126,10 @@ export async function* filterEmptyResponseStream(
|
|
|
103
126
|
|
|
104
127
|
yield event;
|
|
105
128
|
}
|
|
129
|
+
|
|
130
|
+
if (bufferingIndex !== null) {
|
|
131
|
+
yield* flushBufferedTextBlock();
|
|
132
|
+
}
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
// =============================================================================
|
package/src/tools/moonshot.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Type, type Static } from "@earendil-works/pi-ai";
|
|
|
3
3
|
|
|
4
4
|
import { PROVIDER_ID } from "../constants.ts";
|
|
5
5
|
import { getCommonHeaders } from "../device.ts";
|
|
6
|
+
import { refreshKimiAuthToken } from "../oauth.ts";
|
|
6
7
|
|
|
7
8
|
const MOONSHOT_BASE_V1 = "https://api.kimi.com/coding/v1";
|
|
8
9
|
const MOONSHOT_TIMEOUT_MS = 180_000;
|
|
@@ -47,6 +48,7 @@ export interface MoonshotFetchResult {
|
|
|
47
48
|
interface MoonshotToolDeps {
|
|
48
49
|
fetch: typeof fetch;
|
|
49
50
|
getAccessToken: () => string | null;
|
|
51
|
+
refreshAccessToken: (currentToken: string) => Promise<string | null>;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface BuildMoonshotToolOptions {
|
|
@@ -68,6 +70,7 @@ function buildDeps(options: BuildMoonshotToolOptions = {}): MoonshotToolDeps {
|
|
|
68
70
|
return {
|
|
69
71
|
fetch: options.deps?.fetch ?? fetch,
|
|
70
72
|
getAccessToken: options.deps?.getAccessToken ?? defaultGetAccessToken,
|
|
73
|
+
refreshAccessToken: options.deps?.refreshAccessToken ?? refreshKimiAuthToken,
|
|
71
74
|
};
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -196,6 +199,19 @@ async function readErrorBody(response: Response): Promise<string> {
|
|
|
196
199
|
return response.text().catch(() => "");
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
async function fetchWithAuthRetry(
|
|
203
|
+
deps: MoonshotToolDeps,
|
|
204
|
+
accessToken: string,
|
|
205
|
+
request: (token: string) => Promise<Response>,
|
|
206
|
+
): Promise<Response> {
|
|
207
|
+
const response = await request(accessToken);
|
|
208
|
+
if (response.status !== 401) return response;
|
|
209
|
+
|
|
210
|
+
const refreshed = await deps.refreshAccessToken(accessToken);
|
|
211
|
+
if (!refreshed || refreshed === accessToken) return response;
|
|
212
|
+
return request(refreshed);
|
|
213
|
+
}
|
|
214
|
+
|
|
199
215
|
export function buildMoonshotSearchTool(options: BuildMoonshotToolOptions = {}) {
|
|
200
216
|
const deps = buildDeps(options);
|
|
201
217
|
const defaultCollapsed = options.defaultCollapsed ?? true;
|
|
@@ -219,17 +235,19 @@ export function buildMoonshotSearchTool(options: BuildMoonshotToolOptions = {})
|
|
|
219
235
|
const includeContent = params.include_content === true;
|
|
220
236
|
const timeout = buildTimeoutSignal(signal);
|
|
221
237
|
try {
|
|
222
|
-
const response = await deps
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
238
|
+
const response = await fetchWithAuthRetry(deps, accessToken, (token) =>
|
|
239
|
+
deps.fetch(`${MOONSHOT_BASE_V1}/search`, {
|
|
240
|
+
method: "POST",
|
|
241
|
+
headers: buildHeaders(token, toolCallId),
|
|
242
|
+
body: JSON.stringify({
|
|
243
|
+
text_query: params.query,
|
|
244
|
+
limit,
|
|
245
|
+
enable_page_crawling: includeContent,
|
|
246
|
+
timeout_seconds: 30,
|
|
247
|
+
}),
|
|
248
|
+
signal: timeout.signal,
|
|
230
249
|
}),
|
|
231
|
-
|
|
232
|
-
});
|
|
250
|
+
);
|
|
233
251
|
|
|
234
252
|
if (!response.ok) {
|
|
235
253
|
const body = await readErrorBody(response);
|
|
@@ -279,15 +297,17 @@ export function buildMoonshotFetchTool(options: BuildMoonshotToolOptions = {}) {
|
|
|
279
297
|
|
|
280
298
|
const timeout = buildTimeoutSignal(signal);
|
|
281
299
|
try {
|
|
282
|
-
const response = await deps
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
const response = await fetchWithAuthRetry(deps, accessToken, (token) =>
|
|
301
|
+
deps.fetch(`${MOONSHOT_BASE_V1}/fetch`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
...buildHeaders(token, toolCallId),
|
|
305
|
+
Accept: "text/markdown",
|
|
306
|
+
},
|
|
307
|
+
body: JSON.stringify({ url: params.url }),
|
|
308
|
+
signal: timeout.signal,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
291
311
|
|
|
292
312
|
if (!response.ok) {
|
|
293
313
|
const body = await readErrorBody(response);
|