pi-provider-kimi-code 0.6.0 → 0.6.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 +34 -9
- package/index.ts +255 -69
- package/package.json +1 -1
- package/src/config.ts +450 -92
- package/src/constants.ts +29 -11
- package/src/device.ts +14 -0
- package/src/models.ts +97 -52
- package/src/payload.ts +81 -50
- package/src/schema-dedup.ts +256 -0
- package/src/stream.ts +54 -20
- package/src/tools/datasource.ts +52 -4
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
**Use Kimi Code in Pi, with the Kimi parts handled.**
|
|
7
7
|
|
|
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,
|
|
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, tool schema compatibility, live model metadata, measured cache behavior, and API-tested parameter handling.
|
|
9
9
|
|
|
10
|
-
> **Kimi K2.7 supported.** The provider automatically discovers the current model from Kimi's `/v1/models` endpoint at login time, so new rollouts (including K2.7) are picked up immediately without a package update.
|
|
10
|
+
> **Kimi K2.7 supported.** The provider automatically discovers the current model from Kimi's `/v1/models` endpoint at login, refresh, and `/kimi-settings` time, so new rollouts (including K2.7) are picked up immediately without a package update.
|
|
11
11
|
|
|
12
12
|
## Why this exists
|
|
13
13
|
|
|
@@ -18,19 +18,24 @@ Kimi's API surface goes beyond the LLM itself. It includes file uploads, web sea
|
|
|
18
18
|
- **Use your Kimi account.** Log in with `/login kimi-coding`, or reuse an existing `kimi-code` session.
|
|
19
19
|
- **Send files the Kimi way.** Large inline images go through Kimi's Files API and become `ms://` references instead of huge base64 payloads.
|
|
20
20
|
- **Know what the cache is doing.** Kimi caches by content prefix. This repo measures that behavior instead of pretending `prompt_cache_key` controls it.
|
|
21
|
-
- **
|
|
21
|
+
- **Keep Pi's tools working.** Moonshot's API rejects tool schemas over 15 KB — a limit that Pi's extension ecosystem regularly hits ([#16](https://github.com/Leechael/pi-provider-kimi-code/issues/16), [#21](https://github.com/Leechael/pi-provider-kimi-code/issues/21)). This extension automatically deduplicates schemas with `$ref`/`$defs` before sending, so subagents and other extensions don't break.
|
|
22
|
+
- **Tested against the live API.** Thinking config, parameter constraints, protocol compatibility, and streaming behavior are all verified against Kimi's Coding endpoint — not assumed from docs. When the API rejects something (wrong `temperature`, unsupported `tool_choice`), the provider strips it before you see a 400.
|
|
22
23
|
- **Turn on Kimi-native tools when you want them.** `moonshot_search`, `moonshot_fetch`, and `kimi_datasource` are opt-in, configurable per user or per project.
|
|
24
|
+
- **Embed in your own build.** `KimiCode()` factory lets you ship Kimi Code support inside a custom Pi agent with programmatic config overrides — no file-based extension path needed.
|
|
23
25
|
|
|
24
26
|
## What this package adds
|
|
25
27
|
|
|
26
28
|
- Kimi account login in Pi, plus `KIMI_API_KEY` for CI or pay-per-token use.
|
|
27
29
|
- `kimi-code` credential reuse from `~/.kimi/credentials/kimi-code.json`.
|
|
28
30
|
- Kimi Files API uploads for large inline images.
|
|
29
|
-
- Live model metadata from Kimi at login
|
|
31
|
+
- Live model metadata from Kimi at login, refresh, and `/kimi-settings` time.
|
|
30
32
|
- OpenAI-compatible mode by default, Anthropic-compatible mode on request.
|
|
31
|
-
- Kimi K2.
|
|
33
|
+
- Kimi K2.7 reasoning level mapping for Pi's reasoning controls.
|
|
34
|
+
- Tool schema dedup to stay under Moonshot's 15 KB per-tool-schema limit.
|
|
35
|
+
- K2.7 parameter guard: strips `temperature`, `top_p`, and `tool_choice` values that the API rejects.
|
|
32
36
|
- Stream cleanup for Kimi's thinking-only placeholder text.
|
|
33
37
|
- Optional `moonshot_search`, `moonshot_fetch`, and unified datasource `kimi_datasource` tools via `/kimi-settings`.
|
|
38
|
+
- Programmatic `KimiCode()` factory for embedding in custom Pi builds.
|
|
34
39
|
- No build step; Pi loads the TypeScript extension directly.
|
|
35
40
|
|
|
36
41
|
## Install
|
|
@@ -56,6 +61,21 @@ curl -L https://github.com/Leechael/pi-provider-kimi-code/releases/latest/downlo
|
|
|
56
61
|
pi install /tmp/pi-provider-kimi-code
|
|
57
62
|
```
|
|
58
63
|
|
|
64
|
+
### Programmatic usage
|
|
65
|
+
|
|
66
|
+
Use `KimiCode()` to embed Kimi Code as a provider inside a custom Pi build:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { main } from "@earendil-works/pi-coding-agent";
|
|
70
|
+
import { KimiCode } from "pi-provider-kimi-code";
|
|
71
|
+
|
|
72
|
+
main(process.argv.slice(2), {
|
|
73
|
+
extensionFactories: [KimiCode({ protocol: "anthropic" })],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`KimiCode()` with no arguments behaves identically to the file-based extension. Pass a `KimiCodeConfigPatch` to override defaults (protocol, upload threshold, tools, model parameters). See [docs/programmatic-usage.md](docs/programmatic-usage.md) for the full API.
|
|
78
|
+
|
|
59
79
|
## Sign in
|
|
60
80
|
|
|
61
81
|
Inside Pi, run:
|
|
@@ -103,7 +123,7 @@ Fallback values:
|
|
|
103
123
|
- Input: text and image
|
|
104
124
|
- Reasoning: enabled
|
|
105
125
|
|
|
106
|
-
The provider maps Pi's reasoning levels to Kimi's
|
|
126
|
+
The provider maps Pi's reasoning levels to Kimi's `reasoning_effort` and top-level `thinking` parameters. The mapping refreshes automatically on credential refresh. Opening `/kimi-settings` also re-discovers the latest model metadata.
|
|
107
127
|
|
|
108
128
|
## Optional tools
|
|
109
129
|
|
|
@@ -115,7 +135,9 @@ Inside Pi, run:
|
|
|
115
135
|
/kimi-settings
|
|
116
136
|
```
|
|
117
137
|
|
|
118
|
-
That command shows
|
|
138
|
+
That command shows the current server-side model name (e.g. "K2.7 Code High Speed"), your Kimi usage summary, and lets you edit the home or project config. Changes apply to the active session tool set.
|
|
139
|
+
|
|
140
|
+
Configurable settings include protocol mode, upload threshold, and per-tool enable/collapse.
|
|
119
141
|
|
|
120
142
|
Config files are JSON:
|
|
121
143
|
|
|
@@ -136,6 +158,8 @@ Project config overrides home config with a deep merge. Missing files or missing
|
|
|
136
158
|
|
|
137
159
|
```json
|
|
138
160
|
{
|
|
161
|
+
"protocol": "openai",
|
|
162
|
+
"uploads": { "thresholdBytes": 1048576 },
|
|
139
163
|
"tools": {
|
|
140
164
|
"moonshot_search": { "enabled": true, "default_collapsed": true },
|
|
141
165
|
"moonshot_fetch": { "enabled": true, "default_collapsed": true },
|
|
@@ -176,9 +200,9 @@ If you already use another search or fetch tool, pick one path for a session. Ov
|
|
|
176
200
|
Most users do not need environment variables. Two are worth knowing:
|
|
177
201
|
|
|
178
202
|
- `KIMI_API_KEY` — static API key for CI or pay-per-token use.
|
|
179
|
-
- `KIMI_CODE_PROTOCOL` — `openai` by default; set to `anthropic` if your Pi setup needs Anthropic-compatible requests.
|
|
203
|
+
- `KIMI_CODE_PROTOCOL` — `openai` by default; set to `anthropic` if your Pi setup needs Anthropic-compatible requests. Can also be set through `/kimi-settings` or JSON config.
|
|
180
204
|
|
|
181
|
-
|
|
205
|
+
Tools, protocol, and upload threshold are all configurable through `/kimi-settings` or JSON config files.
|
|
182
206
|
|
|
183
207
|
The full env list, including base URL overrides, `kimi-code` path overrides, upload tuning, debug logs, and model metadata overrides, lives in [docs/ENV.md](docs/ENV.md).
|
|
184
208
|
|
|
@@ -263,6 +287,7 @@ In OpenAI mode this extension maps the `developer` role to `system` (Kimi's Codi
|
|
|
263
287
|
|
|
264
288
|
- Pi: [earendil-works/pi](https://github.com/earendil-works/pi)
|
|
265
289
|
- Environment variables: [docs/ENV.md](docs/ENV.md)
|
|
290
|
+
- Programmatic usage: [docs/programmatic-usage.md](docs/programmatic-usage.md)
|
|
266
291
|
- Testing guide: [docs/TESTING.md](docs/TESTING.md)
|
|
267
292
|
- Cache behavior: [docs/caching.md](docs/caching.md)
|
|
268
293
|
- Architecture notes: [docs/architecture.md](docs/architecture.md)
|
package/index.ts
CHANGED
|
@@ -18,17 +18,18 @@
|
|
|
18
18
|
* src/stream.ts — empty-response filter + streamSimpleKimi orchestrator
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
22
21
|
import {
|
|
23
22
|
AuthStorage,
|
|
24
23
|
type ExtensionAPI,
|
|
25
24
|
type ExtensionCommandContext,
|
|
25
|
+
type ExtensionFactory,
|
|
26
26
|
} from "@earendil-works/pi-coding-agent";
|
|
27
27
|
import os from "node:os";
|
|
28
28
|
import { relative } from "node:path";
|
|
29
29
|
|
|
30
30
|
import {
|
|
31
31
|
type KimiCodeConfig,
|
|
32
|
+
type KimiCodeConfigPatch,
|
|
32
33
|
KIMI_TOOL_NAMES,
|
|
33
34
|
getProjectKimiCodeConfigPath,
|
|
34
35
|
getGlobalKimiCodeConfigPath,
|
|
@@ -40,20 +41,18 @@ import {
|
|
|
40
41
|
saveProjectKimiCodeConfig,
|
|
41
42
|
type KimiToolName,
|
|
42
43
|
} from "./src/config.ts";
|
|
43
|
-
import {
|
|
44
|
-
DEFAULT_KIMI_MODEL_INPUT,
|
|
45
|
-
KIMI_API_TYPE,
|
|
46
|
-
PROVIDER_ID,
|
|
47
|
-
getBaseUrl,
|
|
48
|
-
} from "./src/constants.ts";
|
|
44
|
+
import { KIMI_CODE_VERSION, PROVIDER_ID, getBaseUrl, getKimiApiType } from "./src/constants.ts";
|
|
49
45
|
import { getCommonHeaders } from "./src/device.ts";
|
|
50
46
|
import {
|
|
51
47
|
type KimiOAuthCredentials,
|
|
52
|
-
|
|
48
|
+
type KimiOAuthExtras,
|
|
49
|
+
buildKimiModelFromConfig,
|
|
53
50
|
applyKimiOAuthExtrasToModel,
|
|
51
|
+
discoverKimiModelMetadata,
|
|
52
|
+
resolveKimiModelConfig,
|
|
54
53
|
} from "./src/models.ts";
|
|
55
54
|
import { loginKimiCode, refreshKimiAuthToken, refreshKimiCodeToken } from "./src/oauth.ts";
|
|
56
|
-
import { streamSimpleKimi } from "./src/stream.ts";
|
|
55
|
+
import { setStoreResolvedKimiConfig, streamSimpleKimi } from "./src/stream.ts";
|
|
57
56
|
import { buildMoonshotFetchTool, buildMoonshotSearchTool } from "./src/tools/moonshot.ts";
|
|
58
57
|
import { buildKimiDatasourceTool } from "./src/tools/datasource.ts";
|
|
59
58
|
type KimiConfigScope = "project" | "home";
|
|
@@ -72,6 +71,13 @@ interface UsageRow {
|
|
|
72
71
|
limit: number;
|
|
73
72
|
}
|
|
74
73
|
|
|
74
|
+
interface KimiRuntimeState {
|
|
75
|
+
cwd: string;
|
|
76
|
+
config: KimiCodeConfig;
|
|
77
|
+
modelExtras: KimiOAuthExtras;
|
|
78
|
+
overrides?: KimiCodeConfigPatch;
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
function buildKimiTool(toolName: KimiToolName, config: KimiCodeConfig) {
|
|
76
82
|
const opts = { defaultCollapsed: config.tools[toolName].default_collapsed };
|
|
77
83
|
if (toolName === "moonshot_search") return buildMoonshotSearchTool(opts);
|
|
@@ -105,6 +111,29 @@ function registerConfiguredMoonshotTools(
|
|
|
105
111
|
pi.setActiveTools([...activeTools]);
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
function reloadEffectiveKimiRuntimeConfig(state: KimiRuntimeState, cwd: string): KimiCodeConfig {
|
|
115
|
+
const config = loadKimiCodeConfig({ cwd, home: os.homedir() }, state.overrides);
|
|
116
|
+
state.cwd = cwd;
|
|
117
|
+
state.config = config;
|
|
118
|
+
setStoreResolvedKimiConfig({
|
|
119
|
+
model: resolveKimiModelConfig(config.model, state.modelExtras),
|
|
120
|
+
protocol: config.protocol,
|
|
121
|
+
uploads: config.uploads,
|
|
122
|
+
});
|
|
123
|
+
return config;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function applyEffectiveKimiRuntimeConfig(
|
|
127
|
+
pi: ExtensionAPI,
|
|
128
|
+
state: KimiRuntimeState,
|
|
129
|
+
cwd: string,
|
|
130
|
+
options: { updateActiveTools: boolean },
|
|
131
|
+
): KimiCodeConfig {
|
|
132
|
+
const config = reloadEffectiveKimiRuntimeConfig(state, cwd);
|
|
133
|
+
registerConfiguredMoonshotTools(pi, config, options);
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
|
|
108
137
|
function getKimiUsageToken(): string | null {
|
|
109
138
|
const credential = AuthStorage.create().get(PROVIDER_ID);
|
|
110
139
|
if (credential?.type === "oauth" && credential.access) return credential.access;
|
|
@@ -231,6 +260,7 @@ function moonshotStatus(config: KimiCodeConfig): string {
|
|
|
231
260
|
|
|
232
261
|
function toggleEnabled(config: KimiCodeConfig, toolName: KimiToolName): KimiCodeConfig {
|
|
233
262
|
return {
|
|
263
|
+
...config,
|
|
234
264
|
tools: {
|
|
235
265
|
...config.tools,
|
|
236
266
|
[toolName]: {
|
|
@@ -243,6 +273,7 @@ function toggleEnabled(config: KimiCodeConfig, toolName: KimiToolName): KimiCode
|
|
|
243
273
|
|
|
244
274
|
function toggleCollapsed(config: KimiCodeConfig, toolName: KimiToolName): KimiCodeConfig {
|
|
245
275
|
return {
|
|
276
|
+
...config,
|
|
246
277
|
tools: {
|
|
247
278
|
...config.tools,
|
|
248
279
|
[toolName]: {
|
|
@@ -253,13 +284,34 @@ function toggleCollapsed(config: KimiCodeConfig, toolName: KimiToolName): KimiCo
|
|
|
253
284
|
};
|
|
254
285
|
}
|
|
255
286
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
287
|
+
function setProtocol(config: KimiCodeConfig, protocol: KimiCodeConfig["protocol"]): KimiCodeConfig {
|
|
288
|
+
return { ...config, protocol };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function setUploadThreshold(config: KimiCodeConfig, thresholdBytes: number): KimiCodeConfig {
|
|
292
|
+
return { ...config, uploads: { ...config.uploads, thresholdBytes } };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function refreshModelExtras(state: KimiRuntimeState): Promise<void> {
|
|
296
|
+
const token = getKimiUsageToken();
|
|
297
|
+
if (!token) return;
|
|
298
|
+
const extras = await discoverKimiModelMetadata(token, state.config.protocol);
|
|
299
|
+
if (Object.keys(extras).length > 0) {
|
|
300
|
+
Object.assign(state.modelExtras, extras);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function runKimiCommand(
|
|
305
|
+
pi: ExtensionAPI,
|
|
306
|
+
ctx: ExtensionCommandContext,
|
|
307
|
+
state: KimiRuntimeState,
|
|
308
|
+
): Promise<void> {
|
|
309
|
+
let config = applyEffectiveKimiRuntimeConfig(pi, state, ctx.cwd, { updateActiveTools: true });
|
|
310
|
+
let [usage] = await Promise.all([fetchKimiUsageSummary(), refreshModelExtras(state)]);
|
|
259
311
|
ctx.ui.notify(usage);
|
|
260
312
|
|
|
261
313
|
while (true) {
|
|
262
|
-
const choice = await ctx.ui.select(buildKimiMainTitle(config, ctx.cwd), [
|
|
314
|
+
const choice = await ctx.ui.select(buildKimiMainTitle(config, ctx.cwd, state.modelExtras), [
|
|
263
315
|
`Edit project config (${relative(ctx.cwd, getProjectKimiCodeConfigPath(ctx.cwd))})`,
|
|
264
316
|
`Edit home config (${homeRelative(getGlobalKimiCodeConfigPath(os.homedir()))})`,
|
|
265
317
|
"Refresh usage",
|
|
@@ -273,9 +325,9 @@ async function runKimiCommand(pi: ExtensionAPI, ctx: ExtensionCommandContext): P
|
|
|
273
325
|
continue;
|
|
274
326
|
}
|
|
275
327
|
if (choice.startsWith("Edit project config")) {
|
|
276
|
-
config = await editConfigScope(pi, ctx, "project");
|
|
328
|
+
config = await editConfigScope(pi, ctx, state, "project");
|
|
277
329
|
} else if (choice.startsWith("Edit home config")) {
|
|
278
|
-
config = await editConfigScope(pi, ctx, "home");
|
|
330
|
+
config = await editConfigScope(pi, ctx, state, "home");
|
|
279
331
|
}
|
|
280
332
|
}
|
|
281
333
|
}
|
|
@@ -283,26 +335,75 @@ async function runKimiCommand(pi: ExtensionAPI, ctx: ExtensionCommandContext): P
|
|
|
283
335
|
async function editConfigScope(
|
|
284
336
|
pi: ExtensionAPI,
|
|
285
337
|
ctx: ExtensionCommandContext,
|
|
338
|
+
state: KimiRuntimeState,
|
|
286
339
|
scope: KimiConfigScope,
|
|
287
340
|
): Promise<KimiCodeConfig> {
|
|
288
341
|
let current = loadScopeKimiCodeConfig(scope, ctx.cwd);
|
|
289
342
|
while (true) {
|
|
290
343
|
const items = KIMI_TOOL_NAMES.map((name) => toolMenuItem(current, name));
|
|
291
|
-
items.push("Back");
|
|
344
|
+
items.push(protocolMenuItem(current), uploadThresholdMenuItem(current), "Back");
|
|
292
345
|
const choice = await ctx.ui.select(buildConfigScopeTitle(scope, current, ctx.cwd), items);
|
|
293
346
|
if (!choice || choice === "Back") {
|
|
294
347
|
return loadKimiCodeConfig({ cwd: ctx.cwd, home: os.homedir() });
|
|
295
348
|
}
|
|
296
349
|
const toolName = KIMI_TOOL_NAMES.find((name) => choice.startsWith(name));
|
|
297
350
|
if (toolName) {
|
|
298
|
-
current = await editMoonshotTool(pi, ctx, scope, current, toolName);
|
|
351
|
+
current = await editMoonshotTool(pi, ctx, state, scope, current, toolName);
|
|
352
|
+
} else if (choice.startsWith("Protocol")) {
|
|
353
|
+
current = await editProtocol(pi, ctx, state, scope, current);
|
|
354
|
+
} else if (choice.startsWith("Upload threshold")) {
|
|
355
|
+
current = await editUploadThreshold(pi, ctx, state, scope, current);
|
|
299
356
|
}
|
|
300
357
|
}
|
|
301
358
|
}
|
|
302
359
|
|
|
360
|
+
async function editProtocol(
|
|
361
|
+
pi: ExtensionAPI,
|
|
362
|
+
ctx: ExtensionCommandContext,
|
|
363
|
+
state: KimiRuntimeState,
|
|
364
|
+
scope: KimiConfigScope,
|
|
365
|
+
config: KimiCodeConfig,
|
|
366
|
+
): Promise<KimiCodeConfig> {
|
|
367
|
+
const choice = await ctx.ui.select(`Edit protocol\n\n${formatProtocolStatus(config)}`, [
|
|
368
|
+
"Use openai protocol",
|
|
369
|
+
"Use anthropic protocol",
|
|
370
|
+
"Back",
|
|
371
|
+
]);
|
|
372
|
+
if (!choice || choice === "Back") return config;
|
|
373
|
+
const protocol = choice.includes("anthropic") ? "anthropic" : "openai";
|
|
374
|
+
const current = setProtocol(config, protocol);
|
|
375
|
+
saveAndApplyKimiCodeConfig(pi, ctx, state, scope, current);
|
|
376
|
+
ctx.ui.notify("Saved protocol config", "info");
|
|
377
|
+
return current;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function editUploadThreshold(
|
|
381
|
+
pi: ExtensionAPI,
|
|
382
|
+
ctx: ExtensionCommandContext,
|
|
383
|
+
state: KimiRuntimeState,
|
|
384
|
+
scope: KimiConfigScope,
|
|
385
|
+
config: KimiCodeConfig,
|
|
386
|
+
): Promise<KimiCodeConfig> {
|
|
387
|
+
const input = await ctx.ui.input(
|
|
388
|
+
"Edit upload threshold\n\nExamples: 512 KiB, 2 MiB, 1.5 MB. Plain numbers are MiB.",
|
|
389
|
+
formatByteSize(config.uploads.thresholdBytes),
|
|
390
|
+
);
|
|
391
|
+
if (input === undefined) return config;
|
|
392
|
+
const thresholdBytes = parseByteSizeInput(input);
|
|
393
|
+
if (thresholdBytes === undefined || thresholdBytes <= 0) {
|
|
394
|
+
ctx.ui.notify("Upload threshold must be a positive size, for example 2 MiB", "error");
|
|
395
|
+
return config;
|
|
396
|
+
}
|
|
397
|
+
const current = setUploadThreshold(config, thresholdBytes);
|
|
398
|
+
saveAndApplyKimiCodeConfig(pi, ctx, state, scope, current);
|
|
399
|
+
ctx.ui.notify("Saved upload threshold config", "info");
|
|
400
|
+
return current;
|
|
401
|
+
}
|
|
402
|
+
|
|
303
403
|
async function editMoonshotTool(
|
|
304
404
|
pi: ExtensionAPI,
|
|
305
405
|
ctx: ExtensionCommandContext,
|
|
406
|
+
state: KimiRuntimeState,
|
|
306
407
|
scope: KimiConfigScope,
|
|
307
408
|
config: KimiCodeConfig,
|
|
308
409
|
toolName: KimiToolName,
|
|
@@ -324,14 +425,23 @@ async function editMoonshotTool(
|
|
|
324
425
|
} else if (choice.endsWith("previews by default")) {
|
|
325
426
|
current = toggleCollapsed(current, toolName);
|
|
326
427
|
}
|
|
327
|
-
|
|
328
|
-
const effective = loadKimiCodeConfig({ cwd: ctx.cwd, home: os.homedir() });
|
|
329
|
-
registerConfiguredMoonshotTools(pi, effective, { updateActiveTools: true });
|
|
428
|
+
saveAndApplyKimiCodeConfig(pi, ctx, state, scope, current);
|
|
330
429
|
ctx.ui.notify(`Saved ${toolName} config`, "info");
|
|
331
430
|
return current;
|
|
332
431
|
}
|
|
333
432
|
}
|
|
334
433
|
|
|
434
|
+
function saveAndApplyKimiCodeConfig(
|
|
435
|
+
pi: ExtensionAPI,
|
|
436
|
+
ctx: ExtensionCommandContext,
|
|
437
|
+
state: KimiRuntimeState,
|
|
438
|
+
scope: KimiConfigScope,
|
|
439
|
+
config: KimiCodeConfig,
|
|
440
|
+
): void {
|
|
441
|
+
saveScopeKimiCodeConfig(scope, ctx.cwd, config);
|
|
442
|
+
applyEffectiveKimiRuntimeConfig(pi, state, ctx.cwd, { updateActiveTools: true });
|
|
443
|
+
}
|
|
444
|
+
|
|
335
445
|
function loadScopeKimiCodeConfig(scope: KimiConfigScope, cwd: string): KimiCodeConfig {
|
|
336
446
|
if (scope === "project") return loadProjectKimiCodeConfig(cwd);
|
|
337
447
|
return loadHomeKimiCodeConfig(os.homedir());
|
|
@@ -349,10 +459,15 @@ function saveScopeKimiCodeConfig(
|
|
|
349
459
|
}
|
|
350
460
|
}
|
|
351
461
|
|
|
352
|
-
function buildKimiMainTitle(config: KimiCodeConfig, cwd: string): string {
|
|
462
|
+
function buildKimiMainTitle(config: KimiCodeConfig, cwd: string, extras: KimiOAuthExtras): string {
|
|
353
463
|
const sources = loadKimiCodeConfigSources({ cwd, home: os.homedir() });
|
|
464
|
+
const modelName = extras.modelDisplay || "kimi-for-coding";
|
|
354
465
|
return [
|
|
355
|
-
|
|
466
|
+
`Kimi settings (provider v${KIMI_CODE_VERSION})`,
|
|
467
|
+
"",
|
|
468
|
+
`Model: ${modelName}`,
|
|
469
|
+
`Protocol: ${config.protocol} (${sources.protocol})`,
|
|
470
|
+
`Upload threshold: ${formatByteSize(config.uploads.thresholdBytes)} (${sources.uploads.thresholdBytes})`,
|
|
356
471
|
"",
|
|
357
472
|
"Effective tools:",
|
|
358
473
|
...KIMI_TOOL_NAMES.map((toolName) => {
|
|
@@ -371,7 +486,15 @@ function buildConfigScopeTitle(
|
|
|
371
486
|
scope === "project"
|
|
372
487
|
? relative(cwd, getProjectKimiCodeConfigPath(cwd))
|
|
373
488
|
: homeRelative(getGlobalKimiCodeConfigPath(os.homedir()));
|
|
374
|
-
return [
|
|
489
|
+
return [
|
|
490
|
+
`Edit ${scope} config`,
|
|
491
|
+
`File: ${path}`,
|
|
492
|
+
"",
|
|
493
|
+
formatProtocolStatus(config),
|
|
494
|
+
formatUploadThresholdStatus(config),
|
|
495
|
+
"",
|
|
496
|
+
moonshotStatus(config),
|
|
497
|
+
].join("\n");
|
|
375
498
|
}
|
|
376
499
|
|
|
377
500
|
function homeRelative(filePath: string): string {
|
|
@@ -383,6 +506,64 @@ function toolMenuItem(config: KimiCodeConfig, toolName: KimiToolName): string {
|
|
|
383
506
|
return `${toolName} -> ${formatToolStatus(config, toolName)}`;
|
|
384
507
|
}
|
|
385
508
|
|
|
509
|
+
function protocolMenuItem(config: KimiCodeConfig): string {
|
|
510
|
+
return `Protocol -> ${config.protocol}`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function uploadThresholdMenuItem(config: KimiCodeConfig): string {
|
|
514
|
+
return `Upload threshold -> ${formatByteSize(config.uploads.thresholdBytes)}`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function formatByteSize(bytes: number): string {
|
|
518
|
+
const units = [
|
|
519
|
+
{ suffix: "GiB", size: 1024 ** 3 },
|
|
520
|
+
{ suffix: "MiB", size: 1024 ** 2 },
|
|
521
|
+
{ suffix: "KiB", size: 1024 },
|
|
522
|
+
];
|
|
523
|
+
for (const unit of units) {
|
|
524
|
+
if (bytes >= unit.size && bytes % unit.size === 0) {
|
|
525
|
+
return `${bytes / unit.size} ${unit.suffix}`;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
for (const unit of units) {
|
|
529
|
+
if (bytes >= unit.size) {
|
|
530
|
+
return `${(bytes / unit.size).toFixed(2).replace(/\.00$/, "")} ${unit.suffix}`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return `${bytes} B`;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function parseByteSizeInput(input: string): number | undefined {
|
|
537
|
+
const match = input.trim().match(/^(\d+(?:\.\d+)?)\s*(b|bytes?|kib|kb|mib|mb|gib|gb)?$/i);
|
|
538
|
+
if (!match) return undefined;
|
|
539
|
+
const value = Number(match[1]);
|
|
540
|
+
if (!Number.isFinite(value) || value <= 0) return undefined;
|
|
541
|
+
const unit = (match[2] ?? "mib").toLowerCase();
|
|
542
|
+
const multipliers: Record<string, number> = {
|
|
543
|
+
b: 1,
|
|
544
|
+
byte: 1,
|
|
545
|
+
bytes: 1,
|
|
546
|
+
kib: 1024,
|
|
547
|
+
kb: 1000,
|
|
548
|
+
mib: 1024 ** 2,
|
|
549
|
+
mb: 1000 ** 2,
|
|
550
|
+
gib: 1024 ** 3,
|
|
551
|
+
gb: 1000 ** 3,
|
|
552
|
+
};
|
|
553
|
+
const multiplier = multipliers[unit];
|
|
554
|
+
if (!multiplier) return undefined;
|
|
555
|
+
const bytes = Math.round(value * multiplier);
|
|
556
|
+
return Number.isSafeInteger(bytes) && bytes > 0 ? bytes : undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function formatProtocolStatus(config: KimiCodeConfig): string {
|
|
560
|
+
return `protocol: ${config.protocol}`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function formatUploadThresholdStatus(config: KimiCodeConfig): string {
|
|
564
|
+
return `upload threshold: ${formatByteSize(config.uploads.thresholdBytes)}`;
|
|
565
|
+
}
|
|
566
|
+
|
|
386
567
|
function formatToolStatus(config: KimiCodeConfig, toolName: KimiToolName): string {
|
|
387
568
|
const tool = config.tools[toolName];
|
|
388
569
|
const enabled = tool.enabled ? "enabled" : "disabled";
|
|
@@ -390,53 +571,58 @@ function formatToolStatus(config: KimiCodeConfig, toolName: KimiToolName): strin
|
|
|
390
571
|
return `${enabled}, ${collapsed}`;
|
|
391
572
|
}
|
|
392
573
|
|
|
393
|
-
export
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return
|
|
429
|
-
|
|
574
|
+
export function KimiCode(overrides?: KimiCodeConfigPatch): ExtensionFactory {
|
|
575
|
+
return async (pi: ExtensionAPI) => {
|
|
576
|
+
const cwd = process.cwd();
|
|
577
|
+
const config = loadKimiCodeConfig({ cwd, home: os.homedir() }, overrides);
|
|
578
|
+
const baseModel = buildKimiModelFromConfig(config.model);
|
|
579
|
+
const discoveryToken = getKimiUsageToken();
|
|
580
|
+
const discovered = discoveryToken
|
|
581
|
+
? await discoverKimiModelMetadata(discoveryToken, config.protocol)
|
|
582
|
+
: {};
|
|
583
|
+
const state: KimiRuntimeState = { cwd, config, modelExtras: discovered, overrides };
|
|
584
|
+
reloadEffectiveKimiRuntimeConfig(state, cwd);
|
|
585
|
+
const model = applyKimiOAuthExtrasToModel(baseModel, discovered);
|
|
586
|
+
|
|
587
|
+
pi.registerProvider(PROVIDER_ID, {
|
|
588
|
+
baseUrl: getBaseUrl(config.protocol),
|
|
589
|
+
apiKey: "$KIMI_API_KEY",
|
|
590
|
+
api: getKimiApiType(config.protocol),
|
|
591
|
+
streamSimple: streamSimpleKimi,
|
|
592
|
+
|
|
593
|
+
models: [model],
|
|
594
|
+
|
|
595
|
+
oauth: {
|
|
596
|
+
name: "Kimi Code (OAuth)",
|
|
597
|
+
login: loginKimiCode,
|
|
598
|
+
refreshToken: refreshKimiCodeToken,
|
|
599
|
+
getApiKey: (cred) => cred.access,
|
|
600
|
+
// Reflect server-side model identity on the registered model after login
|
|
601
|
+
// / refresh. We never rewrite the model id (pi-side `/model` selections
|
|
602
|
+
// and persisted sessions reference it); only the human-facing name, the
|
|
603
|
+
// context window, and an out-of-band `wireModelId` carried into the
|
|
604
|
+
// request payload by streamSimpleKimi.
|
|
605
|
+
modifyModels: (models, cred) => {
|
|
606
|
+
const extras = cred as KimiOAuthCredentials;
|
|
607
|
+
state.modelExtras = extras;
|
|
608
|
+
reloadEffectiveKimiRuntimeConfig(state, state.cwd);
|
|
609
|
+
return models.map((model) => {
|
|
610
|
+
if (model.id !== "kimi-for-coding") return model;
|
|
611
|
+
return applyKimiOAuthExtrasToModel(model, extras);
|
|
612
|
+
});
|
|
613
|
+
},
|
|
430
614
|
},
|
|
431
|
-
}
|
|
432
|
-
});
|
|
615
|
+
});
|
|
433
616
|
|
|
434
|
-
|
|
617
|
+
registerConfiguredMoonshotTools(pi, state.config, { updateActiveTools: false });
|
|
435
618
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
619
|
+
pi.registerCommand("kimi-settings", {
|
|
620
|
+
description: "Show Kimi usage and configure optional Kimi tools",
|
|
621
|
+
handler: async (_args, ctx) => {
|
|
622
|
+
await runKimiCommand(pi, ctx, state);
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
};
|
|
442
626
|
}
|
|
627
|
+
|
|
628
|
+
export default KimiCode();
|