@catgirls/openrouter
Version:
Nyaa~! A TypeScript client for OpenRouter that's both kawaii and powerful! 😻
1 lines • 16.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/lib/error-adapter.ts","../src/lib/http-client.ts","../src/lib/stream-handler.ts","../src/lib/client.ts"],"names":["OpenRouterErrorAdapter","error","status","data","code","message","metadata","baseMessage","HttpClient","baseURL","headers","axios","url","config","StreamHandler","stream","eventEmitter","EventEmitter","buffer","chunk","lines","line","parsed","choice","content","role","tool_calls","OpenRouterClient","apiKey","defaultConfig","httpClient","streamHandler","options","response","generationId"],"mappings":"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","file":"index.mjs","sourcesContent":["import { AxiosError } from \"axios\";\n\nexport interface ErrorResponse {\n error: {\n code: number;\n message: string;\n metadata?: ModerationErrorMetadata | ProviderErrorMetadata;\n };\n}\n\ninterface ModerationErrorMetadata {\n reasons: string[];\n flagged_input: string;\n provider_name: string;\n model_slug: string;\n}\n\ninterface ProviderErrorMetadata {\n provider_name: string;\n raw: unknown;\n}\n\nexport class OpenRouterErrorAdapter {\n private static readonly ERROR_MESSAGES = {\n 400: \"Bad Request: Invalid or missing parameters, or CORS issue\",\n 401: \"Invalid credentials: OAuth session expired or disabled/invalid API key\",\n 402: \"Insufficient credits: Add more credits and retry the request\",\n 403: \"Content moderation: Your input was flagged\",\n 408: \"Request timeout: Your request took too long to process\",\n 429: \"Rate limited: You are sending too many requests\",\n 502: \"Provider error: Model is down or invalid response received\",\n 503: \"No provider: No available model provider meets your routing requirements\",\n } as const;\n\n static handleError(error: unknown): never {\n if (this.isAxiosError(error)) {\n throw this.handleAxiosError(error);\n }\n\n throw new Error(\n `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n private static isAxiosError(\n error: unknown,\n ): error is AxiosError<ErrorResponse> {\n return error instanceof Error && \"isAxiosError\" in error;\n }\n\n private static handleAxiosError(error: AxiosError<ErrorResponse>): Error {\n if (error.response) {\n const { status, data } = error.response;\n\n if (this.isOpenRouterError(data)) {\n return this.createDetailedError(data.error);\n }\n\n return new Error(\n `HTTP ${status}: ${this.ERROR_MESSAGES[status as keyof typeof this.ERROR_MESSAGES] || \"Unknown error\"}`,\n );\n }\n\n if (error.request) {\n return new Error(\"No response received from OpenRouter API\");\n }\n\n return new Error(`Failed to make request: ${error.message}`);\n }\n\n private static isOpenRouterError(data: unknown): data is ErrorResponse {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"error\" in data &&\n typeof (data as ErrorResponse).error.code === \"number\" &&\n typeof (data as ErrorResponse).error.message === \"string\"\n );\n }\n\n private static createDetailedError({\n code,\n message,\n metadata,\n }: ErrorResponse[\"error\"]): Error {\n const baseMessage = `${this.ERROR_MESSAGES[code as keyof typeof this.ERROR_MESSAGES] || \"Unknown error\"}: ${message}`;\n\n if (!metadata) {\n return new Error(baseMessage);\n }\n\n if (this.isModerationError(metadata)) {\n return new Error(\n `${baseMessage}\\nFlagged by ${metadata.provider_name} for: ${metadata.reasons.join(\", \")}\\n` +\n `Flagged content: \"${metadata.flagged_input}\"`,\n );\n }\n\n if (this.isProviderError(metadata)) {\n return new Error(\n `${baseMessage}\\nProvider ${metadata.provider_name} error: ${JSON.stringify(metadata.raw)}`,\n );\n }\n\n return new Error(baseMessage);\n }\n\n private static isModerationError(\n metadata: unknown,\n ): metadata is ModerationErrorMetadata {\n return (\n typeof metadata === \"object\" &&\n metadata !== null &&\n \"reasons\" in metadata &&\n \"flagged_input\" in metadata &&\n \"provider_name\" in metadata &&\n \"model_slug\" in metadata\n );\n }\n\n private static isProviderError(\n metadata: unknown,\n ): metadata is ProviderErrorMetadata {\n return (\n typeof metadata === \"object\" &&\n metadata !== null &&\n \"provider_name\" in metadata &&\n \"raw\" in metadata\n );\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport axios, { AxiosInstance } from \"axios\";\nimport { IHttpClient } from \"../types\";\n\nexport class HttpClient implements IHttpClient {\n private client: AxiosInstance;\n\n constructor(baseURL: string, headers: Record<string, string>) {\n this.client = axios.create({\n baseURL,\n headers,\n });\n }\n\n async post(url: string, data: any, config?: any) {\n return this.client.post(url, data, config);\n }\n\n async get(url: string) {\n return this.client.get(url);\n }\n}\n","import { EventEmitter } from \"events\";\nimport type * as Event from \"events\";\nimport { IStreamHandler } from \"../types\";\nimport { OpenRouterErrorAdapter } from \"./error-adapter\";\n\nexport class StreamHandler implements IStreamHandler {\n handleStream(stream: Event): EventEmitter {\n const eventEmitter = new EventEmitter();\n let buffer = \"\";\n\n stream.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (line.trim() === \"\" || line.startsWith(\":\")) {\n // Handle SSE comments (OpenRouter processing messages)\n if (line.includes(\"OPENROUTER PROCESSING\")) {\n eventEmitter.emit(\"processing\");\n }\n continue;\n }\n\n try {\n const message = line.replace(/^data: /, \"\");\n if (message === \"[DONE]\") {\n eventEmitter.emit(\"done\");\n continue;\n }\n\n const parsed = JSON.parse(message);\n\n // Emit the full chunk for raw access\n eventEmitter.emit(\"chunk\", parsed);\n\n // Handle the standardized response format\n if (parsed.choices?.[0]) {\n const choice = parsed.choices[0];\n\n // For streaming responses, we'll get delta updates\n if (choice.delta) {\n // Extract the delta components\n const { content, role, tool_calls } = choice.delta;\n\n // Emit specific events for different delta types\n if (content) {\n // Emit both for backwards compatibility\n eventEmitter.emit(\"token\", content);\n eventEmitter.emit(\"content\", content);\n }\n if (role) {\n eventEmitter.emit(\"role\", role);\n }\n if (tool_calls) {\n eventEmitter.emit(\"tool_calls\", tool_calls);\n }\n }\n\n // Handle finish reason if present\n if (choice.finish_reason) {\n eventEmitter.emit(\"finish\", choice.finish_reason);\n }\n }\n\n // Handle usage statistics in the final message\n if (parsed.usage) {\n eventEmitter.emit(\"usage\", parsed.usage);\n }\n } catch (error) {\n eventEmitter.emit(\"error\", OpenRouterErrorAdapter.handleError(error));\n }\n }\n });\n\n stream.on(\"end\", () => eventEmitter.emit(\"done\"));\n stream.on(\"error\", (error: Error) => {\n eventEmitter.emit(\"error\", OpenRouterErrorAdapter.handleError(error));\n });\n\n return eventEmitter;\n }\n}\n","import { RouterModel } from \"../models\";\nimport {\n ChatCompletionResult,\n GenerationStats,\n IHttpClient,\n IStreamHandler,\n Message,\n QueryResponseModel,\n Request,\n} from \"../types\";\nimport { OpenRouterErrorAdapter } from \"./error-adapter\";\nimport { HttpClient } from \"./http-client\";\nimport { StreamHandler } from \"./stream-handler\";\n\ntype BaseRequest = Omit<Request, \"messages\" | \"prompt\">;\n\ntype MessagesRequest = BaseRequest & {\n messages: Message[];\n prompt?: never;\n};\n\ntype PromptRequest = BaseRequest & {\n prompt: string;\n messages?: never;\n};\n\ntype CompletionRequest = MessagesRequest | PromptRequest;\n\nexport class OpenRouterClient {\n private readonly baseURL = \"https://openrouter.ai/api/v1\";\n private httpClient: IHttpClient;\n private streamHandler: IStreamHandler;\n\n constructor(\n private readonly apiKey: string,\n private readonly defaultConfig: {\n siteUrl?: string;\n siteName?: string;\n model?: RouterModel;\n } = {},\n httpClient?: IHttpClient,\n streamHandler?: IStreamHandler,\n ) {\n this.httpClient =\n httpClient || new HttpClient(this.baseURL, this.getDefaultHeaders());\n this.streamHandler = streamHandler || new StreamHandler();\n }\n\n private getDefaultHeaders() {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n };\n\n if (this.defaultConfig.siteUrl) {\n headers[\"HTTP-Referer\"] = this.defaultConfig.siteUrl;\n }\n if (this.defaultConfig.siteName) {\n headers[\"X-Title\"] = this.defaultConfig.siteName;\n }\n\n return headers;\n }\n\n async chatCompletion<\n TStreaming extends boolean = false,\n TRequest extends CompletionRequest = CompletionRequest,\n >(\n options: TRequest & { stream?: TStreaming },\n ): Promise<ChatCompletionResult<TStreaming, TRequest>> {\n try {\n const data = {\n ...options,\n model: options.model || this.defaultConfig.model,\n stream: options.stream,\n };\n\n if (options.stream) {\n const response = await this.httpClient.post(\"/chat/completions\", data, {\n responseType: \"stream\",\n signal: options.signal,\n });\n // We know it's an EventEmitter if streaming is true\n return this.streamHandler.handleStream(\n response.data,\n ) as ChatCompletionResult<TStreaming, TRequest>;\n }\n\n const response = await this.httpClient.post(\"/chat/completions\", data);\n // TypeScript will infer the correct type based on the request shape\n return response.data as ChatCompletionResult<TStreaming, TRequest>;\n } catch (error) {\n throw OpenRouterErrorAdapter.handleError(error);\n }\n }\n\n async getGenerationStats(generationId: string): Promise<GenerationStats> {\n try {\n const response = await this.httpClient.get(\n `/generation?id=${generationId}`,\n );\n return response.data.data; // i shit you not\n } catch (error) {\n throw OpenRouterErrorAdapter.handleError(error);\n }\n }\n\n async getModels(): Promise<QueryResponseModel[]> {\n try {\n const response = await this.httpClient.get(\"/models\");\n return response.data.data; // yep\n } catch (error) {\n throw OpenRouterErrorAdapter.handleError(error);\n }\n }\n}\n"]}