@absolutejs/ai 0.0.52 → 0.0.54-beta.0
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 +43 -3
- package/dist/ai/index.js +91 -2
- package/dist/ai/index.js.map +5 -5
- package/dist/ai/providers/openrouter.js +89 -1
- package/dist/ai/providers/openrouter.js.map +4 -4
- package/dist/ai/providers/openrouterSDK.js +37 -0
- package/dist/ai/providers/openrouterSDK.js.map +10 -0
- package/dist/src/ai/index.d.ts +1 -1
- package/dist/src/ai/providers/openrouter.d.ts +143 -4
- package/dist/src/ai/providers/openrouterClient.d.ts +13 -0
- package/dist/src/ai/providers/openrouterSDK.d.ts +21 -0
- package/package.json +199 -191
package/README.md
CHANGED
|
@@ -170,7 +170,10 @@ message transforms, native reasoning controls, prompt and response caching,
|
|
|
170
170
|
text-plus-audio output, verbosity, user attribution, and an `extraBody` escape
|
|
171
171
|
hatch for new OpenRouter parameters. The escape hatch cannot replace models,
|
|
172
172
|
fallbacks, providers, presets, messages, plugins, or tools; those fields use
|
|
173
|
-
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.
|
|
174
177
|
|
|
175
178
|
URL images and PDFs, base64 audio, and URL/base64 video inputs use the ordinary
|
|
176
179
|
AbsoluteJS content-block contract. URL citations are emitted as `citation`
|
|
@@ -213,6 +216,14 @@ const reranked = await openrouterClient.rerank({
|
|
|
213
216
|
documents: ["response caching", "CSS layout"],
|
|
214
217
|
});
|
|
215
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
|
+
|
|
216
227
|
const batch = await openrouterClient.createBatch({
|
|
217
228
|
endpoint: "/v1/chat/completions",
|
|
218
229
|
model: "anthropic/claude-sonnet-4.6",
|
|
@@ -249,6 +260,36 @@ const { key } = await exchangeOpenRouterAuthCode({
|
|
|
249
260
|
});
|
|
250
261
|
```
|
|
251
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
|
+
|
|
252
293
|
For a strict model-origin policy, also assign an OpenRouter key/workspace
|
|
253
294
|
guardrail with the same model allowlist. Provider allowlists restrict where a
|
|
254
295
|
model runs; they do not identify who developed it. Presets and router aliases
|
|
@@ -257,8 +298,7 @@ the request. The raw client is intentionally unopinionated and should be limited
|
|
|
257
298
|
to trusted server-side administration code. OpenRouter currently documents
|
|
258
299
|
reporting generation feedback through Chatroom and Logs, not through a public
|
|
259
300
|
feedback API, so AbsoluteJS exposes generation IDs/content without inventing an
|
|
260
|
-
unstable endpoint.
|
|
261
|
-
for specialized organization, SSO, SCIM, and BYOK administration.
|
|
301
|
+
unstable endpoint.
|
|
262
302
|
|
|
263
303
|
Use `openrouterResponses(config)` when an AbsoluteJS agent should stream through
|
|
264
304
|
OpenRouter's stateless Responses API, or `openrouterMessages(config)` for the
|
package/dist/ai/index.js
CHANGED
|
@@ -2850,6 +2850,13 @@ var createOpenRouterClient = (config2) => {
|
|
|
2850
2850
|
},
|
|
2851
2851
|
method: "POST"
|
|
2852
2852
|
}),
|
|
2853
|
+
chat: (body) => {
|
|
2854
|
+
assertAllowedModelsInValue(body, allowedModels);
|
|
2855
|
+
return request("/chat/completions", {
|
|
2856
|
+
body: { ...body, stream: false },
|
|
2857
|
+
method: "POST"
|
|
2858
|
+
});
|
|
2859
|
+
},
|
|
2853
2860
|
createPresetFromChatCompletions: (slug, body) => {
|
|
2854
2861
|
assertAllowedModelsInValue(body, allowedModels);
|
|
2855
2862
|
return request(`/presets/${encodeURIComponent(slug)}/chat/completions`, { body, method: "POST" });
|
|
@@ -2952,6 +2959,7 @@ var createOpenRouterClient = (config2) => {
|
|
|
2952
2959
|
getVideo: (id) => request(`/videos/${encodeURIComponent(id)}`),
|
|
2953
2960
|
getWorkspace: (id) => request(`/workspaces/${encodeURIComponent(id)}`),
|
|
2954
2961
|
listImageModels: async () => filterModelList(await request("/images/models")),
|
|
2962
|
+
listEmbeddingModels: async () => filterModelList(await request("/embeddings/models")),
|
|
2955
2963
|
listFiles: (query) => request("/files", {
|
|
2956
2964
|
query: {
|
|
2957
2965
|
...query,
|
|
@@ -3223,6 +3231,84 @@ var assertIndirectModels = (value, allowedModels, key = "") => {
|
|
|
3223
3231
|
assertIndirectModels(child, allowedModels, childKey);
|
|
3224
3232
|
}
|
|
3225
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
|
+
};
|
|
3226
3312
|
var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProviders) => {
|
|
3227
3313
|
assertAllowedPreset(options.preset, allowedPresets);
|
|
3228
3314
|
assertRequestRoutingPolicy(options.routing, allowedProviders);
|
|
@@ -3242,6 +3328,8 @@ var assertRequestOptions = (options, allowedModels, allowedPresets, allowedProvi
|
|
|
3242
3328
|
assertIndirectModels(options.serverTools, allowedModels);
|
|
3243
3329
|
assertIndirectModels(options.messagesTools, allowedModels);
|
|
3244
3330
|
assertIndirectModels(options.plugins, allowedModels);
|
|
3331
|
+
assertPluginOptions(options.plugins);
|
|
3332
|
+
assertServerToolOptions(options.serverTools);
|
|
3245
3333
|
if (options.extraBody) {
|
|
3246
3334
|
const unsafe = Object.keys(options.extraBody).find((key) => SECURITY_SENSITIVE_EXTRA_BODY_FIELDS.has(key));
|
|
3247
3335
|
if (unsafe)
|
|
@@ -3401,6 +3489,7 @@ var openrouterMessages = (config2) => {
|
|
|
3401
3489
|
|
|
3402
3490
|
// src/plugins/aiChat.ts
|
|
3403
3491
|
import { Elysia } from "elysia";
|
|
3492
|
+
import { websocket } from "elysia/websocket";
|
|
3404
3493
|
|
|
3405
3494
|
// src/ai/memoryStore.ts
|
|
3406
3495
|
var createMemoryStore = () => {
|
|
@@ -4781,7 +4870,7 @@ var aiChat = (config2) => {
|
|
|
4781
4870
|
return new Response(html || '<div class="empty">No conversations</div>', { headers: { "Content-Type": "text/html" } });
|
|
4782
4871
|
});
|
|
4783
4872
|
};
|
|
4784
|
-
const app = new Elysia().ws(path, {
|
|
4873
|
+
const app = new Elysia().use(websocket()).ws(path, {
|
|
4785
4874
|
message: async (ws, raw) => {
|
|
4786
4875
|
const msg = parseAIMessage(raw);
|
|
4787
4876
|
if (!msg) {
|
|
@@ -7580,5 +7669,5 @@ export {
|
|
|
7580
7669
|
BUILTIN_UI_CARDS
|
|
7581
7670
|
};
|
|
7582
7671
|
|
|
7583
|
-
//# debugId=
|
|
7672
|
+
//# debugId=1D47D14F189FCCCF64756E2164756E21
|
|
7584
7673
|
//# sourceMappingURL=index.js.map
|