@providerprotocol/ai 0.0.15 → 0.0.17

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.
@@ -1,4 +1,29 @@
1
- import { b as Provider, M as ModelReference, a as LLMHandler } from '../provider-Bi0nyNhA.js';
1
+ import { d as Provider, f as ModelReference, b as LLMHandler, c as EmbeddingHandler } from '../provider-D5MO3-pS.js';
2
+
3
+ /**
4
+ * @fileoverview OpenRouter Embeddings API Handler
5
+ *
6
+ * This module implements the embedding handler for OpenRouter's embeddings API.
7
+ * OpenRouter provides access to multiple embedding providers through an OpenAI-compatible endpoint.
8
+ *
9
+ * @see {@link https://openrouter.ai/docs/api/reference/embeddings OpenRouter Embeddings API Reference}
10
+ * @module providers/openrouter/embed
11
+ */
12
+
13
+ /**
14
+ * OpenRouter embedding parameters.
15
+ * Passed through unchanged to the API.
16
+ */
17
+ interface OpenRouterEmbedParams {
18
+ /** Output dimensions (model-dependent) */
19
+ dimensions?: number;
20
+ /** Encoding format: 'float' or 'base64' */
21
+ encoding_format?: 'float' | 'base64';
22
+ /** A unique identifier representing your end-user */
23
+ user?: string;
24
+ /** Input type hint for some models */
25
+ input_type?: string;
26
+ }
2
27
 
3
28
  /**
4
29
  * OpenRouter-specific types for the Unified Provider Protocol.
@@ -53,6 +78,18 @@ interface OpenRouterCompletionsParams {
53
78
  parallel_tool_calls?: boolean;
54
79
  /** Response format for structured output */
55
80
  response_format?: OpenRouterResponseFormat;
81
+ /**
82
+ * Output modalities for multimodal generation.
83
+ * Set to `['text', 'image']` to enable image generation with compatible models.
84
+ * @see {@link https://openrouter.ai/docs/guides/overview/multimodal/image-generation}
85
+ */
86
+ modalities?: Array<'text' | 'image'>;
87
+ /**
88
+ * Image generation configuration for Gemini models.
89
+ * Only applies when `modalities` includes 'image'.
90
+ * @see {@link https://openrouter.ai/docs/guides/overview/multimodal/image-generation}
91
+ */
92
+ image_config?: OpenRouterImageConfig;
56
93
  /**
57
94
  * Prompt transforms to apply
58
95
  * See: https://openrouter.ai/docs/guides/features/message-transforms
@@ -87,6 +124,27 @@ interface OpenRouterCompletionsParams {
87
124
  echo_upstream_body?: boolean;
88
125
  };
89
126
  }
127
+ /**
128
+ * Image generation configuration for OpenRouter.
129
+ *
130
+ * Used with Gemini image generation models to control output dimensions.
131
+ *
132
+ * @see {@link https://openrouter.ai/docs/guides/overview/multimodal/image-generation}
133
+ */
134
+ interface OpenRouterImageConfig {
135
+ /**
136
+ * Aspect ratio for generated images.
137
+ * Supported values range from '1:1' (1024×1024) to '21:9' (1536×672).
138
+ */
139
+ aspect_ratio?: string;
140
+ /**
141
+ * Resolution level for generated images.
142
+ * - '1K': Standard resolution
143
+ * - '2K': Higher resolution
144
+ * - '4K': Highest resolution
145
+ */
146
+ image_size?: '1K' | '2K' | '4K';
147
+ }
90
148
  /**
91
149
  * Parameters for OpenRouter's Responses API (beta).
92
150
  *
@@ -111,6 +169,18 @@ interface OpenRouterResponsesParams {
111
169
  reasoning?: {
112
170
  effort?: 'low' | 'medium' | 'high';
113
171
  };
172
+ /**
173
+ * Output modalities for multimodal generation.
174
+ * Set to `['text', 'image']` to enable image generation with compatible models.
175
+ * @see {@link https://openrouter.ai/docs/guides/overview/multimodal/image-generation}
176
+ */
177
+ modalities?: Array<'text' | 'image'>;
178
+ /**
179
+ * Image generation configuration.
180
+ * Only applies when `modalities` includes 'image'.
181
+ * @see {@link https://openrouter.ai/docs/guides/overview/multimodal/image-generation}
182
+ */
183
+ image_config?: OpenRouterImageConfig;
114
184
  }
