pi-provider-kimi-code 0.5.1 → 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/index.ts CHANGED
@@ -18,43 +18,43 @@
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,
33
+ KIMI_TOOL_NAMES,
32
34
  getProjectKimiCodeConfigPath,
33
35
  getGlobalKimiCodeConfigPath,
34
36
  loadHomeKimiCodeConfig,
35
37
  loadKimiCodeConfig,
38
+ loadKimiCodeConfigSources,
36
39
  loadProjectKimiCodeConfig,
37
40
  saveHomeKimiCodeConfig,
38
41
  saveProjectKimiCodeConfig,
42
+ type KimiToolName,
39
43
  } from "./src/config.ts";
40
- import {
41
- DEFAULT_KIMI_MODEL_INPUT,
42
- KIMI_API_TYPE,
43
- PROVIDER_ID,
44
- getBaseUrl,
45
- } from "./src/constants.ts";
44
+ import { KIMI_CODE_VERSION, PROVIDER_ID, getBaseUrl, getKimiApiType } from "./src/constants.ts";
46
45
  import { getCommonHeaders } from "./src/device.ts";
47
46
  import {
48
47
  type KimiOAuthCredentials,
49
- applyKimiEnvOverridesToModel,
48
+ type KimiOAuthExtras,
49
+ buildKimiModelFromConfig,
50
50
  applyKimiOAuthExtrasToModel,
51
+ discoverKimiModelMetadata,
52
+ resolveKimiModelConfig,
51
53
  } from "./src/models.ts";
52
- import { loginKimiCode, refreshKimiCodeToken } from "./src/oauth.ts";
53
- import { streamSimpleKimi } from "./src/stream.ts";
54
+ import { loginKimiCode, refreshKimiAuthToken, refreshKimiCodeToken } from "./src/oauth.ts";
55
+ import { setStoreResolvedKimiConfig, streamSimpleKimi } from "./src/stream.ts";
54
56
  import { buildMoonshotFetchTool, buildMoonshotSearchTool } from "./src/tools/moonshot.ts";
55
-
56
- const MOONSHOT_TOOL_NAMES = ["moonshot_search", "moonshot_fetch"] as const;
57
- type MoonshotToolName = (typeof MOONSHOT_TOOL_NAMES)[number];
57
+ import { buildKimiDatasourceTool } from "./src/tools/datasource.ts";
58
58
  type KimiConfigScope = "project" | "home";
