"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GatewayClient = exports.asReasonText = void 0;
const logger_1 = require("../utils/logger");
const ConfigManager_1 = require("./ConfigManager");
const proxy_1 = require("../config/proxy");
const log = (0, logger_1.createLogger)('GatewayClient');
const asReasonText = (value) => {
    if (typeof value === 'string') {
        return value;
    }
    if (typeof value === 'number' || typeof value === 'boolean') {
        return String(value);
    }
    if (value && typeof value === 'object') {
        const candidate = value;
        if (typeof candidate.message === 'string') {
            return candidate.message;
        }
        if (typeof candidate.error === 'string') {
            return candidate.error;
        }
        try {
            return JSON.stringify(value);
        }
        catch {
            return '';
        }
    }
    return '';
};
exports.asReasonText = asReasonText;
/**
 * Sends authenticated requests to the Rivet LLM gateway.
 * Handles JWT auth, automatic token refresh on 401, and rate limit errors.
 */
class GatewayClient {
    configManager = (0, ConfigManager_1.getConfigManager)();
    gatewayUrl;
    proxyUrl;
    constructor(gatewayUrl, proxyUrl) {
        this.gatewayUrl = gatewayUrl || proxy_1.GATEWAY_URL;
        this.proxyUrl = proxyUrl || proxy_1.PROXY_URL;
    }
    /** Send a message to the gateway and return the raw text response. */
    async sendMessage(options) {
        const result = await this.postMessages({
            model: options.model,
            max_tokens: options.maxTokens,
            temperature: options.temperature,
            messages: [{ role: 'user', content: options.prompt }],
        }, 0);
        const content = result.content?.[0]?.text;
        if (!content) {
            throw new Error('No response content from LLM');
        }
        return content;
    }
    /**
     * Send a request that forces the model to invoke a single tool, and return
     * the parsed `input` of that tool call. Throws if the model didn't call the
     * tool (which the API guarantees won't happen with `tool_choice: { type:
     * 'tool' }`, but we check anyway).
     */
    async sendToolUse(options) {
        const systemContent = options.cacheSystemPrompt
            ? [{ type: 'text', text: options.system, cache_control: { type: 'ephemeral' } }]
            : options.system;
        const result = await this.postMessages({
            model: options.model,
            max_tokens: options.maxTokens,
            temperature: options.temperature,
            system: systemContent,
            messages: [{ role: 'user', content: options.prompt }],
            tools: [options.tool],
            tool_choice: { type: 'tool', name: options.tool.name },
        }, 0, { enableCaching: options.cacheSystemPrompt });
        const toolUse = result.content
            ?.find((b) => b.type === 'tool_use');
        if (!toolUse || toolUse.input === undefined) {
            throw new Error('Model did not return a tool_use block');
        }
        return toolUse.input;
    }
    async postMessages(body, retryCount, options) {
        const authToken = this.configManager.getAuthToken();
        if (!authToken) {
            throw new Error('Not authenticated. Please run `rivet login` to authenticate.');
        }
        const headers = {
            Authorization: `Bearer ${authToken}`,
            'Content-Type': 'application/json',
            'anthropic-version': '2023-06-01',
        };
        if (options?.enableCaching) {
            headers['anthropic-beta'] = 'prompt-caching-2024-07-31';
        }
        const response = await fetch(`${this.gatewayUrl}/v1/messages`, {
            method: 'POST',
            headers,
            body: JSON.stringify({ ...body, service_tier: 'auto' }),
        });
        // Handle 401 — could be token expiry or a spend limit rejection from the gateway
        if (response.status === 401 && retryCount === 0) {
            const errBody = await response.json().catch(() => ({}));
            const reason = (0, exports.asReasonText)(errBody.message ?? errBody.error ?? errBody);
            if (reason.includes('SPEND_LIMIT_EXCEEDED:')) {
                throw new Error(reason);
            }
            log.warn('Token expired, attempting to refresh...');
            const isRefreshed = await this.refreshAccessToken();
            if (isRefreshed) {
                return this.postMessages(body, retryCount + 1, options);
            }
            throw new Error('Authentication expired. Please run `rivet logout` and `rivet login` to re-authenticate.');
        }
        if (response.status === 429) {
            const error = await response.json().catch(() => ({}));
            const reason = (0, exports.asReasonText)(error.message ?? error.error ?? error);
            if (reason.includes('SPEND_LIMIT_EXCEEDED:')) {
                throw new Error(reason);
            }
            throw new Error(reason || 'Rate limit exceeded. Please try again shortly.');
        }
        if (!response.ok) {
            const errorText = await response
                .text()
                .catch(() => 'Unable to read error');
            throw new Error(`Gateway request failed (${response.status}): ${errorText}`);
        }
        return response.json();
    }
    /** Refresh the access token using the stored refresh token. */
    async refreshAccessToken() {
        const refreshToken = this.configManager.getRefreshToken();
        if (!refreshToken) {
            log.warn('No refresh token available');
            return false;
        }
        try {
            log.info('Refreshing access token...');
            const response = await fetch(`${this.proxyUrl}/api/auth/refresh`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ refreshToken }),
            });
            if (!response.ok) {
                log.warn('Token refresh failed');
                return false;
            }
            const result = await response.json();
            if (result.success && result.token) {
                const email = this.configManager.getEmail() || '';
                this.configManager.setAuth(result.token, email, result.refreshToken);
                log.info('Access token refreshed successfully');
                return true;
            }
            return false;
        }
        catch (error) {
            log.warn('Error refreshing token:', error);
            return false;
        }
    }
}
exports.GatewayClient = GatewayClient;
//# sourceMappingURL=GatewayClient.js.map