@ai-sdk/provider-utils 5.0.0-beta.4 → 5.0.0-beta.49

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.
Files changed (117) hide show
  1. package/CHANGELOG.md +373 -11
  2. package/dist/index.d.ts +1460 -553
  3. package/dist/index.js +1044 -361
  4. package/dist/index.js.map +1 -1
  5. package/dist/test/index.d.ts +2 -1
  6. package/dist/test/index.js +18 -37
  7. package/dist/test/index.js.map +1 -1
  8. package/package.json +16 -16
  9. package/src/add-additional-properties-to-json-schema.ts +1 -1
  10. package/src/as-array.ts +12 -0
  11. package/src/cancel-response-body.ts +19 -0
  12. package/src/convert-image-model-file-to-data-uri.ts +1 -1
  13. package/src/convert-inline-file-data-to-uint8-array.ts +30 -0
  14. package/src/create-tool-name-mapping.ts +1 -1
  15. package/src/detect-media-type.ts +312 -0
  16. package/src/download-blob.ts +8 -9
  17. package/src/extract-lines.ts +31 -0
  18. package/src/fetch-with-validated-redirects.ts +87 -0
  19. package/src/filter-nullable.ts +11 -0
  20. package/src/get-error-message.ts +1 -15
  21. package/src/get-from-api.ts +2 -2
  22. package/src/has-required-key.ts +6 -0
  23. package/src/index.ts +47 -12
  24. package/src/inject-json-instruction.ts +1 -1
  25. package/src/is-browser-runtime.ts +13 -0
  26. package/src/is-buffer.ts +9 -0
  27. package/src/is-json-serializable.ts +29 -0
  28. package/src/is-provider-reference.ts +21 -0
  29. package/src/is-same-origin.ts +19 -0
  30. package/src/is-url-supported.ts +17 -2
  31. package/src/load-api-key.ts +1 -1
  32. package/src/load-setting.ts +1 -1
  33. package/src/map-reasoning-to-provider.ts +108 -0
  34. package/src/maybe-promise-like.ts +3 -0
  35. package/src/parse-json-event-stream.ts +3 -3
  36. package/src/parse-json.ts +3 -3
  37. package/src/parse-provider-options.ts +1 -1
  38. package/src/post-to-api.ts +4 -4
  39. package/src/provider-defined-tool-factory.ts +129 -0
  40. package/src/provider-executed-tool-factory.ts +69 -0
  41. package/src/read-response-with-size-limit.ts +4 -0
  42. package/src/resolve-full-media-type.ts +49 -0
  43. package/src/resolve-provider-reference.ts +26 -0
  44. package/src/resolve.ts +16 -1
  45. package/src/response-handler.ts +3 -3
  46. package/src/schema.ts +11 -4
  47. package/src/secure-json-parse.ts +1 -1
  48. package/src/serialize-model-options.ts +63 -0
  49. package/src/streaming-tool-call-tracker.ts +241 -0
  50. package/src/test/convert-response-stream-to-array.ts +1 -1
  51. package/src/test/is-node-version.ts +22 -1
  52. package/src/to-json-schema/zod3-to-json-schema/options.ts +3 -3
  53. package/src/to-json-schema/zod3-to-json-schema/parse-def.ts +3 -3
  54. package/src/to-json-schema/zod3-to-json-schema/parse-types.ts +22 -22
  55. package/src/to-json-schema/zod3-to-json-schema/parsers/array.ts +3 -3
  56. package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.ts +1 -1
  57. package/src/to-json-schema/zod3-to-json-schema/parsers/branded.ts +2 -2
  58. package/src/to-json-schema/zod3-to-json-schema/parsers/catch.ts +2 -2
  59. package/src/to-json-schema/zod3-to-json-schema/parsers/date.ts +4 -4
  60. package/src/to-json-schema/zod3-to-json-schema/parsers/default.ts +3 -3
  61. package/src/to-json-schema/zod3-to-json-schema/parsers/effects.ts +3 -3
  62. package/src/to-json-schema/zod3-to-json-schema/parsers/enum.ts +1 -1
  63. package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.ts +5 -5
  64. package/src/to-json-schema/zod3-to-json-schema/parsers/literal.ts +1 -1
  65. package/src/to-json-schema/zod3-to-json-schema/parsers/map.ts +4 -5
  66. package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.ts +1 -1
  67. package/src/to-json-schema/zod3-to-json-schema/parsers/never.ts +1 -2
  68. package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.ts +4 -4
  69. package/src/to-json-schema/zod3-to-json-schema/parsers/number.ts +1 -1
  70. package/src/to-json-schema/zod3-to-json-schema/parsers/object.ts +3 -3
  71. package/src/to-json-schema/zod3-to-json-schema/parsers/optional.ts +3 -3
  72. package/src/to-json-schema/zod3-to-json-schema/parsers/pipeline.ts +10 -8
  73. package/src/to-json-schema/zod3-to-json-schema/parsers/promise.ts +3 -3
  74. package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.ts +2 -2
  75. package/src/to-json-schema/zod3-to-json-schema/parsers/record.ts +9 -10
  76. package/src/to-json-schema/zod3-to-json-schema/parsers/set.ts +3 -3
  77. package/src/to-json-schema/zod3-to-json-schema/parsers/string.ts +2 -2
  78. package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.ts +3 -3
  79. package/src/to-json-schema/zod3-to-json-schema/parsers/undefined.ts +1 -2
  80. package/src/to-json-schema/zod3-to-json-schema/parsers/union.ts +3 -3
  81. package/src/to-json-schema/zod3-to-json-schema/parsers/unknown.ts +1 -2
  82. package/src/to-json-schema/zod3-to-json-schema/refs.ts +3 -3
  83. package/src/to-json-schema/zod3-to-json-schema/select-parser.ts +2 -2
  84. package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.ts +3 -3
  85. package/src/types/assistant-model-message.ts +5 -3
  86. package/src/types/content-part.ts +158 -19
  87. package/src/types/context.ts +4 -0
  88. package/src/types/executable-tool.ts +17 -0
  89. package/src/types/execute-tool.ts +29 -9
  90. package/src/types/file-data.ts +48 -0
  91. package/src/types/index.ts +29 -11
  92. package/src/types/infer-tool-context.ts +41 -0
  93. package/src/types/infer-tool-input.ts +7 -0
  94. package/src/types/infer-tool-output.ts +7 -0
  95. package/src/types/infer-tool-set-context.ts +44 -0
  96. package/src/types/model-message.ts +4 -4
  97. package/src/types/never-optional.ts +7 -0
  98. package/src/types/provider-options.ts +1 -1
  99. package/src/types/provider-reference.ts +10 -0
  100. package/src/types/sandbox.ts +217 -0
  101. package/src/types/system-model-message.ts +1 -1
  102. package/src/types/tool-approval-request.ts +13 -0
  103. package/src/types/tool-execute-function.ts +56 -0
  104. package/src/types/tool-model-message.ts +3 -3
  105. package/src/types/tool-needs-approval-function.ts +39 -0
  106. package/src/types/tool-set.ts +22 -0
  107. package/src/types/tool.ts +278 -225
  108. package/src/types/user-model-message.ts +2 -2
  109. package/src/validate-download-url.ts +120 -33
  110. package/src/validate-types.ts +5 -3
  111. package/dist/index.d.mts +0 -1455
  112. package/dist/index.mjs +0 -2754
  113. package/dist/index.mjs.map +0 -1
  114. package/dist/test/index.d.mts +0 -17
  115. package/dist/test/index.mjs +0 -77
  116. package/dist/test/index.mjs.map +0 -1
  117. package/src/provider-tool-factory.ts +0 -125
