@absolutejs/ai 0.0.51 → 0.0.53

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/README.md CHANGED
@@ -111,8 +111,29 @@ const provider = openrouter({
111
111
  The adapter intentionally has no built-in geopolitical model list. Omitting
112
112
  `allowedModels` exposes the full OpenRouter catalog; applications that need a
113
113
  restricted catalog can define their own `allowedModels` and `allowedProviders`
114
- policy. Avoid `openrouter/auto` under a strict policy unless it is deliberately
115
- allowed.
114
+ policy. Auto Router is fully supported with `openrouter/auto` (or
115
+ `openrouter/auto-beta`), a sticky `sessionId`, and its typed plugin controls:
116
+
117
+ ```ts
118
+ const provider = openrouter({
119
+ apiKey: process.env.OPENROUTER_API_KEY,
120
+ // Omit this for unrestricted access to every OpenRouter model.
121
+ allowedModels: ["openrouter/auto", "anthropic/*", "openai/*"],
122
+ requestOptions: {
123
+ sessionId: conversationId,
124
+ plugins: [
125
+ {
126
+ id: "auto-router",
127
+ cost_tier: "low",
128
+ allowed_models: ["anthropic/*", "openai/*"],
129
+ },
130
+ ],
131
+ },
132
+ });
133
+ ```
134
+
135
+ Under a strict policy, Auto Router's `allowed_models` is checked locally along
136
+ with fallback, Fusion, advisor, subagent, and other indirectly selected models.
116
137
 
117
138
  Provider usage callbacks include OpenRouter's reported `costCredits`,
118
139
  `upstreamInferenceCostCredits`, cache-read/write token counts, and reasoning
@@ -149,7 +170,10 @@ message transforms, native reasoning controls, prompt and response caching,
149
170
  text-plus-audio output, verbosity, user attribution, and an `extraBody` escape
150
171
  hatch for new OpenRouter parameters. The escape hatch cannot replace models,
151
172
  fallbacks, providers, presets, messages, plugins, or tools; those fields use
152
- policy-aware typed options instead.
173
+ policy-aware typed options instead. Advisor, Fusion, Shell, Subagent, model
174
+ search, web search/fetch, image generation, Datetime, and Apply Patch server
175
+ tools have typed wire parameters and documented range checks. OpenRouter's old
176
+ `web` plugin is deprecated; use `openrouter:web_search` instead.
153
177
 
154
178
  URL images and PDFs, base64 audio, and URL/base64 video inputs use the ordinary
155
179
  AbsoluteJS content-block contract. URL citations are emitted as `citation`
@@ -161,8 +185,9 @@ metadata when reported.
161
185
 
162
186
  `createOpenRouterClient()` covers model/provider discovery, embeddings,
163
187
  reranking, streamed and non-streamed image generation, reusable files,
164
- Responses, speech, transcription, video jobs and downloads, batches, presets,
165
- credits, key metadata, and generation metadata. It also exports
188
+ Responses, speech, typed transcription, video jobs and downloads, beta batches,
189
+ presets, workspaces and budgets, activity/analytics, task classifications,
190
+ credits, key metadata, and generation content. It also exports
166
191
  `verifyOpenRouterWebhookSignature()` for video completion webhooks. Its typed
167
192
  operations enforce the same model allowlist. `request()` and `requestRaw()` are
168
193
  forward-compatible access to new or administrative OpenRouter endpoints.
@@ -190,16 +215,90 @@ const reranked = await openrouterClient.rerank({
190
215
  query: "cost controls",
191
216
  documents: ["response caching", "CSS layout"],
192
217
  });
218
+
219
+ // Response healing is documented for non-streaming structured responses.
220
+ const healed = await openrouterClient.chat({
221
+ model: "anthropic/claude-sonnet-4.6",
222
+ messages: [{ role: "user", content: "Return a JSON object" }],
223
+ response_format: { type: "json_object" },
224
+ plugins: [{ id: "response-healing" }],
225
+ });
226
+
227
+ const batch = await openrouterClient.createBatch({
228
+ endpoint: "/v1/chat/completions",
229
+ model: "anthropic/claude-sonnet-4.6",
230
+ requests: [
231
+ { custom_id: "one", body: { messages: [{ role: "user", content: "Hi" }] } },
232
+ ],
233
+ });
234
+ const completed = await openrouterClient.waitForBatch(batch.id, {
235
+ signal: abortController.signal,
236
+ timeoutMs: 60_000,
237
+ });
238
+ ```
239
+
240
+ Batch traffic uses OpenRouter's separate `/api/beta/batches` API and returns
241
+ inline typed results. `estimateOpenRouterModelCost()` calculates prompt,
242
+ completion, request, image, web-search, reasoning, and cache costs directly from
243
+ model-discovery pricing fields.
244
+
245
+ OAuth helpers cover S256 PKCE, web and headless authorization URLs, code
246
+ exchange, authenticated code creation, and user key deep-links:
247
+
248
+ ```ts
249
+ const pkce = await generateOpenRouterPKCE();
250
+ const authorizationUrl = createOpenRouterAuthorizationUrl({
251
+ callbackUrl: "https://example.com/openrouter/callback",
252
+ codeChallenge: pkce.codeChallenge,
253
+ codeChallengeMethod: pkce.codeChallengeMethod,
254
+ });
255
+
256
+ const { key } = await exchangeOpenRouterAuthCode({
257
+ code,
258
+ code_verifier: pkce.codeVerifier,
259
+ code_challenge_method: pkce.codeChallengeMethod,
260
+ });
193
261
  ```
194
262
 
263
+ ### Complete OpenRouter management SDK
264
+
265
+ The inference adapter stays small and policy-aware. The separate
266
+ `@absolutejs/ai/openrouter/sdk` entry point configures and exposes OpenRouter's
267
+ official OpenAPI-generated TypeScript SDK for the complete administrative and
268
+ data surface:
269
+
270
+ ```ts
271
+ import { createOpenRouterSDK } from "@absolutejs/ai/openrouter/sdk";
272
+
273
+ const openrouterAdmin = createOpenRouterSDK({
274
+ tokenSource: getRotatingManagementKey,
275
+ appName: "My AbsoluteJS App",
276
+ appUrl: "https://example.com",
277
+ timeoutMs: 15_000,
278
+ retryConfig: { strategy: "backoff" },
279
+ });
280
+
281
+ const keys = await openrouterAdmin.apiKeys.list();
282
+ const guardrails = await openrouterAdmin.guardrails.list();
283
+ const embeddingModels = await openrouterAdmin.embeddings.listModels();
284
+ ```
285
+
286
+ This entry point includes API-key management, BYOK, guardrails and assignments,
287
+ observability destinations, organizations, SCIM, workspaces, public datasets,
288
+ benchmarks, typed pagination, retries, per-call timeouts, and abort signals. It
289
+ tracks compatible patch releases of OpenRouter's generated SDK so new OpenAPI
290
+ fields do not depend on a handwritten AbsoluteJS type update. Normal
291
+ `@absolutejs/ai/openrouter` imports do not load this management surface.
292
+
195
293
  For a strict model-origin policy, also assign an OpenRouter key/workspace
196
294
  guardrail with the same model allowlist. Provider allowlists restrict where a
197
295
  model runs; they do not identify who developed it. Presets and router aliases
198
296
  must be explicitly allowed, because their resolved model is controlled outside
199
297
  the request. The raw client is intentionally unopinionated and should be limited
200
- to trusted server-side administration code. OpenRouter's official SDK can be
201
- used alongside this package for its complete organization, SSO, SCIM, BYOK, and
202
- analytics type surface.
298
+ to trusted server-side administration code. OpenRouter currently documents
299
+ reporting generation feedback through Chatroom and Logs, not through a public
300
+ feedback API, so AbsoluteJS exposes generation IDs/content without inventing an
301
+ unstable endpoint.
203
302
 
204
303
  Use `openrouterResponses(config)` when an AbsoluteJS agent should stream through
205
304
  OpenRouter's stateless Responses API, or `openrouterMessages(config)` for the
package/dist/ai/index.js CHANGED
@@ -2584,7 +2584,46 @@ var ollama = (config2 = {}) => {
2584
2584
  };
2585
2585
 
2586
2586
  // src/ai/providers/openrouterClient.ts
2587
+ var OPENROUTER_PRICING_KEYS = [
2588
+ "prompt",
2589
+ "completion",
2590
+ "request",
2591
+ "image",
2592
+ "web_search",
2593
+ "internal_reasoning",
2594
+ "input_cache_read",
2595
+ "input_cache_write"
2596
+ ];
2597
+ var estimateOpenRouterCost = (pricing, units) => {
2598
+ const components = {};
2599
+ let total = 0;
2600
+ for (const key of OPENROUTER_PRICING_KEYS) {
2601
+ const quantity = units[key];
2602
+ if (quantity === undefined)
2603
+ continue;
2604
+ if (!Number.isFinite(quantity) || quantity < 0)
2605
+ throw new Error(`OpenRouter ${key} units must be non-negative`);
2606
+ const rawPrice = pricing[key];
2607
+ if (rawPrice === undefined)
2608
+ continue;
2609
+ const price = Number(rawPrice);
2610
+ if (!Number.isFinite(price) || price < 0)
2611
+ throw new Error(`OpenRouter ${key} price must be non-negative`);
2612
+ components[key] = price * quantity;
2613
+ total += components[key];
2614
+ }
2615
+ return { components, total };
2616
+ };
2617
+ var estimateOpenRouterModelCost = (model, units) => estimateOpenRouterCost(model.pricing ?? {}, units);
2587
2618
  var DEFAULT_BASE_URL6 = "https://openrouter.ai/api/v1";
2619
+ var DEFAULT_BATCH_BASE_URL = "https://openrouter.ai/api/beta";
2620
+ var DEFAULT_SITE_URL = "https://openrouter.ai";
2621
+ var TERMINAL_BATCH_STATUSES = new Set([
2622
+ "completed",
2623
+ "failed",
2624
+ "expired",
2625
+ "cancelled"
2626
+ ]);
2588
2627
  var withoutLatestPrefix = (model) => model.startsWith("~") ? model.slice(1) : model;
2589
2628
  var openRouterModelMatchesRule = (model, rule) => {
2590
2629
  const normalizedModel = withoutLatestPrefix(model);
@@ -2598,6 +2637,22 @@ var assertAllowedModel = (model, allowedModels) => {
2598
2637
  return;
2599
2638
  throw new Error(`OpenRouter model "${model}" is not allowed`);
2600
2639
  };
2640
+ var assertAllowedModelsInValue = (value, allowedModels, key = "") => {
2641
+ if (key === "model" && typeof value === "string")
2642
+ assertAllowedModel(value, allowedModels);
2643
+ if ((key === "models" || key === "analysis_models" || key === "allowed_models") && Array.isArray(value)) {
2644
+ for (const model of value)
2645
+ if (typeof model === "string")
2646
+ assertAllowedModel(model, allowedModels);
2647
+ }
2648
+ if (Array.isArray(value)) {
2649
+ for (const item of value)
2650
+ assertAllowedModelsInValue(item, allowedModels);
2651
+ } else if (value && typeof value === "object") {
2652
+ for (const [childKey, child] of Object.entries(value))
2653
+ assertAllowedModelsInValue(child, allowedModels, childKey);
2654
+ }
2655
+ };
2601
2656
  var normalizePath = (path) => path.startsWith("/") ? path : `/${path}`;
2602
2657
  var encodeModelPath = (model) => model.split("/").map(encodeURIComponent).join("/");
2603
2658
  var withQuery = (url, query) => {
@@ -2648,6 +2703,54 @@ var parseImageSSE = async function* (response) {
2648
2703
  }
2649
2704
  };
2650
2705
  var toBytes = (value) => typeof value === "string" ? new TextEncoder().encode(value) : value;
2706
+ var toBase64Url = (bytes) => {
2707
+ let binary = "";
2708
+ for (const byte of bytes)
2709
+ binary += String.fromCharCode(byte);
2710
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
2711
+ };
2712
+ var generateOpenRouterPKCE = async () => {
2713
+ const random = crypto.getRandomValues(new Uint8Array(32));
2714
+ const codeVerifier = toBase64Url(random);
2715
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
2716
+ return {
2717
+ codeChallenge: toBase64Url(new Uint8Array(digest)),
2718
+ codeChallengeMethod: "S256",
2719
+ codeVerifier
2720
+ };
2721
+ };
2722
+ var createOpenRouterAuthorizationUrl = (options = {}) => {
2723
+ const url = new URL("/auth", options.baseUrl ?? DEFAULT_SITE_URL);
2724
+ if (options.callbackUrl)
2725
+ url.searchParams.set("callback_url", options.callbackUrl);
2726
+ if (options.codeChallenge)
2727
+ url.searchParams.set("code_challenge", options.codeChallenge);
2728
+ if (options.codeChallengeMethod)
2729
+ url.searchParams.set("code_challenge_method", options.codeChallengeMethod);
2730
+ if (options.keyLabel)
2731
+ url.searchParams.set("key_label", options.keyLabel);
2732
+ return url.toString();
2733
+ };
2734
+ var exchangeOpenRouterAuthCode = async (body, options = {}) => {
2735
+ const response = await (options.fetch ?? globalThis.fetch)(`${(options.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "")}/auth/keys`, {
2736
+ body: JSON.stringify(body),
2737
+ headers: { "Content-Type": "application/json" },
2738
+ method: "POST"
2739
+ });
2740
+ if (!response.ok)
2741
+ throw ProviderError.fromResponse("openrouter", response.status, await response.text());
2742
+ return response.json();
2743
+ };
2744
+ var createOpenRouterKeyLinks = async (key, siteUrl = DEFAULT_SITE_URL) => {
2745
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key)));
2746
+ const hash = Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
2747
+ const root = siteUrl.replace(/\/$/, "");
2748
+ return {
2749
+ hash,
2750
+ logsUrl: `${root}/logs?api_key_hash=${hash}`,
2751
+ settingsUrl: `${root}/keys/${hash}`
2752
+ };
2753
+ };
2651
2754
  var hexToBytes = (hex) => {
2652
2755
  if (!/^[0-9a-f]+$/iu.test(hex) || hex.length % 2 !== 0)
2653
2756
  return;
@@ -2693,9 +2796,11 @@ var createOpenRouterClient = (config2) => {
2693
2796
  if (!config2.apiKey && !config2.tokenSource)
2694
2797
  throw new Error("createOpenRouterClient() requires either apiKey or tokenSource");
2695
2798
  const baseUrl = (config2.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
2799
+ const batchBaseUrl = (config2.batchBaseUrl ?? (config2.baseUrl ? new URL("../beta", `${baseUrl}/`).toString() : DEFAULT_BATCH_BASE_URL)).replace(/\/$/, "");
2696
2800
  const fetchImpl = config2.fetch ?? globalThis.fetch;
2697
2801
  const allowedModels = config2.allowedModels ? [...config2.allowedModels] : undefined;
2698
- const requestRaw = async (path, options = {}) => {
2802
+ const defaultWorkspaceId = config2.workspaceId;
2803
+ const requestRawAt = async (rootUrl, path, options = {}) => {
2699
2804
  const token = config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
2700
2805
  const suppliedHeaders = typeof config2.headers === "function" ? await config2.headers() : config2.headers ?? {};
2701
2806
  const headers = new Headers(suppliedHeaders);
@@ -2709,13 +2814,16 @@ var createOpenRouterClient = (config2) => {
2709
2814
  body = JSON.stringify(options.body);
2710
2815
  }
2711
2816
  const { query, ...requestInit } = options;
2712
- const response = await fetchImpl(withQuery(`${baseUrl}${normalizePath(path)}`, options.query), { ...requestInit, body, headers });
2817
+ const response = await fetchImpl(withQuery(`${rootUrl}${normalizePath(path)}`, options.query), { ...requestInit, body, headers });
2713
2818
  if (!response.ok) {
2714
2819
  throw ProviderError.fromResponse("openrouter", response.status, await response.text());
2715
2820
  }
2716
2821
  return response;
2717
2822
  };
2823
+ const requestRaw = (path, options = {}) => requestRawAt(baseUrl, path, options);
2718
2824
  const request = async (path, options = {}) => (await requestRaw(path, options)).json();
2825
+ const requestBatch = async (path, options = {}) => (await requestRawAt(batchBaseUrl, path, options)).json();
2826
+ const getBatch = (id) => requestBatch(`/batches/${encodeURIComponent(id)}`);
2719
2827
  const listModels = async (query) => {
2720
2828
  const result = await request("/models", { query });
2721
2829
  if (!allowedModels)
@@ -2734,10 +2842,52 @@ var createOpenRouterClient = (config2) => {
2734
2842
  };
2735
2843
  };
2736
2844
  return {
2737
- cancelBatch: (id) => request(`/batches/${encodeURIComponent(id)}/cancel`, {
2845
+ addWorkspaceMembers: (id, userIds) => request(`/workspaces/${encodeURIComponent(id)}/members/add`, { body: { user_ids: [...userIds] }, method: "POST" }),
2846
+ createAuthCode: (body) => request("/auth/keys/code", {
2847
+ body: {
2848
+ ...body,
2849
+ workspace_id: body.workspace_id ?? defaultWorkspaceId
2850
+ },
2738
2851
  method: "POST"
2739
2852
  }),
2740
- createBatch: (body) => request("/batches", { body, method: "POST" }),
2853
+ chat: (body) => {
2854
+ assertAllowedModelsInValue(body, allowedModels);
2855
+ return request("/chat/completions", {
2856
+ body: { ...body, stream: false },
2857
+ method: "POST"
2858
+ });
2859
+ },
2860
+ createPresetFromChatCompletions: (slug, body) => {
2861
+ assertAllowedModelsInValue(body, allowedModels);
2862
+ return request(`/presets/${encodeURIComponent(slug)}/chat/completions`, { body, method: "POST" });
2863
+ },
2864
+ createPresetFromMessages: (slug, body) => {
2865
+ assertAllowedModelsInValue(body, allowedModels);
2866
+ return request(`/presets/${encodeURIComponent(slug)}/messages`, { body, method: "POST" });
2867
+ },
2868
+ createPresetFromResponses: (slug, body) => {
2869
+ assertAllowedModelsInValue(body, allowedModels);
2870
+ return request(`/presets/${encodeURIComponent(slug)}/responses`, { body, method: "POST" });
2871
+ },
2872
+ createWorkspace: (body) => {
2873
+ assertAllowedModelsInValue(body, allowedModels);
2874
+ return request("/workspaces", {
2875
+ body,
2876
+ method: "POST"
2877
+ });
2878
+ },
2879
+ createBatch: (body) => {
2880
+ assertAllowedModel(body.model, allowedModels);
2881
+ return requestBatch("/batches", {
2882
+ body: {
2883
+ endpoint: body.endpoint,
2884
+ model: body.model,
2885
+ requests: body.requests,
2886
+ ...body.completion_window ? { completion_window: body.completion_window } : {}
2887
+ },
2888
+ method: "POST"
2889
+ });
2890
+ },
2741
2891
  createEmbedding: (body) => {
2742
2892
  assertAllowedModel(body.model, allowedModels);
2743
2893
  return request("/embeddings", {
@@ -2752,8 +2902,12 @@ var createOpenRouterClient = (config2) => {
2752
2902
  method: "POST"
2753
2903
  });
2754
2904
  },
2755
- deleteFile: (id, workspaceId) => request(`/files/${encodeURIComponent(id)}`, { method: "DELETE", query: { workspace_id: workspaceId } }),
2756
- downloadFile: (id, workspaceId) => requestRaw(`/files/${encodeURIComponent(id)}/content`, {
2905
+ deleteFile: (id, workspaceId = defaultWorkspaceId) => request(`/files/${encodeURIComponent(id)}`, { method: "DELETE", query: { workspace_id: workspaceId } }),
2906
+ deleteWorkspace: (id) => request(`/workspaces/${encodeURIComponent(id)}`, {
2907
+ method: "DELETE"
2908
+ }),
2909
+ deleteWorkspaceBudget: (id, interval) => request(`/workspaces/${encodeURIComponent(id)}/budgets/${interval}`, { method: "DELETE" }),
2910
+ downloadFile: (id, workspaceId = defaultWorkspaceId) => requestRaw(`/files/${encodeURIComponent(id)}/content`, {
2757
2911
  query: { workspace_id: workspaceId }
2758
2912
  }),
2759
2913
  downloadVideo: (id, index = 0) => requestRaw(`/videos/${encodeURIComponent(id)}/content`, {
@@ -2766,15 +2920,25 @@ var createOpenRouterClient = (config2) => {
2766
2920
  method: "POST"
2767
2921
  });
2768
2922
  },
2769
- getBatch: (id) => request(`/batches/${encodeURIComponent(id)}`),
2923
+ getBatch,
2924
+ getActivity: (query) => request("/activity", {
2925
+ query: {
2926
+ ...query,
2927
+ workspace_id: query?.workspace_id ?? defaultWorkspaceId
2928
+ }
2929
+ }),
2930
+ getAnalyticsMeta: () => request("/analytics/meta"),
2770
2931
  getCredits: () => request("/credits"),
2771
2932
  getCurrentKey: () => request("/key"),
2772
- getFile: (id, workspaceId) => request(`/files/${encodeURIComponent(id)}`, {
2933
+ getFile: (id, workspaceId = defaultWorkspaceId) => request(`/files/${encodeURIComponent(id)}`, {
2773
2934
  query: { workspace_id: workspaceId }
2774
2935
  }),
2775
2936
  getGeneration: (id) => request("/generation", {
2776
2937
  query: { id }
2777
2938
  }),
2939
+ getGenerationContent: (id) => request("/generation/content", {
2940
+ query: { id }
2941
+ }),
2778
2942
  getModelEndpoints: (model) => {
2779
2943
  assertAllowedModel(model, allowedModels);
2780
2944
  return request(`/models/${encodeModelPath(model)}/endpoints`);
@@ -2787,9 +2951,21 @@ var createOpenRouterClient = (config2) => {
2787
2951
  assertAllowedModel(model, allowedModels);
2788
2952
  return request(`/images/models/${encodeModelPath(model)}/endpoints`);
2789
2953
  },
2954
+ getPreset: (slug) => request(`/presets/${encodeURIComponent(slug)}`),
2955
+ getPresetVersion: (slug, version) => request(`/presets/${encodeURIComponent(slug)}/versions/${encodeURIComponent(String(version))}`),
2956
+ getTaskClassifications: (window2 = "7d") => request("/classifications/task", {
2957
+ query: { window: window2 }
2958
+ }),
2790
2959
  getVideo: (id) => request(`/videos/${encodeURIComponent(id)}`),
2960
+ getWorkspace: (id) => request(`/workspaces/${encodeURIComponent(id)}`),
2791
2961
  listImageModels: async () => filterModelList(await request("/images/models")),
2792
- listFiles: (query) => request("/files", { query }),
2962
+ listEmbeddingModels: async () => filterModelList(await request("/embeddings/models")),
2963
+ listFiles: (query) => request("/files", {
2964
+ query: {
2965
+ ...query,
2966
+ workspace_id: query?.workspace_id ?? defaultWorkspaceId
2967
+ }
2968
+ }),
2793
2969
  listModels,
2794
2970
  listUserModels: async () => filterModelList(await request("/models/user")),
2795
2971
  listZdrEndpoints: async () => {
@@ -2804,11 +2980,20 @@ var createOpenRouterClient = (config2) => {
2804
2980
  countModels: (outputModalities) => request("/models/count", {
2805
2981
  query: { output_modalities: outputModalities }
2806
2982
  }),
2807
- listPresets: (offset = 0, limit = 100) => request("/presets", { query: { limit, offset } }),
2983
+ listPresets: (offset = 0, limit = 100) => request("/presets", {
2984
+ query: { limit, offset }
2985
+ }),
2808
2986
  listPresetVersions: (slug, offset = 0, limit = 100) => request(`/presets/${encodeURIComponent(slug)}/versions`, { query: { limit, offset } }),
2809
2987
  listProviders: () => request("/providers"),
2810
2988
  listRerankModels: async () => filterModelList(await request("/rerank/models")),
2811
2989
  listVideoModels: async () => filterModelList(await request("/videos/models")),
2990
+ listWorkspaceBudgets: (id) => request(`/workspaces/${encodeURIComponent(id)}/budgets`),
2991
+ listWorkspaces: (offset = 0, limit = 100) => request("/workspaces", { query: { limit, offset } }),
2992
+ queryAnalytics: (body) => request("/analytics/query", {
2993
+ body,
2994
+ method: "POST"
2995
+ }),
2996
+ removeWorkspaceMembers: (id, userIds) => request(`/workspaces/${encodeURIComponent(id)}/members/remove`, { body: { user_ids: [...userIds] }, method: "POST" }),
2812
2997
  request,
2813
2998
  requestRaw,
2814
2999
  streamImage: async function* (body, options = {}) {
@@ -2861,8 +3046,41 @@ var createOpenRouterClient = (config2) => {
2861
3046
  return request("/files", {
2862
3047
  body,
2863
3048
  method: "POST",
2864
- query: { workspace_id: options.workspaceId }
3049
+ query: { workspace_id: options.workspaceId ?? defaultWorkspaceId }
2865
3050
  });
3051
+ },
3052
+ updateWorkspace: (id, body) => {
3053
+ assertAllowedModelsInValue(body, allowedModels);
3054
+ return request(`/workspaces/${encodeURIComponent(id)}`, { body, method: "PATCH" });
3055
+ },
3056
+ upsertWorkspaceBudget: (id, interval, limitUsd) => request(`/workspaces/${encodeURIComponent(id)}/budgets/${interval}`, { body: { limit_usd: limitUsd }, method: "PUT" }),
3057
+ waitForBatch: async (id, options = {}) => {
3058
+ const intervalMs = options.intervalMs ?? 1000;
3059
+ const timeoutMs = options.timeoutMs;
3060
+ if (!Number.isFinite(intervalMs) || intervalMs < 0)
3061
+ throw new Error("OpenRouter batch intervalMs must be non-negative");
3062
+ if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs < 0))
3063
+ throw new Error("OpenRouter batch timeoutMs must be non-negative");
3064
+ const startedAt = Date.now();
3065
+ for (;; ) {
3066
+ options.signal?.throwIfAborted();
3067
+ const batch = await getBatch(id);
3068
+ if (TERMINAL_BATCH_STATUSES.has(batch.status))
3069
+ return batch;
3070
+ if (timeoutMs !== undefined && Date.now() - startedAt + intervalMs > timeoutMs)
3071
+ throw new Error(`Timed out waiting for OpenRouter batch "${id}"`);
3072
+ await new Promise((resolve, reject) => {
3073
+ const onAbort = () => {
3074
+ clearTimeout(timeout);
3075
+ reject(options.signal?.reason);
3076
+ };
3077
+ const timeout = setTimeout(() => {
3078
+ options.signal?.removeEventListener("abort", onAbort);
3079
+ resolve();
3080
+ }, intervalMs);
3081
+ options.signal?.addEventListener("abort", onAbort, { once: true });
3082
+ });
3083
+ }
2866
3084
  }
2867
3085
  };
2868
3086
  };
@@ -2999,7 +3217,7 @@ var assertAllowedPreset = (preset, allowedPresets) => {
2999
3217
  var assertIndirectModels = (value, allowedModels, key = "") => {
3000
3218
  if (key === "model" && typeof value === "string")
3001
3219
  assertAllowedModel2(value, allowedModels);
3002
- if ((key === "models" || key === "analysis_models") && Array.isArray(value)) {
3220
+ if ((key === "models" || key === "analysis_models" || key === "allowed_models") && Array.isArray(value)) {
3003
3221
  for (const model of value) {
3004
3222
  if (typeof model === "string")
3005
3223
  assertAllowedModel2(model, allowedModels);
@@ -3013,6 +3231,84 @@ var assertIndirectModels = (value, allowedModels, key = "") => {
3013
3231
  assertIndirectModels(child, allowedModels, childKey);
3014
3232
  }
3015
3233
  };
3234
+ var assertIntegerRange = (label, value, minimum, maximum) => {
3235
+ if (value === undefined)
3236
+ return;
3237
+ if (!Number.isInteger(value) || value < minimum || maximum !== undefined && value > maximum)
3238
+ throw new Error(`OpenRouter ${label} must be an integer from ${minimum}${maximum === undefined ? " or greater" : ` to ${maximum}`}`);
3239
+ };
3240
+ var assertPluginOptions = (plugins) => {
3241
+ for (const plugin of plugins ?? []) {
3242
+ if (plugin.id === "response-healing")
3243
+ throw new Error("OpenRouter response-healing requires a non-streaming request; use createOpenRouterClient().chat()");
3244
+ if (plugin.id === "fusion") {
3245
+ const fusion = plugin;
3246
+ if (fusion.analysis_models && (fusion.analysis_models.length < 1 || fusion.analysis_models.length > 8))
3247
+ throw new Error("OpenRouter Fusion analysis_models must contain 1-8 models");
3248
+ assertIntegerRange("Fusion max_tool_calls", fusion.max_tool_calls, 1, 16);
3249
+ }
3250
+ if (plugin.id === "web") {
3251
+ const web = plugin;
3252
+ assertIntegerRange("web plugin max_results", web.max_results, 1, web.engine === "perplexity" ? 20 : 25);
3253
+ if (web.include_domains?.length && web.exclude_domains?.length && (web.engine === "firecrawl" || web.engine === "parallel" || web.engine === "perplexity"))
3254
+ throw new Error(`OpenRouter ${web.engine} web plugin cannot combine include_domains and exclude_domains`);
3255
+ }
3256
+ }
3257
+ };
3258
+ var assertServerToolOptions = (tools) => {
3259
+ for (const tool of tools ?? []) {
3260
+ if (tool.type === "openrouter:web_search") {
3261
+ const parameters = tool.parameters;
3262
+ assertIntegerRange("web search max_results", parameters?.max_results, 1, 25);
3263
+ if (parameters?.engine === "perplexity" && (parameters.max_results ?? 0) > 20)
3264
+ throw new Error("OpenRouter Perplexity web search max_results must be at most 20");
3265
+ assertIntegerRange("web search max_characters", parameters?.max_characters, 1, 1e5);
3266
+ assertIntegerRange("web search max_total_results", parameters?.max_total_results, 1);
3267
+ }
3268
+ if (tool.type === "openrouter:web_fetch") {
3269
+ assertIntegerRange("web fetch max_uses", tool.parameters?.max_uses, 1);
3270
+ assertIntegerRange("web fetch max_content_tokens", tool.parameters?.max_content_tokens, 1);
3271
+ }
3272
+ if (tool.type === "openrouter:image_generation") {
3273
+ const compression = tool.parameters?.output_compression;
3274
+ if (compression !== undefined && (!Number.isFinite(compression) || compression < 0 || compression > 100))
3275
+ throw new Error("OpenRouter image generation output_compression must be 0-100");
3276
+ }
3277
+ if (tool.type === "openrouter:subagent") {
3278
+ assertIntegerRange("subagent max_completion_tokens", tool.parameters.max_completion_tokens, 1);
3279
+ assertIntegerRange("subagent max_tool_calls", tool.parameters.max_tool_calls, 1, 25);
3280
+ const temperature = tool.parameters.temperature;
3281
+ if (temperature !== undefined && (!Number.isFinite(temperature) || temperature < 0 || temperature > 2))
3282
+ throw new Error("OpenRouter subagent temperature must be 0-2");
3283
+ assertServerToolOptions(tool.parameters.tools);
3284
+ }
3285
+ if (tool.type === "openrouter:advisor") {
3286
+ assertIntegerRange("advisor max_completion_tokens", tool.parameters?.max_completion_tokens, 1);
3287
+ const temperature = tool.parameters?.temperature;
3288
+ if (temperature !== undefined && (!Number.isFinite(temperature) || temperature < 0 || temperature > 2))
3289
+ throw new Error("OpenRouter advisor temperature must be 0-2");
3290
+ }
3291
+ if (tool.type === "openrouter:fusion") {
3292
+ const analysisModels = tool.parameters?.analysis_models;
3293
+ if (analysisModels && (analysisModels.length < 1 || analysisModels.length > 8))
3294
+ throw new Error("OpenRouter Fusion server tool analysis_models must contain 1-8 models");
3295
+ assertIntegerRange("Fusion server tool max_completion_tokens", tool.parameters?.max_completion_tokens, 1);
3296
+ assertIntegerRange("Fusion server tool max_tool_calls", tool.parameters?.max_tool_calls, 1, 16);
3297
+ const temperature = tool.parameters?.temperature;
3298
+ if (temperature !== undefined && (!Number.isFinite(temperature) || temperature < 0 || temperature > 2))
3299
+ throw new Error("OpenRouter Fusion server tool temperature must be 0-2");
3300
+ assertServerToolOptions(tool.parameters?.tools);
3301
+ }
3302
+ if (tool.type === "openrouter:shell") {
3303
+ assertIntegerRange("shell sleep_after_seconds", tool.parameters?.sleep_after_seconds, 0, 2592000);
3304
+ const environment = tool.parameters?.environment;
3305
+ if (environment?.type === "container_reference" && (environment.container_id.length < 1 || environment.container_id.length > 20))
3306
+ throw new Error("OpenRouter shell container_id must contain 1-20 characters");
3307
+ }
3308
+ if (tool.type === "openrouter:experimental__search_models")
3309
+ assertIntegerRange("model search max_results", tool.parameters?.max_results, 1, 20);
3310
+ }
3311
+ };
3016
3312
  var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProviders) => {
3017
3313
  assertAllowedPreset(options.preset, allowedPresets);
3018
3314
  assertRequestRoutingPolicy(options.routing, allowedProviders);
@@ -3032,6 +3328,8 @@ var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProvi
3032
3328
  assertIndirectModels(options.serverTools, allowedModels);
3033
3329
  assertIndirectModels(options.messagesTools, allowedModels);
3034
3330
  assertIndirectModels(options.plugins, allowedModels);
3331
+ assertPluginOptions(options.plugins);
3332
+ assertServerToolOptions(options.serverTools);
3035
3333
  if (options.extraBody) {
3036
3334
  const unsafe = Object.keys(options.extraBody).find((key) => SECURITY_SENSITIVE_EXTRA_BODY_FIELDS.has(key));
3037
3335
  if (unsafe)
@@ -7313,6 +7611,7 @@ export {
7313
7611
  meta,
7314
7612
  google,
7315
7613
  getProviderHealth,
7614
+ generateOpenRouterPKCE,
7316
7615
  generateObjectAI,
7317
7616
  generateId,
7318
7617
  generateAIWithTools,
@@ -7320,13 +7619,18 @@ export {
7320
7619
  gemini,
7321
7620
  formCard,
7322
7621
  fetchProviderApiStatus,
7622
+ exchangeOpenRouterAuthCode,
7623
+ estimateOpenRouterModelCost,
7624
+ estimateOpenRouterCost,
7323
7625
  diffCard,
7324
7626
  deepseek,
7325
7627
  credentialCard,
7326
7628
  createUiCards,
7327
7629
  createSyncConversationStore,
7328
7630
  createProviderProxyResponse,
7631
+ createOpenRouterKeyLinks,
7329
7632
  createOpenRouterClient,
7633
+ createOpenRouterAuthorizationUrl,
7330
7634
  createOAuth2ClientCredentialsTokenSource,
7331
7635
  createMemoryStore,
7332
7636
  createConversationManager,
@@ -7364,5 +7668,5 @@ export {
7364
7668
  BUILTIN_UI_CARDS
7365
7669
  };
7366
7670
 
7367
- //# debugId=3B5ACA11D1118B9564756E2164756E21
7671
+ //# debugId=1A3329E4751DCFB764756E2164756E21
7368
7672
  //# sourceMappingURL=index.js.map