@standardagents/openrouter 0.24.5 → 0.24.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -173,11 +173,32 @@ declare class OpenRouterProvider implements LLMProviderInterface {
173
173
  private static modelsCache;
174
174
  private static modelsCacheTime;
175
175
  private static readonly CACHE_TTL;
176
+ /** In-flight catalog download shared by concurrent callers. */
177
+ private static modelsFetchPromise;
178
+ /** Timestamp of the last failed catalog fetch, for negative caching. */
179
+ private static modelsFetchFailedAt;
180
+ private static readonly FAILURE_RETRY_MS;
176
181
  /**
177
182
  * Get capabilities for a specific model.
178
183
  * Fetches from OpenRouter's models API which provides rich metadata.
179
184
  */
180
185
  getModelCapabilities(modelId: string): Promise<ModelCapabilities | null>;
186
+ /**
187
+ * How long a streaming finish chunk may wait on the catalog pricing lookup.
188
+ * The lookup is started when the stream starts, so by finish time it is
189
+ * almost always resolved; the bound guarantees a slow catalog download can
190
+ * never stall the finish chunk into the runtime's stream watchdog.
191
+ */
192
+ private static readonly USAGE_PRICING_FINISH_WAIT_MS;
193
+ private static settleUsagePricing;
194
+ /** Bounded-wait pricing attachment shared by the non-streaming paths. */
195
+ private attachUsagePricing;
196
+ /**
197
+ * Exact unit prices for a model from OpenRouter's live catalog (cached),
198
+ * attached to response usage so request logs can record specific prices.
199
+ * Best-effort: any failure just leaves pricing off the usage payload.
200
+ */
201
+ private getUsagePricing;
181
202
  private fetchModelsWithCache;
182
203
  private mapOpenRouterCapabilities;
183
204
  /**
package/dist/index.js CHANGED
@@ -214,11 +214,17 @@ function transformChatAssistantMessage(msg) {
214
214
  }));
215
215
  }
216
216
  if (msg.reasoningDetails && msg.reasoningDetails.length > 0) {
217
- const details = msg.reasoningDetails.filter((detail) => detail.type !== "encrypted" || typeof detail.data === "string" && detail.data.length > 0).map((detail) => {
218
- if (detail.type === "text") {
217
+ const details = msg.reasoningDetails.filter((detail) => {
218
+ const type = detail.type;
219
+ if (type !== "encrypted" && type !== "reasoning.encrypted") return true;
220
+ return typeof detail.data === "string" && detail.data.length > 0;
221
+ }).map((detail) => {
222
+ const type = detail.type;
223
+ const legacy = detail;
224
+ if (type === "text" || type === "reasoning.text") {
219
225
  return { type: "reasoning.text", text: detail.text, format: detail.format };
220
- } else if (detail.type === "summary") {
221
- return { type: "reasoning.summary", summary: detail.text, format: detail.format };
226
+ } else if (type === "summary" || type === "reasoning.summary") {
227
+ return { type: "reasoning.summary", summary: detail.text ?? legacy.summary, format: detail.format };
222
228
  }
223
229
  return { type: "reasoning.encrypted", data: detail.data, id: detail.id, format: detail.format };
224
230
  });
@@ -1944,6 +1950,25 @@ function getModelIconDataUri(modelId) {
1944
1950
  }
1945
1951
 
1946
1952
  // src/OpenRouterProvider.ts
1953
+ function usagePricingFromCatalogPricing(pricing) {
1954
+ if (!pricing) return void 0;
1955
+ const perMillion = (value) => {
1956
+ if (value === void 0) return void 0;
1957
+ const parsed = Number.parseFloat(value);
1958
+ if (!Number.isFinite(parsed)) return void 0;
1959
+ return Number((parsed * 1e6).toFixed(12));
1960
+ };
1961
+ const inputPerMillion = perMillion(pricing.prompt);
1962
+ const outputPerMillion = perMillion(pricing.completion);
1963
+ if (inputPerMillion === void 0 || outputPerMillion === void 0) {
1964
+ return void 0;
1965
+ }
1966
+ return {
1967
+ inputPerMillion,
1968
+ cachedInputPerMillion: perMillion(pricing.input_cache_read),
1969
+ outputPerMillion
1970
+ };
1971
+ }
1947
1972
  var OpenRouterProvider = class _OpenRouterProvider {
1948
1973
  name = "openrouter";
1949
1974
  specificationVersion = "1";
@@ -2075,6 +2100,11 @@ var OpenRouterProvider = class _OpenRouterProvider {
2075
2100
  static modelsCacheTime = 0;
2076
2101
  static CACHE_TTL = 5 * 60 * 1e3;
2077
2102
  // 5 minutes
2103
+ /** In-flight catalog download shared by concurrent callers. */
2104
+ static modelsFetchPromise = null;
2105
+ /** Timestamp of the last failed catalog fetch, for negative caching. */
2106
+ static modelsFetchFailedAt = 0;
2107
+ static FAILURE_RETRY_MS = 60 * 1e3;
2078
2108
  /**
2079
2109
  * Get capabilities for a specific model.
2080
2110
  * Fetches from OpenRouter's models API which provides rich metadata.
@@ -2092,19 +2122,73 @@ var OpenRouterProvider = class _OpenRouterProvider {
2092
2122
  return null;
2093
2123
  }
2094
2124
  }
2125
+ /**
2126
+ * How long a streaming finish chunk may wait on the catalog pricing lookup.
2127
+ * The lookup is started when the stream starts, so by finish time it is
2128
+ * almost always resolved; the bound guarantees a slow catalog download can
2129
+ * never stall the finish chunk into the runtime's stream watchdog.
2130
+ */
2131
+ static USAGE_PRICING_FINISH_WAIT_MS = 2500;
2132
+ static async settleUsagePricing(pricingPromise) {
2133
+ let timer;
2134
+ const timeout = new Promise((resolve) => {
2135
+ timer = setTimeout(() => resolve(void 0), _OpenRouterProvider.USAGE_PRICING_FINISH_WAIT_MS);
2136
+ });
2137
+ try {
2138
+ return await Promise.race([pricingPromise, timeout]);
2139
+ } finally {
2140
+ if (timer !== void 0) clearTimeout(timer);
2141
+ }
2142
+ }
2143
+ /** Bounded-wait pricing attachment shared by the non-streaming paths. */
2144
+ async attachUsagePricing(result, pricingPromise) {
2145
+ const pricing = await _OpenRouterProvider.settleUsagePricing(pricingPromise);
2146
+ if (pricing) result.usage.pricing = pricing;
2147
+ return result;
2148
+ }
2149
+ /**
2150
+ * Exact unit prices for a model from OpenRouter's live catalog (cached),
2151
+ * attached to response usage so request logs can record specific prices.
2152
+ * Best-effort: any failure just leaves pricing off the usage payload.
2153
+ */
2154
+ async getUsagePricing(modelId) {
2155
+ if (!modelId) return void 0;
2156
+ try {
2157
+ const models = await this.fetchModelsWithCache();
2158
+ const model = models.find((m) => m.id === modelId);
2159
+ return usagePricingFromCatalogPricing(model?.pricing);
2160
+ } catch {
2161
+ return void 0;
2162
+ }
2163
+ }
2095
2164
  async fetchModelsWithCache() {
2096
2165
  const now = Date.now();
2097
2166
  if (_OpenRouterProvider.modelsCache && now - _OpenRouterProvider.modelsCacheTime < _OpenRouterProvider.CACHE_TTL) {
2098
2167
  return _OpenRouterProvider.modelsCache;
2099
2168
  }
2100
- const response = await fetch("https://openrouter.ai/api/v1/models");
2101
- if (!response.ok) {
2102
- throw new Error(`OpenRouter API error: ${response.status}`);
2169
+ if (now - _OpenRouterProvider.modelsFetchFailedAt < _OpenRouterProvider.FAILURE_RETRY_MS) {
2170
+ throw new Error("OpenRouter models fetch recently failed");
2103
2171
  }
2104
- const data = await response.json();
2105
- _OpenRouterProvider.modelsCache = data.data || [];
2106
- _OpenRouterProvider.modelsCacheTime = now;
2107
- return _OpenRouterProvider.modelsCache;
2172
+ if (!_OpenRouterProvider.modelsFetchPromise) {
2173
+ _OpenRouterProvider.modelsFetchPromise = (async () => {
2174
+ try {
2175
+ const response = await fetch("https://openrouter.ai/api/v1/models");
2176
+ if (!response.ok) {
2177
+ throw new Error(`OpenRouter API error: ${response.status}`);
2178
+ }
2179
+ const data = await response.json();
2180
+ _OpenRouterProvider.modelsCache = data.data || [];
2181
+ _OpenRouterProvider.modelsCacheTime = Date.now();
2182
+ return _OpenRouterProvider.modelsCache;
2183
+ } catch (error) {
2184
+ _OpenRouterProvider.modelsFetchFailedAt = Date.now();
2185
+ throw error;
2186
+ } finally {
2187
+ _OpenRouterProvider.modelsFetchPromise = null;
2188
+ }
2189
+ })();
2190
+ }
2191
+ return _OpenRouterProvider.modelsFetchPromise;
2108
2192
  }
2109
2193
  mapOpenRouterCapabilities(model) {
2110
2194
  const capabilities = {
@@ -2189,6 +2273,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2189
2273
  if (this.config.providers && this.config.providers.length > 0) {
2190
2274
  params.provider = { only: this.config.providers };
2191
2275
  }
2276
+ const pricingPromise = this.getUsagePricing(params.model);
2192
2277
  const response = await fetch(`${baseUrl}/chat/completions`, {
2193
2278
  method: "POST",
2194
2279
  headers: {
@@ -2210,7 +2295,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2210
2295
  throw new ProviderError(errorMessage, "invalid_request", response.status);
2211
2296
  }
2212
2297
  const data = await response.json();
2213
- return transformChatResponse(data);
2298
+ return this.attachUsagePricing(transformChatResponse(data), pricingPromise);
2214
2299
  } catch (error) {
2215
2300
  throw this.toProviderError(error);
2216
2301
  }
@@ -2224,6 +2309,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2224
2309
  try {
2225
2310
  const params = buildCreateParams(request);
2226
2311
  this.applyConfiguredProvidersToResponses(params);
2312
+ const pricingPromise = this.getUsagePricing(params.model);
2227
2313
  if (this.hasOpenRouterServerTools(params)) {
2228
2314
  const response2 = await fetch(`${baseUrl}/responses`, {
2229
2315
  method: "POST",
@@ -2241,14 +2327,14 @@ var OpenRouterProvider = class _OpenRouterProvider {
2241
2327
  await this.throwOpenRouterFetchError(response2);
2242
2328
  }
2243
2329
  const data = await response2.json();
2244
- return transformResponse(data);
2330
+ return this.attachUsagePricing(transformResponse(data), pricingPromise);
2245
2331
  }
2246
2332
  const client = await this.getClient();
2247
2333
  const response = await client.beta.responses.send({
2248
2334
  ...this.toSdkResponsesRequest(params),
2249
2335
  stream: false
2250
2336
  });
2251
- return transformResponse(response);
2337
+ return this.attachUsagePricing(transformResponse(response), pricingPromise);
2252
2338
  } catch (error) {
2253
2339
  throw this.toProviderError(error);
2254
2340
  }
@@ -2272,6 +2358,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2272
2358
  if (this.config.providers && this.config.providers.length > 0) {
2273
2359
  params.provider = { only: this.config.providers };
2274
2360
  }
2361
+ const pricingPromise = this.getUsagePricing(params.model);
2275
2362
  const response = await fetch(`${baseUrl}/chat/completions`, {
2276
2363
  method: "POST",
2277
2364
  headers: {
@@ -2311,6 +2398,10 @@ var OpenRouterProvider = class _OpenRouterProvider {
2311
2398
  }
2312
2399
  }
2313
2400
  if (finishChunk) {
2401
+ if (finishChunk.type === "finish") {
2402
+ const pricing = await _OpenRouterProvider.settleUsagePricing(pricingPromise);
2403
+ if (pricing) finishChunk.usage.pricing = pricing;
2404
+ }
2314
2405
  const finishWithMeta = {
2315
2406
  ...finishChunk,
2316
2407
  _asyncMetadata: responseId ? {
@@ -2341,6 +2432,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2341
2432
  try {
2342
2433
  const params = buildCreateParams(request);
2343
2434
  this.applyConfiguredProvidersToResponses(params);
2435
+ const pricingPromise = this.getUsagePricing(params.model);
2344
2436
  const response = await fetch(`${baseUrl}/responses`, {
2345
2437
  method: "POST",
2346
2438
  headers: {
@@ -2379,6 +2471,10 @@ var OpenRouterProvider = class _OpenRouterProvider {
2379
2471
  }
2380
2472
  }
2381
2473
  if (finishChunk) {
2474
+ if (finishChunk.type === "finish") {
2475
+ const pricing = await _OpenRouterProvider.settleUsagePricing(pricingPromise);
2476
+ if (pricing) finishChunk.usage.pricing = pricing;
2477
+ }
2382
2478
  const finishWithMeta = {
2383
2479
  ...finishChunk,
2384
2480
  // Include what's needed to fetch metadata later