@@ -9,9 +9,10 @@ declare function convertReadableStreamToArray<T>(stream: ReadableStream<T>): Pro
9
9
  declare function convertResponseStreamToArray(response: Response): Promise<string[]>;
10
10
 
11
11
  declare function isNodeVersion(version: number): boolean;
12
+ declare function isNodeVersionAtLeast(major: number, minor?: number, patch?: number): boolean;
12
13
 
13
14
  declare function mockId({ prefix, }?: {
14
15
  prefix?: string;
15
16
  }): () => string;
16
17
 
17
- export { convertArrayToAsyncIterable, convertArrayToReadableStream, convertAsyncIterableToArray, convertReadableStreamToArray, convertResponseStreamToArray, isNodeVersion, mockId };
18
+ export { convertArrayToAsyncIterable, convertArrayToReadableStream, convertAsyncIterableToArray, convertReadableStreamToArray, convertResponseStreamToArray, isNodeVersion, isNodeVersionAtLeast, mockId };
@@ -1,35 +1,3 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/test/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- convertArrayToAsyncIterable: () => convertArrayToAsyncIterable,
24
- convertArrayToReadableStream: () => convertArrayToReadableStream,
25
- convertAsyncIterableToArray: () => convertAsyncIterableToArray,
26
- convertReadableStreamToArray: () => convertReadableStreamToArray,
27
- convertResponseStreamToArray: () => convertResponseStreamToArray,
28
- isNodeVersion: () => isNodeVersion,
29
- mockId: () => mockId
30
- });
31
- module.exports = __toCommonJS(index_exports);
32
-
33
1
  // src/test/convert-array-to-async-iterable.ts
