95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
// ─── Header Parsing ──────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Standard headers used by most providers (OpenAI, Fireworks, etc.)
|
|
*/
|
|
export const STANDARD_HEADERS = {
|
|
limit: "x-ratelimit-limit-requests",
|
|
remaining: "x-ratelimit-remaining-requests",
|
|
reset: "x-ratelimit-reset-requests",
|
|
limitTokens: "x-ratelimit-limit-tokens",
|
|
remainingTokens: "x-ratelimit-remaining-tokens",
|
|
resetTokens: "x-ratelimit-reset-tokens",
|
|
retryAfter: "retry-after",
|
|
overLimit: "x-ratelimit-over-limit",
|
|
};
|
|
|
|
/**
|
|
* Anthropic uses custom headers
|
|
*/
|
|
export const ANTHROPIC_HEADERS = {
|
|
limit: "anthropic-ratelimit-requests-limit",
|
|
remaining: "anthropic-ratelimit-requests-remaining",
|
|
reset: "anthropic-ratelimit-requests-reset",
|
|
limitTokens: "anthropic-ratelimit-input-tokens-limit",
|
|
remainingTokens: "anthropic-ratelimit-input-tokens-remaining",
|
|
resetTokens: "anthropic-ratelimit-input-tokens-reset",
|
|
retryAfter: "retry-after",
|
|
};
|
|
|
|
/**
|
|
* Parse a reset time string into milliseconds.
|
|
* Formats: "1s", "1m", "1h", "1ms", "60", ISO date, Unix timestamp
|
|
*/
|
|
export function parseResetTime(value) {
|
|
if (!value) return null;
|
|
|
|
// Duration strings: "1s", "500ms", "1m30s"
|
|
const durationMatch = value.match(/^(?:(\d+)h)?(?:(\d+)m(?!s))?(?:(\d+)s)?(?:(\d+)ms)?$/);
|
|
if (durationMatch) {
|
|
const [, h, m, s, ms] = durationMatch;
|
|
return (
|
|
(parseInt(h || 0) * 3600 + parseInt(m || 0) * 60 + parseInt(s || 0)) * 1000 +
|
|
parseInt(ms || 0)
|
|
);
|
|
}
|
|
|
|
// Pure number: assume seconds
|
|
const num = parseFloat(value);
|
|
if (!isNaN(num) && num > 0) {
|
|
// If it looks like a Unix timestamp (> year 2025)
|
|
if (num > 1700000000) {
|
|
return Math.max(0, num * 1000 - Date.now());
|
|
}
|
|
return num * 1000;
|
|
}
|
|
|
|
// ISO date string
|
|
try {
|
|
const date = new Date(value);
|
|
if (!isNaN(date.getTime())) {
|
|
return Math.max(0, date.getTime() - Date.now());
|
|
}
|
|
} catch {}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function toPlainHeaders(headers: unknown): Record<string, string> {
|
|
if (!headers) return {};
|
|
const plain: Record<string, string> = {};
|
|
const obj = headers as Record<string, unknown>;
|
|
if (typeof obj.forEach === "function") {
|
|
try {
|
|
(obj.forEach as (cb: (v: string, k: string) => void) => void)((v: string, k: string) => {
|
|
plain[k.toLowerCase()] = v;
|
|
});
|
|
return plain;
|
|
} catch {}
|
|
}
|
|
if (typeof obj.entries === "function") {
|
|
try {
|
|
for (const [k, v] of (obj.entries as () => Iterable<[string, string]>)()) {
|
|
plain[k.toLowerCase()] = v;
|
|
}
|
|
return plain;
|
|
} catch {}
|
|
}
|
|
try {
|
|
for (const [k, v] of Object.entries(obj)) {
|
|
plain[k.toLowerCase()] = v == null ? "" : String(v);
|
|
}
|
|
} catch {}
|
|
return plain;
|
|
}
|