59
59
  const MEMBERSHIP_LEVEL_NAMES: Record<string, string> = {
60
60
  LEVEL_FREE: "Free",
@@ -71,30 +71,37 @@ interface UsageRow {
71
71
  limit: number;
72
72
  }
73
73
 
74
+ interface KimiRuntimeState {
75
+ cwd: string;
76
+ config: KimiCodeConfig;
77
+ modelExtras: KimiOAuthExtras;
78
+ overrides?: KimiCodeConfigPatch;
79
+ }
80
+
81
+ function buildKimiTool(toolName: KimiToolName, config: KimiCodeConfig) {
82
+ const opts = { defaultCollapsed: config.tools[toolName].default_collapsed };
83
+ if (toolName === "moonshot_search") return buildMoonshotSearchTool(opts);
84
+ if (toolName === "moonshot_fetch") return buildMoonshotFetchTool(opts);
85
+ if (toolName === "kimi_datasource") return buildKimiDatasourceTool(opts);
86
+ return undefined;
87
+ }
88
+
74
89
  function registerConfiguredMoonshotTools(
75
90
  pi: ExtensionAPI,
76
91
  config: KimiCodeConfig,
77
92
  options: { updateActiveTools: boolean },
78
93
  ): void {
79
- if (config.tools.moonshot_search.enabled) {
80
- pi.registerTool(
81
- buildMoonshotSearchTool({
82
- defaultCollapsed: config.tools.moonshot_search.default_collapsed,
83
- }),
84
- );
85
- }
86
- if (config.tools.moonshot_fetch.enabled) {
87
- pi.registerTool(
88
- buildMoonshotFetchTool({
89
- defaultCollapsed: config.tools.moonshot_fetch.default_collapsed,
90
- }),
91
- );
94
+ for (const toolName of KIMI_TOOL_NAMES) {
95
+ if (config.tools[toolName].enabled) {
96
+ const tool = buildKimiTool(toolName, config);
97
+ if (tool) pi.registerTool(tool);
98
+ }
92
99
  }
93
100
 
94
101
  if (!options.updateActiveTools) return;
95
102
 
96
103
  const activeTools = new Set(pi.getActiveTools());
97
- for (const toolName of MOONSHOT_TOOL_NAMES) {
104
+ for (const toolName of KIMI_TOOL_NAMES) {
98
105
  if (config.tools[toolName].enabled) {
99
106
  activeTools.add(toolName);
100
107
  } else {
@@ -104,6 +111,29 @@ function registerConfiguredMoonshotTools(
104
111
  pi.setActiveTools([...activeTools]);
105
112
  }
106
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
+
107
137
  function getKimiUsageToken(): string | null {
108
138
  const credential = AuthStorage.create().get(PROVIDER_ID);
109
139
  if (credential?.type === "oauth" && credential.access) return credential.access;
@@ -186,6 +216,17 @@ function quotaBar(remaining: number, limit: number): string {
186
216
  return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
187
217
  }
188
218
 
219
+ function fetchKimiUsage(token: string, signal: AbortSignal): Promise<Response> {
220
+ return fetch("https://api.kimi.com/coding/v1/usages", {
221
+ method: "GET",
222
+ headers: {
223
+ ...getCommonHeaders(),
224
+ Authorization: `Bearer ${token}`,
225
+ },
226
+ signal,
227
+ });
228
+ }
229
+
189
230
  async function fetchKimiUsageSummary(): Promise<string> {
190
231
  const token = getKimiUsageToken();
191
232
  if (!token) return "Usage: missing credentials. Run /login kimi-coding.";
@@ -193,14 +234,11 @@ async function fetchKimiUsageSummary(): Promise<string> {
193
234
  const controller = new AbortController();
194
235
  const timeout = setTimeout(() => controller.abort(), 15_000);
195
236
  try {
196
- const response = await fetch("https://api.kimi.com/coding/v1/usages", {
197
- method: "GET",
198
- headers: {
199
- ...getCommonHeaders(),
200
- Authorization: `Bearer ${token}`,
201
- },
202
- signal: controller.signal,
203
- });
237
+ let response = await fetchKimiUsage(token, controller.signal);
238
+ if (response.status === 401) {
239
+ const refreshed = await refreshKimiAuthToken(token);
240
+ if (refreshed) response = await fetchKimiUsage(refreshed, controller.signal);
241
+ }
204
242
  if (!response.ok) return `Usage: fetch failed (${response.status})`;
205
243
  return parseUsageSummary(await response.json());
206
244
  } catch (error) {
@@ -212,7 +250,7 @@ async function fetchKimiUsageSummary(): Promise<string> {
212
250
  }
213
251
 
214
252
  function moonshotStatus(config: KimiCodeConfig): string {
215
- return MOONSHOT_TOOL_NAMES.map((toolName) => {
253
+ return KIMI_TOOL_NAMES.map((toolName) => {
216
254
  const tool = config.tools[toolName];
217
255
  const enabled = tool.enabled ? "enabled" : "disabled";
218
256
  const collapsed = tool.default_collapsed ? "collapsed" : "expanded";
@@ -220,8 +258,9 @@ function moonshotStatus(config: KimiCodeConfig): string {
220
258
  }).join("\n");
221
259
  }
222
260
 
223
- function toggleEnabled(config: KimiCodeConfig, toolName: MoonshotToolName): KimiCodeConfig {
261
+ function toggleEnabled(config: KimiCodeConfig, toolName: KimiToolName): KimiCodeConfig {
224
262
  return {
263
+ ...config,
225
264
  tools: {
226
265
  ...config.tools,
227
266
  [toolName]: {
@@ -232,8 +271,9 @@ function toggleEnabled(config: KimiCodeConfig, toolName: MoonshotToolName): Kimi
232
271
  };
233
272
  }
234
273
 
235
- function toggleCollapsed(config: KimiCodeConfig, toolName: MoonshotToolName): KimiCodeConfig {
274
+ function toggleCollapsed(config: KimiCodeConfig, toolName: KimiToolName): KimiCodeConfig {
236
275
  return {
276
+ ...config,
237
277
  tools: {
238
278
  ...config.tools,
239
279
  [toolName]: {
@@ -244,13 +284,34 @@ function toggleCollapsed(config: KimiCodeConfig, toolName: MoonshotToolName): Ki
244
284
  };
245
285
  }
246
286
 
247
- async function runKimiCommand(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
248
- let config = loadKimiCodeConfig({ cwd: ctx.cwd, home: os.homedir() });
249
- let usage = await fetchKimiUsageSummary();
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)]);
250
311
  ctx.ui.notify(usage);
251
312
 
252
313
  while (true) {
253
- const choice = await ctx.ui.select(buildKimiMainTitle(config, ctx.cwd), [
314
+ const choice = await ctx.ui.select(buildKimiMainTitle(config, ctx.cwd, state.modelExtras), [
254
315
  `Edit project config (${relative(ctx.cwd, getProjectKimiCodeConfigPath(ctx.cwd))})`,
255
316
  `Edit home config (${homeRelative(getGlobalKimiCodeConfigPath(os.homedir()))})`,
256
317
  "Refresh usage",
@@ -264,9 +325,9 @@ async function runKimiCommand(pi: ExtensionAPI, ctx: ExtensionCommandContext): P
264
325
  continue;
265
326
  }
266
327
  if (choice.startsWith("Edit project config")) {
267
- config = await editConfigScope(pi, ctx, "project");
328
+ config = await editConfigScope(pi, ctx, state, "project");
268
329
  } else if (choice.startsWith("Edit home config")) {
269
- config = await editConfigScope(pi, ctx, "home");
330
+ config = await editConfigScope(pi, ctx, state, "home");
270
331
  }
271
332
  }
272
333
  }
@@ -274,32 +335,78 @@ async function runKimiCommand(pi: ExtensionAPI, ctx: ExtensionCommandContext): P
274
335
  async function editConfigScope(
275
336
  pi: ExtensionAPI,
276
337
  ctx: ExtensionCommandContext,
338
+ state: KimiRuntimeState,
277
339
  scope: KimiConfigScope,
278
340
  ): Promise<KimiCodeConfig> {
279
341
  let current = loadScopeKimiCodeConfig(scope, ctx.cwd);
280
342
  while (true) {
281
- const choice = await ctx.ui.select(buildConfigScopeTitle(scope, current, ctx.cwd), [
282
- toolMenuItem(current, "moonshot_search"),
283
- toolMenuItem(current, "moonshot_fetch"),
284
- "Back",
285
- ]);
343
+ const items = KIMI_TOOL_NAMES.map((name) => toolMenuItem(current, name));
344
+ items.push(protocolMenuItem(current), uploadThresholdMenuItem(current), "Back");
345
+ const choice = await ctx.ui.select(buildConfigScopeTitle(scope, current, ctx.cwd), items);
286
346
  if (!choice || choice === "Back") {
287
347
  return loadKimiCodeConfig({ cwd: ctx.cwd, home: os.homedir() });
288
348
  }
289
- if (choice.startsWith("moonshot_search")) {
290
- current = await editMoonshotTool(pi, ctx, scope, current, "moonshot_search");
291
- } else if (choice.startsWith("moonshot_fetch")) {
292
- current = await editMoonshotTool(pi, ctx, scope, current, "moonshot_fetch");
349
+ const toolName = KIMI_TOOL_NAMES.find((name) => choice.startsWith(name));
350
+ if (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);
293
356
  }
294
357
  }
295
358
  }
296
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
+
297
403
  async function editMoonshotTool(
298
404
  pi: ExtensionAPI,
299
405
  ctx: ExtensionCommandContext,
406
+ state: KimiRuntimeState,
300
407
  scope: KimiConfigScope,
301
408
  config: KimiCodeConfig,
302
- toolName: MoonshotToolName,
409
+ toolName: KimiToolName,
303
410
  ): Promise<KimiCodeConfig> {
304
411
  let current = config;
305
412
  while (true) {
@@ -318,13 +425,23 @@ async function editMoonshotTool(
318
425
  } else if (choice.endsWith("previews by default")) {
319
426
  current = toggleCollapsed(current, toolName);
320
427
  }
321
- saveScopeKimiCodeConfig(scope, ctx.cwd, current);
322
- const effective = loadKimiCodeConfig({ cwd: ctx.cwd, home: os.homedir() });
323
- registerConfiguredMoonshotTools(pi, effective, { updateActiveTools: true });
428
+ saveAndApplyKimiCodeConfig(pi, ctx, state, scope, current);
324
429
  ctx.ui.notify(`Saved ${toolName} config`, "info");
430
+ return current;
325
431
  }
326
432
  }
327
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
+
328
445
  function loadScopeKimiCodeConfig(scope: KimiConfigScope, cwd: string): KimiCodeConfig {
329
446
  if (scope === "project") return loadProjectKimiCodeConfig(cwd);
330
447
  return loadHomeKimiCodeConfig(os.homedir());
@@ -342,8 +459,22 @@ function saveScopeKimiCodeConfig(
342
459
  }
343
460
  }
344
461
 
345
- function buildKimiMainTitle(_config: KimiCodeConfig, _cwd: string): string {
346
- return "Kimi settings";
462
+ function buildKimiMainTitle(config: KimiCodeConfig, cwd: string, extras: KimiOAuthExtras): string {
463
+ const sources = loadKimiCodeConfigSources({ cwd, home: os.homedir() });
464
+ const modelName = extras.modelDisplay || "kimi-for-coding";
465
+ return [
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})`,
471
+ "",
472
+ "Effective tools:",
473
+ ...KIMI_TOOL_NAMES.map((toolName) => {
474
+ const enabled = config.tools[toolName].enabled ? "enabled" : "disabled";
475
+ return `- ${toolName}: ${enabled} (${sources.tools[toolName].enabled})`;
476
+ }),
477
+ ].join("\n");
347
478
  }
348
479
 
349
480
  function buildConfigScopeTitle(
@@ -355,7 +486,15 @@ function buildConfigScopeTitle(
355
486
  scope === "project"
356
487
  ? relative(cwd, getProjectKimiCodeConfigPath(cwd))
357
488
  : homeRelative(getGlobalKimiCodeConfigPath(os.homedir()));
358
- return [`Edit ${scope} config`, `File: ${path}`, "", moonshotStatus(config)].join("\n");
489
+ return [
490
+ `Edit ${scope} config`,
491
+ `File: ${path}`,
492
+ "",
493
+ formatProtocolStatus(config),
494
+ formatUploadThresholdStatus(config),
495
+ "",
496
+ moonshotStatus(config),
497
+ ].join("\n");
359
498
  }
360
499
 
361
500
  function homeRelative(filePath: string): string {
@@ -363,66 +502,127 @@ function homeRelative(filePath: string): string {
363
502
  return filePath.startsWith(`${home}/`) ? `~/${filePath.slice(home.length + 1)}` : filePath;
364
503
  }
365
504
 
366
- function toolMenuItem(config: KimiCodeConfig, toolName: MoonshotToolName): string {
505
+ function toolMenuItem(config: KimiCodeConfig, toolName: KimiToolName): string {
367
506
  return `${toolName} -> ${formatToolStatus(config, toolName)}`;
368
507
  }
369
508
 
370
- function formatToolStatus(config: KimiCodeConfig, toolName: MoonshotToolName): string {
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
+
567
+ function formatToolStatus(config: KimiCodeConfig, toolName: KimiToolName): string {
371
568
  const tool = config.tools[toolName];
372
569
  const enabled = tool.enabled ? "enabled" : "disabled";
373
570
  const collapsed = tool.default_collapsed ? "default collapsed" : "default expanded";
374
571
  return `${enabled}, ${collapsed}`;
375
572
  }
376
573
 
377
- export default function (pi: ExtensionAPI) {
378
- const config = loadKimiCodeConfig({ cwd: process.cwd(), home: os.homedir() });
379
-
380
- pi.registerProvider(PROVIDER_ID, {
381
- baseUrl: getBaseUrl(),
382
- apiKey: "KIMI_API_KEY",
383
- api: KIMI_API_TYPE,
384
- streamSimple: streamSimpleKimi,
385
-
386
- headers: getCommonHeaders(),
387
-
388
- models: [
389
- applyKimiEnvOverridesToModel({
390
- id: "kimi-for-coding",
391
- name: "Kimi for Coding",
392
- reasoning: true,
393
- input: [...DEFAULT_KIMI_MODEL_INPUT] as unknown as ("text" | "image")[],
394
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
395
- contextWindow: 262144,
396
- maxTokens: 32000,
397
- } as Model<Api>),
398
- ],
399
-
400
- oauth: {
401
- name: "Kimi Code (OAuth)",
402
- login: loginKimiCode,
403
- refreshToken: refreshKimiCodeToken,
404
- getApiKey: (cred) => cred.access,
405
- // Reflect server-side model identity on the registered model after login
406
- // / refresh. We never rewrite the model id (pi-side `/model` selections
407
- // and persisted sessions reference it); only the human-facing name, the
408
- // context window, and an out-of-band `wireModelId` carried into the
409
- // request payload by streamSimpleKimi.
410
- modifyModels: (models, cred) => {
411
- const extras = cred as KimiOAuthCredentials;
412
- return models.map((model) => {
413
- if (model.id !== "kimi-for-coding") return model;
414
- return applyKimiEnvOverridesToModel(applyKimiOAuthExtrasToModel(model, extras));
415
- });
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
+ },
416
614
  },
417
- },
418
- });
615
+ });
419
616
 
420
- registerConfiguredMoonshotTools(pi, config, { updateActiveTools: false });
617
+ registerConfiguredMoonshotTools(pi, state.config, { updateActiveTools: false });
421
618
 
422
- pi.registerCommand("kimi-settings", {
423
- description: "Show Kimi usage and configure optional Kimi tools",
424
- handler: async (_args, ctx) => {
425
- await runKimiCommand(pi, ctx);
426
- },
427
- });
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
+ };
428
626
  }
627
+
628
+ export default KimiCode();
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "pi-provider-kimi-code",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Use Kimi Code in Pi without losing the Kimi-specific bits",
5
5
  "keywords": [
6
6
  "ai-provider",
7
7
  "coding-agent",
8
- "device-code",
9
8
  "kimi",
10
- "kimi-coder",
9
+ "kimi-code",
11
10
  "kimi-for-coding",
12
11
  "kimi-k2",
13
12
  "kimi-k2.5",
14
13
  "kimi-k2.6",
14
+ "kimi-k2.7",
15
15
  "llm",
16
16
  "llm-provider",
17
17
  "moonshot",
@@ -20,10 +20,8 @@
20
20
  "pi",
21
21
  "pi-coding-agent",
22
22
  "pi-extension",
23
- "pi-mono",
24
23
  "pi-package",
25
- "pi-provider",
26
- "subscription"
24
+ "pi-provider"
27
25
  ],
28
26
  "bugs": {
29
27
  "url": "https://github.com/Leechael/pi-provider-kimi-code/issues"
@@ -54,7 +52,8 @@
54
52
  "devDependencies": {
55
53
  "@earendil-works/pi-ai": "^0.78.0",
56
54
  "@earendil-works/pi-coding-agent": "^0.78.0",
57
- "@j178/prek": "^0.3.8",
55
+ "@earendil-works/pi-tui": "^0.78.1",
56
+ "@j178/prek": "^0.4.4",
58
57
  "@types/node": "^24.0.0",
59
58
  "oxfmt": "^0.44.0",
60
59
  "oxlint": "^1.59.0",
@@ -62,7 +61,8 @@
62
61
  },
63
62
  "peerDependencies": {
64
63
  "@earendil-works/pi-ai": "*",
65
- "@earendil-works/pi-coding-agent": "*"
64
+ "@earendil-works/pi-coding-agent": "*",
65
+ "@earendil-works/pi-tui": "*"
66
66
  },
67
67
  "pi": {
68
68
  "extensions": [