@anyr/ai-sdk-provider 0.1.3 → 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;
@@ -247,7 +273,21 @@ function emitResponseMetadata(chunk, state, fallbackModelId, controller) {
247
273
  modelId: chunk.model ?? fallbackModelId
248
274
  });
249
275
  }
276
+ function closeReasoning(state, controller) {
277
+ if (state.activeReasoningId !== null) {
278
+ controller.enqueue({ type: "reasoning-end", id: state.activeReasoningId });
279
+ state.activeReasoningId = null;
280
+ }
281
+ }
282
+ function emitReasoningDelta(content, state, controller) {
283
+ if (state.activeReasoningId === null) {
284
+ state.activeReasoningId = generateId();
285
+ controller.enqueue({ type: "reasoning-start", id: state.activeReasoningId });
286
+ }
287
+ controller.enqueue({ type: "reasoning-delta", id: state.activeReasoningId, delta: content });
288
+ }
250
289
  function emitTextDelta(content, state, controller) {
290
+ closeReasoning(state, controller);
251
291
  if (state.activeTextId === null) {
252
292
  state.activeTextId = generateId();
253
293
  controller.enqueue({ type: "text-start", id: state.activeTextId });
@@ -276,7 +316,8 @@ function emitToolCallDeltas(toolCalls, acc, controller) {
276
316
  }
277
317
  }
278
318
  }
279
- function emitFinish(choice, chunk, state, acc, controller) {
319
+ function emitFinish(choice, usage, state, acc, controller) {
320
+ closeReasoning(state, controller);
280
321
  if (state.activeTextId !== null) {
281
322
  controller.enqueue({ type: "text-end", id: state.activeTextId });
282
323
  state.activeTextId = null;
@@ -294,8 +335,8 @@ function emitFinish(choice, chunk, state, acc, controller) {
294
335
  }
295
336
  controller.enqueue({
296
337
  type: "finish",
297
- usage: buildUsage(chunk.usage),
298
- finishReason: mapFinishReason(choice.finish_reason)
338
+ usage: buildUsage(usage),
339
+ finishReason: mapFinishReason(choice?.finish_reason)
299
340
  });
300
341
  }
301
342
  function mapToolChoice(toolChoice) {
@@ -343,14 +384,15 @@ function buildTools(tools) {
343
384
  }));
344
385
  return fns.length > 0 ? fns : void 0;
345
386
  }
