@anyr/ai-sdk-provider 0.1.4 → 0.1.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.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { withoutTrailingSlash, parseProviderOptions, postJsonToApi, createJsonResponseHandler, createJsonErrorResponseHandler, combineHeaders, createEventSourceResponseHandler, loadApiKey, generateId } from '@ai-sdk/provider-utils';
2
2
  import { z } from 'zod';
3
3
  import { NoSuchModelError, UnsupportedFunctionalityError } from '@ai-sdk/provider';
4
- import { DEFAULT_BASE_URL, AnyRouter } from '@anyr/sdk';
4
+ import { DEFAULT_BASE_URL, AnyRouter, buildAppAttributionHeaders } from '@anyr/sdk';
5
5
 
6
6
  // src/anyrouter-chat-language-model.ts
7
- function convertToAnyRouterChatMessages(prompt) {
7
+ function convertToAnyRouterChatMessages(prompt, fileCache) {
8
8
  const messages = [];
9
9
  for (const message of prompt) {
10
10
  switch (message.role) {
@@ -12,7 +12,7 @@ function convertToAnyRouterChatMessages(prompt) {
12
12
  messages.push({ role: "system", content: message.content });
13
13
  break;
14
14
  case "user":
15
- messages.push(convertUserMessage(message));
15
+ messages.push(convertUserMessage(message, fileCache));
16
16
  break;
17
17
  case "assistant":
18
18
  messages.push(convertAssistantMessage(message));
@@ -32,7 +32,7 @@ function convertToAnyRouterChatMessages(prompt) {
32
32
  }
33
33
  return messages;
34
34
  }
35
- function convertUserMessage(message) {
35
+ function convertUserMessage(message, fileCache) {
36
36
  if (message.content.length === 1 && message.content[0].type === "text") {
37
37
  return { role: "user", content: message.content[0].text };
38
38
  }
@@ -43,7 +43,7 @@ function convertUserMessage(message) {
43
43
  } else if (part.type === "file") {
44
44
  const filePart = part;
45
45
  const mediaType = filePart.mediaType;
46
- const url = toDataUrlOrString(filePart.data, mediaType);
46
+ const url = fileDataToString(filePart, mediaType, fileCache);
47
47
  if (mediaType.startsWith("image/")) {
48
48
  parts.push({ type: "image_url", image_url: { url } });
49
49
  } else {
@@ -123,13 +123,28 @@ function toolResultOutputToString(output) {
123
123
  return JSON.stringify(output);
124
124
  }
125
125
  }
126
- function toDataUrlOrString(data, mediaType) {
127
- if (data instanceof URL) return data.toString();
128
- if (typeof data === "string") {
129
- if (data.startsWith("http") || data.startsWith("data:")) return data;
130
- return `data:${mediaType};base64,${data}`;
126
+ function fileDataToString(filePart, mediaType, fileCache) {
127
+ const d = filePart.data;
128
+ switch (d.type) {
129
+ case "data": {
130
+ if (typeof d.data === "string") {
131
+ if (d.data.startsWith("data:")) return d.data;
132
+ return `data:${mediaType};base64,${d.data}`;
133
+ }
134
+ return `data:${mediaType};base64,${uint8ArrayToBase64(d.data)}`;
135
+ }
136
+ case "url":
137
+ return d.url.toString();
138
+ case "text":
139
+ return `data:${mediaType};base64,${btoa(d.text)}`;
140
+ case "reference": {
141
+ const entry = fileCache?.lookup(d.reference);
142
+ if (entry) return entry.url;
143
+ throw new UnsupportedFunctionalityError({
144
+ functionality: "Unresolved provider file reference \u2014 call provider.files().uploadFile() first to register the file in the session cache"
145
+ });
146
+ }
131
147
  }
132
- return `data:${mediaType};base64,${uint8ArrayToBase64(data)}`;
133
148
  }
134
149
  function uint8ArrayToBase64(bytes) {
135
150
  let binary = "";
@@ -146,7 +161,13 @@ var anyRouterProviderOptionsSchema = z.object({
146
161
  /** Session ID forwarded as `session_id` in the request body. */
147
162
  sessionId: z.string().optional(),
148
163
  /** Extra body fields merged in last (highest precedence). */
149
- extraBody: z.record(z.string(), z.unknown()).optional()
164
+ extraBody: z.record(z.string(), z.unknown()).optional(),
165
+ /** Cache this response for N seconds, forwarded as the `cf-aig-cache-ttl` header. */
166
+ cacheTtl: z.number().optional(),
167
+ /** Custom cache key, forwarded as the `cf-aig-cache-key` header. */
168
+ cacheKey: z.string().optional(),
169
+ /** Skip response caching for this call, forwarded as `cf-aig-skip-cache: true`. */
170
+ skipCache: z.boolean().optional()
150
171
  });
151
172
  var anyRouterErrorSchema = z.object({
152
173
  error: z.object({
@@ -238,6 +259,11 @@ function buildUsage(raw) {
238
259
  }
239
260
  };
240
261
  }
262
+ function mapReasoningEffort(reasoning) {
263
+ if (!reasoning || reasoning === "provider-default") return void 0;
264
+ if (reasoning === "xhigh") return "high";
265
+ return reasoning;
266
+ }
241
267
  function emitResponseMetadata(chunk, state, fallbackModelId, controller) {
242
268
  if (state.metadataEmitted || !(chunk.id || chunk.model)) return;
243
269
  state.metadataEmitted = true;
@@ -290,7 +316,7 @@ function emitToolCallDeltas(toolCalls, acc, controller) {
290
316
  }
291
317
  }
292
318
  }
293
- function emitFinish(choice, chunk, state, acc, controller) {
319
+ function emitFinish(choice, usage, state, acc, controller) {
294
320
  closeReasoning(state, controller);
295
321
  if (state.activeTextId !== null) {
296
322
  controller.enqueue({ type: "text-end", id: state.activeTextId });
@@ -309,8 +335,8 @@ function emitFinish(choice, chunk, state, acc, controller) {
309
335
  }
310
336
  controller.enqueue({
311
337
  type: "finish",
312
- usage: buildUsage(chunk.usage),
313
- finishReason: mapFinishReason(choice.finish_reason)
338
+ usage: buildUsage(usage),
339
+ finishReason: mapFinishReason(choice?.finish_reason)
314
340
  });
315
341
  }
316
342
  function mapToolChoice(toolChoice) {
@@ -358,14 +384,15 @@ function buildTools(tools) {
358
384
  }));
359
385
  return fns.length > 0 ? fns : void 0;
360
386
  }
361
- function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
362
- const messages = convertToAnyRouterChatMessages(options.prompt);
387
+ function buildRequestBody(modelId, options, settings, stream, perCallOptions, fileCache) {
388
+ const messages = convertToAnyRouterChatMessages(options.prompt, fileCache);
363
389
  const responseFormat = buildResponseFormat(options.responseFormat);
364
390
  const tools = buildTools(options.tools);
365
391
  const toolChoice = mapToolChoice(options.toolChoice);
366
392
  const provider = perCallOptions.provider ?? settings.provider;
367
393
  const sessionId = perCallOptions.sessionId ?? settings.sessionId;
368
394
  const extraBody = { ...settings.extraBody, ...perCallOptions.extraBody };
395
+ const reasoningEffort = mapReasoningEffort(options.reasoning);
369
396
  const body = {
370
397
  model: modelId,
371
398
  messages,
@@ -383,6 +410,7 @@ function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
383
410
  tool_choice: toolChoice,
384
411
  provider: provider ?? void 0,
385
412
  session_id: sessionId ?? void 0,
413
+ reasoning_effort: reasoningEffort,
386
414
  ...extraBody
387
415
  };
388
416
  for (const key of Object.keys(body)) {
@@ -390,8 +418,18 @@ function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
390
418
  }
391
419
  return body;
392
420
  }
421
+ function buildCacheHeaders(settings, perCallOptions) {
422
+ const cacheTtl = perCallOptions.cacheTtl ?? settings.cacheTtl;
423
+ const cacheKey = perCallOptions.cacheKey ?? settings.cacheKey;
424
+ const skipCache = perCallOptions.skipCache ?? settings.skipCache;
425
+ const headers = {};
426
+ if (cacheTtl !== void 0) headers["cf-aig-cache-ttl"] = String(cacheTtl);
427
+ if (cacheKey !== void 0) headers["cf-aig-cache-key"] = cacheKey;
428
+ if (skipCache === true) headers["cf-aig-skip-cache"] = "true";
429
+ return headers;
430
+ }
393
431
  var AnyRouterChatLanguageModel = class {
394
- specificationVersion = "v3";
432
+ specificationVersion = "v4";
395
433
  provider;
396
434
  modelId;
397
435
  supportedUrls = {};
@@ -413,11 +451,16 @@ var AnyRouterChatLanguageModel = class {
413
451
  options,
414
452
  this.config.settings,
415
453
  false,
416
- perCallOptions
454
+ perCallOptions,
455
+ this.config.fileCache
417
456
  );
418
457
  const { value: response, responseHeaders } = await postJsonToApi({
419
458
  url: `${this.config.baseURL}/chat/completions`,
420
- headers: combineHeaders(this.config.headers(), options.headers),
459
+ headers: combineHeaders(
460
+ this.config.headers(),
461
+ buildCacheHeaders(this.config.settings, perCallOptions),
462
+ options.headers
463
+ ),
421
464
  body,
422
465
  failedResponseHandler: createJsonErrorResponseHandler({
423
466
  errorSchema: anyRouterErrorSchema,
@@ -469,10 +512,21 @@ var AnyRouterChatLanguageModel = class {
469
512
  providerOptions: options.providerOptions,
470
513
  schema: anyRouterProviderOptionsSchema
471
514
  }) ?? {};
472
- const body = buildRequestBody(this.modelId, options, this.config.settings, true, perCallOptions);
515
+ const body = buildRequestBody(
516
+ this.modelId,
517
+ options,
518
+ this.config.settings,
519
+ true,
520
+ perCallOptions,
521
+ this.config.fileCache
522
+ );
473
523
  const { value: stream, responseHeaders } = await postJsonToApi({
474
524
  url: `${this.config.baseURL}/chat/completions`,
475
- headers: combineHeaders(this.config.headers(), options.headers),
525
+ headers: combineHeaders(
526
+ this.config.headers(),
527
+ buildCacheHeaders(this.config.settings, perCallOptions),
528
+ options.headers
529
+ ),
476
530
  body,
477
531
  failedResponseHandler: createJsonErrorResponseHandler({
478
532
  errorSchema: anyRouterErrorSchema,
@@ -490,6 +544,8 @@ var AnyRouterChatLanguageModel = class {
490
544
  metadataEmitted: false
491
545
  };
492
546
  const selfModelId = this.modelId;
547
+ let streamUsage;
548
+ let finishChoice;
493
549
  const transformStream = new TransformStream({
494
550
  start(controller) {
495
551
  controller.enqueue({ type: "stream-start", warnings: [] });
@@ -501,6 +557,7 @@ var AnyRouterChatLanguageModel = class {
501
557
  }
502
558
  const chunk = chunkResult.value;
503
559
  const choice = chunk.choices?.[0];
560
+ if (chunk.usage) streamUsage = chunk.usage;
504
561
  emitResponseMetadata(chunk, state, selfModelId, controller);
505
562
  const delta = choice?.delta;
506
563
  if (delta) {
@@ -515,14 +572,11 @@ var AnyRouterChatLanguageModel = class {
515
572
  }
516
573
  }
517
574
  if (choice?.finish_reason) {
518
- emitFinish(choice, chunk, state, toolCallAccumulator, controller);
575
+ finishChoice = choice;
519
576
  }
520
577
  },
521
578
  flush(controller) {
522
- closeReasoning(state, controller);
523
- if (state.activeTextId !== null) {
524
- controller.enqueue({ type: "text-end", id: state.activeTextId });
525
- }
579
+ emitFinish(finishChoice, streamUsage, state, toolCallAccumulator, controller);
526
580
  }
527
581
  });
528
582
  return {
@@ -550,7 +604,7 @@ var embeddingResponseSchema = z.object({
550
604
  }).optional()
551
605
  });
552
606
  var AnyRouterEmbeddingModel = class {
553
- specificationVersion = "v3";
607
+ specificationVersion = "v4";
554
608
  provider;
555
609
  modelId;
556
610
  /** No hard per-call limit imposed by the provider layer. */
@@ -588,6 +642,94 @@ var AnyRouterEmbeddingModel = class {
588
642
  };
589
643
  }
590
644
  };
645
+
646
+ // src/file-reference-cache.ts
647
+ var PROVIDER_KEY = "anyrouter";
648
+ var FileReferenceCache = class {
649
+ store = /* @__PURE__ */ new Map();
650
+ /**
651
+ * Look up a cached entry via a `SharedV4ProviderReference`.
652
+ * Returns `undefined` when the reference was not created by this cache.
653
+ */
654
+ lookup(reference) {
655
+ const hash = reference[PROVIDER_KEY];
656
+ return hash ? this.store.get(hash) : void 0;
657
+ }
658
+ /** Add an entry and return the corresponding provider reference. */
659
+ set(hash, entry) {
660
+ this.store.set(hash, entry);
661
+ return { [PROVIDER_KEY]: hash };
662
+ }
663
+ /** Returns true when the content hash is already cached. */
664
+ has(hash) {
665
+ return this.store.has(hash);
666
+ }
667
+ /**
668
+ * Build a `SharedV4ProviderReference` for the given hash without writing
669
+ * to the cache. Use this to construct the return value of `uploadFile`
670
+ * after confirming the hash is already stored.
671
+ */
672
+ referenceFor(hash) {
673
+ return { [PROVIDER_KEY]: hash };
674
+ }
675
+ };
676
+ async function hashFileData(data) {
677
+ let bytes;
678
+ if (data.type === "text") {
679
+ bytes = new TextEncoder().encode(data.text);
680
+ } else if (typeof data.data === "string") {
681
+ bytes = new TextEncoder().encode(data.data);
682
+ } else {
683
+ bytes = data.data;
684
+ }
685
+ const hashBuffer = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes));
686
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
687
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
688
+ }
689
+ function fileDataVariantToUrl(data, mediaType) {
690
+ if (data.type === "text") {
691
+ return `data:${mediaType};base64,${btoa(data.text)}`;
692
+ }
693
+ if (typeof data.data === "string") {
694
+ if (data.data.startsWith("data:")) return data.data;
695
+ return `data:${mediaType};base64,${data.data}`;
696
+ }
697
+ let binary = "";
698
+ for (let i = 0; i < data.data.length; i++) {
699
+ binary += String.fromCharCode(data.data[i]);
700
+ }
701
+ return `data:${mediaType};base64,${btoa(binary)}`;
702
+ }
703
+
704
+ // src/anyrouter-files.ts
705
+ var AnyRouterFiles = class {
706
+ specificationVersion = "v4";
707
+ provider;
708
+ cache;
709
+ constructor(provider, cache) {
710
+ this.provider = provider;
711
+ this.cache = cache;
712
+ }
713
+ /**
714
+ * Register a file in the in-session cache and return a provider reference.
715
+ *
716
+ * Calling this multiple times with the same file content is idempotent —
717
+ * the hash check prevents redundant re-encoding.
718
+ */
719
+ async uploadFile(options) {
720
+ const hash = await hashFileData(options.data);
721
+ if (!this.cache.has(hash)) {
722
+ const url = fileDataVariantToUrl(options.data, options.mediaType);
723
+ this.cache.set(hash, { url, mediaType: options.mediaType });
724
+ }
725
+ return {
726
+ providerReference: this.cache.referenceFor(hash),
727
+ mediaType: options.mediaType,
728
+ filename: options.filename,
729
+ warnings: []
730
+ };
731
+ }
732
+ };
591
733
  var anyRouterErrorSchema3 = z.object({
592
734
  error: z.object({
593
735
  code: z.string().optional(),
@@ -605,7 +747,7 @@ var imageGenerationResponseSchema = z.object({
605
747
  )
606
748
  });
607
749
  var AnyRouterImageModel = class {
608
- specificationVersion = "v3";
750
+ specificationVersion = "v4";
609
751
  provider;
610
752
  modelId;
611
753
  /**
@@ -625,6 +767,11 @@ var AnyRouterImageModel = class {
625
767
  functionality: "mask (inpainting) for AnyRouter image generation"
626
768
  });
627
769
  }
770
+ if (options.files && options.files.length > 0) {
771
+ throw new UnsupportedFunctionalityError({
772
+ functionality: "image editing (files) for AnyRouter image generation"
773
+ });
774
+ }
628
775
  const warnings = [];
629
776
  if (options.n > 1) {
630
777
  warnings.push({
@@ -675,13 +822,18 @@ var AnyRouterImageModel = class {
675
822
  var PROVIDER_ID = "anyrouter";
676
823
  function createAnyRouter(options = {}) {
677
824
  const baseURL = withoutTrailingSlash(options.baseURL ?? DEFAULT_BASE_URL) ?? DEFAULT_BASE_URL;
825
+ const sharedFileCache = new FileReferenceCache();
678
826
  function getHeaders() {
827
+ const attribution = options.app ? buildAppAttributionHeaders(options.app) : {};
828
+ if (options.keyless) {
829
+ return combineHeaders(attribution, options.headers);
830
+ }
679
831
  const apiKey = loadApiKey({
680
832
  apiKey: options.apiKey,
681
833
  environmentVariableName: "ANYROUTER_API_KEY",
682
834
  description: "AnyRouter"
683
835
  });
684
- return combineHeaders({ Authorization: `Bearer ${apiKey}` }, options.headers);
836
+ return combineHeaders({ Authorization: `Bearer ${apiKey}`, ...attribution }, options.headers);
685
837
  }
686
838
  function chatModel(modelId) {
687
839
  return new AnyRouterChatLanguageModel(modelId, {
@@ -689,7 +841,8 @@ function createAnyRouter(options = {}) {
689
841
  baseURL,
690
842
  headers: getHeaders,
691
843
  fetch: options.fetch,
692
- settings: options
844
+ settings: options,
845
+ fileCache: sharedFileCache
693
846
  });
694
847
  }
695
848
  function embeddingModel(modelId) {
@@ -710,9 +863,9 @@ function createAnyRouter(options = {}) {
710
863
  }
711
864
  const provider = (modelId) => chatModel(modelId);
712
865
  const providerObj = Object.assign(provider, {
713
- // ProviderV3 required fields
714
- specificationVersion: "v3",
715
- // ProviderV3 required methods
866
+ // ProviderV4 required fields
867
+ specificationVersion: "v4",
868
+ // ProviderV4 required methods
716
869
  languageModel(modelId) {
717
870
  return chatModel(modelId);
718
871
  },
@@ -722,15 +875,15 @@ function createAnyRouter(options = {}) {
722
875
  imageModel(modelId) {
723
876
  return imageModel(modelId);
724
877
  },
725
- // ProviderV3 optional methods (deprecated textEmbeddingModel alias)
726
- textEmbeddingModel(modelId) {
727
- return embeddingModel(modelId);
728
- },
729
878
  // AnyRouter convenience alias
730
879
  chat(modelId) {
731
880
  return chatModel(modelId);
732
881
  },
733
- // Unsupported model types — throw NoSuchModelError per ProviderV3 contract
882
+ // ProviderV4 file-management interface
883
+ files() {
884
+ return new AnyRouterFiles(PROVIDER_ID, sharedFileCache);
885
+ },
886
+ // Unsupported optional model types — throw NoSuchModelError per ProviderV4 contract
734
887
  transcriptionModel(modelId) {
735
888
  throw new NoSuchModelError({ modelId, modelType: "transcriptionModel" });
736
889
  },
@@ -746,6 +899,7 @@ function createAnyRouter(options = {}) {
746
899
  apiKey: options.apiKey,
747
900
  baseURL: options.baseURL,
748
901
  headers: options.headers,
902
+ app: options.app,
749
903
  fetch: options.fetch
750
904
  });
751
905
  }
@@ -754,4 +908,4 @@ function createAnyRouter(options = {}) {
754
908
  }
755
909
  var anyrouter = createAnyRouter();
756
910
 
757
- export { AnyRouterChatLanguageModel, AnyRouterEmbeddingModel, AnyRouterImageModel, anyrouter, createAnyRouter };
911
+ export { AnyRouterChatLanguageModel, AnyRouterEmbeddingModel, AnyRouterFiles, AnyRouterImageModel, FileReferenceCache, anyrouter, createAnyRouter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anyr/ai-sdk-provider",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "The AnyRouter provider for the Vercel AI SDK — call every AnyRouter model through `createAnyRouter`.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -61,19 +61,19 @@
61
61
  "access": "public"
62
62
  },
63
63
  "dependencies": {
64
- "@anyr/sdk": "^0.1.1"
64
+ "@anyr/sdk": "^0.1.3"
65
65
  },
66
66
  "peerDependencies": {
67
- "@ai-sdk/provider": "^3.0.10 || ^4.0.0-beta",
68
- "@ai-sdk/provider-utils": "^4.0.0 || ^5.0.0-beta",
69
- "ai": "^6.0.0 || ^7.0.0-beta",
67
+ "@ai-sdk/provider": "^4.0.0",
68
+ "@ai-sdk/provider-utils": "^5.0.0",
69
+ "ai": "^7.0.0",
70
70
  "zod": "^3.25.0 || ^4.0.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@ai-sdk/provider": "^3.0.10",
74
- "@ai-sdk/provider-utils": "^4.0.0",
73
+ "@ai-sdk/provider": "^4.0.0",
74
+ "@ai-sdk/provider-utils": "^5.0.0",
75
75
  "@types/node": "^24.0.0",
76
- "ai": "^6.0.159",
76
+ "ai": "^7.0.0",
77
77
  "zod": "^4.4.3"
78
78
  }
79
79
  }