Files

121 lines
3.4 KiB
JavaScript

import { readAuthFile } from '../../opencode/auth.js';
import {
getAuthEntry,
normalizeAuthEntry,
buildResult,
toUsageWindow,
toNumber,
toTimestamp,
durationToLabel,
durationToSeconds
} from '../utils/index.js';
export const providerId = 'kimi-for-coding';
export const providerName = 'Kimi for Coding';
const aliases = ['kimi-for-coding', 'kimi'];
// Kimi's weekly `usage` block reports `used`; its rate-limit `limits[].detail`
// blocks report `remaining` instead. Neither field is guaranteed present, so
// derive usedPercent from whichever one the API actually returned.
const computeUsedPercent = (total, used, remaining) => {
if (!total) return null;
if (used !== null) {
return Math.max(0, Math.min(100, (used / total) * 100));
}
if (remaining !== null) {
return Math.max(0, Math.min(100, 100 - (remaining / total) * 100));
}
return null;
};
export const isConfigured = () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
return Boolean(entry?.key || entry?.token);
};
export const fetchQuota = async () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
const apiKey = entry?.key ?? entry?.token;
if (!apiKey) {
return buildResult({
providerId,
providerName,
ok: false,
configured: false,
error: 'Not configured'
});
}
try {
const response = await fetch('https://api.kimi.com/coding/v1/usages', {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: `API error: ${response.status}`
});
}
const payload = await response.json();
const windows = {};
const usage = payload?.usage ?? null;
if (usage) {
const limit = toNumber(usage.limit);
const used = toNumber(usage.used);
const remaining = toNumber(usage.remaining);
const usedPercent = computeUsedPercent(limit, used, remaining);
windows.weekly = toUsageWindow({
usedPercent,
windowSeconds: null,
resetAt: toTimestamp(usage.resetTime)
});
}
const limits = Array.isArray(payload?.limits) ? payload.limits : [];
for (const limit of limits) {
const window = limit?.window;
const detail = limit?.detail;
const rawLabel = durationToLabel(window?.duration, window?.timeUnit);
const windowSeconds = durationToSeconds(window?.duration, window?.timeUnit);
const label = windowSeconds === 5 * 60 * 60 ? `Rate Limit (${rawLabel})` : rawLabel;
const total = toNumber(detail?.limit);
const used = toNumber(detail?.used);
const remaining = toNumber(detail?.remaining);
const usedPercent = computeUsedPercent(total, used, remaining);
windows[label] = toUsageWindow({
usedPercent,
windowSeconds,
resetAt: toTimestamp(detail?.resetTime)
});
}
return buildResult({
providerId,
providerName,
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};