Fix Kimi usage credential lookup

This commit is contained in:
Mohamed Boudra
2026-06-20 19:10:40 +07:00
parent 80fc11541f
commit 6a3856b639
3 changed files with 67 additions and 8 deletions

View File

@@ -67,6 +67,8 @@ To add plan usage for a provider, add `packages/server/src/services/quota-fetche
Keep the protocol shape provider-agnostic. Do not add provider-specific renderers for new limit windows; labels and generic bars should carry the UI. API responses should be parsed and normalized with Zod inside the fetcher, while the protocol boundary stays strict so old/new client compatibility is explicit.
Kimi Code usage follows the CLI-managed credential file at `KIMI_CODE_HOME` or `~/.kimi-code/credentials/kimi-code.json`; do not probe the legacy `~/.kimi` path as the primary source for current Kimi Code installs.
---
## ACP Provider Checklist

View File

@@ -88,13 +88,24 @@ export class KimiQuotaProvider implements ProviderUsageFetcher {
}
private async readKimiToken(): Promise<string | null> {
const path = join(homedir(), ".kimi", "credentials", "kimi-code.json");
if (!existsSync(path)) return null;
try {
const credentials = KimiAuthSchema.parse(JSON.parse(await fs.readFile(path, "utf8")));
return credentials.access_token ?? null;
} catch {
return null;
const paths = [
join(
process.env["KIMI_CODE_HOME"] || join(homedir(), ".kimi-code"),
"credentials",
"kimi-code.json",
),
join(homedir(), ".kimi", "credentials", "kimi-code.json"),
];
for (const path of paths) {
if (!existsSync(path)) continue;
try {
const credentials = KimiAuthSchema.parse(JSON.parse(await fs.readFile(path, "utf8")));
if (credentials.access_token) return credentials.access_token;
} catch {
continue;
}
}
return null;
}
}

View File

@@ -1,4 +1,4 @@
import { readFileSync, writeFileSync } from "node:fs";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -36,6 +36,20 @@ function writeCodexAuth(dir: string, accessToken: string, refreshToken = "rt_cod
);
}
function writeKimiCredentials(dir: string, accessToken: string): void {
mkdirSync(join(dir, "credentials"), { recursive: true });
writeFileSync(
join(dir, "credentials", "kimi-code.json"),
JSON.stringify({
access_token: accessToken,
refresh_token: "rt_kimi",
expires_at: 1_798_812_800,
scope: "kimi-code",
token_type: "Bearer",
}),
);
}
function makeClaudeResponse(
overrides: Partial<{
five_hour: { utilization: number | string; resets_at: string };
@@ -299,6 +313,7 @@ describe("real provider usage fetchers", () => {
"GROK_TOKEN",
"KIMI_TOKEN",
"KIMI_API_KEY",
"KIMI_CODE_HOME",
"CODEX_HOME",
]) {
delete process.env[key];
@@ -694,4 +709,35 @@ describe("real provider usage fetchers", () => {
],
});
});
it("fetches Kimi usage from the CLI credential home", async () => {
writeKimiCredentials(join(homeDir, ".kimi-code"), "kimi_cli_token");
fetchApi = vi.fn(async (url: RequestInfo | URL, init?: RequestInit) => {
expect(url.toString()).toBe("https://api.kimi.com/coding/v1/usages");
expect((init?.headers as Record<string, string> | undefined)?.Authorization).toBe(
"Bearer kimi_cli_token",
);
return jsonResponse({
usage: {
limit: "200",
remaining: "150",
resetTime: "2026-06-23T05:12:17Z",
},
});
}) as unknown as typeof fetch;
const kimi = findProvider(await service().listUsage(), "kimi");
expect(kimi).toMatchObject({
status: "available",
windows: [
expect.objectContaining({
id: "coding_usage",
usedPct: 25,
remainingPct: 75,
resetsAt: "2026-06-23T05:12:17Z",
}),
],
});
});
});