115
185
  /**
116
186
  * API mode selection for OpenRouter provider.
@@ -244,10 +314,11 @@ interface OpenRouterProvider extends Provider<OpenRouterProviderOptions> {
244
314
  readonly version: string;
245
315
  /**
246
316
  * Supported modalities for this provider.
247
- * OpenRouter currently only supports LLM (text generation).
317
+ * OpenRouter supports LLM (text generation) and Embedding.
248
318
  */
249
319
  readonly modalities: {
250
320
  llm: LLMHandler<OpenRouterLLMParamsUnion>;
321
+ embedding: EmbeddingHandler<OpenRouterEmbedParams>;
251
322
  };
252
323
  }
253
324
  /**
@@ -301,4 +372,4 @@ interface OpenRouterProvider extends Provider<OpenRouterProviderOptions> {
301
372
  */
302
373
  declare const openrouter: OpenRouterProvider;
303
374
 
304
- export { type OpenRouterAPIMode, type OpenRouterCompletionsParams, type OpenRouterConfig, type OpenRouterModelOptions, type OpenRouterModelReference, type OpenRouterProviderPreferences, type OpenRouterResponsesParams, openrouter };
375
+ export { type OpenRouterAPIMode, type OpenRouterCompletionsParams, type OpenRouterConfig, type OpenRouterEmbedParams, type OpenRouterModelOptions, type OpenRouterModelReference, type OpenRouterProviderPreferences, type OpenRouterResponsesParams, openrouter };
@@ -190,15 +190,23 @@ function transformResponse(data) {
190
190
  if (!choice) {
191
191
  throw new Error("No choices in OpenRouter response");
192
192
  }
193
- const textContent = [];
193
+ const content = [];
194
194
  let structuredData;
195
195
  if (choice.message.content) {
196
- textContent.push({ type: "text", text: choice.message.content });
196
+ content.push({ type: "text", text: choice.message.content });
197
197
  try {
198
198
  structuredData = JSON.parse(choice.message.content);
199
199
  } catch {
200
200
  }
201
201
  }
202
+ if (choice.message.images && choice.message.images.length > 0) {
203
+ for (const image of choice.message.images) {
204
+ const imageBlock = parseGeneratedImage(image.image_url.url);
205
+ if (imageBlock) {
206
+ content.push(imageBlock);
207
+ }
208
+ }
209
+ }
202
210
  const toolCalls = [];
203
211
  if (choice.message.tool_calls) {
204
212
  for (const call of choice.message.tool_calls) {
@@ -215,7 +223,7 @@ function transformResponse(data) {
215
223
  }
216
224
  }
217
225
  const message = new AssistantMessage(
218
- textContent,
226
+ content,
219
227
  toolCalls.length > 0 ? toolCalls : void 0,
220
228
  {
221
229
  id: data.id,
@@ -257,12 +265,28 @@ function transformResponse(data) {
257
265
  data: structuredData
258
266
  };
259
267
  }
268
+ function parseGeneratedImage(dataUrl) {
269
+ const match = dataUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
270
+ if (!match) {
271
+ return null;
272
+ }
273
+ const [, mimeType, data] = match;
274
+ if (!mimeType || !data) {
275
+ return null;
276
+ }
277
+ return {
278
+ type: "image",
279
+ mimeType,
280
+ source: { type: "base64", data }
281
+ };
282
+ }
260
283
  function createStreamState() {
261
284
  return {
262
285
  id: "",
263
286
  model: "",
264
287
  text: "",
265
288
  toolCalls: /* @__PURE__ */ new Map(),
289
+ images: [],
266
290
  finishReason: null,
267
291
  inputTokens: 0,
268
292
  outputTokens: 0,
@@ -316,6 +340,11 @@ function transformStreamEvent(chunk, state) {
316
340
  }
317
341
  }
318
342
  }
343
+ if (choice.delta.images) {
344
+ for (const image of choice.delta.images) {
345
+ state.images.push(image.image_url.url);
346
+ }
347
+ }
319
348
  if (choice.finish_reason) {
320
349
  state.finishReason = choice.finish_reason;
321
350
  events.push({ type: "message_stop", index: 0, delta: {} });
@@ -329,15 +358,21 @@ function transformStreamEvent(chunk, state) {
329
358
  return events;
330
359
  }
331
360
  function buildResponseFromState(state) {
332
- const textContent = [];
361
+ const content = [];
333
362
  let structuredData;
334
363
  if (state.text) {
335
- textContent.push({ type: "text", text: state.text });
364
+ content.push({ type: "text", text: state.text });
336
365
  try {
337
366
  structuredData = JSON.parse(state.text);
338
367
  } catch {
339
368
  }
340
369
  }
370
+ for (const imageUrl of state.images) {
371
+ const imageBlock = parseGeneratedImage(imageUrl);
372
+ if (imageBlock) {
373
+ content.push(imageBlock);
374
+ }
375
+ }
341
376
  const toolCalls = [];
342
377
  for (const [, toolCall] of state.toolCalls) {
343
378
  let args = {};
@@ -354,7 +389,7 @@ function buildResponseFromState(state) {
354
389
  });
355
390
  }
356
391
  const message = new AssistantMessage(
357
- textContent,
392
+ content,
358
393
  toolCalls.length > 0 ? toolCalls : void 0,
359
394
  {
360
395
  id: state.id,
@@ -403,6 +438,7 @@ var OPENROUTER_CAPABILITIES = {
403
438
  tools: true,
404
439
  structuredOutput: true,
405
440
  imageInput: true,
441
+ imageOutput: true,
406
442
  videoInput: false,
407
443
  audioInput: false
408
444
  };
@@ -742,7 +778,7 @@ function transformTool2(tool) {
742
778
  };
743
779
  }
744
780
  function transformResponse2(data) {
745
- const textContent = [];
781
+ const content = [];
746
782
  const toolCalls = [];
747
783
  const functionCallItems = [];
748
784
  let hadRefusal = false;
@@ -750,17 +786,17 @@ function transformResponse2(data) {
750
786
  for (const item of data.output) {
751
787
  if (item.type === "message") {
752
788
  const messageItem = item;
753
- for (const content of messageItem.content) {
754
- if (content.type === "output_text") {
755
- textContent.push({ type: "text", text: content.text });
789
+ for (const part of messageItem.content) {
790
+ if (part.type === "output_text") {
791
+ content.push({ type: "text", text: part.text });
756
792
  if (structuredData === void 0) {
757
793
  try {
758
- structuredData = JSON.parse(content.text);
794
+ structuredData = JSON.parse(part.text);
759
795
  } catch {
760
796
  }
761
797
  }
762
- } else if (content.type === "refusal") {
763
- textContent.push({ type: "text", text: content.refusal });
798
+ } else if (part.type === "refusal") {
799
+ content.push({ type: "text", text: part.refusal });
764
800
  hadRefusal = true;
765
801
  }
766
802
  }
@@ -782,10 +818,19 @@ function transformResponse2(data) {
782
818
  name: functionCall.name,
783
819
  arguments: functionCall.arguments
784
820
  });
821
+ } else if (item.type === "image_generation_call") {
822
+ const imageGen = item;
823
+ if (imageGen.result) {
824
+ content.push({
825
+ type: "image",
826
+ mimeType: "image/png",
827
+ source: { type: "base64", data: imageGen.result }
828
+ });
829
+ }
785
830
  }
786
831
  }
787
832
  const message = new AssistantMessage(
788
- textContent,
833
+ content,
789
834
  toolCalls.length > 0 ? toolCalls : void 0,
790
835
  {
791
836
  id: data.id,
@@ -831,6 +876,7 @@ function createStreamState2() {
831
876
  model: "",
832
877
  textByIndex: /* @__PURE__ */ new Map(),
833
878
  toolCalls: /* @__PURE__ */ new Map(),
879
+ images: /* @__PURE__ */ new Map(),
834
880
  status: "in_progress",
835
881
  inputTokens: 0,
836
882
  outputTokens: 0,
@@ -927,6 +973,11 @@ function transformStreamEvent2(event, state) {
927
973
  }
928
974
  }
929
975
  }
976
+ } else if (event.item.type === "image_generation_call") {
977
+ const imageGen = event.item;
978
+ if (imageGen.result) {
979
+ state.images.set(event.output_index, imageGen.result);
980
+ }
930
981
  }
931
982
  events.push({
932
983
  type: "content_block_stop",
@@ -1022,11 +1073,11 @@ function transformStreamEvent2(event, state) {
1022
1073
  return events;
1023
1074
  }
1024
1075
  function buildResponseFromState2(state) {
1025
- const textContent = [];
1076
+ const content = [];
1026
1077
  let structuredData;
1027
1078
  for (const [, text] of state.textByIndex) {
1028
1079
  if (text) {
1029
- textContent.push({ type: "text", text });
1080
+ content.push({ type: "text", text });
1030
1081
  if (structuredData === void 0) {
1031
1082
  try {
1032
1083
  structuredData = JSON.parse(text);
@@ -1035,6 +1086,15 @@ function buildResponseFromState2(state) {
1035
1086
  }
1036
1087
  }
1037
1088
  }
1089
+ for (const [, imageData] of state.images) {
1090
+ if (imageData) {
1091
+ content.push({
1092
+ type: "image",
1093
+ mimeType: "image/png",
1094
+ source: { type: "base64", data: imageData }
1095
+ });
1096
+ }
1097
+ }
1038
1098
  const toolCalls = [];
1039
1099
  const functionCallItems = [];
1040
1100
  for (const [, toolCall] of state.toolCalls) {
@@ -1063,7 +1123,7 @@ function buildResponseFromState2(state) {
1063
1123
  }
1064
1124
  }
1065
1125
  const message = new AssistantMessage(
1066
- textContent,
1126
+ content,
1067
1127
  toolCalls.length > 0 ? toolCalls : void 0,
1068
1128
  {
1069
1129
  id: state.id,
@@ -1109,6 +1169,7 @@ var OPENROUTER_CAPABILITIES2 = {
1109
1169
  tools: true,
1110
1170
  structuredOutput: true,
1111
1171
  imageInput: true,
1172
+ imageOutput: true,
1112
1173
  videoInput: false,
1113
1174
  audioInput: false
1114
1175
  };
@@ -1275,11 +1336,121 @@ function createResponsesLLMHandler() {
1275
1336
  };
1276
1337
  }
1277
1338
 
1339
+ // src/providers/openrouter/embed.ts
1340
+ var OPENROUTER_EMBEDDINGS_API_URL = "https://openrouter.ai/api/v1/embeddings";
1341
+ function getDefaultDimensions(modelId) {
1342
+ if (modelId.includes("text-embedding-3-large")) {
1343
+ return 3072;
1344
+ }
1345
+ if (modelId.includes("text-embedding-3-small") || modelId.includes("ada-002")) {
1346
+ return 1536;
1347
+ }
1348
+ if (modelId.includes("gemini-embedding")) {
1349
+ return 3072;
1350
+ }
1351
+ return 1536;
1352
+ }
1353
+ function createEmbeddingHandler() {
1354
+ let providerRef = null;
1355
+ return {
1356
+ supportedInputs: ["text"],
1357
+ _setProvider(provider) {
1358
+ providerRef = provider;
1359
+ },
1360
+ bind(modelId) {
1361
+ if (!providerRef) {
1362
+ throw new UPPError(
1363
+ "Provider reference not set. Handler must be used with createProvider().",
1364
+ "INVALID_REQUEST",
1365
+ "openrouter",
1366
+ "embedding"
1367
+ );
1368
+ }
1369
+ const model = {
1370
+ modelId,
1371
+ maxBatchSize: 2048,
1372
+ maxInputLength: 8191,
1373
+ dimensions: getDefaultDimensions(modelId),
1374
+ get provider() {
1375
+ return providerRef;
1376
+ },
1377
+ async embed(request) {
1378
+ const apiKey = await resolveApiKey(
1379
+ request.config,
1380
+ "OPENROUTER_API_KEY",
1381
+ "openrouter",
1382
+ "embedding"
1383
+ );
1384
+ const baseUrl = request.config.baseUrl ?? OPENROUTER_EMBEDDINGS_API_URL;
1385
+ const inputTexts = request.inputs.map((input) => {
1386
+ if (typeof input === "string") {
1387
+ return input;
1388
+ }
1389
+ if ("text" in input) {
1390
+ return input.text;
1391
+ }
1392
+ throw new UPPError(
1393
+ "OpenRouter embeddings only support text input",
1394
+ "INVALID_REQUEST",
1395
+ "openrouter",
1396
+ "embedding"
1397
+ );
1398
+ });
1399
+ const body = {
1400
+ model: modelId,
1401
+ input: inputTexts
1402
+ };
1403
+ if (request.params?.dimensions !== void 0) {
1404
+ body.dimensions = request.params.dimensions;
1405
+ }
1406
+ if (request.params?.encoding_format !== void 0) {
1407
+ body.encoding_format = request.params.encoding_format;
1408
+ }
1409
+ if (request.params?.user !== void 0) {
1410
+ body.user = request.params.user;
1411
+ }
1412
+ if (request.params?.input_type !== void 0) {
1413
+ body.input_type = request.params.input_type;
1414
+ }
1415
+ const headers = {
1416
+ "Content-Type": "application/json",
1417
+ Authorization: `Bearer ${apiKey}`
1418
+ };
1419
+ if (request.config.headers) {
1420
+ for (const [key, value] of Object.entries(request.config.headers)) {
1421
+ if (value !== void 0) {
1422
+ headers[key] = value;
1423
+ }
1424
+ }
1425
+ }
1426
+ const response = await doFetch(baseUrl, {
1427
+ method: "POST",
1428
+ headers,
1429
+ body: JSON.stringify(body),
1430
+ signal: request.signal
1431
+ }, request.config, "openrouter", "embedding");
1432
+ const data = await response.json();
1433
+ return {
1434
+ embeddings: data.data.map((d) => ({
1435
+ vector: d.embedding,
1436
+ index: d.index
1437
+ })),
1438
+ usage: { totalTokens: data.usage.total_tokens },
1439
+ metadata: data.usage.cost !== void 0 ? { cost: data.usage.cost } : void 0
1440
+ };
1441
+ }
1442
+ };
1443
+ return model;
1444
+ }
1445
+ };
1446
+ }
1447
+
1278
1448
  // src/providers/openrouter/index.ts
1279
1449
  function createOpenRouterProvider() {
1280
1450
  let currentApiMode = "completions";
1281
1451
  const completionsHandler = createCompletionsLLMHandler();
1282
1452
  const responsesHandler = createResponsesLLMHandler();
1453
+ const embeddingHandler = createEmbeddingHandler();
1283
1454
  const fn = function(modelId, options) {
1284
1455
  const apiMode = options?.api ?? "completions";
1285
1456
  currentApiMode = apiMode;
@@ -1288,7 +1459,8 @@ function createOpenRouterProvider() {
1288
1459
  const modalities = {
1289
1460
  get llm() {
1290
1461
  return currentApiMode === "responses" ? responsesHandler : completionsHandler;
1291
- }
1462
+ },
1463
+ embedding: embeddingHandler
1292
1464
  };
1293
1465
  Object.defineProperties(fn, {
1294
1466
  name: {
@@ -1310,6 +1482,7 @@ function createOpenRouterProvider() {
1310
1482
  const provider = fn;
1311
1483
  completionsHandler._setProvider?.(provider);
1312
1484
  responsesHandler._setProvider?.(provider);
1485
+ embeddingHandler._setProvider?.(provider);
1313
1486
  return provider;
1314
1487
  }
1315
1488
  var openrouter = createOpenRouterProvider();