34
2
  function convertArrayToAsyncIterable(values) {
35
3
  return {
@@ -79,16 +47,29 @@ async function convertReadableStreamToArray(stream) {
79
47
 
80
48
  // src/test/convert-response-stream-to-array.ts
81
49
  async function convertResponseStreamToArray(response) {
82
- return convertReadableStreamToArray(
50
+ return await convertReadableStreamToArray(
83
51
  response.body.pipeThrough(new TextDecoderStream())
84
52
  );
85
53
  }
86
54
 
87
55
  // src/test/is-node-version.ts
56
+ function getNodeVersionParts() {
57
+ return process.versions.node.split(".").map((version) => Number.parseInt(version, 10));
58
+ }
88
59
  function isNodeVersion(version) {
89
- const nodeMajorVersion = parseInt(process.version.slice(1).split(".")[0], 10);
60
+ const [nodeMajorVersion] = getNodeVersionParts();
90
61
  return nodeMajorVersion === version;
91
62
  }
63
+ function isNodeVersionAtLeast(major, minor = 0, patch = 0) {
64
+ const [nodeMajorVersion, nodeMinorVersion, nodePatchVersion] = getNodeVersionParts();
65
+ if (nodeMajorVersion !== major) {
66
+ return nodeMajorVersion > major;
67
+ }
68
+ if (nodeMinorVersion !== minor) {
69
+ return nodeMinorVersion > minor;
70
+ }
71
+ return nodePatchVersion >= patch;
72
+ }
92
73
 
93
74
  // src/test/mock-id.ts
94
75
  function mockId({
@@ -97,14 +78,14 @@ function mockId({
97
78
  let counter = 0;
98
79
  return () => `${prefix}-${counter++}`;
99
80
  }
100
- // Annotate the CommonJS export names for ESM import in node:
101
- 0 && (module.exports = {
81
+ export {
102
82
  convertArrayToAsyncIterable,
103
83
  convertArrayToReadableStream,
104
84
  convertAsyncIterableToArray,
105
85
  convertReadableStreamToArray,
106
86
  convertResponseStreamToArray,
107
87
  isNodeVersion,
88
+ isNodeVersionAtLeast,
108
89
  mockId
109
- });
90
+ };
110
91
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/test/index.ts","../../src/test/convert-array-to-async-iterable.ts","../../src/test/convert-array-to-readable-stream.ts","../../src/test/convert-async-iterable-to-array.ts","../../src/test/convert-readable-stream-to-array.ts","../../src/test/convert-response-stream-to-array.ts","../../src/test/is-node-version.ts","../../src/test/mock-id.ts"],"sourcesContent":["export * from './convert-array-to-async-iterable';\nexport * from './convert-array-to-readable-stream';\nexport * from './convert-async-iterable-to-array';\nexport * from './convert-readable-stream-to-array';\nexport * from './convert-response-stream-to-array';\nexport * from './is-node-version';\nexport * from './mock-id';\n","export function convertArrayToAsyncIterable<T>(values: T[]): AsyncIterable<T> {\n return {\n async *[Symbol.asyncIterator]() {\n for (const value of values) {\n yield value;\n }\n },\n };\n}\n","export function convertArrayToReadableStream<T>(\n values: T[],\n): ReadableStream<T> {\n return new ReadableStream({\n start(controller) {\n try {\n for (const value of values) {\n controller.enqueue(value);\n }\n } finally {\n controller.close();\n }\n },\n });\n}\n","export async function convertAsyncIterableToArray<T>(\n iterable: AsyncIterable<T>,\n): Promise<T[]> {\n const result: T[] = [];\n for await (const item of iterable) {\n result.push(item);\n }\n return result;\n}\n","export async function convertReadableStreamToArray<T>(\n stream: ReadableStream<T>,\n): Promise<T[]> {\n const reader = stream.getReader();\n const result: T[] = [];\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n result.push(value);\n }\n\n return result;\n}\n","import { convertReadableStreamToArray } from './convert-readable-stream-to-array';\n\nexport async function convertResponseStreamToArray(\n response: Response,\n): Promise<string[]> {\n return convertReadableStreamToArray(\n response.body!.pipeThrough(new TextDecoderStream()),\n );\n}\n","export function isNodeVersion(version: number) {\n const nodeMajorVersion = parseInt(process.version.slice(1).split('.')[0], 10);\n return nodeMajorVersion === version;\n}\n","export function mockId({\n prefix = 'id',\n}: {\n prefix?: string;\n} = {}): () => string {\n let counter = 0;\n return () => `${prefix}-${counter++}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,SAAS,4BAA+B,QAA+B;AAC5E,SAAO;AAAA,IACL,QAAQ,OAAO,aAAa,IAAI;AAC9B,iBAAW,SAAS,QAAQ;AAC1B,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;ACRO,SAAS,6BACd,QACmB;AACnB,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,YAAY;AAChB,UAAI;AACF,mBAAW,SAAS,QAAQ;AAC1B,qBAAW,QAAQ,KAAK;AAAA,QAC1B;AAAA,MACF,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;ACdA,eAAsB,4BACpB,UACc;AACd,QAAM,SAAc,CAAC;AACrB,mBAAiB,QAAQ,UAAU;AACjC,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;;;ACRA,eAAsB,6BACpB,QACc;AACd,QAAM,SAAS,OAAO,UAAU;AAChC,QAAM,SAAc,CAAC;AAErB,SAAO,MAAM;AACX,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AACV,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,SAAO;AACT;;;ACXA,eAAsB,6BACpB,UACmB;AACnB,SAAO;AAAA,IACL,SAAS,KAAM,YAAY,IAAI,kBAAkB,CAAC;AAAA,EACpD;AACF;;;ACRO,SAAS,cAAc,SAAiB;AAC7C,QAAM,mBAAmB,SAAS,QAAQ,QAAQ,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE;AAC5E,SAAO,qBAAqB;AAC9B;;;ACHO,SAAS,OAAO;AAAA,EACrB,SAAS;AACX,IAEI,CAAC,GAAiB;AACpB,MAAI,UAAU;AACd,SAAO,MAAM,GAAG,MAAM,IAAI,SAAS;AACrC;","names":[]}
1
+ {"version":3,"sources":["../../src/test/convert-array-to-async-iterable.ts","../../src/test/convert-array-to-readable-stream.ts","../../src/test/convert-async-iterable-to-array.ts","../../src/test/convert-readable-stream-to-array.ts","../../src/test/convert-response-stream-to-array.ts","../../src/test/is-node-version.ts","../../src/test/mock-id.ts"],"sourcesContent":["export function convertArrayToAsyncIterable<T>(values: T[]): AsyncIterable<T> {\n return {\n async *[Symbol.asyncIterator]() {\n for (const value of values) {\n yield value;\n }\n },\n };\n}\n","export function convertArrayToReadableStream<T>(\n values: T[],\n): ReadableStream<T> {\n return new ReadableStream({\n start(controller) {\n try {\n for (const value of values) {\n controller.enqueue(value);\n }\n } finally {\n controller.close();\n }\n },\n });\n}\n","export async function convertAsyncIterableToArray<T>(\n iterable: AsyncIterable<T>,\n): Promise<T[]> {\n const result: T[] = [];\n for await (const item of iterable) {\n result.push(item);\n }\n return result;\n}\n","export async function convertReadableStreamToArray<T>(\n stream: ReadableStream<T>,\n): Promise<T[]> {\n const reader = stream.getReader();\n const result: T[] = [];\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n result.push(value);\n }\n\n return result;\n}\n","import { convertReadableStreamToArray } from './convert-readable-stream-to-array';\n\nexport async function convertResponseStreamToArray(\n response: Response,\n): Promise<string[]> {\n return await convertReadableStreamToArray(\n response.body!.pipeThrough(new TextDecoderStream()),\n );\n}\n","function getNodeVersionParts() {\n return process.versions.node\n .split('.')\n .map(version => Number.parseInt(version, 10));\n}\n\nexport function isNodeVersion(version: number) {\n const [nodeMajorVersion] = getNodeVersionParts();\n return nodeMajorVersion === version;\n}\n\nexport function isNodeVersionAtLeast(major: number, minor = 0, patch = 0) {\n const [nodeMajorVersion, nodeMinorVersion, nodePatchVersion] =\n getNodeVersionParts();\n\n if (nodeMajorVersion !== major) {\n return nodeMajorVersion > major;\n }\n\n if (nodeMinorVersion !== minor) {\n return nodeMinorVersion > minor;\n }\n\n return nodePatchVersion >= patch;\n}\n","export function mockId({\n prefix = 'id',\n}: {\n prefix?: string;\n} = {}): () => string {\n let counter = 0;\n return () => `${prefix}-${counter++}`;\n}\n"],"mappings":";AAAO,SAAS,4BAA+B,QAA+B;AAC5E,SAAO;AAAA,IACL,QAAQ,OAAO,aAAa,IAAI;AAC9B,iBAAW,SAAS,QAAQ;AAC1B,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;ACRO,SAAS,6BACd,QACmB;AACnB,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,YAAY;AAChB,UAAI;AACF,mBAAW,SAAS,QAAQ;AAC1B,qBAAW,QAAQ,KAAK;AAAA,QAC1B;AAAA,MACF,UAAE;AACA,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;ACdA,eAAsB,4BACpB,UACc;AACd,QAAM,SAAc,CAAC;AACrB,mBAAiB,QAAQ,UAAU;AACjC,WAAO,KAAK,IAAI;AAAA,EAClB;AACA,SAAO;AACT;;;ACRA,eAAsB,6BACpB,QACc;AACd,QAAM,SAAS,OAAO,UAAU;AAChC,QAAM,SAAc,CAAC;AAErB,SAAO,MAAM;AACX,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AACV,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,SAAO;AACT;;;ACXA,eAAsB,6BACpB,UACmB;AACnB,SAAO,MAAM;AAAA,IACX,SAAS,KAAM,YAAY,IAAI,kBAAkB,CAAC;AAAA,EACpD;AACF;;;ACRA,SAAS,sBAAsB;AAC7B,SAAO,QAAQ,SAAS,KACrB,MAAM,GAAG,EACT,IAAI,aAAW,OAAO,SAAS,SAAS,EAAE,CAAC;AAChD;AAEO,SAAS,cAAc,SAAiB;AAC7C,QAAM,CAAC,gBAAgB,IAAI,oBAAoB;AAC/C,SAAO,qBAAqB;AAC9B;AAEO,SAAS,qBAAqB,OAAe,QAAQ,GAAG,QAAQ,GAAG;AACxE,QAAM,CAAC,kBAAkB,kBAAkB,gBAAgB,IACzD,oBAAoB;AAEtB,MAAI,qBAAqB,OAAO;AAC9B,WAAO,mBAAmB;AAAA,EAC5B;AAEA,MAAI,qBAAqB,OAAO;AAC9B,WAAO,mBAAmB;AAAA,EAC5B;AAEA,SAAO,oBAAoB;AAC7B;;;ACxBO,SAAS,OAAO;AAAA,EACrB,SAAS;AACX,IAEI,CAAC,GAAiB;AACpB,MAAI,UAAU;AACd,SAAO,MAAM,GAAG,MAAM,IAAI,SAAS;AACrC;","names":[]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@ai-sdk/provider-utils",
3
- "version": "5.0.0-beta.4",
3
+ "version": "5.0.0-beta.49",
4
+ "type": "module",
4
5
  "license": "Apache-2.0",
5
6
  "sideEffects": false,
6
7
  "main": "./dist/index.js",
7
- "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
9
9
  "source": "./src/index.ts",
10
10
  "files": [
@@ -22,25 +22,25 @@
22
22
  "./package.json": "./package.json",
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
- "import": "./dist/index.mjs",
26
- "require": "./dist/index.js"
25
+ "import": "./dist/index.js",
26
+ "default": "./dist/index.js"
27
27
  },
28
28
  "./test": {
29
29
  "types": "./dist/test/index.d.ts",
30
- "import": "./dist/test/index.mjs",
31
- "module": "./dist/test/index.mjs",
32
- "require": "./dist/test/index.js"
30
+ "import": "./dist/test/index.js",
31
+ "default": "./dist/test/index.js"
33
32
  }
34
33
  },
35
34
  "dependencies": {
36
35
  "@standard-schema/spec": "^1.1.0",
37
- "eventsource-parser": "^3.0.6",
38
- "@ai-sdk/provider": "4.0.0-beta.2"
36
+ "@workflow/serde": "4.1.0",
37
+ "eventsource-parser": "^3.0.8",
38
+ "@ai-sdk/provider": "4.0.0-beta.19"
39
39
  },
40
40
  "devDependencies": {
41
- "@types/node": "20.17.24",
41
+ "@types/node": "22.19.19",
42
42
  "msw": "2.7.0",
43
- "tsup": "^8",
43
+ "tsup": "^8.5.1",
44
44
  "typescript": "5.8.3",
45
45
  "zod": "3.25.76",
46
46
  "@vercel/ai-tsconfig": "0.0.0"
@@ -49,15 +49,17 @@
49
49
  "zod": "^3.25.76 || ^4.1.8"
50
50
  },
51
51
  "engines": {
52
- "node": ">=18"
52
+ "node": ">=22"
53
53
  },
54
54
  "publishConfig": {
55
- "access": "public"
55
+ "access": "public",
56
+ "provenance": true
56
57
  },
57
58
  "homepage": "https://ai-sdk.dev/docs",
58
59
  "repository": {
59
60
  "type": "git",
60
- "url": "git+https://github.com/vercel/ai.git"
61
+ "url": "https://github.com/vercel/ai",
62
+ "directory": "packages/provider-utils"
61
63
  },
62
64
  "bugs": {
63
65
  "url": "https://github.com/vercel/ai/issues"
@@ -69,9 +71,7 @@
69
71
  "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
70
72
  "build:watch": "pnpm clean && tsup --watch",
71
73
  "clean": "del-cli dist *.tsbuildinfo",
72
- "lint": "eslint \"./**/*.ts*\"",
73
74
  "type-check": "tsc --build",
74
- "prettier-check": "prettier --check \"./**/*.ts*\"",
75
75
  "test": "pnpm test:node && pnpm test:edge",
76
76
  "test:update": "pnpm test:node -u",
77
77
  "test:watch": "vitest --config vitest.node.config.js",
@@ -1,4 +1,4 @@
1
- import { JSONSchema7, JSONSchema7Definition } from '@ai-sdk/provider';
1
+ import type { JSONSchema7, JSONSchema7Definition } from '@ai-sdk/provider';
2
2
 
3
3
  /**
4
4
  * Recursively adds additionalProperties: false to the JSON schema. This is necessary because some providers (e.g. OpenAI) do not support additionalProperties: true.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A value that can be provided either as a single item, an array of items,
3
+ * or be left undefined.
4
+ */
5
+ export type Arrayable<T> = T | T[] | undefined;
6
+
7
+ /**
8
+ * Normalizes a possibly undefined or non-array value into an array.
9
+ */
10
+ export function asArray<T>(value: Arrayable<T>): T[] {
11
+ return value === undefined ? [] : Array.isArray(value) ? value : [value];
12
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cancels a response body to release the underlying connection.
3
+ *
4
+ * When a fetch Response is rejected without consuming its body (e.g. a failed
5
+ * status code, an open-redirect rejection, or a Content-Length that exceeds the
6
+ * size limit), the underlying TCP socket is not returned to the connection pool
7
+ * and may stay open until the process runs out of file descriptors. Cancelling
8
+ * the body avoids this leak.
9
+ *
10
+ * Errors thrown while cancelling are ignored: the body may already be locked,
11
+ * disturbed, or absent, none of which should mask the original rejection.
12
+ */
13
+ export async function cancelResponseBody(response: Response): Promise<void> {
14
+ try {
15
+ await response.body?.cancel();
16
+ } catch {
17
+ // Ignore cancel errors so the original rejection is preserved.
18
+ }
19
+ }
@@ -1,4 +1,4 @@
1
- import { ImageModelV4File } from '@ai-sdk/provider';
1
+ import type { ImageModelV4File } from '@ai-sdk/provider';
2
2
  import { convertUint8ArrayToBase64 } from './uint8-utils';
3
3
 
4
4
  /**
@@ -0,0 +1,30 @@
1
+ import type { FilePart } from './types/content-part';
2
+ import { convertBase64ToUint8Array } from './uint8-utils';
3
+
4
+ type InlineFileData = Extract<
5
+ FilePart['data'],
6
+ { type: 'data' } | { type: 'text' }
7
+ >;
8
+
9
+ /**
10
+ * Converts inline file data (a tagged `data` or `text` shape) into raw bytes.
11
+ *
12
+ * - `{ type: 'text', text }` → UTF-8 encoded bytes
13
+ * - `{ type: 'data', data: Uint8Array | Buffer }` → returned as-is
14
+ * - `{ type: 'data', data: ArrayBuffer }` → wrapped in a `Uint8Array`
15
+ * - `{ type: 'data', data: string }` → decoded as base64
16
+ */
17
+ export function convertInlineFileDataToUint8Array(
18
+ data: InlineFileData,
19
+ ): Uint8Array {
20
+ if (data.type === 'text') {
21
+ return new TextEncoder().encode(data.text);
22
+ }
23
+ if (data.data instanceof Uint8Array) {
24
+ return data.data;
25
+ }
26
+ if (data.data instanceof ArrayBuffer) {
27
+ return new Uint8Array(data.data);
28
+ }
29
+ return convertBase64ToUint8Array(data.data);
30
+ }
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  LanguageModelV4FunctionTool,
3
3
  LanguageModelV4ProviderTool,
4
4
  } from '@ai-sdk/provider';
@@ -0,0 +1,312 @@
1
+ import { convertBase64ToUint8Array } from './uint8-utils';
2
+
3
+ const imageMediaTypeSignatures = [
4
+ {
5
+ mediaType: 'image/gif' as const,
6
+ bytesPrefix: [0x47, 0x49, 0x46], // GIF
7
+ },
8
+ {
9
+ mediaType: 'image/png' as const,
10
+ bytesPrefix: [0x89, 0x50, 0x4e, 0x47], // PNG
11
+ },
12
+ {
13
+ mediaType: 'image/jpeg' as const,
14
+ bytesPrefix: [0xff, 0xd8], // JPEG
15
+ },
16
+ {
17
+ mediaType: 'image/webp' as const,
18
+ bytesPrefix: [
19
+ 0x52,
20
+ 0x49,
21
+ 0x46,
22
+ 0x46, // "RIFF"
23
+ null,
24
+ null,
25
+ null,
26
+ null, // file size (variable)
27
+ 0x57,
28
+ 0x45,
29
+ 0x42,
30
+ 0x50, // "WEBP"
31
+ ],
32
+ },
33
+ {
34
+ mediaType: 'image/bmp' as const,
35
+ bytesPrefix: [0x42, 0x4d],
36
+ },
37
+ {
38
+ mediaType: 'image/tiff' as const,
39
+ bytesPrefix: [0x49, 0x49, 0x2a, 0x00],
40
+ },
41
+ {
42
+ mediaType: 'image/tiff' as const,
43
+ bytesPrefix: [0x4d, 0x4d, 0x00, 0x2a],
44
+ },
45
+ {
46
+ mediaType: 'image/avif' as const,
47
+ bytesPrefix: [
48
+ 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66,
49
+ ],
50
+ },
51
+ {
52
+ mediaType: 'image/heic' as const,
53
+ bytesPrefix: [
54
+ 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63,
55
+ ],
56
+ },
57
+ ] as const;
58
+
59
+ const documentMediaTypeSignatures = [
60
+ {
61
+ mediaType: 'application/pdf' as const,
62
+ bytesPrefix: [0x25, 0x50, 0x44, 0x46], // %PDF
63
+ },
64
+ ] as const;
65
+
66
+ const audioMediaTypeSignatures = [
67
+ {
68
+ mediaType: 'audio/mpeg' as const,
69
+ bytesPrefix: [0xff, 0xfb],
70
+ },
71
+ {
72
+ mediaType: 'audio/mpeg' as const,
73
+ bytesPrefix: [0xff, 0xfa],
74
+ },
75
+ {
76
+ mediaType: 'audio/mpeg' as const,
77
+ bytesPrefix: [0xff, 0xf3],
78
+ },
79
+ {
80
+ mediaType: 'audio/mpeg' as const,
81
+ bytesPrefix: [0xff, 0xf2],
82
+ },
83
+ {
84
+ mediaType: 'audio/mpeg' as const,
85
+ bytesPrefix: [0xff, 0xe3],
86
+ },
87
+ {
88
+ mediaType: 'audio/mpeg' as const,
89
+ bytesPrefix: [0xff, 0xe2],
90
+ },
91
+ {
92
+ mediaType: 'audio/wav' as const,
93
+ bytesPrefix: [
94
+ 0x52, // R
95
+ 0x49, // I
96
+ 0x46, // F
97
+ 0x46, // F
98
+ null,
99
+ null,
100
+ null,
101
+ null,
102
+ 0x57, // W
103
+ 0x41, // A
104
+ 0x56, // V
105
+ 0x45, // E
106
+ ],
107
+ },
108
+ {
109
+ mediaType: 'audio/ogg' as const,
110
+ bytesPrefix: [0x4f, 0x67, 0x67, 0x53],
111
+ },
112
+ {
113
+ mediaType: 'audio/flac' as const,
114
+ bytesPrefix: [0x66, 0x4c, 0x61, 0x43],
115
+ },
116
+ {
117
+ mediaType: 'audio/aac' as const,
118
+ bytesPrefix: [0x40, 0x15, 0x00, 0x00],
119
+ },
120
+ {
121
+ mediaType: 'audio/mp4' as const,
122
+ bytesPrefix: [0x66, 0x74, 0x79, 0x70],
123
+ },
124
+ {
125
+ mediaType: 'audio/webm',
126
+ bytesPrefix: [0x1a, 0x45, 0xdf, 0xa3],
127
+ },
128
+ ] as const;
129
+
130
+ const videoMediaTypeSignatures = [
131
+ {
132
+ mediaType: 'video/mp4' as const,
133
+ bytesPrefix: [
134
+ 0x00,
135
+ 0x00,
136
+ 0x00,
137
+ null,
138
+ 0x66,
139
+ 0x74,
140
+ 0x79,
141
+ 0x70, // ftyp
142
+ ],
143
+ },
144
+ {
145
+ mediaType: 'video/webm' as const,
146
+ bytesPrefix: [0x1a, 0x45, 0xdf, 0xa3], // EBML
147
+ },
148
+ {
149
+ mediaType: 'video/quicktime' as const,
150
+ bytesPrefix: [
151
+ 0x00,
152
+ 0x00,
153
+ 0x00,
154
+ 0x14,
155
+ 0x66,
156
+ 0x74,
157
+ 0x79,
158
+ 0x70,
159
+ 0x71,
160
+ 0x74, // ftypqt
161
+ ],
162
+ },
163
+ {
164
+ mediaType: 'video/x-msvideo' as const,
165
+ bytesPrefix: [0x52, 0x49, 0x46, 0x46], // RIFF (AVI)
166
+ },
167
+ ] as const;
168
+
169
+ const stripID3 = (data: Uint8Array | string) => {
170
+ const bytes =
171
+ typeof data === 'string' ? convertBase64ToUint8Array(data) : data;
172
+ const id3Size =
173
+ ((bytes[6] & 0x7f) << 21) |
174
+ ((bytes[7] & 0x7f) << 14) |
175
+ ((bytes[8] & 0x7f) << 7) |
176
+ (bytes[9] & 0x7f);
177
+
178
+ // The raw MP3 starts here
179
+ return bytes.slice(id3Size + 10);
180
+ };
181
+
182
+ function stripID3TagsIfPresent(data: Uint8Array | string): Uint8Array | string {
183
+ const hasId3 =
184
+ (typeof data === 'string' && data.startsWith('SUQz')) ||
185
+ (typeof data !== 'string' &&
186
+ data.length > 10 &&
187
+ data[0] === 0x49 && // 'I'
188
+ data[1] === 0x44 && // 'D'
189
+ data[2] === 0x33); // '3'
190
+
191
+ return hasId3 ? stripID3(data) : data;
192
+ }
193
+
194
+ type MediaTypeSignatures = ReadonlyArray<{
195
+ readonly mediaType: string;
196
+ readonly bytesPrefix: ReadonlyArray<number | null>;
197
+ }>;
198
+
199
+ function detectMediaTypeBySignatures<T extends MediaTypeSignatures>({
200
+ data,
201
+ signatures,
202
+ }: {
203
+ data: Uint8Array | string;
204
+ signatures: T;
205
+ }): T[number]['mediaType'] | undefined {
206
+ const processedData = stripID3TagsIfPresent(data);
207
+
208
+ // Convert the first ~18 bytes (24 base64 chars) for consistent detection logic:
209
+ const bytes =
210
+ typeof processedData === 'string'
211
+ ? convertBase64ToUint8Array(
212
+ processedData.substring(0, Math.min(processedData.length, 24)),
213
+ )
214
+ : processedData;
215
+
216
+ for (const signature of signatures) {
217
+ if (
218
+ bytes.length >= signature.bytesPrefix.length &&
219
+ signature.bytesPrefix.every(
220
+ (byte, index) => byte === null || bytes[index] === byte,
221
+ )
222
+ ) {
223
+ return signature.mediaType;
224
+ }
225
+ }
226
+
227
+ return undefined;
228
+ }
229
+
230
+ const topLevelSignatureTables = {
231
+ image: imageMediaTypeSignatures,
232
+ audio: audioMediaTypeSignatures,
233
+ video: videoMediaTypeSignatures,
234
+ application: documentMediaTypeSignatures,
235
+ } as const;
236
+
237
+ type TopLevelMediaType = keyof typeof topLevelSignatureTables;
238
+
239
+ /**
240
+ * Detect the IANA media type of a file from its raw bytes or base64 string.
241
+ *
242
+ * - When `topLevelType` is omitted, every known signature is considered
243
+ * (image, audio, video, and application). Returns `undefined` when the
244
+ * bytes do not match any known signature.
245
+ * - When `topLevelType` is provided, only signatures for that top-level
246
+ * segment are considered. Returns `undefined` for unsupported segments
247
+ * (e.g. `"text"`) or when no signature matches.
248
+ */
249
+ export function detectMediaType({
250
+ data,
251
+ topLevelType,
252
+ }: {
253
+ data: Uint8Array | string;
254
+ topLevelType?: string;
255
+ }): string | undefined {
256
+ if (topLevelType === undefined) {
257
+ return detectMediaTypeBySignatures({
258
+ data,
259
+ signatures: [
260
+ ...imageMediaTypeSignatures,
261
+ ...documentMediaTypeSignatures,
262
+ ...audioMediaTypeSignatures,
263
+ ...videoMediaTypeSignatures,
264
+ ],
265
+ });
266
+ }
267
+
268
+ const signatures = topLevelSignatureTables[topLevelType as TopLevelMediaType];
269
+
270
+ if (signatures === undefined) {
271
+ return undefined;
272
+ }
273
+
274
+ return detectMediaTypeBySignatures({ data, signatures });
275
+ }
276
+
277
+ /**
278
+ * Returns the top-level segment of a media type (the portion before `/`).
279
+ *
280
+ * Examples:
281
+ * - `"image/png"` -> `"image"`
282
+ * - `"image/*"` -> `"image"`
283
+ * - `"image"` -> `"image"`
284
+ * - `"image/"` -> `"image"`
285
+ * - `""` -> `""`
286
+ * - `"/"` -> `""`
287
+ */
288
+ export function getTopLevelMediaType(mediaType: string): string {
289
+ const slashIndex = mediaType.indexOf('/');
290
+ return slashIndex === -1 ? mediaType : mediaType.substring(0, slashIndex);
291
+ }
292
+
293
+ /**
294
+ * Returns `true` only when the given media type has a non-empty, non-wildcard
295
+ * subtype (i.e. matches the form `type/subtype`, and `subtype` is not `*`).
296
+ *
297
+ * Examples:
298
+ * - `"image/png"` -> `true`
299
+ * - `"image/*"` -> `false`
300
+ * - `"image"` -> `false`
301
+ * - `"image/"` -> `false`
302
+ * - `""` -> `false`
303
+ * - `"/"` -> `false`
304
+ */
305
+ export function isFullMediaType(mediaType: string): boolean {
306
+ const slashIndex = mediaType.indexOf('/');
307
+ if (slashIndex === -1) {
308
+ return false;
309
+ }
310
+ const subtype = mediaType.substring(slashIndex + 1);
311
+ return subtype.length > 0 && subtype !== '*';
312
+ }
@@ -1,9 +1,10 @@
1
+ import { cancelResponseBody } from './cancel-response-body';
1
2
  import { DownloadError } from './download-error';
3
+ import { fetchWithValidatedRedirects } from './fetch-with-validated-redirects';
2
4
  import {
3
5
  readResponseWithSizeLimit,
4
6
  DEFAULT_MAX_DOWNLOAD_SIZE,
5
7
  } from './read-response-with-size-limit';
6
- import { validateDownloadUrl } from './validate-download-url';
7
8
 
8
9
  /**
9
10
  * Download a file from a URL and return it as a Blob.
@@ -20,18 +21,16 @@ export async function downloadBlob(
20
21
  url: string,
21
22
  options?: { maxBytes?: number; abortSignal?: AbortSignal },
22
23
  ): Promise<Blob> {
23
- validateDownloadUrl(url);
24
24
  try {
25
- const response = await fetch(url, {
26
- signal: options?.abortSignal,
25
+ const response = await fetchWithValidatedRedirects({
26
+ url,
27
+ abortSignal: options?.abortSignal,
27
28
  });
28
29
 
29
- // Validate final URL after redirects to prevent SSRF via open redirect
30
- if (response.redirected) {
31
- validateDownloadUrl(response.url);
32
- }
33
-
34
30
  if (!response.ok) {
31
+ // Release the connection before rejecting so an error status from an
32
+ // attacker-controlled origin cannot leak open sockets.
33
+ await cancelResponseBody(response);
35
34
  throw new DownloadError({
36
35
  url,
37
36
  statusCode: response.status,
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Extracts a 1-based inclusive line range from `text`, auto-detecting the
3
+ * file's line ending (`\r\n`, `\n`, or `\r`, in that priority).
4
+ *
5
+ * Mixed line endings are not supported: detection picks one and uses it for
6
+ * both the split and the rejoin, so files that mix conventions will not slice
7
+ * cleanly. When neither `startLine` nor `endLine` is provided, the input is
8
+ * returned unchanged. `endLine` past EOF clamps to the last line.
9
+ */
10
+ export function extractLines({
11
+ text,
12
+ startLine,
13
+ endLine,
14
+ }: {
15
+ text: string;
16
+ startLine?: number;
17
+ endLine?: number;
18
+ }): string {
19
+ if (startLine == null && endLine == null) return text;
20
+ const lineEnding = text.includes('\r\n')
21
+ ? '\r\n'
22
+ : text.includes('\n')
23
+ ? '\n'
24
+ : text.includes('\r')
25
+ ? '\r'
26
+ : '\n';
27
+ const lines = text.split(lineEnding);
28
+ const start = Math.max(1, startLine ?? 1) - 1;
29
+ const end = Math.min(lines.length, endLine ?? lines.length);
30
+ return lines.slice(start, end).join(lineEnding);
31
+ }