| 139 | } |
| 140 | |
| 141 | export class OpenRouterHandler extends BaseProvider implements SingleCompletionHandler { |
| 142 | protected options: ApiHandlerOptions |
| 143 | private client: OpenAI |
| 144 | protected models: ModelRecord = {} |
| 145 | protected endpoints: ModelRecord = {} |
| 146 | private readonly providerName = "OpenRouter" |
| 147 | private currentReasoningDetails: any[] = [] |
| 148 | |
| 149 | constructor(options: ApiHandlerOptions) { |
| 150 | super() |
| 151 | this.options = options |
| 152 | |
| 153 | const baseURL = this.options.openRouterBaseUrl || "https://openrouter.ai/api/v1" |
| 154 | const apiKey = this.options.openRouterApiKey ?? "not-provided" |
| 155 | |
| 156 | this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: DEFAULT_HEADERS, timeout: this.timeoutMs }) |
| 157 | |
| 158 | // Load models asynchronously to populate cache before getModel() is called |
| 159 | this.loadDynamicModels().catch((error) => { |
| 160 | console.error("[OpenRouterHandler] Failed to load dynamic models:", error) |
| 161 | }) |
| 162 | } |
| 163 | |
| 164 | private async loadDynamicModels(): Promise<void> { |
| 165 | try { |
| 166 | const [models, endpoints] = await Promise.all([ |
| 167 | getModels({ provider: "openrouter" }), |
| 168 | getModelEndpoints({ |
| 169 | router: "openrouter", |
| 170 | modelId: this.options.openRouterModelId, |
| 171 | endpoint: this.options.openRouterSpecificProvider, |
| 172 | }), |
| 173 | ]) |
| 174 | |
| 175 | this.models = models |
| 176 | this.endpoints = endpoints |
| 177 | } catch (error) { |
| 178 | console.error("[OpenRouterHandler] Error loading dynamic models:", { |
| 179 | error: error instanceof Error ? error.message : String(error), |
| 180 | stack: error instanceof Error ? error.stack : undefined, |
| 181 | }) |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | getReasoningDetails(): any[] | undefined { |
| 186 | return this.currentReasoningDetails.length > 0 ? this.currentReasoningDetails : undefined |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Handle OpenRouter streaming error response and report to telemetry. |
| 191 | * OpenRouter may include metadata.raw with the actual upstream provider error. |
| 192 | * @param error The error object (not wrapped - receives the error directly) |
| 193 | */ |
| 194 | private handleStreamingError(error: OpenRouterError, modelId: string, operation: string): never { |
| 195 | const rawString = error?.metadata?.raw |
| 196 | const parsedError = extractErrorFromMetadataRaw(rawString) |
| 197 | const rawErrorMessage = parsedError || error?.message || "Unknown error" |
| 198 |
nothing calls this directly
no outgoing calls
no test coverage detected