feat(kimi-coding): add Kimi Coding plan quota display (#279)

- Add getKimiUsage() with X-Msh-* device headers for Kimi API
- Parse weekly quota + rate-limit breakdown from /v1/usages endpoint
- Fix X-Msh-* headers in refreshKimiCodingToken (was missing)
- Wire kimi-coding into getUsageForProvider switch
- Add quota capability flag to providers.ts
- Remove console.warn/error (production-safe silent returns)

Co-authored-by: nyatoru <nyarutoru0002@outlook.co.th>
This commit is contained in:
diegosouzapw
2026-03-10 09:04:03 -03:00
5 changed files with 231 additions and 13 deletions

View File

@@ -20,6 +20,7 @@ const PROVIDER_CONFIG = {
kiro: { label: "Kiro AI", color: "#FF6B35" },
codex: { label: "OpenAI Codex", color: "#10A37F" },
claude: { label: "Claude Code", color: "#D97757" },
"kimi-coding": { label: "Kimi Coding", color: "#1E3A8A" },
};
const TIER_FILTERS = [
@@ -236,7 +237,7 @@ export default function ProviderLimits() {
);
const sortedConnections = useMemo(() => {
const priority = { antigravity: 1, github: 2, codex: 3, claude: 4, kiro: 5 };
const priority = { antigravity: 1, github: 2, codex: 3, claude: 4, kiro: 5, "kimi-coding": 6 };
return [...filteredConnections].sort(
(a, b) => (priority[a.provider] || 9) - (priority[b.provider] || 9)
);

View File

@@ -1,4 +1,26 @@
import { KIMI_CODING_CONFIG } from "../constants/oauth";
import { randomUUID } from "node:crypto";
import { hostname } from "node:os";
// Generate device ID (persistent per installation)
const DEVICE_ID = randomUUID();
const PLATFORM = "omniroute";
const VERSION = "2.1.2";
const DEVICE_NAME = hostname();
const DEVICE_MODEL = `${process.platform} ${process.arch}`;
// Custom headers required by Kimi OAuth
function getKimiOAuthHeaders() {
return {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
"X-Msh-Platform": PLATFORM,
"X-Msh-Version": VERSION,
"X-Msh-Device-Name": DEVICE_NAME,
"X-Msh-Device-Model": DEVICE_MODEL,
"X-Msh-Device-Id": DEVICE_ID,
};
}
export const kimiCoding = {
config: KIMI_CODING_CONFIG,
@@ -6,10 +28,7 @@ export const kimiCoding = {
requestDeviceCode: async (config) => {
const response = await fetch(config.deviceCodeUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
headers: getKimiOAuthHeaders(),
body: new URLSearchParams({
client_id: config.clientId,
}),
@@ -24,10 +43,10 @@ export const kimiCoding = {
return {
device_code: data.device_code,
user_code: data.user_code,
verification_uri: data.verification_uri || `https://www.kimi.com/code/authorize_device`,
verification_uri: data.verification_uri || `https://auth.kimi.com/activate`,
verification_uri_complete:
data.verification_uri_complete ||
`https://www.kimi.com/code/authorize_device?user_code=${data.user_code}`,
`https://auth.kimi.com/activate?user_code=${data.user_code}`,
expires_in: data.expires_in,
interval: data.interval || 5,
};
@@ -35,14 +54,11 @@ export const kimiCoding = {
pollToken: async (config, deviceCode) => {
const response = await fetch(config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
headers: getKimiOAuthHeaders(),
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
client_id: config.clientId,
device_code: deviceCode,
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
}),
});
@@ -63,5 +79,7 @@ export const kimiCoding = {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
tokenType: tokens.token_type,
scope: tokens.scope,
}),
};

View File

@@ -400,7 +400,14 @@ export const ID_TO_ALIAS = Object.values(AI_PROVIDERS).reduce((acc, p) => {
}, {});
// Providers that support usage/quota API
export const USAGE_SUPPORTED_PROVIDERS = ["antigravity", "kiro", "github", "codex", "claude"];
export const USAGE_SUPPORTED_PROVIDERS = [
"antigravity",
"kiro",
"github",
"codex",
"claude",
"kimi-coding",
];
// ── Zod validation at module load (Phase 7.2) ──
import { validateProviders } from "../validation/providerSchema";