pi-provider-kimi-code 0.6.12 → 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/README.md +25 -7
- package/index.ts +218 -170
- package/package.json +16 -13
- package/src/config.ts +33 -1
- package/src/constants.ts +86 -9
- package/src/errors.ts +31 -0
- package/src/models.ts +95 -13
- package/src/oauth.ts +60 -1
- package/src/payload.ts +266 -1
- package/src/schema-dedup.ts +8 -0
- package/src/settings-ui.ts +28 -0
- package/src/stream.ts +17 -3
- package/src/tools/common.ts +2 -3
- package/src/tools/datasource-catalog.ts +420 -0
- package/src/tools/datasource.ts +54 -68
- package/src/tools/moonshot.ts +7 -7
- package/src/usage.ts +116 -7
package/src/usage.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PROVIDER_ID, getBaseUrl } from "./constants.ts";
|
|
1
|
+
import { PROVIDER_ID, getBaseUrl, normalizeKimiBaseV1 } from "./constants.ts";
|
|
2
2
|
import { getKimiProviderHeaders } from "./device.ts";
|
|
3
3
|
import { readStoredOAuthCredential, refreshKimiAuthToken } from "./oauth.ts";
|
|
4
4
|
|
|
@@ -51,6 +51,13 @@ function toNumber(value: unknown): number | null {
|
|
|
51
51
|
return Number.isFinite(number) ? number : null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function toOptionalNumber(value: unknown): number | null {
|
|
55
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
56
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
57
|
+
const number = Number(value);
|
|
58
|
+
return Number.isFinite(number) ? number : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
function toStringValue(value: unknown): string | undefined {
|
|
55
62
|
if (typeof value === "string" && value.trim()) return value;
|
|
56
63
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
@@ -141,6 +148,85 @@ function formatExtraUsage(info: BoosterWalletInfo): string[] {
|
|
|
141
148
|
return lines;
|
|
142
149
|
}
|
|
143
150
|
|
|
151
|
+
// Quota model (upstream kimi-code #3787): the platform /usages payload now
|
|
152
|
+
// serves per-window used ratios under `usages` instead of absolute used/limit
|
|
153
|
+
// rows. Keys are snake_case (`limit_5h`, `limit_7d`, `limit_month_total`,
|
|
154
|
+
// `limit_month_code`); each entry carries `used_ratio` (0..1, sometimes a
|
|
155
|
+
// decimal string) and an optional `reset_time`. Rows are presence-driven —
|
|
156
|
+
// the backend omits windows the plan does not have. The legacy `usage` /
|
|
157
|
+
// `limits` rows are still served alongside during the transition, but the
|
|
158
|
+
// legacy wire is gone from kap-server, so quota rows win when present.
|
|
159
|
+
interface QuotaEntry {
|
|
160
|
+
usedRatio: number;
|
|
161
|
+
resetTime?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const QUOTA_WINDOWS = [
|
|
165
|
+
["limit_5h", "5h limit"],
|
|
166
|
+
["limit_7d", "Weekly limit"],
|
|
167
|
+
["limit_month_total", "Monthly limit"],
|
|
168
|
+
] as const;
|
|
169
|
+
|
|
170
|
+
const QUOTA_MONTH_CODE_KEY = "limit_month_code";
|
|
171
|
+
|
|
172
|
+
function isRecordValue(value: unknown): value is Record<string, unknown> {
|
|
173
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function toRatio(value: unknown): number | undefined {
|
|
177
|
+
if (typeof value === "number") {
|
|
178
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : undefined;
|
|
179
|
+
}
|
|
180
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
181
|
+
const parsed = Number(value);
|
|
182
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseQuotaEntry(value: unknown): QuotaEntry | undefined {
|
|
186
|
+
if (!isRecordValue(value)) return undefined;
|
|
187
|
+
const usedRatio = toRatio(value.used_ratio);
|
|
188
|
+
if (usedRatio === undefined) return undefined;
|
|
189
|
+
const resetTime = toStringValue(value.reset_time);
|
|
190
|
+
return {
|
|
191
|
+
usedRatio,
|
|
192
|
+
...(resetTime ? { resetTime } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function parseQuotaWindows(record: Record<string, unknown>): Map<string, QuotaEntry> {
|
|
197
|
+
const windows = new Map<string, QuotaEntry>();
|
|
198
|
+
const usages = record.usages;
|
|
199
|
+
if (!isRecordValue(usages)) return windows;
|
|
200
|
+
for (const key of [...QUOTA_WINDOWS.map(([key]) => key), QUOTA_MONTH_CODE_KEY]) {
|
|
201
|
+
const entry = parseQuotaEntry(usages[key]);
|
|
202
|
+
if (entry) windows.set(key, entry);
|
|
203
|
+
}
|
|
204
|
+
return windows;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function formatQuotaRow(label: string, entry: QuotaEntry, options: UsageFormatOptions): string {
|
|
208
|
+
const percent = Math.min(100, Math.max(0, Math.ceil(entry.usedRatio * 100)));
|
|
209
|
+
const lines = [label, `${quotaBar(entry.usedRatio * 100, 100)} ${percent}% used`];
|
|
210
|
+
const reset = formatResetTime(entry.resetTime, options);
|
|
211
|
+
if (reset) lines.push(`Resets ${reset}`);
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// The kimi/code split of the monthly quota on the new plan: `code` is the
|
|
216
|
+
// code-typed share of the monthly total as served, `kimi` the remainder
|
|
217
|
+
// (mirrors upstream monthlyBreakdown in apps/kimi-code usage-format).
|
|
218
|
+
function formatMonthlyBreakdown(
|
|
219
|
+
total: QuotaEntry,
|
|
220
|
+
windows: Map<string, QuotaEntry>,
|
|
221
|
+
): string | null {
|
|
222
|
+
const code = windows.get(QUOTA_MONTH_CODE_KEY);
|
|
223
|
+
if (!code) return null;
|
|
224
|
+
const codePercent = Math.min(100, Math.max(0, Math.ceil(code.usedRatio * 100)));
|
|
225
|
+
const totalPercent = Math.min(100, Math.max(0, Math.ceil(total.usedRatio * 100)));
|
|
226
|
+
const kimiPercent = Math.max(0, totalPercent - codePercent);
|
|
227
|
+
return `kimi ${kimiPercent}% · code ${codePercent}%`;
|
|
228
|
+
}
|
|
229
|
+
|
|
144
230
|
export function parseUsageRow(value: unknown, fallbackLabel: string): UsageRow | null {
|
|
145
231
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
146
232
|
const record = value as Record<string, unknown>;
|
|
@@ -164,11 +250,28 @@ export function parseUsageSummary(payload: unknown, options: UsageFormatOptions
|
|
|
164
250
|
|
|
165
251
|
const record = payload as Record<string, unknown>;
|
|
166
252
|
const lines: string[] = [];
|
|
167
|
-
|
|
253
|
+
|
|
254
|
+
const quotaWindows = parseQuotaWindows(record);
|
|
255
|
+
// A code-split entry without a monthly total renders no row of its own; it
|
|
256
|
+
// only feeds the breakdown line, so it must not suppress the legacy rows.
|
|
257
|
+
const hasQuotaRows = QUOTA_WINDOWS.some(([key]) => quotaWindows.has(key));
|
|
258
|
+
if (hasQuotaRows) {
|
|
259
|
+
for (const [key, label] of QUOTA_WINDOWS) {
|
|
260
|
+
const entry = quotaWindows.get(key);
|
|
261
|
+
if (!entry) continue;
|
|
262
|
+
if (lines.length > 0) lines.push("");
|
|
263
|
+
lines.push(...formatQuotaRow(label, entry, options).split("\n"));
|
|
264
|
+
const breakdown =
|
|
265
|
+
key === "limit_month_total" ? formatMonthlyBreakdown(entry, quotaWindows) : null;
|
|
266
|
+
if (breakdown) lines.push(breakdown);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const summary = hasQuotaRows ? null : parseUsageRow(record.usage, "Current week");
|
|
168
271
|
if (summary)
|
|
169
272
|
lines.push(formatUsageRow({ ...summary, label: normalizeUsageLabel(summary.label) }, options));
|
|
170
273
|
|
|
171
|
-
if (Array.isArray(record.limits)) {
|
|
274
|
+
if (!hasQuotaRows && Array.isArray(record.limits)) {
|
|
172
275
|
for (const [index, item] of record.limits.entries()) {
|
|
173
276
|
const itemRecord =
|
|
174
277
|
typeof item === "object" && item !== null && !Array.isArray(item)
|
|
@@ -307,8 +410,7 @@ function partialBlock(value: number): string {
|
|
|
307
410
|
}
|
|
308
411
|
|
|
309
412
|
export function buildKimiUsageUrl(baseUrl = getBaseUrl("openai")): string {
|
|
310
|
-
|
|
311
|
-
return normalized.endsWith("/v1") ? `${normalized}/usages` : `${normalized}/v1/usages`;
|
|
413
|
+
return `${normalizeKimiBaseV1(baseUrl)}/usages`;
|
|
312
414
|
}
|
|
313
415
|
|
|
314
416
|
function fetchKimiUsage(token: string, signal: AbortSignal): Promise<Response> {
|
|
@@ -383,6 +485,12 @@ export interface KimiUserInfo {
|
|
|
383
485
|
userLevel: number;
|
|
384
486
|
userLevelName: string;
|
|
385
487
|
email?: string;
|
|
488
|
+
/**
|
|
489
|
+
* Plan generation from the managed /me profile (upstream #3921). upstream
|
|
490
|
+
* gates feedback surveys and plan-dependent surfaces by it; the monthly
|
|
491
|
+
* kimi/code quota split only exists on newer goods versions.
|
|
492
|
+
*/
|
|
493
|
+
goodsVersion?: number;
|
|
386
494
|
}
|
|
387
495
|
|
|
388
496
|
export function parseKimiUserInfo(payload: unknown): KimiUserInfo | null {
|
|
@@ -391,12 +499,14 @@ export function parseKimiUserInfo(payload: unknown): KimiUserInfo | null {
|
|
|
391
499
|
const userId = toStringValue(record.user_id);
|
|
392
500
|
if (!userId) return null;
|
|
393
501
|
const email = toStringValue(record.email);
|
|
502
|
+
const goodsVersion = toOptionalNumber(record.goods_version);
|
|
394
503
|
return {
|
|
395
504
|
userId,
|
|
396
505
|
nickname: toStringValue(record.nickname) ?? "",
|
|
397
506
|
userLevel: toNumber(record.user_level) ?? 0,
|
|
398
507
|
userLevelName: toStringValue(record.user_level_name) ?? "",
|
|
399
508
|
...(email ? { email } : {}),
|
|
509
|
+
...(goodsVersion !== null ? { goodsVersion } : {}),
|
|
400
510
|
};
|
|
401
511
|
}
|
|
402
512
|
|
|
@@ -412,8 +522,7 @@ export function formatKimiUserInfo(info: KimiUserInfo): string {
|
|
|
412
522
|
}
|
|
413
523
|
|
|
414
524
|
export function buildKimiUserInfoUrl(baseUrl = getBaseUrl("openai")): string {
|
|
415
|
-
|
|
416
|
-
return normalized.endsWith("/v1") ? `${normalized}/me` : `${normalized}/v1/me`;
|
|
525
|
+
return `${normalizeKimiBaseV1(baseUrl)}/me`;
|
|
417
526
|
}
|
|
418
527
|
|
|
419
528
|
function fetchKimiUserInfo(token: string, signal: AbortSignal): Promise<Response> {
|