@tokenrouter/sdk
Version:
TypeScript/JavaScript SDK for TokenRouter - Intelligent LLM Routing API
304 lines • 11.2 kB
JavaScript
/**
* TokenRouter SDK Client
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenRouterAsyncClient = exports.TokenRouterClient = void 0;
const axios_1 = __importDefault(require("axios"));
const errors_1 = require("./errors");
const version_1 = require("./version");
class TokenRouterClient {
constructor(options = {}) {
// Removed utilities per Request_1
/**
* OpenAI-compatible chat namespace
*/
this.chat = {
completions: {
create: (request) => this.createChatCompletion(request),
},
};
this.completions = {
create: async (request) => {
const payload = {
model: request.model || 'auto',
// prompt: request.prompt,
mode: request.mode,
model_preferences: request.model_preferences,
...request,
};
if (payload.stream)
return this.streamCompletions(payload);
return this.request('POST', '/v1/completions', payload);
},
};
this.apiKey = options.apiKey || process.env.TOKENROUTER_API_KEY || '';
if (!this.apiKey) {
throw new errors_1.AuthenticationError('API key is required. Set TOKENROUTER_API_KEY environment variable or pass apiKey parameter.');
}
this.baseUrl = (options.baseUrl ||
process.env.TOKENROUTER_BASE_URL ||
'https://api.tokenrouter.io').replace(/\/$/, '');
this.timeout = options.timeout || 60000;
this.maxRetries = options.maxRetries || 3;
const headers = {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'User-Agent': `tokenrouter-node/${version_1.VERSION}`,
...options.headers,
};
this.client = axios_1.default.create({
baseURL: this.baseUrl,
timeout: this.timeout,
headers,
});
this.setupInterceptors();
}
setupInterceptors() {
this.client.interceptors.response.use((response) => response, async (error) => {
if (error.response) {
this.handleResponseError(error.response);
}
else if (error.request) {
throw new errors_1.APIConnectionError('Connection failed: ' + error.message);
}
else {
throw new errors_1.TokenRouterError('Request failed: ' + error.message);
}
});
}
/**
* Convert axios headers to Record<string, string>
*/
convertHeaders(headers) {
if (!headers)
return undefined;
const result = {};
for (const [key, value] of Object.entries(headers)) {
if (typeof value === 'string') {
result[key] = value;
}
else if (value != null) {
result[key] = String(value);
}
}
return result;
}
handleResponseError(response) {
const status = response.status;
const data = response.data;
const message = data?.detail || data?.error || response.statusText;
const headers = this.convertHeaders(response.headers);
switch (status) {
case 401:
throw new errors_1.AuthenticationError(message, status, data, headers);
case 429:
const retryAfter = response.headers['retry-after'];
throw new errors_1.RateLimitError(message, status, data, headers, retryAfter ? parseInt(retryAfter) : undefined);
case 400:
throw new errors_1.InvalidRequestError(message, status, data, headers);
case 403:
if (message.toLowerCase().includes('quota')) {
throw new errors_1.QuotaExceededError(message, status, data, headers);
}
throw new errors_1.AuthenticationError(message, status, data, headers);
default:
if (status >= 500) {
throw new errors_1.APIStatusError(message, status, data, headers);
}
throw new errors_1.TokenRouterError(message, status, data, headers);
}
}
async request(method, path, data, params, retryCount = 0) {
try {
const response = await this.client.request({
method,
url: path,
data,
params,
});
return response.data;
}
catch (error) {
if (retryCount < this.maxRetries &&
error instanceof errors_1.APIStatusError &&
error.statusCode &&
error.statusCode >= 500) {
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
return this.request(method, path, data, params, retryCount + 1);
}
throw error;
}
}
/** Create chat completion (POST /v1/chat/completions) */
async createChatCompletion(request) {
const payload = { ...request, model: request.model || 'auto' };
if (payload.stream)
return this.streamChatCompletion(payload);
return this.request('POST', '/v1/chat/completions', payload);
}
/** Native TokenRouter create (POST /route) */
async create(request) {
const payload = { ...request, model: request.model || 'auto' };
if (payload.stream)
return this.streamCreate(payload);
return this.request('POST', '/route', payload);
}
/**
* Stream a chat completion
*/
async *streamChatCompletion(request) {
const url = `${this.baseUrl}/v1/chat/completions`;
const headers = {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
};
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ ...request, stream: true }),
});
if (!response.ok) {
const error = await response.text();
throw new errors_1.APIStatusError(error, response.status);
}
const reader = response.body?.getReader();
if (!reader) {
throw new errors_1.TokenRouterError('Failed to get response reader');
}
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
return;
}
try {
const chunk = JSON.parse(data);
yield chunk;
}
catch (e) {
// Ignore parsing errors
}
}
}
}
}
/**
* Stream native create (/route)
*/
async *streamCreate(request) {
const url = `${this.baseUrl}/route`;
const headers = {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
};
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ ...request, stream: true }),
});
if (!response.ok) {
const error = await response.text();
throw new errors_1.APIStatusError(error, response.status);
}
const reader = response.body?.getReader();
if (!reader) {
throw new errors_1.TokenRouterError('Failed to get response reader');
}
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
return;
}
try {
const chunk = JSON.parse(data);
yield chunk;
}
catch (e) {
// Ignore parsing errors
}
}
}
}
}
// Removed shorthand helpers per Request_1
/**
* OpenAI legacy completions: client.completions.create
*/
async *streamCompletions(payload) {
const url = `${this.baseUrl}/v1/completions`;
const headers = {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
};
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ ...payload, stream: true }),
});
if (!response.ok) {
const error = await response.text();
throw new errors_1.APIStatusError(error, response.status);
}
const reader = response.body?.getReader();
if (!reader) {
throw new errors_1.TokenRouterError('Failed to get response reader');
}
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]')
return;
try {
yield JSON.parse(data);
}
catch (e) {
// ignore
}
}
}
}
}
}
exports.TokenRouterClient = TokenRouterClient;
/**
* Async client (alias for compatibility)
*/
class TokenRouterAsyncClient extends TokenRouterClient {
}
exports.TokenRouterAsyncClient = TokenRouterAsyncClient;
//# sourceMappingURL=client.js.map
;