@standardagents/openrouter 0.24.4 → 0.24.6

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
@@ -1944,6 +1944,25 @@ function getModelIconDataUri(modelId) {
1944
1944
  }
1945
1945
 
1946
1946
  // src/OpenRouterProvider.ts
1947
+ function usagePricingFromCatalogPricing(pricing) {
1948
+ if (!pricing) return void 0;
1949
+ const perMillion = (value) => {
1950
+ if (value === void 0) return void 0;
1951
+ const parsed = Number.parseFloat(value);
1952
+ if (!Number.isFinite(parsed)) return void 0;
1953
+ return Number((parsed * 1e6).toFixed(12));
1954
+ };
1955
+ const inputPerMillion = perMillion(pricing.prompt);
1956
+ const outputPerMillion = perMillion(pricing.completion);
1957
+ if (inputPerMillion === void 0 || outputPerMillion === void 0) {
1958
+ return void 0;
1959
+ }
1960
+ return {
1961
+ inputPerMillion,
1962
+ cachedInputPerMillion: perMillion(pricing.input_cache_read),
1963
+ outputPerMillion
1964
+ };
1965
+ }
1947
1966
  var OpenRouterProvider = class _OpenRouterProvider {
1948
1967
  name = "openrouter";
1949
1968
  specificationVersion = "1";
@@ -2075,6 +2094,11 @@ var OpenRouterProvider = class _OpenRouterProvider {
2075
2094
  static modelsCacheTime = 0;
2076
2095
  static CACHE_TTL = 5 * 60 * 1e3;
2077
2096
  // 5 minutes
2097
+ /** In-flight catalog download shared by concurrent callers. */
2098
+ static modelsFetchPromise = null;
2099
+ /** Timestamp of the last failed catalog fetch, for negative caching. */
2100
+ static modelsFetchFailedAt = 0;
2101
+ static FAILURE_RETRY_MS = 60 * 1e3;
2078
2102
  /**
2079
2103
  * Get capabilities for a specific model.
2080
2104
  * Fetches from OpenRouter's models API which provides rich metadata.
@@ -2092,19 +2116,73 @@ var OpenRouterProvider = class _OpenRouterProvider {
2092
2116
  return null;
2093
2117
  }
2094
2118
  }
2119
+ /**
2120
+ * How long a streaming finish chunk may wait on the catalog pricing lookup.
2121
+ * The lookup is started when the stream starts, so by finish time it is
2122
+ * almost always resolved; the bound guarantees a slow catalog download can
2123
+ * never stall the finish chunk into the runtime's stream watchdog.
2124
+ */
2125
+ static USAGE_PRICING_FINISH_WAIT_MS = 2500;
2126
+ static async settleUsagePricing(pricingPromise) {
2127
+ let timer;
2128
+ const timeout = new Promise((resolve) => {
2129
+ timer = setTimeout(() => resolve(void 0), _OpenRouterProvider.USAGE_PRICING_FINISH_WAIT_MS);
2130
+ });
2131
+ try {
2132
+ return await Promise.race([pricingPromise, timeout]);
2133
+ } finally {
2134
+ if (timer !== void 0) clearTimeout(timer);
2135
+ }
2136
+ }
2137
+ /** Bounded-wait pricing attachment shared by the non-streaming paths. */
2138
+ async attachUsagePricing(result, pricingPromise) {
2139
+ const pricing = await _OpenRouterProvider.settleUsagePricing(pricingPromise);
2140
+ if (pricing) result.usage.pricing = pricing;
2141
+ return result;
2142
+ }
2143
+ /**
2144
+ * Exact unit prices for a model from OpenRouter's live catalog (cached),
2145
+ * attached to response usage so request logs can record specific prices.
2146
+ * Best-effort: any failure just leaves pricing off the usage payload.
2147
+ */
2148
+ async getUsagePricing(modelId) {
2149
+ if (!modelId) return void 0;
2150
+ try {
2151
+ const models = await this.fetchModelsWithCache();
2152
+ const model = models.find((m) => m.id === modelId);
2153
+ return usagePricingFromCatalogPricing(model?.pricing);
2154
+ } catch {
2155
+ return void 0;
2156
+ }
2157
+ }
2095
2158
  async fetchModelsWithCache() {
2096
2159
  const now = Date.now();
2097
2160
  if (_OpenRouterProvider.modelsCache && now - _OpenRouterProvider.modelsCacheTime < _OpenRouterProvider.CACHE_TTL) {
2098
2161
  return _OpenRouterProvider.modelsCache;
2099
2162
  }
2100
- const response = await fetch("https://openrouter.ai/api/v1/models");
2101
- if (!response.ok) {
2102
- throw new Error(`OpenRouter API error: ${response.status}`);
2163
+ if (now - _OpenRouterProvider.modelsFetchFailedAt < _OpenRouterProvider.FAILURE_RETRY_MS) {
2164
+ throw new Error("OpenRouter models fetch recently failed");
2103
2165
  }
2104
- const data = await response.json();
2105
- _OpenRouterProvider.modelsCache = data.data || [];
2106
- _OpenRouterProvider.modelsCacheTime = now;
2107
- return _OpenRouterProvider.modelsCache;
2166
+ if (!_OpenRouterProvider.modelsFetchPromise) {
2167
+ _OpenRouterProvider.modelsFetchPromise = (async () => {
2168
+ try {
2169
+ const response = await fetch("https://openrouter.ai/api/v1/models");
2170
+ if (!response.ok) {
2171
+ throw new Error(`OpenRouter API error: ${response.status}`);
2172
+ }
2173
+ const data = await response.json();
2174
+ _OpenRouterProvider.modelsCache = data.data || [];
2175
+ _OpenRouterProvider.modelsCacheTime = Date.now();
2176
+ return _OpenRouterProvider.modelsCache;
2177
+ } catch (error) {
2178
+ _OpenRouterProvider.modelsFetchFailedAt = Date.now();
2179
+ throw error;
2180
+ } finally {
2181
+ _OpenRouterProvider.modelsFetchPromise = null;
2182
+ }
2183
+ })();
2184
+ }
2185
+ return _OpenRouterProvider.modelsFetchPromise;
2108
2186
  }
2109
2187
  mapOpenRouterCapabilities(model) {
2110
2188
  const capabilities = {
@@ -2189,6 +2267,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2189
2267
  if (this.config.providers && this.config.providers.length > 0) {
2190
2268
  params.provider = { only: this.config.providers };
2191
2269
  }
2270
+ const pricingPromise = this.getUsagePricing(params.model);
2192
2271
  const response = await fetch(`${baseUrl}/chat/completions`, {
2193
2272
  method: "POST",
2194
2273
  headers: {
@@ -2210,7 +2289,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2210
2289
  throw new ProviderError(errorMessage, "invalid_request", response.status);
2211
2290
  }
2212
2291
  const data = await response.json();
2213
- return transformChatResponse(data);
2292
+ return this.attachUsagePricing(transformChatResponse(data), pricingPromise);
2214
2293
  } catch (error) {
2215
2294
  throw this.toProviderError(error);
2216
2295
  }
@@ -2224,6 +2303,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2224
2303
  try {
2225
2304
  const params = buildCreateParams(request);
2226
2305
  this.applyConfiguredProvidersToResponses(params);
2306
+ const pricingPromise = this.getUsagePricing(params.model);
2227
2307
  if (this.hasOpenRouterServerTools(params)) {
2228
2308
  const response2 = await fetch(`${baseUrl}/responses`, {
2229
2309
  method: "POST",
@@ -2241,14 +2321,14 @@ var OpenRouterProvider = class _OpenRouterProvider {
2241
2321
  await this.throwOpenRouterFetchError(response2);
2242
2322
  }
2243
2323
  const data = await response2.json();
2244
- return transformResponse(data);
2324
+ return this.attachUsagePricing(transformResponse(data), pricingPromise);
2245
2325
  }
2246
2326
  const client = await this.getClient();
2247
2327
  const response = await client.beta.responses.send({
2248
2328
  ...this.toSdkResponsesRequest(params),
2249
2329
  stream: false
2250
2330
  });
2251
- return transformResponse(response);
2331
+ return this.attachUsagePricing(transformResponse(response), pricingPromise);
2252
2332
  } catch (error) {
2253
2333
  throw this.toProviderError(error);
2254
2334
  }
@@ -2272,6 +2352,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2272
2352
  if (this.config.providers && this.config.providers.length > 0) {
2273
2353
  params.provider = { only: this.config.providers };
2274
2354
  }
2355
+ const pricingPromise = this.getUsagePricing(params.model);
2275
2356
  const response = await fetch(`${baseUrl}/chat/completions`, {
2276
2357
  method: "POST",
2277
2358
  headers: {
@@ -2311,6 +2392,10 @@ var OpenRouterProvider = class _OpenRouterProvider {
2311
2392
  }
2312
2393
  }
2313
2394
  if (finishChunk) {
2395
+ if (finishChunk.type === "finish") {
2396
+ const pricing = await _OpenRouterProvider.settleUsagePricing(pricingPromise);
2397
+ if (pricing) finishChunk.usage.pricing = pricing;
2398
+ }
2314
2399
  const finishWithMeta = {
2315
2400
  ...finishChunk,
2316
2401
  _asyncMetadata: responseId ? {
@@ -2341,6 +2426,7 @@ var OpenRouterProvider = class _OpenRouterProvider {
2341
2426
  try {
2342
2427
  const params = buildCreateParams(request);
2343
2428
  this.applyConfiguredProvidersToResponses(params);
2429
+ const pricingPromise = this.getUsagePricing(params.model);
2344
2430
  const response = await fetch(`${baseUrl}/responses`, {
2345
2431
  method: "POST",
2346
2432
  headers: {
@@ -2379,6 +2465,10 @@ var OpenRouterProvider = class _OpenRouterProvider {
2379
2465
  }
2380
2466
  }
2381
2467
  if (finishChunk) {
2468
+ if (finishChunk.type === "finish") {
2469
+ const pricing = await _OpenRouterProvider.settleUsagePricing(pricingPromise);
2470
+ if (pricing) finishChunk.usage.pricing = pricing;
2471
+ }
2382
2472
  const finishWithMeta = {
2383
2473
  ...finishChunk,
2384
2474
  // Include what's needed to fetch metadata later