"use strict"; /** * Suno API HTTP client with rate limiting and polling helpers. * * Rate limit: 20 requests per 10 seconds (sunoapi.org). * Proxy support via SUNO_HTTP_PROXY / HTTPS_PROXY / HTTP_PROXY env vars. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SunoClient = void 0; const undici_1 = require("undici"); function getProxyUrl() { return (process.env.SUNO_HTTP_PROXY || process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy); } class SunoClient { baseUrl = 'https://api.sunoapi.org/api/v1'; apiKey; maxRequests = 20; windowMs = 10_000; proxyAgent; requestTimestamps = []; constructor(apiKey) { this.apiKey = apiKey; const proxyUrl = getProxyUrl(); if (proxyUrl) { this.proxyAgent = new undici_1.ProxyAgent(proxyUrl); } } /** * Enforce rate limit: max 20 requests per 10 seconds. */ async throttle() { const now = Date.now(); this.requestTimestamps = this.requestTimestamps.filter((ts) => now - ts < this.windowMs); if (this.requestTimestamps.length >= this.maxRequests) { const oldest = this.requestTimestamps[0]; const wait = this.windowMs - (now - oldest); if (wait > 0) { await new Promise((resolve) => setTimeout(resolve, wait)); return this.throttle(); } } this.requestTimestamps.push(now); } async request(method, path, body) { await this.throttle(); const url = path.startsWith('http') ? path : `${this.baseUrl}${path}`; const headers = { Authorization: `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', }; const fetchOptions = { method, headers, body: body ? JSON.stringify(body) : undefined, }; if (this.proxyAgent) { fetchOptions.dispatcher = this.proxyAgent; } const response = await fetch(url, fetchOptions); const text = await response.text(); let json; try { json = JSON.parse(text); } catch { throw new Error(`Invalid JSON response from Suno API: ${text}`); } if (json.code !== 200) { throw new Error(`Suno API error ${json.code}: ${json.msg || 'unknown error'}`); } return json; } get(path) { return this.request('GET', path); } post(path, body) { return this.request('POST', path, body); } /** * Poll task status until terminal state or timeout. */ async pollTaskStatus(taskId, options = {}) { const intervalMs = options.intervalMs ?? 15_000; const maxAttempts = options.maxAttempts ?? 60; const statusPath = options.statusPath ?? `/generate/record-info?taskId=${encodeURIComponent(taskId)}`; for (let attempt = 1; attempt <= maxAttempts; attempt++) { const result = await this.get(statusPath); const status = result.data.status; if (status === 'SUCCESS' || status === 'FAILED' || status === 'CREATE_TASK_FAILED' || status === 'GENERATE_AUDIO_FAILED' || status === 'CALLBACK_EXCEPTION' || status === 'SENSITIVE_WORD_ERROR') { return result.data; } if (attempt < maxAttempts) { await new Promise((resolve) => setTimeout(resolve, intervalMs)); } } throw new Error(`Polling timeout for task ${taskId} after ${maxAttempts} attempts`); } } exports.SunoClient = SunoClient; //# sourceMappingURL=client.js.map