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/README.md +48 -112
- package/index.ts +195 -25
- package/package.json +9 -5
- package/src/constants.ts +6 -2
- package/src/device.ts +59 -17
- package/src/models.ts +61 -6
- package/src/oauth.ts +7 -1
- package/src/payload.ts +154 -40
- package/src/stream.ts +32 -4
- package/src/tools/datasource.ts +22 -1
- package/src/usage.ts +98 -8
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 {
|
|
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"
|
package/src/tools/datasource.ts
CHANGED
|
@@ -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
|
-
|
|
324
|
-
|
|
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:
|
|
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
|
|
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
|
|
352
|
+
if (refreshed) response = await endpoint.request(refreshed, controller.signal);
|
|
338
353
|
}
|
|
339
354
|
if (!response.ok) {
|
|
340
|
-
return { summary:
|
|
355
|
+
return { summary: `${endpoint.prefix}: fetch failed (${response.status})` };
|
|
341
356
|
}
|
|
342
357
|
const payload = (await response.json()) as unknown;
|
|
343
|
-
return { summary:
|
|
358
|
+
return { summary: endpoint.summarize(payload) };
|
|
344
359
|
} catch (error) {
|
|
345
360
|
const message = error instanceof Error ? error.message : String(error);
|
|
346
|
-
return { summary:
|
|
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
|
+
}
|