pi-provider-kimi-code 0.6.11 → 0.6.13
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 +26 -8
- package/index.ts +31 -3
- package/package.json +9 -8
- package/src/config.ts +40 -4
- package/src/constants.ts +112 -20
- package/src/models.ts +25 -4
- package/src/oauth.ts +42 -1
- package/src/payload.ts +440 -79
- package/src/schema-dedup.ts +9 -4
- package/src/settings-ui.ts +20 -0
- package/src/stream.ts +89 -25
- package/src/tools/common.ts +2 -3
- package/src/usage.ts +116 -7
package/src/settings-ui.ts
CHANGED
|
@@ -58,6 +58,13 @@ export function setProtocol(
|
|
|
58
58
|
return { ...config, protocol };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export function setRegion(
|
|
62
|
+
config: KimiCodeConfig,
|
|
63
|
+
region: KimiCodeConfig["region"],
|
|
64
|
+
): KimiCodeConfig {
|
|
65
|
+
return { ...config, region };
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
export function setUploadThreshold(config: KimiCodeConfig, thresholdBytes: number): KimiCodeConfig {
|
|
62
69
|
return { ...config, uploads: { ...config.uploads, thresholdBytes } };
|
|
63
70
|
}
|
|
@@ -73,6 +80,7 @@ export function buildKimiMainTitle(
|
|
|
73
80
|
"",
|
|
74
81
|
`Model: ${modelName}`,
|
|
75
82
|
`Protocol: ${config.protocol} (${sources.protocol})`,
|
|
83
|
+
`Region: ${formatRegionValue(config.region)} (${sources.region})`,
|
|
76
84
|
`Upload threshold: ${formatByteSize(config.uploads.thresholdBytes)} (${sources.uploads.thresholdBytes})`,
|
|
77
85
|
"",
|
|
78
86
|
"Effective tools:",
|
|
@@ -138,6 +146,18 @@ export function toolMenuItem(config: KimiCodeConfig, toolName: KimiToolName): st
|
|
|
138
146
|
return `${toolName} -> ${formatToolStatus(config, toolName)}`;
|
|
139
147
|
}
|
|
140
148
|
|
|
149
|
+
export function formatRegionValue(region: KimiCodeConfig["region"]): string {
|
|
150
|
+
return region ?? "auto";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function formatRegionStatus(config: KimiCodeConfig): string {
|
|
154
|
+
return `region: ${formatRegionValue(config.region)}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function regionMenuItem(config: KimiCodeConfig): string {
|
|
158
|
+
return `Region -> ${formatRegionValue(config.region)}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
141
161
|
export function protocolMenuItem(config: KimiCodeConfig): string {
|
|
142
162
|
return `Protocol -> ${config.protocol}`;
|
|
143
163
|
}
|
package/src/stream.ts
CHANGED
|
@@ -32,8 +32,10 @@ type KimiStreamSimple = (
|
|
|
32
32
|
const piAiRuntime = piAi as unknown as {
|
|
33
33
|
anthropicMessagesApi?: () => { streamSimple: KimiStreamSimple };
|
|
34
34
|
openAICompletionsApi?: () => { streamSimple: KimiStreamSimple };
|
|
35
|
+
openAIResponsesApi?: () => { streamSimple: KimiStreamSimple };
|
|
35
36
|
streamSimpleAnthropic?: KimiStreamSimple;
|
|
36
37
|
streamSimpleOpenAICompletions?: KimiStreamSimple;
|
|
38
|
+
streamSimpleOpenAIResponses?: KimiStreamSimple;
|
|
37
39
|
lazyApi?: (load: () => Promise<object>) => { streamSimple: KimiStreamSimple };
|
|
38
40
|
};
|
|
39
41
|
|
|
@@ -43,22 +45,43 @@ const piAiRuntime = piAi as unknown as {
|
|
|
43
45
|
// evaluated in plain-Node environments where the subpaths do resolve.
|
|
44
46
|
const anthropicMessagesModule = "@earendil-works/pi-ai/api/anthropic-messages";
|
|
45
47
|
const openAICompletionsModule = "@earendil-works/pi-ai/api/openai-completions";
|
|
48
|
+
const openAIResponsesModule = "@earendil-works/pi-ai/api/openai-responses";
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
// lazyApi is the last resort; it only exists on pi-ai versions that ship the
|
|
51
|
+
// ./api/* subpath modules. Undefined when none of the three lookup paths
|
|
52
|
+
// resolve (e.g. pi <=0.79 has no responses entry point at all).
|
|
53
|
+
function lazyStreamSimple(load: () => Promise<object>): KimiStreamSimple | undefined {
|
|
54
|
+
return piAiRuntime.lazyApi?.(load)?.streamSimple;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Not readonly: tests swap entries to simulate runtimes missing an API.
|
|
58
|
+
const streamSimpleImpls: Record<KimiWireProtocol, KimiStreamSimple | undefined> = {
|
|
59
|
+
openai:
|
|
60
|
+
piAiRuntime.openAICompletionsApi?.().streamSimple ??
|
|
61
|
+
piAiRuntime.streamSimpleOpenAICompletions ??
|
|
62
|
+
lazyStreamSimple(() => import(openAICompletionsModule)),
|
|
63
|
+
anthropic:
|
|
64
|
+
piAiRuntime.anthropicMessagesApi?.().streamSimple ??
|
|
65
|
+
piAiRuntime.streamSimpleAnthropic ??
|
|
66
|
+
lazyStreamSimple(() => import(anthropicMessagesModule)),
|
|
67
|
+
responses:
|
|
68
|
+
piAiRuntime.openAIResponsesApi?.().streamSimple ??
|
|
69
|
+
piAiRuntime.streamSimpleOpenAIResponses ??
|
|
70
|
+
lazyStreamSimple(() => import(openAIResponsesModule)),
|
|
71
|
+
};
|
|
55
72
|
import {
|
|
56
73
|
DEFAULT_KIMI_CODE_CONFIG,
|
|
57
74
|
type KimiCodeConfig,
|
|
58
75
|
type KimiResolvedModelConfig,
|
|
59
76
|
} from "./config.ts";
|
|
60
77
|
|
|
61
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
ENV_KIMI_CODE_PROTOCOL,
|
|
80
|
+
PROVIDER_ID,
|
|
81
|
+
type KimiWireProtocol,
|
|
82
|
+
getApiProtocol,
|
|
83
|
+
getBaseUrl,
|
|
84
|
+
} from "./constants.ts";
|
|
62
85
|
import { getKimiProviderHeaders } from "./device.ts";
|
|
63
86
|
import {
|
|
64
87
|
isKimiAuthErrorMessage,
|
|
@@ -224,6 +247,40 @@ export function mergeKimiRequestHeaders(headers?: HeaderMap): HeaderMap {
|
|
|
224
247
|
return { ...getKimiProviderHeaders(), ...headers };
|
|
225
248
|
}
|
|
226
249
|
|
|
250
|
+
function streamSimpleForProtocol(
|
|
251
|
+
protocol: KimiWireProtocol,
|
|
252
|
+
model: Model<Api>,
|
|
253
|
+
context: Context,
|
|
254
|
+
options: SimpleStreamOptions,
|
|
255
|
+
): AssistantMessageEventStream {
|
|
256
|
+
const impl = streamSimpleImpls[protocol];
|
|
257
|
+
// Resolution runs once at module load against whichever pi-ai shape is
|
|
258
|
+
// installed, so an old pi-ai can leave the entry point undefined. Throw a
|
|
259
|
+
// descriptive error here — the caller invokes this inside the retry loop's
|
|
260
|
+
// try, so it surfaces as a stream error event instead of an unhandled
|
|
261
|
+
// rejection from a bare "not a function" TypeError.
|
|
262
|
+
if (!impl) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`installed pi-ai has no streamSimple entry point for ${getApiProtocol(protocol)}; ` +
|
|
265
|
+
"upgrade pi-ai or choose another KIMI_CODE_PROTOCOL",
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return impl(model, context, options);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Test hook: swap a resolved stream factory (pass undefined to simulate a
|
|
272
|
+
// pi-ai runtime that lacks the entry point). Returns a restore function.
|
|
273
|
+
export function overrideStreamSimpleForTests(
|
|
274
|
+
protocol: KimiWireProtocol,
|
|
275
|
+
impl: KimiStreamSimple | undefined,
|
|
276
|
+
): () => void {
|
|
277
|
+
const original = streamSimpleImpls[protocol];
|
|
278
|
+
streamSimpleImpls[protocol] = impl;
|
|
279
|
+
return () => {
|
|
280
|
+
streamSimpleImpls[protocol] = original;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
227
284
|
export function streamSimpleKimi(
|
|
228
285
|
model: Model<Api>,
|
|
229
286
|
context: Context,
|
|
@@ -303,6 +360,7 @@ export function streamSimpleKimi(
|
|
|
303
360
|
cacheKey,
|
|
304
361
|
cacheRetention,
|
|
305
362
|
reasoning: options?.reasoning,
|
|
363
|
+
requestMaxTokens: options?.maxTokens,
|
|
306
364
|
modelConfig,
|
|
307
365
|
});
|
|
308
366
|
if (
|
|
@@ -332,8 +390,9 @@ export function streamSimpleKimi(
|
|
|
332
390
|
const patchedOptions = buildPatchedOptions(currentKey);
|
|
333
391
|
// Route by the module-level protocol flag, not model.api, since we
|
|
334
392
|
// register with a custom api type (kimi-openai-completions /
|
|
335
|
-
// kimi-anthropic-messages) to avoid overriding
|
|
336
|
-
// Anthropic/OpenAI stream handlers.
|
|
393
|
+
// kimi-anthropic-messages / kimi-openai-responses) to avoid overriding
|
|
394
|
+
// the built-in Anthropic/OpenAI stream handlers.
|
|
395
|
+
const existingCompat = (model as { compat?: Record<string, unknown> }).compat;
|
|
337
396
|
const runtimeModel = {
|
|
338
397
|
...model,
|
|
339
398
|
api: apiProtocol,
|
|
@@ -342,33 +401,38 @@ export function streamSimpleKimi(
|
|
|
342
401
|
// rejects/ignores the interleaved-thinking beta header; pi-ai >=0.82
|
|
343
402
|
// keys both behaviors off these compat flags (its own built-in
|
|
344
403
|
// kimi-coding models carry them in generated metadata).
|
|
404
|
+
// Responses rejects prompt_cache_retention (400) unless this is off.
|
|
345
405
|
...(wireProtocol === "anthropic"
|
|
346
406
|
? {
|
|
347
407
|
compat: {
|
|
348
|
-
...
|
|
408
|
+
...existingCompat,
|
|
349
409
|
forceAdaptiveThinking: true,
|
|
350
410
|
allowEmptySignature: true,
|
|
351
411
|
},
|
|
352
412
|
}
|
|
353
|
-
:
|
|
413
|
+
: wireProtocol === "responses"
|
|
414
|
+
? {
|
|
415
|
+
compat: {
|
|
416
|
+
...existingCompat,
|
|
417
|
+
supportsLongCacheRetention: false,
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
: {}),
|
|
354
421
|
} as Model<Api>;
|
|
355
|
-
const upstream =
|
|
356
|
-
wireProtocol === "openai"
|
|
357
|
-
? streamSimpleOpenAICompletions(
|
|
358
|
-
runtimeModel as Model<"openai-completions">,
|
|
359
|
-
context,
|
|
360
|
-
patchedOptions,
|
|
361
|
-
)
|
|
362
|
-
: streamSimpleAnthropic(
|
|
363
|
-
runtimeModel as Model<"anthropic-messages">,
|
|
364
|
-
context,
|
|
365
|
-
patchedOptions,
|
|
366
|
-
);
|
|
367
422
|
|
|
368
423
|
let shouldRetry = false;
|
|
369
424
|
let prefixBuffer: AssistantMessageEvent[] = [];
|
|
370
425
|
|
|
371
426
|
try {
|
|
427
|
+
// Inside the try so a missing pi-ai stream entry point (old pi-ai +
|
|
428
|
+
// a protocol it does not ship) surfaces through the catch's error
|
|
429
|
+
// event instead of escaping the async IIFE as an unhandled rejection.
|
|
430
|
+
const upstream = streamSimpleForProtocol(
|
|
431
|
+
wireProtocol,
|
|
432
|
+
runtimeModel,
|
|
433
|
+
context,
|
|
434
|
+
patchedOptions,
|
|
435
|
+
);
|
|
372
436
|
for await (const event of filterEmptyResponseStream(upstream)) {
|
|
373
437
|
// streamAnthropic emits a synthetic "start" event synchronously,
|
|
374
438
|
// before the for-await loop begins iterating and therefore before
|
package/src/tools/common.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
|
-
import { PROVIDER_ID, getBaseUrl } from "../constants.ts";
|
|
4
|
+
import { PROVIDER_ID, getBaseUrl, normalizeKimiBaseV1 } from "../constants.ts";
|
|
5
5
|
import { getKimiProviderHeaders } from "../device.ts";
|
|
6
6
|
import {
|
|
7
7
|
KIMI_LOGIN_REQUIRED_MESSAGE,
|
|
@@ -57,8 +57,7 @@ export function buildTimeoutSignal(signal: AbortSignal | undefined): {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function getKimiBaseV1(): string {
|
|
60
|
-
|
|
61
|
-
return base.endsWith("/v1") ? base : `${base}/v1`;
|
|
60
|
+
return normalizeKimiBaseV1(getBaseUrl());
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
export function getKimiDatasourceUrl(): string {
|
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> {
|