@anyr/ai-sdk-provider 0.1.4 → 0.1.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.js CHANGED
@@ -1,12 +1,35 @@
1
1
  'use strict';
2
2
 
3
+ var provider = require('@ai-sdk/provider');
3
4
  var providerUtils = require('@ai-sdk/provider-utils');
4
5
  var zod = require('zod');
5
- var provider = require('@ai-sdk/provider');
6
6
  var sdk = require('@anyr/sdk');
7
7
 
8
- // src/anyrouter-chat-language-model.ts
9
- function convertToAnyRouterChatMessages(prompt) {
8
+ // src/app-attribution.ts
9
+ var APP_SOURCE = "anyr-ai-sdk-provider";
10
+ var APP_TITLE = "AnyRouter AI SDK Provider";
11
+ var APP_VERSION = "0.1.7";
12
+ var PROVIDER_ATTRIBUTION = {
13
+ source: APP_SOURCE,
14
+ title: APP_TITLE,
15
+ version: APP_VERSION
16
+ };
17
+ function buildAppAttributionHeaders(app) {
18
+ if (!app) return {};
19
+ const headers = {};
20
+ const put = (name, value) => {
21
+ const trimmed = value?.trim();
22
+ if (trimmed) headers[name] = trimmed;
23
+ };
24
+ put("X-AnyRouter-Source", app.source);
25
+ put("X-AnyRouter-Title", app.title);
26
+ put("X-AnyRouter-Version", app.version);
27
+ put("HTTP-Referer", app.url);
28
+ const categories = app.categories?.map((c) => c.trim()).filter(Boolean);
29
+ if (categories?.length) headers["X-AnyRouter-Categories"] = categories.join(",");
30
+ return headers;
31
+ }
32
+ function convertToAnyRouterChatMessages(prompt, fileCache) {
10
33
  const messages = [];
11
34
  for (const message of prompt) {
12
35
  switch (message.role) {
@@ -14,7 +37,7 @@ function convertToAnyRouterChatMessages(prompt) {
14
37
  messages.push({ role: "system", content: message.content });
15
38
  break;
16
39
  case "user":
17
- messages.push(convertUserMessage(message));
40
+ messages.push(convertUserMessage(message, fileCache));
18
41
  break;
19
42
  case "assistant":
20
43
  messages.push(convertAssistantMessage(message));
@@ -34,7 +57,7 @@ function convertToAnyRouterChatMessages(prompt) {
34
57
  }
35
58
  return messages;
36
59
  }
37
- function convertUserMessage(message) {
60
+ function convertUserMessage(message, fileCache) {
38
61
  if (message.content.length === 1 && message.content[0].type === "text") {
39
62
  return { role: "user", content: message.content[0].text };
40
63
  }
@@ -45,7 +68,7 @@ function convertUserMessage(message) {
45
68
  } else if (part.type === "file") {
46
69
  const filePart = part;
47
70
  const mediaType = filePart.mediaType;
48
- const url = toDataUrlOrString(filePart.data, mediaType);
71
+ const url = fileDataToString(filePart, mediaType, fileCache);
49
72
  if (mediaType.startsWith("image/")) {
50
73
  parts.push({ type: "image_url", image_url: { url } });
51
74
  } else {
@@ -125,13 +148,28 @@ function toolResultOutputToString(output) {
125
148
  return JSON.stringify(output);
126
149
  }
127
150
  }
128
- function toDataUrlOrString(data, mediaType) {
129
- if (data instanceof URL) return data.toString();
130
- if (typeof data === "string") {
131
- if (data.startsWith("http") || data.startsWith("data:")) return data;
132
- return `data:${mediaType};base64,${data}`;
151
+ function fileDataToString(filePart, mediaType, fileCache) {
152
+ const d = filePart.data;
153
+ switch (d.type) {
154
+ case "data": {
155
+ if (typeof d.data === "string") {
156
+ if (d.data.startsWith("data:")) return d.data;
157
+ return `data:${mediaType};base64,${d.data}`;
158
+ }
159
+ return `data:${mediaType};base64,${uint8ArrayToBase64(d.data)}`;
160
+ }
161
+ case "url":
162
+ return d.url.toString();
163
+ case "text":
164
+ return `data:${mediaType};base64,${btoa(d.text)}`;
165
+ case "reference": {
166
+ const entry = fileCache?.lookup(d.reference);
167
+ if (entry) return entry.url;
168
+ throw new provider.UnsupportedFunctionalityError({
169
+ functionality: "Unresolved provider file reference \u2014 call provider.files().uploadFile() first to register the file in the session cache"
170
+ });
171
+ }
133
172
  }
134
- return `data:${mediaType};base64,${uint8ArrayToBase64(data)}`;
135
173
  }
136
174
  function uint8ArrayToBase64(bytes) {
137
175
  let binary = "";
@@ -148,7 +186,11 @@ var anyRouterProviderOptionsSchema = zod.z.object({
148
186
  /** Session ID forwarded as `session_id` in the request body. */
149
187
  sessionId: zod.z.string().optional(),
150
188
  /** Extra body fields merged in last (highest precedence). */
151
- extraBody: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
189
+ extraBody: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
190
+ /** Cache this response for N seconds, forwarded as the `cf-aig-cache-ttl` header. */
191
+ cacheTtl: zod.z.number().optional(),
192
+ /** Skip response caching for this call, forwarded as `cf-aig-skip-cache: true`. */
193
+ skipCache: zod.z.boolean().optional()
152
194
  });
153
195
  var anyRouterErrorSchema = zod.z.object({
154
196
  error: zod.z.object({
@@ -182,7 +224,7 @@ var toolCallDeltaSchema = zod.z.object({
182
224
  var messageSchema = zod.z.object({
183
225
  content: zod.z.string().nullish(),
184
226
  reasoning_content: zod.z.string().nullish(),
185
- tool_calls: zod.z.array(toolCallSchema).optional()
227
+ tool_calls: zod.z.array(toolCallSchema).nullish()
186
228
  });
187
229
  var choiceSchema = zod.z.object({
188
230
  message: messageSchema.optional(),
@@ -192,12 +234,12 @@ var generateResponseSchema = zod.z.object({
192
234
  id: zod.z.string().optional(),
193
235
  model: zod.z.string().optional(),
194
236
  choices: zod.z.array(choiceSchema).optional(),
195
- usage: completionUsageSchema.optional()
237
+ usage: completionUsageSchema.nullish()
196
238
  });
197
239
  var deltaSchema = zod.z.object({
198
240
  content: zod.z.string().nullish(),
199
241
  reasoning_content: zod.z.string().nullish(),
200
- tool_calls: zod.z.array(toolCallDeltaSchema).optional()
242
+ tool_calls: zod.z.array(toolCallDeltaSchema).nullish()
201
243
  });
202
244
  var streamChoiceSchema = zod.z.object({
203
245
  delta: deltaSchema.optional(),
@@ -207,7 +249,10 @@ var streamChunkSchema = zod.z.object({
207
249
  id: zod.z.string().optional(),
208
250
  model: zod.z.string().optional(),
209
251
  choices: zod.z.array(streamChoiceSchema).optional(),
210
- usage: completionUsageSchema.optional()
252
+ usage: completionUsageSchema.nullish(),
253
+ // A mid-stream failure arrives as a `data: {"error":{...}}` frame on a 200
254
+ // response, in the same envelope the gateway uses for non-2xx bodies.
255
+ error: anyRouterErrorSchema.shape.error
211
256
  });
212
257
  function mapFinishReason(raw) {
213
258
  switch (raw) {
@@ -221,6 +266,8 @@ function mapFinishReason(raw) {
221
266
  return { unified: "tool-calls", raw };
222
267
  case "content_filter":
223
268
  return { unified: "content-filter", raw };
269
+ case "error":
270
+ return { unified: "error", raw };
224
271
  default:
225
272
  return { unified: "other", raw: raw ?? void 0 };
226
273
  }
@@ -240,6 +287,11 @@ function buildUsage(raw) {
240
287
  }
241
288
  };
242
289
  }
290
+ function mapReasoningEffort(reasoning) {
291
+ if (!reasoning || reasoning === "provider-default") return void 0;
292
+ if (reasoning === "xhigh") return "high";
293
+ return reasoning;
294
+ }
243
295
  function emitResponseMetadata(chunk, state, fallbackModelId, controller) {
244
296
  if (state.metadataEmitted || !(chunk.id || chunk.model)) return;
245
297
  state.metadataEmitted = true;
@@ -292,7 +344,31 @@ function emitToolCallDeltas(toolCalls, acc, controller) {
292
344
  }
293
345
  }
294
346
  }
295
- function emitFinish(choice, chunk, state, acc, controller) {
347
+ function emitDelta(delta, state, acc, controller) {
348
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
349
+ emitReasoningDelta(delta.reasoning_content, state, controller);
350
+ }
351
+ if (typeof delta.content === "string" && delta.content.length > 0) {
352
+ emitTextDelta(delta.content, state, controller);
353
+ }
354
+ if (delta.tool_calls) {
355
+ emitToolCallDeltas(delta.tool_calls, acc, controller);
356
+ }
357
+ }
358
+ function errorFramePart(chunk, request) {
359
+ return {
360
+ type: "error",
361
+ error: new provider.APICallError({
362
+ message: chunk.error?.message ?? "AnyRouter API error",
363
+ url: request.url,
364
+ requestBodyValues: request.body,
365
+ responseHeaders: request.responseHeaders,
366
+ responseBody: JSON.stringify(chunk),
367
+ data: chunk
368
+ })
369
+ };
370
+ }
371
+ function emitFinish(choice, usage, state, acc, controller) {
296
372
  closeReasoning(state, controller);
297
373
  if (state.activeTextId !== null) {
298
374
  controller.enqueue({ type: "text-end", id: state.activeTextId });
@@ -311,8 +387,8 @@ function emitFinish(choice, chunk, state, acc, controller) {
311
387
  }
312
388
  controller.enqueue({
313
389
  type: "finish",
314
- usage: buildUsage(chunk.usage),
315
- finishReason: mapFinishReason(choice.finish_reason)
390
+ usage: buildUsage(usage),
391
+ finishReason: mapFinishReason(choice?.finish_reason)
316
392
  });
317
393
  }
318
394
  function mapToolChoice(toolChoice) {
@@ -360,14 +436,15 @@ function buildTools(tools) {
360
436
  }));
361
437
  return fns.length > 0 ? fns : void 0;
362
438
  }
363
- function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
364
- const messages = convertToAnyRouterChatMessages(options.prompt);
439
+ function buildRequestBody(modelId, options, settings, stream, perCallOptions, fileCache) {
440
+ const messages = convertToAnyRouterChatMessages(options.prompt, fileCache);
365
441
  const responseFormat = buildResponseFormat(options.responseFormat);
366
442
  const tools = buildTools(options.tools);
367
443
  const toolChoice = mapToolChoice(options.toolChoice);
368
444
  const provider = perCallOptions.provider ?? settings.provider;
369
445
  const sessionId = perCallOptions.sessionId ?? settings.sessionId;
370
446
  const extraBody = { ...settings.extraBody, ...perCallOptions.extraBody };
447
+ const reasoningEffort = mapReasoningEffort(options.reasoning);
371
448
  const body = {
372
449
  model: modelId,
373
450
  messages,
@@ -385,6 +462,7 @@ function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
385
462
  tool_choice: toolChoice,
386
463
  provider: provider ?? void 0,
387
464
  session_id: sessionId ?? void 0,
465
+ reasoning_effort: reasoningEffort,
388
466
  ...extraBody
389
467
  };
390
468
  for (const key of Object.keys(body)) {
@@ -392,8 +470,16 @@ function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
392
470
  }
393
471
  return body;
394
472
  }
473
+ function buildCacheHeaders(settings, perCallOptions) {
474
+ const cacheTtl = perCallOptions.cacheTtl ?? settings.cacheTtl;
475
+ const skipCache = perCallOptions.skipCache ?? settings.skipCache;
476
+ const headers = {};
477
+ if (cacheTtl !== void 0) headers["cf-aig-cache-ttl"] = String(cacheTtl);
478
+ if (skipCache === true) headers["cf-aig-skip-cache"] = "true";
479
+ return headers;
480
+ }
395
481
  var AnyRouterChatLanguageModel = class {
396
- specificationVersion = "v3";
482
+ specificationVersion = "v4";
397
483
  provider;
398
484
  modelId;
399
485
  supportedUrls = {};
@@ -403,7 +489,6 @@ var AnyRouterChatLanguageModel = class {
403
489
  this.provider = config.provider;
404
490
  this.config = config;
405
491
  }
406
- // ─── doGenerate ─────────────────────────────────────────────────────────
407
492
  async doGenerate(options) {
408
493
  const perCallOptions = await providerUtils.parseProviderOptions({
409
494
  provider: "anyrouter",
@@ -415,11 +500,16 @@ var AnyRouterChatLanguageModel = class {
415
500
  options,
416
501
  this.config.settings,
417
502
  false,
418
- perCallOptions
503
+ perCallOptions,
504
+ this.config.fileCache
419
505
  );
420
506
  const { value: response, responseHeaders } = await providerUtils.postJsonToApi({
421
507
  url: `${this.config.baseURL}/chat/completions`,
422
- headers: providerUtils.combineHeaders(this.config.headers(), options.headers),
508
+ headers: providerUtils.combineHeaders(
509
+ this.config.headers(),
510
+ buildCacheHeaders(this.config.settings, perCallOptions),
511
+ options.headers
512
+ ),
423
513
  body,
424
514
  failedResponseHandler: providerUtils.createJsonErrorResponseHandler({
425
515
  errorSchema: anyRouterErrorSchema,
@@ -464,17 +554,28 @@ var AnyRouterChatLanguageModel = class {
464
554
  }
465
555
  };
466
556
  }
467
- // ─── doStream ───────────────────────────────────────────────────────────
468
557
  async doStream(options) {
469
558
  const perCallOptions = await providerUtils.parseProviderOptions({
470
559
  provider: "anyrouter",
471
560
  providerOptions: options.providerOptions,
472
561
  schema: anyRouterProviderOptionsSchema
473
562
  }) ?? {};
474
- const body = buildRequestBody(this.modelId, options, this.config.settings, true, perCallOptions);
563
+ const body = buildRequestBody(
564
+ this.modelId,
565
+ options,
566
+ this.config.settings,
567
+ true,
568
+ perCallOptions,
569
+ this.config.fileCache
570
+ );
571
+ const url = `${this.config.baseURL}/chat/completions`;
475
572
  const { value: stream, responseHeaders } = await providerUtils.postJsonToApi({
476
- url: `${this.config.baseURL}/chat/completions`,
477
- headers: providerUtils.combineHeaders(this.config.headers(), options.headers),
573
+ url,
574
+ headers: providerUtils.combineHeaders(
575
+ this.config.headers(),
576
+ buildCacheHeaders(this.config.settings, perCallOptions),
577
+ options.headers
578
+ ),
478
579
  body,
479
580
  failedResponseHandler: providerUtils.createJsonErrorResponseHandler({
480
581
  errorSchema: anyRouterErrorSchema,
@@ -492,6 +593,8 @@ var AnyRouterChatLanguageModel = class {
492
593
  metadataEmitted: false
493
594
  };
494
595
  const selfModelId = this.modelId;
596
+ let streamUsage;
597
+ let finishChoice;
495
598
  const transformStream = new TransformStream({
496
599
  start(controller) {
497
600
  controller.enqueue({ type: "stream-start", warnings: [] });
@@ -502,29 +605,21 @@ var AnyRouterChatLanguageModel = class {
502
605
  return;
503
606
  }
504
607
  const chunk = chunkResult.value;
608
+ if (chunk.error) {
609
+ controller.enqueue(errorFramePart(chunk, { url, body, responseHeaders }));
610
+ finishChoice = { finish_reason: "error" };
611
+ return;
612
+ }
505
613
  const choice = chunk.choices?.[0];
614
+ if (chunk.usage) streamUsage = chunk.usage;
506
615
  emitResponseMetadata(chunk, state, selfModelId, controller);
507
- const delta = choice?.delta;
508
- if (delta) {
509
- if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
510
- emitReasoningDelta(delta.reasoning_content, state, controller);
511
- }
512
- if (typeof delta.content === "string" && delta.content.length > 0) {
513
- emitTextDelta(delta.content, state, controller);
514
- }
515
- if (delta.tool_calls) {
516
- emitToolCallDeltas(delta.tool_calls, toolCallAccumulator, controller);
517
- }
518
- }
616
+ if (choice?.delta) emitDelta(choice.delta, state, toolCallAccumulator, controller);
519
617
  if (choice?.finish_reason) {
520
- emitFinish(choice, chunk, state, toolCallAccumulator, controller);
618
+ finishChoice = choice;
521
619
  }
522
620
  },
523
621
  flush(controller) {
524
- closeReasoning(state, controller);
525
- if (state.activeTextId !== null) {
526
- controller.enqueue({ type: "text-end", id: state.activeTextId });
527
- }
622
+ emitFinish(finishChoice, streamUsage, state, toolCallAccumulator, controller);
528
623
  }
529
624
  });
530
625
  return {
@@ -552,7 +647,7 @@ var embeddingResponseSchema = zod.z.object({
552
647
  }).optional()
553
648
  });
554
649
  var AnyRouterEmbeddingModel = class {
555
- specificationVersion = "v3";
650
+ specificationVersion = "v4";
556
651
  provider;
557
652
  modelId;
558
653
  /** No hard per-call limit imposed by the provider layer. */
@@ -590,100 +685,109 @@ var AnyRouterEmbeddingModel = class {
590
685
  };
591
686
  }
592
687
  };
593
- var anyRouterErrorSchema3 = zod.z.object({
594
- error: zod.z.object({
595
- code: zod.z.string().optional(),
596
- message: zod.z.string().optional()
597
- }).optional()
598
- });
599
- var imageGenerationResponseSchema = zod.z.object({
600
- created: zod.z.number().optional(),
601
- data: zod.z.array(
602
- zod.z.object({
603
- url: zod.z.string().optional(),
604
- b64_json: zod.z.string().optional(),
605
- revised_prompt: zod.z.string().optional()
606
- })
607
- )
608
- });
609
- var AnyRouterImageModel = class {
610
- specificationVersion = "v3";
611
- provider;
612
- modelId;
688
+
689
+ // src/file-reference-cache.ts
690
+ var PROVIDER_KEY = "anyrouter";
691
+ var FileReferenceCache = class {
692
+ store = /* @__PURE__ */ new Map();
613
693
  /**
614
- * Most image generation APIs support only 1 image per call via the
615
- * standard `/images/generations` endpoint.
694
+ * Look up a cached entry via a `SharedV4ProviderReference`.
695
+ * Returns `undefined` when the reference was not created by this cache.
616
696
  */
617
- maxImagesPerCall = 1;
618
- config;
619
- constructor(modelId, config) {
620
- this.modelId = modelId;
621
- this.provider = config.provider;
622
- this.config = config;
697
+ lookup(reference) {
698
+ const hash = reference[PROVIDER_KEY];
699
+ return hash ? this.store.get(hash) : void 0;
623
700
  }
624
- async doGenerate(options) {
625
- if (options.mask) {
626
- throw new provider.UnsupportedFunctionalityError({
627
- functionality: "mask (inpainting) for AnyRouter image generation"
628
- });
629
- }
630
- const warnings = [];
631
- if (options.n > 1) {
632
- warnings.push({
633
- type: "unsupported",
634
- feature: `n=${options.n} (AnyRouter image endpoint supports n=1 per call)`
635
- });
636
- }
637
- const body = {
638
- model: this.modelId,
639
- prompt: options.prompt ?? "",
640
- n: Math.min(options.n, 1),
641
- ...options.size ? { size: options.size } : {},
642
- ...options.seed !== void 0 ? { seed: options.seed } : {},
643
- response_format: "b64_json"
644
- };
645
- const { value: response, responseHeaders } = await providerUtils.postJsonToApi({
646
- url: `${this.config.baseURL}/images/generations`,
647
- headers: providerUtils.combineHeaders(this.config.headers(), options.headers),
648
- body,
649
- failedResponseHandler: providerUtils.createJsonErrorResponseHandler({
650
- errorSchema: anyRouterErrorSchema3,
651
- errorToMessage: (data) => data.error?.message ?? "AnyRouter images API error",
652
- isRetryable: (res) => res.status >= 500
653
- }),
654
- successfulResponseHandler: providerUtils.createJsonResponseHandler(imageGenerationResponseSchema),
655
- abortSignal: options.abortSignal,
656
- fetch: this.config.fetch
657
- });
658
- const images = [];
659
- for (const item of response.data) {
660
- if (item.b64_json) {
661
- images.push(item.b64_json);
662
- } else if (item.url) {
663
- images.push(item.url);
664
- }
701
+ /** Add an entry and return the corresponding provider reference. */
702
+ set(hash, entry) {
703
+ this.store.set(hash, entry);
704
+ return { [PROVIDER_KEY]: hash };
705
+ }
706
+ /** Returns true when the content hash is already cached. */
707
+ has(hash) {
708
+ return this.store.has(hash);
709
+ }
710
+ /**
711
+ * Build a `SharedV4ProviderReference` for the given hash without writing
712
+ * to the cache. Use this to construct the return value of `uploadFile`
713
+ * after confirming the hash is already stored.
714
+ */
715
+ referenceFor(hash) {
716
+ return { [PROVIDER_KEY]: hash };
717
+ }
718
+ };
719
+ async function hashFileData(data) {
720
+ let bytes;
721
+ if (data.type === "text") {
722
+ bytes = new TextEncoder().encode(data.text);
723
+ } else if (typeof data.data === "string") {
724
+ bytes = new TextEncoder().encode(data.data);
725
+ } else {
726
+ bytes = data.data;
727
+ }
728
+ const hashBuffer = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes));
729
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
730
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
731
+ }
732
+ function fileDataVariantToUrl(data, mediaType) {
733
+ if (data.type === "text") {
734
+ return `data:${mediaType};base64,${btoa(data.text)}`;
735
+ }
736
+ if (typeof data.data === "string") {
737
+ if (data.data.startsWith("data:")) return data.data;
738
+ return `data:${mediaType};base64,${data.data}`;
739
+ }
740
+ let binary = "";
741
+ for (let i = 0; i < data.data.length; i++) {
742
+ binary += String.fromCharCode(data.data[i]);
743
+ }
744
+ return `data:${mediaType};base64,${btoa(binary)}`;
745
+ }
746
+
747
+ // src/anyrouter-files.ts
748
+ var AnyRouterFiles = class {
749
+ specificationVersion = "v4";
750
+ provider;
751
+ cache;
752
+ constructor(provider, cache) {
753
+ this.provider = provider;
754
+ this.cache = cache;
755
+ }
756
+ /**
757
+ * Register a file in the in-session cache and return a provider reference.
758
+ *
759
+ * Calling this multiple times with the same file content is idempotent —
760
+ * the hash check prevents redundant re-encoding.
761
+ */
762
+ async uploadFile(options) {
763
+ const hash = await hashFileData(options.data);
764
+ if (!this.cache.has(hash)) {
765
+ const url = fileDataVariantToUrl(options.data, options.mediaType);
766
+ this.cache.set(hash, { url, mediaType: options.mediaType });
665
767
  }
666
768
  return {
667
- images,
668
- warnings,
669
- response: {
670
- timestamp: /* @__PURE__ */ new Date(),
671
- modelId: this.modelId,
672
- headers: responseHeaders
673
- }
769
+ providerReference: this.cache.referenceFor(hash),
770
+ mediaType: options.mediaType,
771
+ filename: options.filename,
772
+ warnings: []
674
773
  };
675
774
  }
676
775
  };
677
776
  var PROVIDER_ID = "anyrouter";
678
777
  function createAnyRouter(options = {}) {
679
778
  const baseURL = providerUtils.withoutTrailingSlash(options.baseURL ?? sdk.DEFAULT_BASE_URL) ?? sdk.DEFAULT_BASE_URL;
779
+ const sharedFileCache = new FileReferenceCache();
680
780
  function getHeaders() {
781
+ const attribution = options.app ? buildAppAttributionHeaders(options.app) : {};
782
+ if (options.keyless) {
783
+ return providerUtils.combineHeaders(attribution, options.headers);
784
+ }
681
785
  const apiKey = providerUtils.loadApiKey({
682
786
  apiKey: options.apiKey,
683
787
  environmentVariableName: "ANYROUTER_API_KEY",
684
788
  description: "AnyRouter"
685
789
  });
686
- return providerUtils.combineHeaders({ Authorization: `Bearer ${apiKey}` }, options.headers);
790
+ return providerUtils.combineHeaders({ Authorization: `Bearer ${apiKey}`, ...attribution }, options.headers);
687
791
  }
688
792
  function chatModel(modelId) {
689
793
  return new AnyRouterChatLanguageModel(modelId, {
@@ -691,7 +795,8 @@ function createAnyRouter(options = {}) {
691
795
  baseURL,
692
796
  headers: getHeaders,
693
797
  fetch: options.fetch,
694
- settings: options
798
+ settings: options,
799
+ fileCache: sharedFileCache
695
800
  });
696
801
  }
697
802
  function embeddingModel(modelId) {
@@ -702,37 +807,30 @@ function createAnyRouter(options = {}) {
702
807
  fetch: options.fetch
703
808
  });
704
809
  }
705
- function imageModel(modelId) {
706
- return new AnyRouterImageModel(modelId, {
707
- provider: PROVIDER_ID,
708
- baseURL,
709
- headers: getHeaders,
710
- fetch: options.fetch
711
- });
712
- }
713
810
  const provider$1 = (modelId) => chatModel(modelId);
714
811
  const providerObj = Object.assign(provider$1, {
715
- // ProviderV3 required fields
716
- specificationVersion: "v3",
717
- // ProviderV3 required methods
812
+ // ProviderV4 required fields
813
+ specificationVersion: "v4",
814
+ // ProviderV4 required methods
718
815
  languageModel(modelId) {
719
816
  return chatModel(modelId);
720
817
  },
721
818
  embeddingModel(modelId) {
722
819
  return embeddingModel(modelId);
723
820
  },
724
- imageModel(modelId) {
725
- return imageModel(modelId);
726
- },
727
- // ProviderV3 optional methods (deprecated textEmbeddingModel alias)
728
- textEmbeddingModel(modelId) {
729
- return embeddingModel(modelId);
730
- },
731
821
  // AnyRouter convenience alias
732
822
  chat(modelId) {
733
823
  return chatModel(modelId);
734
824
  },
735
- // Unsupported model types — throw NoSuchModelError per ProviderV3 contract
825
+ // ProviderV4 file-management interface
826
+ files() {
827
+ return new AnyRouterFiles(PROVIDER_ID, sharedFileCache);
828
+ },
829
+ // Unsupported optional model types — throw NoSuchModelError per ProviderV4 contract.
830
+ // Image/video generation is not an AnyRouter product (text + embeddings only).
831
+ imageModel(modelId) {
832
+ throw new provider.NoSuchModelError({ modelId, modelType: "imageModel" });
833
+ },
736
834
  transcriptionModel(modelId) {
737
835
  throw new provider.NoSuchModelError({ modelId, modelType: "transcriptionModel" });
738
836
  },
@@ -758,6 +856,9 @@ var anyrouter = createAnyRouter();
758
856
 
759
857
  exports.AnyRouterChatLanguageModel = AnyRouterChatLanguageModel;
760
858
  exports.AnyRouterEmbeddingModel = AnyRouterEmbeddingModel;
761
- exports.AnyRouterImageModel = AnyRouterImageModel;
859
+ exports.AnyRouterFiles = AnyRouterFiles;
860
+ exports.FileReferenceCache = FileReferenceCache;
861
+ exports.PROVIDER_ATTRIBUTION = PROVIDER_ATTRIBUTION;
762
862
  exports.anyrouter = anyrouter;
863
+ exports.buildAppAttributionHeaders = buildAppAttributionHeaders;
763
864
  exports.createAnyRouter = createAnyRouter;