pi-provider-kimi-code 0.6.8 → 0.6.10

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/src/stream.ts CHANGED
@@ -58,9 +58,13 @@ import {
58
58
  type KimiResolvedModelConfig,
59
59
  } from "./config.ts";
60
60
 
61
- import { ENV_KIMI_CODE_PROTOCOL, getApiProtocol, getBaseUrl } from "./constants.ts";
61
+ import { ENV_KIMI_CODE_PROTOCOL, PROVIDER_ID, getApiProtocol, getBaseUrl } from "./constants.ts";
62
62
  import { getKimiProviderHeaders } from "./device.ts";
63
- import { isKimiAuthErrorMessage, refreshKimiAuthToken } from "./oauth.ts";
63
+ import {
64
+ isKimiAuthErrorMessage,
65
+ readStoredOAuthCredential,
66
+ refreshKimiAuthToken,
67
+ } from "./oauth.ts";
64
68
  import {
65
69
  type Uploader,
66
70
  applyKimiPayloadMutations,
@@ -190,11 +194,21 @@ export async function* filterEmptyResponseStream(
190
194
 
191
195
  const KIMI_API_KEY_ENV_REFERENCES = new Set(["$KIMI_API_KEY", "${KIMI_API_KEY}"]);
192
196
 
197
+ // On the normal request path pi resolves the provider credential through its
198
+ // ModelRuntime and hands it to us. Callers that arrive through pi-ai's global
199
+ // api registry (a bare agentLoop() with no streamFn) have no ModelRuntime
200
+ // behind them and pass nothing, so read the same credential store pi itself
201
+ // would have read instead of sending an unauthenticated request. Kept last so
202
+ // an explicit key and the environment override keep their existing precedence.
203
+ function getStoredKimiAccessToken(): string {
204
+ return readStoredOAuthCredential(PROVIDER_ID)?.access?.trim() || "";
205
+ }
206
+
193
207
  export function resolveKimiApiKey(apiKey: string | undefined): string {
194
208
  if (apiKey !== undefined && KIMI_API_KEY_ENV_REFERENCES.has(apiKey)) {
195
- return process.env.KIMI_API_KEY?.trim() || "";
209
+ return process.env.KIMI_API_KEY?.trim() || getStoredKimiAccessToken();
196
210
  }
197
- return apiKey || process.env.KIMI_API_KEY || "";
211
+ return apiKey || process.env.KIMI_API_KEY || getStoredKimiAccessToken();
198
212
  }
199
213
 
200
214
  // SimpleStreamOptions.headers is Record<string, string> on pi <=0.79 and
@@ -285,6 +299,7 @@ export function streamSimpleKimi(
285
299
  await applyKimiPayloadMutations(nextPayload, {
286
300
  api: apiProtocol,
287
301
  upload,
302
+ uploadCacheScope: apiKey ? `${getBaseUrl(wireProtocol)}:${apiKey}` : undefined,
288
303
  cacheKey,
289
304
  cacheRetention,
290
305
  reasoning: options?.reasoning,
@@ -323,6 +338,19 @@ export function streamSimpleKimi(
323
338
  ...model,
324
339
  api: apiProtocol,
325
340
  baseUrl: getBaseUrl(wireProtocol),
341
+ // Kimi's anthropic-protocol endpoint speaks adaptive thinking and
342
+ // rejects/ignores the interleaved-thinking beta header; pi-ai >=0.82
343
+ // keys both behaviors off these compat flags (its own built-in
344
+ // kimi-coding models carry them in generated metadata).
345
+ ...(wireProtocol === "anthropic"
346
+ ? {
347
+ compat: {
348
+ ...(model as { compat?: Record<string, unknown> }).compat,
349
+ forceAdaptiveThinking: true,
350
+ allowEmptySignature: true,
351
+ },
352
+ }
353
+ : {}),
326
354
  } as Model<Api>;
327
355
  const upstream =
328
356
  wireProtocol === "openai"
@@ -41,6 +41,27 @@ const DATASOURCE_DEFINITIONS = [
41
41
  name: "yuandian_law",
42
42
  description: "Chinese laws, regulations, judicial cases; labor contracts and legal clauses.",
43
43
  },
44
+ {
45
+ name: "wind",
46
+ description:
47
+ "A-share intraday minute series, funds, bonds; map PE/PB/ROE-style field names via wind_search_fields first.",
48
+ },
49
+ {
50
+ name: "imf",
51
+ description: "IMF macro data: FX rates, CPI, GDP forecasts, balance of payments.",
52
+ },
53
+ {
54
+ name: "gildata",
55
+ description: "Natural-language stock/fund screening.",
56
+ },
57
+ {
58
+ name: "sec_edgar",
59
+ description: "US SEC filings: 10-K/10-Q, S-1, Form 4, 13F, 8-K.",
60
+ },
61
+ {
62
+ name: "sp_data",
63
+ description: "S&P fundamentals: consensus estimates, valuation ratios, transcripts.",
64
+ },
44
65
  ] as const;
45
66
 
46
67
  const DATASOURCE_NAMES = DATASOURCE_DEFINITIONS.map((source) => source.name);
@@ -50,7 +71,7 @@ export const kimiDatasourceSchema = Type.Object({
50
71
  DATASOURCE_NAMES.map((name) => Type.Literal(name)),
51
72
  {
52
73
  description:
53
- "Source: stock_finance_data stocks/reports; yahoo_finance quotes/options; world_bank_open_data GDP/population; tianyancha Chinese company registry/legal rep; arxiv preprints; scholar citations; yuandian_law Chinese laws/cases/contracts.",
74
+ "Source: stock_finance_data stocks/reports; yahoo_finance quotes/options; world_bank_open_data GDP/population; imf FX/CPI/GDP forecasts; tianyancha Chinese company registry/legal rep; arxiv preprints; scholar citations; yuandian_law Chinese laws/cases/contracts; wind A-share intraday minute series/funds/bonds (map field names via wind_search_fields first); gildata natural-language stock/fund screening; sec_edgar US filings (10-K/10-Q, S-1, Form 4, 13F, 8-K); sp_data S&P fundamentals (estimates, valuation, transcripts).",
54
75
  },
55
76
  ),
56
77
  api_name: Type.Optional(
package/src/usage.ts CHANGED
@@ -217,6 +217,8 @@ function formatWindowLabel(value: unknown, fallbackLabel: string): string {
217
217
  : unit.includes("MINUTE")
218
218
  ? duration
219
219
  : undefined;
220
+ if (unit.includes("WEEK")) return `Current ${duration}w window`;
221
+ if (unit.includes("DAY")) return `Current ${duration}d window`;
220
222
  if (!minutes) return fallbackLabel;
221
223
  if (minutes % 60 === 0) return `Current ${minutes / 60}h window`;
222
224
  return `Current ${minutes}m window`;
@@ -320,35 +322,123 @@ function fetchKimiUsage(token: string, signal: AbortSignal): Promise<Response> {
320
322
  });
321
323
  }
322
324
 
323
- export async function fetchKimiUsageSnapshot(
324
- options: { timeoutMs?: number; token?: string; refreshOnUnauthorized?: boolean } = {},
325
+ interface KimiSnapshotOptions {
326
+ timeoutMs?: number;
327
+ token?: string;
328
+ refreshOnUnauthorized?: boolean;
329
+ }
330
+
331
+ interface KimiSnapshotEndpoint {
332
+ prefix: string;
333
+ request: (token: string, signal: AbortSignal) => Promise<Response>;
334
+ summarize: (payload: unknown) => string;
335
+ }
336
+
337
+ async function fetchKimiSnapshot(
338
+ endpoint: KimiSnapshotEndpoint,
339
+ options: KimiSnapshotOptions = {},
325
340
  ): Promise<KimiUsageSnapshot> {
326
341
  const token = options.token ?? getKimiUsageToken();
327
342
  if (!token) {
328
- return { summary: "Usage: missing credentials. Run /login kimi-coding." };
343
+ return { summary: `${endpoint.prefix}: missing credentials. Run /login kimi-coding.` };
329
344
  }
330
345
 
331
346
  const controller = new AbortController();
332
347
  const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 15_000);
333
348
  try {
334
- let response = await fetchKimiUsage(token, controller.signal);
349
+ let response = await endpoint.request(token, controller.signal);
335
350
  if (response.status === 401 && options.refreshOnUnauthorized !== false) {
336
351
  const refreshed = await refreshKimiAuthToken(token, { signal: controller.signal });
337
- if (refreshed) response = await fetchKimiUsage(refreshed, controller.signal);
352
+ if (refreshed) response = await endpoint.request(refreshed, controller.signal);
338
353
  }
339
354
  if (!response.ok) {
340
- return { summary: `Usage: fetch failed (${response.status})` };
355
+ return { summary: `${endpoint.prefix}: fetch failed (${response.status})` };
341
356
  }
342
357
  const payload = (await response.json()) as unknown;
343
- return { summary: parseUsageSummary(payload) };
358
+ return { summary: endpoint.summarize(payload) };
344
359
  } catch (error) {
345
360
  const message = error instanceof Error ? error.message : String(error);
346
- return { summary: `Usage: fetch failed (${message})` };
361
+ return { summary: `${endpoint.prefix}: fetch failed (${message})` };
347
362
  } finally {
348
363
  clearTimeout(timeout);
349
364
  }
350
365
  }
351
366
 
367
+ export async function fetchKimiUsageSnapshot(
368
+ options: KimiSnapshotOptions = {},
369
+ ): Promise<KimiUsageSnapshot> {
370
+ return fetchKimiSnapshot(
371
+ { prefix: "Usage", request: fetchKimiUsage, summarize: parseUsageSummary },
372
+ options,
373
+ );
374
+ }
375
+
352
376
  export async function fetchKimiUsageSummary(): Promise<string> {
353
377
  return (await fetchKimiUsageSnapshot()).summary;
354
378
  }
379
+
380
+ export interface KimiUserInfo {
381
+ userId: string;
382
+ nickname: string;
383
+ userLevel: number;
384
+ userLevelName: string;
385
+ email?: string;
386
+ }
387
+
388
+ export function parseKimiUserInfo(payload: unknown): KimiUserInfo | null {
389
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return null;
390
+ const record = payload as Record<string, unknown>;
391
+ const userId = toStringValue(record.user_id);
392
+ if (!userId) return null;
393
+ const email = toStringValue(record.email);
394
+ return {
395
+ userId,
396
+ nickname: toStringValue(record.nickname) ?? "",
397
+ userLevel: toNumber(record.user_level) ?? 0,
398
+ userLevelName: toStringValue(record.user_level_name) ?? "",
399
+ ...(email ? { email } : {}),
400
+ };
401
+ }
402
+
403
+ export function formatKimiUserInfo(info: KimiUserInfo): string {
404
+ const parts = [info.nickname || info.userId];
405
+ if (info.userLevelName) {
406
+ parts.push(
407
+ info.userLevel > 0 ? `${info.userLevelName} (Lv ${info.userLevel})` : info.userLevelName,
408
+ );
409
+ }
410
+ if (info.email) parts.push(info.email);
411
+ return parts.join(" · ");
412
+ }
413
+
414
+ export function buildKimiUserInfoUrl(baseUrl = getBaseUrl("openai")): string {
415
+ const normalized = baseUrl.replace(/\/+$/, "");
416
+ return normalized.endsWith("/v1") ? `${normalized}/me` : `${normalized}/v1/me`;
417
+ }
418
+
419
+ function fetchKimiUserInfo(token: string, signal: AbortSignal): Promise<Response> {
420
+ return fetch(buildKimiUserInfoUrl(), {
421
+ method: "GET",
422
+ headers: {
423
+ ...getKimiProviderHeaders(),
424
+ Authorization: `Bearer ${token}`,
425
+ },
426
+ signal,
427
+ });
428
+ }
429
+
430
+ export async function fetchKimiUserInfoSnapshot(
431
+ options: KimiSnapshotOptions = {},
432
+ ): Promise<KimiUsageSnapshot> {
433
+ return fetchKimiSnapshot(
434
+ {
435
+ prefix: "Account",
436
+ request: fetchKimiUserInfo,
437
+ summarize: (payload) => {
438
+ const info = parseKimiUserInfo(payload);
439
+ return info ? formatKimiUserInfo(info) : "Account: malformed response";
440
+ },
441
+ },
442
+ options,
443
+ );
444
+ }