pi-provider-kimi-code 0.6.13 → 0.6.14
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/CHANGELOG.md +21 -0
- package/index.ts +209 -188
- package/package.json +14 -12
- package/src/constants.ts +1 -1
- package/src/errors.ts +31 -0
- package/src/models.ts +80 -12
- package/src/oauth.ts +18 -0
- package/src/schema-dedup.ts +8 -0
- package/src/settings-ui.ts +8 -0
- package/src/stream.ts +16 -3
- package/src/tools/datasource-catalog.ts +420 -0
- package/src/tools/datasource.ts +54 -68
- package/src/tools/moonshot.ts +7 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.6.14] - 2026-11-15
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Upgraded `@earendil-works/pi-ai` / `pi-coding-agent` / `pi-tui` to `^1.5.0`. This resolves all npm audit findings (undici, minimatch, brace-expansion advisories) and picks up pi's streaming and rendering performance fixes.
|
|
8
|
+
- Tool parameter schemas (`moonshot_search`, `moonshot_fetch`, `kimi_datasource`) now use `@earendil-works/pi-schema` builders instead of TypeBox, following pi 1.5.0's migration. The generated JSON Schema is unchanged.
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- Device-code login now rejects verification URLs that are not `http:`/`https:`, mirroring pi 1.5.0's fix for built-in OAuth providers. A malicious or buggy authorization server can no longer hand pi a `file:` or custom-scheme URL to open.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- The `/kimi-coding` settings dialog now reports pi as blocked on user input (OSC 7501 program status) while it is open, so supporting terminals show the correct waiting state.
|
|
17
|
+
- This changelog: pi 1.5.0 shows extension update notes on the next launch after an update.
|
|
18
|
+
- The `/kimi-settings` dialog now exposes **Keep thinking** (`model.thinkingKeep`: `default` / `all` / `last` / `none`), controlling the `thinking.keep` value sent to the Kimi API — i.e. how much reasoning history the server retains across turns. The wire and config plumbing already existed; this makes it adjustable without editing JSON.
|
|
19
|
+
- `kimi_datasource` now syncs its data-source catalog from the official plugin marketplace (`/coding/v1/plugins/marketplace`) with a lazy 24h disk cache (`~/.kimi-code/kimi-datasource-catalog.json`). The bundled snapshot moves from 12 to 35 sources (adds `china_*`, `who`/`fao`/`oecd`/`fred`, `chembl`/`pubchem`/`pdb`/`uniprot` and other science sources, `dongcai`/`cls`/`finance_research`), `data_source_name` is no longer a closed enum so server-side additions are never rejected by a stale client schema, and unknown names trigger one forced catalog refresh before being rejected with the current source list.
|
|
20
|
+
- Model discovery now prefers the unified `/models` schema (`limit.context`, `reasoning.type`/`reasoning.effort.valid`/`default`, `modalities.input`) that the official kimi-code client switched to in #4209, keeping the legacy flat fields (`context_length`, `supports_reasoning`, `think_efforts`, ...) as fallback while the server still sends both.
|
|
21
|
+
- Kimi's hard quota exhaustion (HTTP 429 with `exceeded_current_quota_error`) is now rewritten into actionable guidance — it is not a transient rate limit and retries will keep failing — pointing at `/kimi-settings` for usage, mirroring upstream kimi-code #4477/#4703. Plain transient 429s are untouched.
|
package/index.ts
CHANGED
|
@@ -224,204 +224,225 @@ async function openSettingsMenu(
|
|
|
224
224
|
let scope: KimiConfigScope = projectTrusted ? "project" : "home";
|
|
225
225
|
let dirty = false;
|
|
226
226
|
|
|
227
|
-
await ctx.ui.custom<void>(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
saveScopeKimiCodeConfig(scope, ctx.cwd, drafts[scope]);
|
|
261
|
-
applyEffectiveKimiRuntimeConfig(pi, state, ctx.cwd, {
|
|
262
|
-
updateActiveTools: true,
|
|
263
|
-
projectTrusted: scope === "project" ? true : projectTrusted,
|
|
264
|
-
});
|
|
265
|
-
dirty = true;
|
|
266
|
-
} catch (error: unknown) {
|
|
267
|
-
ctx.ui.notify((error as Error).message, "error");
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const onChange = (id: string, newValue: string) => {
|
|
272
|
-
if (id === "scope") {
|
|
273
|
-
scope = newValue as KimiConfigScope;
|
|
274
|
-
refreshDisplays();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (id === "protocol") {
|
|
278
|
-
drafts[scope].protocol = newValue as KimiCodeConfig["protocol"];
|
|
279
|
-
list.updateValue(id, newValue);
|
|
280
|
-
save();
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (id === "region") {
|
|
284
|
-
drafts[scope].region = newValue === "auto" ? null : (newValue as KimiRegion);
|
|
285
|
-
list.updateValue(id, newValue);
|
|
286
|
-
save();
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (id === "uploadThreshold") {
|
|
290
|
-
const bytes = parseByteSizeInput(newValue);
|
|
291
|
-
if (bytes === undefined || bytes <= 0) {
|
|
292
|
-
ctx.ui.notify("Upload threshold must be a positive size, for example 2 MiB", "error");
|
|
227
|
+
await ctx.ui.custom<void>(
|
|
228
|
+
(tui, theme, _keybindings, done) => {
|
|
229
|
+
const settingsTheme = buildSettingsTheme(theme);
|
|
230
|
+
|
|
231
|
+
let list: SettingsList;
|
|
232
|
+
|
|
233
|
+
const toolLabel = (toolName: KimiToolName) => {
|
|
234
|
+
if (toolName === "moonshot_search") return "Search tool";
|
|
235
|
+
if (toolName === "moonshot_fetch") return "Fetch tool";
|
|
236
|
+
return "Real-world data API";
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const formatToolMenuValue = (toolName: KimiToolName) => {
|
|
240
|
+
const tool = drafts[scope].tools[toolName];
|
|
241
|
+
if (!tool.enabled) return "disabled";
|
|
242
|
+
return tool.default_collapsed ? "enabled without preview" : "enabled with preview";
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const refreshDisplays = () => {
|
|
246
|
+
scopeItem.description = formatScopeDescription(scope, ctx.cwd);
|
|
247
|
+
for (const toolName of KIMI_TOOL_NAMES) {
|
|
248
|
+
list.updateValue(toolName, formatToolMenuValue(toolName));
|
|
249
|
+
}
|
|
250
|
+
list.updateValue("protocol", drafts[scope].protocol);
|
|
251
|
+
list.updateValue("region", formatRegionValue(drafts[scope].region));
|
|
252
|
+
list.updateValue("uploadThreshold", formatByteSize(drafts[scope].uploads.thresholdBytes));
|
|
253
|
+
list.updateValue("thinkingKeep", drafts[scope].model.thinkingKeep ?? "default");
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const save = () => {
|
|
257
|
+
if (scope === "project" && !projectTrusted) {
|
|
258
|
+
ctx.ui.notify("Project config cannot be saved until the project is trusted.", "warning");
|
|
293
259
|
return;
|
|
294
260
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (field === "enabled") {
|
|
305
|
-
drafts[scope].tools[toolName].enabled = newValue === "true";
|
|
306
|
-
} else {
|
|
307
|
-
drafts[scope].tools[toolName].default_collapsed = newValue !== "true";
|
|
261
|
+
try {
|
|
262
|
+
saveScopeKimiCodeConfig(scope, ctx.cwd, drafts[scope]);
|
|
263
|
+
applyEffectiveKimiRuntimeConfig(pi, state, ctx.cwd, {
|
|
264
|
+
updateActiveTools: true,
|
|
265
|
+
projectTrusted: scope === "project" ? true : projectTrusted,
|
|
266
|
+
});
|
|
267
|
+
dirty = true;
|
|
268
|
+
} catch (error: unknown) {
|
|
269
|
+
ctx.ui.notify((error as Error).message, "error");
|
|
308
270
|
}
|
|
309
|
-
|
|
310
|
-
save();
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const scopeItem: SettingItem = {
|
|
315
|
-
id: "scope",
|
|
316
|
-
label: "Config scope",
|
|
317
|
-
description: projectTrusted
|
|
318
|
-
? formatScopeDescription(scope, ctx.cwd)
|
|
319
|
-
: "Project config disabled until the project is trusted; editing home config only",
|
|
320
|
-
currentValue: scope,
|
|
321
|
-
values: projectTrusted ? ["project", "home"] : ["home"],
|
|
322
|
-
};
|
|
271
|
+
};
|
|
323
272
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
description: `Show ${toolName} result previews by default`,
|
|
345
|
-
currentValue: String(!drafts[scope].tools[toolName].default_collapsed),
|
|
346
|
-
values: ["true", "false"],
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
2,
|
|
350
|
-
settingsTheme,
|
|
351
|
-
onChange,
|
|
352
|
-
() => submenuDone(),
|
|
353
|
-
),
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
items.push({
|
|
357
|
-
id: "protocol",
|
|
358
|
-
label: "Protocol",
|
|
359
|
-
description: "API protocol for Kimi requests",
|
|
360
|
-
currentValue: drafts[scope].protocol,
|
|
361
|
-
values: ["openai", "anthropic", "responses"],
|
|
362
|
-
});
|
|
363
|
-
items.push({
|
|
364
|
-
id: "region",
|
|
365
|
-
label: "Region",
|
|
366
|
-
description: "Managed deployment region (auto follows KIMI_CODE_REGION / default)",
|
|
367
|
-
currentValue: formatRegionValue(drafts[scope].region),
|
|
368
|
-
values: ["auto", "mainland-cn", "global"],
|
|
369
|
-
});
|
|
370
|
-
items.push({
|
|
371
|
-
id: "uploadThreshold",
|
|
372
|
-
label: "Upload threshold",
|
|
373
|
-
description: "Max size for inline file uploads",
|
|
374
|
-
currentValue: formatByteSize(drafts[scope].uploads.thresholdBytes),
|
|
375
|
-
submenu: (_current, submenuDone) => {
|
|
376
|
-
const input = new Input();
|
|
377
|
-
input.setValue(formatByteSize(drafts[scope].uploads.thresholdBytes));
|
|
378
|
-
input.onSubmit = (value) => {
|
|
379
|
-
const bytes = parseByteSizeInput(value);
|
|
273
|
+
const onChange = (id: string, newValue: string) => {
|
|
274
|
+
if (id === "scope") {
|
|
275
|
+
scope = newValue as KimiConfigScope;
|
|
276
|
+
refreshDisplays();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (id === "protocol") {
|
|
280
|
+
drafts[scope].protocol = newValue as KimiCodeConfig["protocol"];
|
|
281
|
+
list.updateValue(id, newValue);
|
|
282
|
+
save();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (id === "region") {
|
|
286
|
+
drafts[scope].region = newValue === "auto" ? null : (newValue as KimiRegion);
|
|
287
|
+
list.updateValue(id, newValue);
|
|
288
|
+
save();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (id === "uploadThreshold") {
|
|
292
|
+
const bytes = parseByteSizeInput(newValue);
|
|
380
293
|
if (bytes === undefined || bytes <= 0) {
|
|
381
294
|
ctx.ui.notify("Upload threshold must be a positive size, for example 2 MiB", "error");
|
|
382
|
-
submenuDone();
|
|
383
295
|
return;
|
|
384
296
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
297
|
+
drafts[scope].uploads.thresholdBytes = bytes;
|
|
298
|
+
list.updateValue(id, formatByteSize(bytes));
|
|
299
|
+
save();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (id === "thinkingKeep") {
|
|
303
|
+
drafts[scope].model.thinkingKeep =
|
|
304
|
+
newValue === "default" ? null : (newValue as KimiCodeConfig["model"]["thinkingKeep"]);
|
|
305
|
+
list.updateValue(id, newValue);
|
|
306
|
+
save();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const toolMatch = /^(?<tool>.+):(?<field>enabled|collapsed)$/.exec(id);
|
|
310
|
+
if (toolMatch?.groups) {
|
|
311
|
+
const toolName = toolMatch.groups.tool as KimiToolName;
|
|
312
|
+
const field = toolMatch.groups.field as "enabled" | "collapsed";
|
|
313
|
+
if (field === "enabled") {
|
|
314
|
+
drafts[scope].tools[toolName].enabled = newValue === "true";
|
|
315
|
+
} else {
|
|
316
|
+
drafts[scope].tools[toolName].default_collapsed = newValue !== "true";
|
|
317
|
+
}
|
|
318
|
+
list.updateValue(toolName, formatToolMenuValue(toolName));
|
|
319
|
+
save();
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const scopeItem: SettingItem = {
|
|
324
|
+
id: "scope",
|
|
325
|
+
label: "Config scope",
|
|
326
|
+
description: projectTrusted
|
|
327
|
+
? formatScopeDescription(scope, ctx.cwd)
|
|
328
|
+
: "Project config disabled until the project is trusted; editing home config only",
|
|
329
|
+
currentValue: scope,
|
|
330
|
+
values: projectTrusted ? ["project", "home"] : ["home"],
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const items: SettingItem[] = [scopeItem];
|
|
334
|
+
for (const toolName of KIMI_TOOL_NAMES) {
|
|
335
|
+
items.push({
|
|
336
|
+
id: toolName,
|
|
337
|
+
label: toolLabel(toolName),
|
|
338
|
+
description: `Configure ${toolName} registration and preview defaults`,
|
|
339
|
+
currentValue: formatToolMenuValue(toolName),
|
|
340
|
+
submenu: (_current, submenuDone) =>
|
|
341
|
+
new SettingsList(
|
|
342
|
+
[
|
|
343
|
+
{
|
|
344
|
+
id: `${toolName}:enabled`,
|
|
345
|
+
label: "Enabled",
|
|
346
|
+
description: `Register ${toolName} at session start`,
|
|
347
|
+
currentValue: String(drafts[scope].tools[toolName].enabled),
|
|
348
|
+
values: ["true", "false"],
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: `${toolName}:collapsed`,
|
|
352
|
+
label: "Show preview",
|
|
353
|
+
description: `Show ${toolName} result previews by default`,
|
|
354
|
+
currentValue: String(!drafts[scope].tools[toolName].default_collapsed),
|
|
355
|
+
values: ["true", "false"],
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
2,
|
|
359
|
+
settingsTheme,
|
|
360
|
+
onChange,
|
|
361
|
+
() => submenuDone(),
|
|
362
|
+
),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
items.push({
|
|
366
|
+
id: "protocol",
|
|
367
|
+
label: "Protocol",
|
|
368
|
+
description: "API protocol for Kimi requests",
|
|
369
|
+
currentValue: drafts[scope].protocol,
|
|
370
|
+
values: ["openai", "anthropic", "responses"],
|
|
371
|
+
});
|
|
372
|
+
items.push({
|
|
373
|
+
id: "region",
|
|
374
|
+
label: "Region",
|
|
375
|
+
description: "Managed deployment region (auto follows KIMI_CODE_REGION / default)",
|
|
376
|
+
currentValue: formatRegionValue(drafts[scope].region),
|
|
377
|
+
values: ["auto", "mainland-cn", "global"],
|
|
378
|
+
});
|
|
379
|
+
items.push({
|
|
380
|
+
id: "thinkingKeep",
|
|
381
|
+
label: "Keep thinking",
|
|
382
|
+
description:
|
|
383
|
+
"thinking.keep sent to the Kimi API: how much reasoning history the server should retain across turns",
|
|
384
|
+
currentValue: drafts[scope].model.thinkingKeep ?? "default",
|
|
385
|
+
values: ["default", "all", "last", "none"],
|
|
386
|
+
});
|
|
387
|
+
items.push({
|
|
388
|
+
id: "uploadThreshold",
|
|
389
|
+
label: "Upload threshold",
|
|
390
|
+
description: "Max size for inline file uploads",
|
|
391
|
+
currentValue: formatByteSize(drafts[scope].uploads.thresholdBytes),
|
|
392
|
+
submenu: (_current, submenuDone) => {
|
|
393
|
+
const input = new Input();
|
|
394
|
+
input.setValue(formatByteSize(drafts[scope].uploads.thresholdBytes));
|
|
395
|
+
input.onSubmit = (value) => {
|
|
396
|
+
const bytes = parseByteSizeInput(value);
|
|
397
|
+
if (bytes === undefined || bytes <= 0) {
|
|
398
|
+
ctx.ui.notify("Upload threshold must be a positive size, for example 2 MiB", "error");
|
|
399
|
+
submenuDone();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
submenuDone(formatByteSize(bytes));
|
|
403
|
+
};
|
|
404
|
+
input.onEscape = () => submenuDone();
|
|
405
|
+
return input;
|
|
406
|
+
},
|
|
407
|
+
});
|
|
391
408
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
409
|
+
list = new SettingsList(items, items.length, settingsTheme, onChange, () => done(), {
|
|
410
|
+
enableSearch: true,
|
|
411
|
+
});
|
|
395
412
|
|
|
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
|
-
|
|
413
|
+
return {
|
|
414
|
+
items,
|
|
415
|
+
onChange,
|
|
416
|
+
render(width: number) {
|
|
417
|
+
const usageLines = usage.split("\n").map((line) => truncateToWidth(` ${line}`, width));
|
|
418
|
+
return [
|
|
419
|
+
truncateToWidth(
|
|
420
|
+
theme.fg("accent", theme.bold(`Kimi settings (provider v${PROVIDER_VERSION})`)),
|
|
421
|
+
width,
|
|
422
|
+
),
|
|
423
|
+
"",
|
|
424
|
+
truncateToWidth(theme.fg("accent", theme.bold("Kimi account")), width),
|
|
425
|
+
truncateToWidth(` ${account}`, width),
|
|
426
|
+
"",
|
|
427
|
+
truncateToWidth(theme.fg("accent", theme.bold("Kimi usage")), width),
|
|
428
|
+
...usageLines,
|
|
429
|
+
"",
|
|
430
|
+
...list.render(width),
|
|
431
|
+
];
|
|
432
|
+
},
|
|
433
|
+
handleInput(data: string) {
|
|
434
|
+
list.handleInput?.(data);
|
|
435
|
+
tui.requestRender();
|
|
436
|
+
},
|
|
437
|
+
invalidate() {
|
|
438
|
+
list.invalidate();
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
blocked: { kind: "question", message: "Kimi settings" },
|
|
444
|
+
},
|
|
445
|
+
);
|
|
425
446
|
|
|
426
447
|
if (dirty) await ctx.reload();
|
|
427
448
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-kimi-code",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
4
4
|
"description": "Kimi Code models (K3, K2.8, HighSpeed) in Pi: OAuth shared login with kimi-code CLI, file uploads, and Kimi-native tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-provider",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"index.ts",
|
|
42
42
|
"src",
|
|
43
43
|
"package.json",
|
|
44
|
-
"README.md"
|
|
44
|
+
"README.md",
|
|
45
|
+
"CHANGELOG.md"
|
|
45
46
|
],
|
|
46
47
|
"type": "module",
|
|
47
48
|
"scripts": {
|
|
@@ -55,23 +56,24 @@
|
|
|
55
56
|
"pre-commit": "prek run"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
59
|
+
"@earendil-works/pi-schema": "^1.5.0",
|
|
58
60
|
"proper-lockfile": "^4.1.2"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
61
|
-
"@earendil-works/pi-ai": "^
|
|
62
|
-
"@earendil-works/pi-coding-agent": "^
|
|
63
|
-
"@earendil-works/pi-tui": "^
|
|
64
|
-
"@j178/prek": "^0.5.
|
|
65
|
-
"@types/node": "^24.
|
|
63
|
+
"@earendil-works/pi-ai": "^1.5.0",
|
|
64
|
+
"@earendil-works/pi-coding-agent": "^1.5.0",
|
|
65
|
+
"@earendil-works/pi-tui": "^1.5.0",
|
|
66
|
+
"@j178/prek": "^0.5.10",
|
|
67
|
+
"@types/node": "^24.19.2",
|
|
66
68
|
"@types/proper-lockfile": "^4.1.4",
|
|
67
|
-
"oxfmt": "^0.
|
|
68
|
-
"oxlint": "^1.
|
|
69
|
+
"oxfmt": "^0.77.0",
|
|
70
|
+
"oxlint": "^1.92.0",
|
|
69
71
|
"typescript": "^5.9.0"
|
|
70
72
|
},
|
|
71
73
|
"peerDependencies": {
|
|
72
|
-
"@earendil-works/pi-ai": "
|
|
73
|
-
"@earendil-works/pi-coding-agent": "
|
|
74
|
-
"@earendil-works/pi-tui": "
|
|
74
|
+
"@earendil-works/pi-ai": ">=1.5.0",
|
|
75
|
+
"@earendil-works/pi-coding-agent": ">=1.5.0",
|
|
76
|
+
"@earendil-works/pi-tui": ">=1.5.0"
|
|
75
77
|
},
|
|
76
78
|
"pi": {
|
|
77
79
|
"extensions": [
|
package/src/constants.ts
CHANGED
|
@@ -133,7 +133,7 @@ export function getDefaultBaseUrl(
|
|
|
133
133
|
|
|
134
134
|
export const DEFAULT_BASE_URL = getDefaultBaseUrl(ENV_KIMI_CODE_PROTOCOL);
|
|
135
135
|
|
|
136
|
-
export const PROVIDER_VERSION = "0.6.
|
|
136
|
+
export const PROVIDER_VERSION = "0.6.14";
|
|
137
137
|
|
|
138
138
|
export const KIMI_CODE_USER_AGENT = `pi-provider-kimi-code/${PROVIDER_VERSION}`;
|
|
139
139
|
export const KIMI_PLATFORM = "pi";
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Kimi error-message classification, mirroring upstream kimi-code #4477 /
|
|
2
|
+
// #4703: quota exhaustion is a hard limit with explicit non-retryability, not
|
|
3
|
+
// a transient rate limit — say so instead of letting the raw 429 body read
|
|
4
|
+
// like something a retry would fix.
|
|
5
|
+
|
|
6
|
+
const QUOTA_ERROR_CODE = "exceeded_current_quota_error";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect Kimi's structured quota error in a flattened error message
|
|
10
|
+
* (status line + JSON body). Both markers are required to avoid matching
|
|
11
|
+
* unrelated 429 rate limits, which ARE transient.
|
|
12
|
+
*/
|
|
13
|
+
export function isKimiQuotaExhaustedMessage(message: unknown): boolean {
|
|
14
|
+
const text = String(message ?? "").toLowerCase();
|
|
15
|
+
return text.includes(QUOTA_ERROR_CODE) && /\b429\b/.test(text);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Rewrite a raw upstream error into actionable text. Returns the input
|
|
20
|
+
* unchanged when nothing applies.
|
|
21
|
+
*/
|
|
22
|
+
export function improveKimiErrorMessage(message: string): string {
|
|
23
|
+
if (!isKimiQuotaExhaustedMessage(message)) return message;
|
|
24
|
+
return (
|
|
25
|
+
"Kimi Code quota exhausted (HTTP 429, exceeded_current_quota_error). " +
|
|
26
|
+
"This is a hard quota limit, not a transient rate limit — retrying will " +
|
|
27
|
+
"keep failing until the quota window resets. Run /kimi-settings to check " +
|
|
28
|
+
"your current usage. " +
|
|
29
|
+
`Original error: ${message}`
|
|
30
|
+
);
|
|
31
|
+
}
|
package/src/models.ts
CHANGED
|
@@ -184,6 +184,13 @@ interface KimiServerModel {
|
|
|
184
184
|
supports_thinking_type?: unknown;
|
|
185
185
|
protocol?: unknown;
|
|
186
186
|
think_efforts?: unknown;
|
|
187
|
+
// Unified schema (upstream kimi-code #4209). The server currently sends
|
|
188
|
+
// both shapes; the official client already reads ONLY these fields, so
|
|
189
|
+
// prefer them and treat the flat legacy fields above as fallback.
|
|
190
|
+
limit?: unknown;
|
|
191
|
+
reasoning?: unknown;
|
|
192
|
+
modalities?: unknown;
|
|
193
|
+
tools?: unknown;
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
function parseSupportsThinkingType(value: unknown): "only" | "no" | "both" | undefined {
|
|
@@ -221,28 +228,89 @@ function parseThinkEfforts(value: unknown): {
|
|
|
221
228
|
};
|
|
222
229
|
}
|
|
223
230
|
|
|
231
|
+
// Unified `reasoning` object: { type: "only" | "both" | "none", effort: {
|
|
232
|
+
// valid: [...], default } }. A missing object means the legacy flat fields
|
|
233
|
+
// decide; a present object always wins (type "none" clears a stale thinking
|
|
234
|
+
// default, matching upstream's three-state mapping to internal "no").
|
|
235
|
+
function parseUnifiedReasoning(value: unknown): {
|
|
236
|
+
readonly matched: boolean;
|
|
237
|
+
readonly supportsThinkingType?: "only" | "no" | "both";
|
|
238
|
+
readonly supportsReasoning?: boolean;
|
|
239
|
+
readonly supportEfforts?: string[];
|
|
240
|
+
readonly defaultEffort?: string;
|
|
241
|
+
} {
|
|
242
|
+
if (!isRecord(value)) return { matched: false };
|
|
243
|
+
const rawType = value.type;
|
|
244
|
+
const supportsThinkingType =
|
|
245
|
+
rawType === "only" || rawType === "both" ? rawType : rawType === "none" ? "no" : undefined;
|
|
246
|
+
const effort = isRecord(value.effort) ? value.effort : undefined;
|
|
247
|
+
const supportEfforts = Array.isArray(effort?.valid)
|
|
248
|
+
? effort.valid.filter((e): e is string => typeof e === "string" && !!e)
|
|
249
|
+
: undefined;
|
|
250
|
+
const rawDefault =
|
|
251
|
+
typeof effort?.default === "string" && effort.default ? effort.default : undefined;
|
|
252
|
+
// A default not listed in valid is dropped, same as upstream.
|
|
253
|
+
const defaultEffort =
|
|
254
|
+
rawDefault && supportEfforts && !supportEfforts.includes(rawDefault) ? undefined : rawDefault;
|
|
255
|
+
return {
|
|
256
|
+
matched: true,
|
|
257
|
+
...(supportsThinkingType ? { supportsThinkingType } : {}),
|
|
258
|
+
supportsReasoning: supportsThinkingType !== undefined && supportsThinkingType !== "no",
|
|
259
|
+
...(supportEfforts && supportEfforts.length > 0 ? { supportEfforts } : {}),
|
|
260
|
+
...(defaultEffort ? { defaultEffort } : {}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function parseInputModalities(value: unknown): {
|
|
265
|
+
supportsImageIn?: boolean;
|
|
266
|
+
supportsVideoIn?: boolean;
|
|
267
|
+
} {
|
|
268
|
+
if (!isRecord(value) || !Array.isArray(value.input)) return {};
|
|
269
|
+
const input = value.input;
|
|
270
|
+
return {
|
|
271
|
+
supportsImageIn: input.includes("image"),
|
|
272
|
+
supportsVideoIn: input.includes("video"),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
224
276
|
function parseKimiModelMetadata(model: KimiServerModel): KimiModelMetadata | undefined {
|
|
225
277
|
if (typeof model.id !== "string" || !model.id) return undefined;
|
|
226
278
|
const metadata: KimiModelMetadata = { wireModelId: model.id };
|
|
227
279
|
if (typeof model.display_name === "string") metadata.modelDisplay = model.display_name;
|
|
228
|
-
|
|
280
|
+
// Unified limit.context wins over legacy context_length.
|
|
281
|
+
const limit = isRecord(model.limit) ? model.limit : undefined;
|
|
282
|
+
if (typeof limit?.context === "number" && limit.context > 0) {
|
|
283
|
+
metadata.contextLength = limit.context;
|
|
284
|
+
} else if (typeof model.context_length === "number" && model.context_length > 0) {
|
|
229
285
|
metadata.contextLength = model.context_length;
|
|
230
286
|
}
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
287
|
+
const unifiedReasoning = parseUnifiedReasoning(model.reasoning);
|
|
288
|
+
if (unifiedReasoning.matched) {
|
|
289
|
+
const { matched: _, ...fields } = unifiedReasoning;
|
|
290
|
+
Object.assign(metadata, fields);
|
|
291
|
+
} else {
|
|
292
|
+
const thinkingType = parseSupportsThinkingType(model.supports_thinking_type);
|
|
293
|
+
if (thinkingType) {
|
|
294
|
+
metadata.supportsThinkingType = thinkingType;
|
|
295
|
+
} else if (typeof model.supports_reasoning === "boolean") {
|
|
296
|
+
metadata.supportsReasoning = model.supports_reasoning;
|
|
297
|
+
}
|
|
298
|
+
Object.assign(metadata, parseThinkEfforts(model.think_efforts));
|
|
239
299
|
}
|
|
240
|
-
|
|
241
|
-
|
|
300
|
+
const modalities = parseInputModalities(model.modalities);
|
|
301
|
+
if (modalities.supportsImageIn !== undefined) {
|
|
302
|
+
metadata.supportsImageIn = modalities.supportsImageIn;
|
|
303
|
+
metadata.supportsVideoIn = modalities.supportsVideoIn;
|
|
304
|
+
} else {
|
|
305
|
+
if (typeof model.supports_image_in === "boolean") {
|
|
306
|
+
metadata.supportsImageIn = model.supports_image_in;
|
|
307
|
+
}
|
|
308
|
+
if (typeof model.supports_video_in === "boolean") {
|
|
309
|
+
metadata.supportsVideoIn = model.supports_video_in;
|
|
310
|
+
}
|
|
242
311
|
}
|
|
243
312
|
const protocol = parseKimiCatalogProtocol(model.protocol);
|
|
244
313
|
if (protocol) metadata.protocol = protocol;
|
|
245
|
-
Object.assign(metadata, parseThinkEfforts(model.think_efforts));
|
|
246
314
|
return metadata;
|
|
247
315
|
}
|
|
248
316
|
|