/** * shelley-proxy.js — Shelley/exe.dev proxy (classic schema + Exedev-Gateway-Cost) * * The Shelley proxy returns standard Anthropic rate-limit headers (classic schema, * not the unified Teams schema) plus an Exedev-Gateway-Cost header with per-call * USD cost. No API key is required — the proxy handles auth internally. * * Header reference: * Anthropic-Ratelimit-Tokens-Limit total token budget * Anthropic-Ratelimit-Tokens-Remaining remaining tokens * Anthropic-Ratelimit-Tokens-Reset ISO 8601 reset time * Anthropic-Ratelimit-Requests-Limit total request budget * Anthropic-Ratelimit-Requests-Remaining remaining requests * Anthropic-Ratelimit-Requests-Reset ISO 8601 reset time * Exedev-Gateway-Cost per-call USD cost (float) * anthropic-organization-id organization UUID */ import { getSeverity } from '../report.js'; /** * Parse classic Anthropic rate-limit headers from a Shelley proxy response. * * @param {Object} headers — fetch Response.headers (or compatible mock with .get(name)) * @param {number} httpStatus — HTTP status code * @returns {Object} normalized provider result */ export function parseShelleyHeaders(headers, httpStatus) { const h = (name) => headers.get(name) || headers.get(name.toLowerCase()); const tokensLimit = parseInt(h('Anthropic-Ratelimit-Tokens-Limit'), 10); const tokensRemaining = parseInt(h('Anthropic-Ratelimit-Tokens-Remaining'), 10); const tokensReset = h('Anthropic-Ratelimit-Tokens-Reset'); const requestsLimit = parseInt(h('Anthropic-Ratelimit-Requests-Limit'), 10); const requestsRemaining = parseInt(h('Anthropic-Ratelimit-Requests-Remaining'), 10); const requestsReset = h('Anthropic-Ratelimit-Requests-Reset'); const costPerCall = h('Exedev-Gateway-Cost'); const orgId = h('anthropic-organization-id'); const result = { type: 'shelley-proxy', status: httpStatus === 429 ? 'rate_limited' : (httpStatus === 200 ? 'ok' : 'error'), tokens_limit: isNaN(tokensLimit) ? null : tokensLimit, tokens_remaining: isNaN(tokensRemaining) ? null : tokensRemaining, tokens_reset: tokensReset || null, requests_limit: isNaN(requestsLimit) ? null : requestsLimit, requests_remaining: isNaN(requestsRemaining) ? null : requestsRemaining, requests_reset: requestsReset || null, cost_per_call_usd: costPerCall ? parseFloat(costPerCall) : null, organization_id: orgId || null, }; result.severity = getSeverity(result); return result; } // Alias used in some internal tooling export const parseClassicHeaders = parseShelleyHeaders; /** * Probe the Shelley proxy by making a minimal API call. * @param {string} providerName * @param {string} baseUrl * @returns {Promise} normalized provider result */ export async function probeShelleyProxy(providerName, baseUrl) { try { const response = await fetch(`${baseUrl}/v1/messages`, { method: 'POST', headers: { 'x-api-key': 'not-needed', 'anthropic-version': '2023-06-01', 'content-type': 'application/json', }, body: JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 1, messages: [{ role: 'user', content: 'Hi' }], }), }); return parseShelleyHeaders(response.headers, response.status); } catch (err) { return { type: 'shelley-proxy', status: 'error', message: err.message, tokens_limit: null, tokens_remaining: null, requests_limit: null, requests_remaining: null, cost_per_call_usd: null, severity: 'unknown', }; } }