346
- function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
347
- const messages = convertToAnyRouterChatMessages(options.prompt);
387
+ function buildRequestBody(modelId, options, settings, stream, perCallOptions, fileCache) {
388
+ const messages = convertToAnyRouterChatMessages(options.prompt, fileCache);
348
389
  const responseFormat = buildResponseFormat(options.responseFormat);
349
390
  const tools = buildTools(options.tools);
350
391
  const toolChoice = mapToolChoice(options.toolChoice);
351
392
  const provider = perCallOptions.provider ?? settings.provider;
352
393
  const sessionId = perCallOptions.sessionId ?? settings.sessionId;
353
394
  const extraBody = { ...settings.extraBody, ...perCallOptions.extraBody };
395
+ const reasoningEffort = mapReasoningEffort(options.reasoning);
354
396
  const body = {
355
397
  model: modelId,
356
398
  messages,
@@ -368,6 +410,7 @@ function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
368
410
  tool_choice: toolChoice,
369
411
  provider: provider ?? void 0,
370
412
  session_id: sessionId ?? void 0,
413
+ reasoning_effort: reasoningEffort,
371
414
  ...extraBody
372
415
  };
373
416
  for (const key of Object.keys(body)) {
@@ -375,8 +418,18 @@ function buildRequestBody(modelId, options, settings, stream, perCallOptions) {
375
418
  }
376
419
  return body;
377
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
+ }
378
431
  var AnyRouterChatLanguageModel = class {
379
- specificationVersion = "v3";
432
+ specificationVersion = "v4";
380
433
  provider;
381
434
  modelId;
382
435
  supportedUrls = {};
@@ -398,11 +451,16 @@ var AnyRouterChatLanguageModel = class {
398
451
  options,
399
452
  this.config.settings,
400
453
  false,
401
- perCallOptions
454
+ perCallOptions,
455
+ this.config.fileCache
402
456
  );
403
457
  const { value: response, responseHeaders } = await postJsonToApi({
404
458
  url: `${this.config.baseURL}/chat/completions`,
405
- headers: combineHeaders(this.config.headers(), options.headers),
459
+ headers: combineHeaders(
460
+ this.config.headers(),
461
+ buildCacheHeaders(this.config.settings, perCallOptions),
462
+ options.headers
463
+ ),
406
464
  body,
407
465
  failedResponseHandler: createJsonErrorResponseHandler({
408
466
  errorSchema: anyRouterErrorSchema,
@@ -416,6 +474,9 @@ var AnyRouterChatLanguageModel = class {
416
474
  const choice = response.choices?.[0];
417
475
  const message = choice?.message;
418
476
  const content = [];
477
+ if (message?.reasoning_content) {
478
+ content.push({ type: "reasoning", text: message.reasoning_content });
479
+ }
419
480
  if (message?.content) {
420
481
  content.push({ type: "text", text: message.content });
421
482
  }
@@ -451,10 +512,21 @@ var AnyRouterChatLanguageModel = class {
451
512
  providerOptions: options.providerOptions,
452
513
  schema: anyRouterProviderOptionsSchema
453
514
  }) ?? {};
454
- 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
+ );
455
523
  const { value: stream, responseHeaders } = await postJsonToApi({
456
524
  url: `${this.config.baseURL}/chat/completions`,
457
- headers: combineHeaders(this.config.headers(), options.headers),
525
+ headers: combineHeaders(
526
+ this.config.headers(),
527
+ buildCacheHeaders(this.config.settings, perCallOptions),
528
+ options.headers
529
+ ),
458
530
  body,
459
531
  failedResponseHandler: createJsonErrorResponseHandler({
460
532
  errorSchema: anyRouterErrorSchema,
@@ -466,8 +538,14 @@ var AnyRouterChatLanguageModel = class {
466
538
  fetch: this.config.fetch
467
539
  });
468
540
  const toolCallAccumulator = {};
469
- const state = { activeTextId: null, metadataEmitted: false };
541
+ const state = {
542
+ activeTextId: null,
543
+ activeReasoningId: null,
544
+ metadataEmitted: false
545
+ };
470
546
  const selfModelId = this.modelId;
547
+ let streamUsage;
548
+ let finishChoice;
471
549
  const transformStream = new TransformStream({
472
550
  start(controller) {
473
551
  controller.enqueue({ type: "stream-start", warnings: [] });
@@ -479,9 +557,13 @@ var AnyRouterChatLanguageModel = class {
479
557
  }
480
558
  const chunk = chunkResult.value;
481
559
  const choice = chunk.choices?.[0];
560
+ if (chunk.usage) streamUsage = chunk.usage;
482
561
  emitResponseMetadata(chunk, state, selfModelId, controller);
483
562
  const delta = choice?.delta;
484
563
  if (delta) {
564
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
565
+ emitReasoningDelta(delta.reasoning_content, state, controller);
566
+ }
485
567
  if (typeof delta.content === "string" && delta.content.length > 0) {
486
568
  emitTextDelta(delta.content, state, controller);
487
569
  }
@@ -490,13 +572,11 @@ var AnyRouterChatLanguageModel = class {
490
572
  }
491
573
  }
492
574
  if (choice?.finish_reason) {
493
- emitFinish(choice, chunk, state, toolCallAccumulator, controller);
575
+ finishChoice = choice;
494
576
  }
495
577
  },
496
578
  flush(controller) {
497
- if (state.activeTextId !== null) {
498
- controller.enqueue({ type: "text-end", id: state.activeTextId });
499
- }
579
+ emitFinish(finishChoice, streamUsage, state, toolCallAccumulator, controller);
500
580
  }
501
581
  });
502
582
  return {
@@ -524,7 +604,7 @@ var embeddingResponseSchema = z.object({
524
604
  }).optional()
525
605
  });
526
606
  var AnyRouterEmbeddingModel = class {
527
- specificationVersion = "v3";
607
+ specificationVersion = "v4";
528
608
  provider;
529
609
  modelId;
530
610
  /** No hard per-call limit imposed by the provider layer. */
@@ -562,6 +642,94 @@ var AnyRouterEmbeddingModel = class {
562
642
  };
563
643
  }
564
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
+ };
565
733
  var anyRouterErrorSchema3 = z.object({
566
734
  error: z.object({
567
735
  code: z.string().optional(),
@@ -579,7 +747,7 @@ var imageGenerationResponseSchema = z.object({
579
747
  )
580
748
  });
581
749
  var AnyRouterImageModel = class {
582
- specificationVersion = "v3";
750
+ specificationVersion = "v4";
583
751
  provider;
584
752
  modelId;
585
753
  /**
@@ -599,6 +767,11 @@ var AnyRouterImageModel = class {
599
767
  functionality: "mask (inpainting) for AnyRouter image generation"
600
768
  });
601
769
  }
770
+ if (options.files && options.files.length > 0) {
771
+ throw new UnsupportedFunctionalityError({
772
+ functionality: "image editing (files) for AnyRouter image generation"
773
+ });
774
+ }
602
775
  const warnings = [];
603
776
  if (options.n > 1) {
604
777
  warnings.push({
@@ -649,13 +822,18 @@ var AnyRouterImageModel = class {
649
822
  var PROVIDER_ID = "anyrouter";
650
823
  function createAnyRouter(options = {}) {
651
824
  const baseURL = withoutTrailingSlash(options.baseURL ?? DEFAULT_BASE_URL) ?? DEFAULT_BASE_URL;
825
+ const sharedFileCache = new FileReferenceCache();
652
826
  function getHeaders() {
827
+ const attribution = options.app ? buildAppAttributionHeaders(options.app) : {};
828
+ if (options.keyless) {
829
+ return combineHeaders(attribution, options.headers);
830
+ }
653
831
  const apiKey = loadApiKey({
654
832
  apiKey: options.apiKey,
655
833
  environmentVariableName: "ANYROUTER_API_KEY",
656
834
  description: "AnyRouter"
657
835
  });
658
- return combineHeaders({ Authorization: `Bearer ${apiKey}` }, options.headers);
836
+ return combineHeaders({ Authorization: `Bearer ${apiKey}`, ...attribution }, options.headers);
659
837
  }
660
838
  function chatModel(modelId) {
661
839
  return new AnyRouterChatLanguageModel(modelId, {
@@ -663,7 +841,8 @@ function createAnyRouter(options = {}) {
663
841
  baseURL,
664
842
  headers: getHeaders,
665
843
  fetch: options.fetch,
666
- settings: options
844
+ settings: options,
845
+ fileCache: sharedFileCache
667
846
  });
668
847
  }
669
848
  function embeddingModel(modelId) {
@@ -684,9 +863,9 @@ function createAnyRouter(options = {}) {
684
863
  }
685
864
  const provider = (modelId) => chatModel(modelId);
686
865
  const providerObj = Object.assign(provider, {
687
- // ProviderV3 required fields
688
- specificationVersion: "v3",
689
- // ProviderV3 required methods
866
+ // ProviderV4 required fields
867
+ specificationVersion: "v4",
868
+ // ProviderV4 required methods
690
869
  languageModel(modelId) {
691
870
  return chatModel(modelId);
692
871
  },
@@ -696,15 +875,15 @@ function createAnyRouter(options = {}) {
696
875
  imageModel(modelId) {
697
876
  return imageModel(modelId);
698
877
  },
699
- // ProviderV3 optional methods (deprecated textEmbeddingModel alias)
700
- textEmbeddingModel(modelId) {
701
- return embeddingModel(modelId);
702
- },
703
878
  // AnyRouter convenience alias
704
879
  chat(modelId) {
705
880
  return chatModel(modelId);
706
881
  },
707
- // 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
708
887
  transcriptionModel(modelId) {
709
888
  throw new NoSuchModelError({ modelId, modelType: "transcriptionModel" });
710
889
  },
@@ -720,6 +899,7 @@ function createAnyRouter(options = {}) {
720
899
  apiKey: options.apiKey,
721
900
  baseURL: options.baseURL,
722
901
  headers: options.headers,
902
+ app: options.app,
723
903
  fetch: options.fetch
724
904
  });
725
905
  }
@@ -728,4 +908,4 @@ function createAnyRouter(options = {}) {
728
908
  }
729
909
  var anyrouter = createAnyRouter();
730
910
 
731
- 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.3",
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
  }