@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
@@ -0,0 +1,129 @@
1
+ import { tool, type ProviderDefinedTool, type Tool } from './types/tool';
2
+ import type { FlexibleSchema } from './schema';
3
+ import type { Context } from './types/context';
4
+ import type { ToolExecuteFunction } from './types/tool-execute-function';
5
+ /**
6
+ * A provider-defined tool is a tool for which the provider defines the input
7
+ * and output schemas, but does not execute the tool.
8
+ */
9
+ export type ProviderDefinedToolFactory<
10
+ INPUT,
11
+ ARGS extends object,
12
+ CONTEXT extends Context = {},
13
+ > = <OUTPUT>(
14
+ options: ARGS & {
15
+ execute?: ToolExecuteFunction<INPUT, OUTPUT, CONTEXT>;
16
+ needsApproval?: Tool<INPUT, OUTPUT, CONTEXT>['needsApproval'];
17
+ toModelOutput?: Tool<INPUT, OUTPUT, CONTEXT>['toModelOutput'];
18
+ onInputStart?: Tool<INPUT, OUTPUT, CONTEXT>['onInputStart'];
19
+ onInputDelta?: Tool<INPUT, OUTPUT, CONTEXT>['onInputDelta'];
20
+ onInputAvailable?: Tool<INPUT, OUTPUT, CONTEXT>['onInputAvailable'];
21
+ },
22
+ ) => ProviderDefinedTool<INPUT, OUTPUT, CONTEXT>;
23
+
24
+ export function createProviderDefinedToolFactory<
25
+ INPUT,
26
+ ARGS extends object,
27
+ CONTEXT extends Context = {},
28
+ >({
29
+ id,
30
+ inputSchema,
31
+ }: {
32
+ id: `${string}.${string}`;
33
+ inputSchema: FlexibleSchema<INPUT>;
34
+ }): ProviderDefinedToolFactory<INPUT, ARGS, CONTEXT> {
35
+ return <OUTPUT>({
36
+ execute,
37
+ outputSchema,
38
+ needsApproval,
39
+ toModelOutput,
40
+ onInputStart,
41
+ onInputDelta,
42
+ onInputAvailable,
43
+ ...args
44
+ }: ARGS & {
45
+ execute?: ToolExecuteFunction<INPUT, OUTPUT, CONTEXT>;
46
+ outputSchema?: FlexibleSchema<OUTPUT>;
47
+ needsApproval?: Tool<INPUT, OUTPUT, CONTEXT>['needsApproval'];
48
+ toModelOutput?: Tool<INPUT, OUTPUT, CONTEXT>['toModelOutput'];
49
+ onInputStart?: Tool<INPUT, OUTPUT, CONTEXT>['onInputStart'];
50
+ onInputDelta?: Tool<INPUT, OUTPUT, CONTEXT>['onInputDelta'];
51
+ onInputAvailable?: Tool<INPUT, OUTPUT, CONTEXT>['onInputAvailable'];
52
+ }): ProviderDefinedTool<INPUT, OUTPUT, CONTEXT> =>
53
+ tool({
54
+ type: 'provider',
55
+ isProviderExecuted: false,
56
+ id,
57
+ args,
58
+ inputSchema,
59
+ outputSchema,
60
+ execute,
61
+ needsApproval,
62
+ toModelOutput,
63
+ onInputStart,
64
+ onInputDelta,
65
+ onInputAvailable,
66
+ }) as ProviderDefinedTool<INPUT, OUTPUT, CONTEXT>;
67
+ }
68
+
69
+ export type ProviderDefinedToolFactoryWithOutputSchema<
70
+ INPUT,
71
+ OUTPUT,
72
+ ARGS extends object,
73
+ CONTEXT extends Context = {},
74
+ > = (
75
+ options: ARGS & {
76
+ execute?: ToolExecuteFunction<INPUT, OUTPUT, CONTEXT>;
77
+ needsApproval?: Tool<INPUT, OUTPUT, CONTEXT>['needsApproval'];
78
+ toModelOutput?: Tool<INPUT, OUTPUT, CONTEXT>['toModelOutput'];
79
+ onInputStart?: Tool<INPUT, OUTPUT, CONTEXT>['onInputStart'];
80
+ onInputDelta?: Tool<INPUT, OUTPUT, CONTEXT>['onInputDelta'];
81
+ onInputAvailable?: Tool<INPUT, OUTPUT, CONTEXT>['onInputAvailable'];
82
+ },
83
+ ) => ProviderDefinedTool<INPUT, OUTPUT, CONTEXT>;
84
+
85
+ export function createProviderDefinedToolFactoryWithOutputSchema<
86
+ INPUT,
87
+ OUTPUT,
88
+ ARGS extends object,
89
+ CONTEXT extends Context = {},
90
+ >({
91
+ id,
92
+ inputSchema,
93
+ outputSchema,
94
+ }: {
95
+ id: `${string}.${string}`;
96
+ inputSchema: FlexibleSchema<INPUT>;
97
+ outputSchema: FlexibleSchema<OUTPUT>;
98
+ }): ProviderDefinedToolFactoryWithOutputSchema<INPUT, OUTPUT, ARGS, CONTEXT> {
99
+ return ({
100
+ execute,
101
+ needsApproval,
102
+ toModelOutput,
103
+ onInputStart,
104
+ onInputDelta,
105
+ onInputAvailable,
106
+ ...args
107
+ }: ARGS & {
108
+ execute?: ToolExecuteFunction<INPUT, OUTPUT, CONTEXT>;
109
+ needsApproval?: Tool<INPUT, OUTPUT, CONTEXT>['needsApproval'];
110
+ toModelOutput?: Tool<INPUT, OUTPUT, CONTEXT>['toModelOutput'];
111
+ onInputStart?: Tool<INPUT, OUTPUT, CONTEXT>['onInputStart'];
112
+ onInputDelta?: Tool<INPUT, OUTPUT, CONTEXT>['onInputDelta'];
113
+ onInputAvailable?: Tool<INPUT, OUTPUT, CONTEXT>['onInputAvailable'];
114
+ }): ProviderDefinedTool<INPUT, OUTPUT, CONTEXT> =>
115
+ tool({
116
+ type: 'provider',
117
+ isProviderExecuted: false,
118
+ id,
119
+ args,
120
+ inputSchema,
121
+ outputSchema,
122
+ execute,
123
+ needsApproval,
124
+ toModelOutput,
125
+ onInputStart,
126
+ onInputDelta,
127
+ onInputAvailable,
128
+ }) as ProviderDefinedTool<INPUT, OUTPUT, CONTEXT>;
129
+ }
@@ -0,0 +1,69 @@
1
+ import type { FlexibleSchema } from './schema';
2
+ import type { Context } from './types/context';
3
+ import { tool, type ProviderExecutedTool, type Tool } from './types/tool';
4
+ /**
5
+ * A provider-executed tool is a tool for which the provider executes the tool.
6
+ */
7
+ export type ProviderExecutedToolFactory<
8
+ INPUT,
9
+ OUTPUT,
10
+ ARGS extends object,
11
+ CONTEXT extends Context = {},
12
+ > = (
13
+ options: ARGS & {
14
+ onInputStart?: Tool<INPUT, OUTPUT, CONTEXT>['onInputStart'];
15
+ onInputDelta?: Tool<INPUT, OUTPUT, CONTEXT>['onInputDelta'];
16
+ onInputAvailable?: Tool<INPUT, OUTPUT, CONTEXT>['onInputAvailable'];
17
+ },
18
+ ) => ProviderExecutedTool<INPUT, OUTPUT, CONTEXT>;
19
+
20
+ export function createProviderExecutedToolFactory<
21
+ INPUT,
22
+ OUTPUT,
23
+ ARGS extends object,
24
+ CONTEXT extends Context = {},
25
+ >({
26
+ id,
27
+ inputSchema,
28
+ outputSchema,
29
+ supportsDeferredResults,
30
+ }: {
31
+ id: `${string}.${string}`;
32
+ inputSchema: FlexibleSchema<INPUT>;
33
+ outputSchema: FlexibleSchema<OUTPUT>;
34
+
35
+ /**
36
+ * Whether this provider-executed tool supports deferred results.
37
+ *
38
+ * When true, the tool result may not be returned in the same turn as the
39
+ * tool call (e.g., when using programmatic tool calling where a server tool
40
+ * triggers a client-executed tool, and the server tool's result is deferred
41
+ * until the client tool is resolved).
42
+ *
43
+ * @default false
44
+ */
45
+ supportsDeferredResults?: boolean;
46
+ }): ProviderExecutedToolFactory<INPUT, OUTPUT, ARGS, CONTEXT> {
47
+ return ({
48
+ onInputStart,
49
+ onInputDelta,
50
+ onInputAvailable,
51
+ ...args
52
+ }: ARGS & {
53
+ onInputStart?: Tool<INPUT, OUTPUT, CONTEXT>['onInputStart'];
54
+ onInputDelta?: Tool<INPUT, OUTPUT, CONTEXT>['onInputDelta'];
55
+ onInputAvailable?: Tool<INPUT, OUTPUT, CONTEXT>['onInputAvailable'];
56
+ }): ProviderExecutedTool<INPUT, OUTPUT, CONTEXT> =>
57
+ tool({
58
+ type: 'provider',
59
+ isProviderExecuted: true,
60
+ id,
61
+ args,
62
+ inputSchema,
63
+ outputSchema,
64
+ onInputStart,
65
+ onInputDelta,
66
+ onInputAvailable,
67
+ supportsDeferredResults,
68
+ }) as ProviderExecutedTool<INPUT, OUTPUT, CONTEXT>;
69
+ }
@@ -1,3 +1,4 @@
1
+ import { cancelResponseBody } from './cancel-response-body';
1
2
  import { DownloadError } from './download-error';
2
3
 
3
4
  /**
@@ -40,6 +41,9 @@ export async function readResponseWithSizeLimit({
40
41
  if (contentLength != null) {
41
42
  const length = parseInt(contentLength, 10);
42
43
  if (!isNaN(length) && length > maxBytes) {
44
+ // Cancel the body so the underlying connection is released back to the
45
+ // pool instead of being left open until the socket is exhausted.
46
+ await cancelResponseBody(response);
43
47
  throw new DownloadError({
44
48
  url,
45
49
  message: `Download of ${url} exceeded maximum size of ${maxBytes} bytes (Content-Length: ${length}).`,
@@ -0,0 +1,49 @@
1
+ import {
2
+ UnsupportedFunctionalityError,
3
+ type LanguageModelV4FilePart,
4
+ } from '@ai-sdk/provider';
5
+ import {
6
+ detectMediaType,
7
+ getTopLevelMediaType,
8
+ isFullMediaType,
9
+ } from './detect-media-type';
10
+
11
+ /**
12
+ * Resolves a file part's media type to a full `type/subtype` form required by
13
+ * providers whose API demands the full IANA media type.
14
+ *
15
+ * - If `part.mediaType` is already a full media type (e.g. `image/png`), it is
16
+ * returned as-is.
17
+ * - Otherwise, when inline bytes are available (`part.data.type === 'data'`),
18
+ * the subtype is sniffed from the bytes using the signature table that
19
+ * corresponds to the top-level segment.
20
+ * - When neither applies (e.g. top-level-only with a URL source, or bytes that
21
+ * cannot be detected), an `UnsupportedFunctionalityError` is thrown.
22
+ */
23
+ export function resolveFullMediaType({
24
+ part,
25
+ }: {
26
+ part: LanguageModelV4FilePart;
27
+ }): string {
28
+ if (isFullMediaType(part.mediaType)) {
29
+ return part.mediaType;
30
+ }
31
+
32
+ if (part.data.type === 'data') {
33
+ const detected = detectMediaType({
34
+ data: part.data.data,
35
+ topLevelType: getTopLevelMediaType(part.mediaType),
36
+ });
37
+ if (detected) {
38
+ return detected;
39
+ }
40
+
41
+ throw new UnsupportedFunctionalityError({
42
+ functionality: `file of media type "${part.mediaType}" must specify subtype since it could not be auto-detected`,
43
+ });
44
+ }
45
+
46
+ throw new UnsupportedFunctionalityError({
47
+ functionality: `file of media type "${part.mediaType}" must specify subtype since it is not passed as inline bytes`,
48
+ });
49
+ }
@@ -0,0 +1,26 @@
1
+ import {
2
+ NoSuchProviderReferenceError,
3
+ type SharedV4ProviderReference,
4
+ } from '@ai-sdk/provider';
5
+ /**
6
+ * Resolves a provider reference to the provider-specific identifier for the
7
+ * given provider. Throws `NoSuchProviderReferenceError` if the provider is not
8
+ * found in the reference mapping.
9
+ */
10
+ export function resolveProviderReference({
11
+ reference,
12
+ provider,
13
+ }: {
14
+ reference: SharedV4ProviderReference;
15
+ provider: string;
16
+ }): string {
17
+ const id = reference[provider];
18
+ if (id != null) {
19
+ return id;
20
+ }
21
+
22
+ throw new NoSuchProviderReferenceError({
23
+ provider,
24
+ reference,
25
+ });
26
+ }
package/src/resolve.ts CHANGED
@@ -1,5 +1,20 @@
1
- import { MaybePromiseLike } from './maybe-promise-like';
1
+ import type { MaybePromiseLike } from './maybe-promise-like';
2
2
 
3
+ /**
4
+ * A value or a lazy provider of a value, each of which may be synchronous or asynchronous.
5
+ *
6
+ * @template T The resolved type after {@link resolve} runs.
7
+ *
8
+ * One of:
9
+ * - A plain value of type {@link T}
10
+ * - A {@link PromiseLike} of {@link T} (e.g. a `Promise<T>`)
11
+ * - A zero-argument function that returns a plain {@link T}
12
+ * - A zero-argument function that returns a {@link PromiseLike} of {@link T}
13
+ *
14
+ * The function form is only invoked when passed to {@link resolve}; it is not distinguished from
15
+ * a {@link T} that happens to be a function—callers should wrap function values if disambiguation
16
+ * is required.
17
+ */
3
18
  export type Resolvable<T> = MaybePromiseLike<T> | (() => MaybePromiseLike<T>);
4
19
 
5
20
  /**
@@ -1,8 +1,8 @@
1
1
  import { APICallError, EmptyResponseBodyError } from '@ai-sdk/provider';
2
2
  import { extractResponseHeaders } from './extract-response-headers';
3
- import { parseJSON, ParseResult, safeParseJSON } from './parse-json';
3
+ import { parseJSON, safeParseJSON, type ParseResult } from './parse-json';
4
4
  import { parseJsonEventStream } from './parse-json-event-stream';
5
- import { FlexibleSchema } from './schema';
5
+ import type { FlexibleSchema } from './schema';
6
6
 
7
7
  export type ResponseHandler<RETURN_TYPE> = (options: {
8
8
  url: string;
@@ -64,7 +64,7 @@ export const createJsonErrorResponseHandler =
64
64
  isRetryable: isRetryable?.(response, parsedError),
65
65
  }),
66
66
  };
67
- } catch (parseError) {
67
+ } catch {
68
68
  return {
69
69
  responseHeaders,
70
70
  value: new APICallError({
package/src/schema.ts CHANGED
@@ -1,6 +1,9 @@
1
- import { JSONSchema7, TypeValidationError } from '@ai-sdk/provider';
2
- import { StandardSchemaV1, StandardJSONSchemaV1 } from '@standard-schema/spec';
3
- import * as z3 from 'zod/v3';
1
+ import { TypeValidationError, type JSONSchema7 } from '@ai-sdk/provider';
2
+ import type {
3
+ StandardSchemaV1,
4
+ StandardJSONSchemaV1,
5
+ } from '@standard-schema/spec';
6
+ import type * as z3 from 'zod/v3';
4
7
  import * as z4 from 'zod/v4';
5
8
  import { addAdditionalPropertiesToJsonSchema } from './add-additional-properties-to-json-schema';
6
9
  import { zod3ToJsonSchema } from './to-json-schema/zod3-to-json-schema';
@@ -133,7 +136,11 @@ export function asSchema<OBJECT>(
133
136
  schema: FlexibleSchema<OBJECT> | undefined,
134
137
  ): Schema<OBJECT> {
135
138
  return schema == null
136
- ? jsonSchema({ properties: {}, additionalProperties: false })
139
+ ? jsonSchema({
140
+ type: 'object',
141
+ properties: {},
142
+ additionalProperties: false,
143
+ })
137
144
  : isSchema(schema)
138
145
  ? schema
139
146
  : '~standard' in schema
@@ -83,7 +83,7 @@ export function secureJsonParse(text: string) {
83
83
  try {
84
84
  // Performance optimization, see https://github.com/fastify/secure-json-parse/pull/90
85
85
  Error.stackTraceLimit = 0;
86
- } catch (e) {
86
+ } catch {
87
87
  // Fallback in case Error is immutable (v8 readonly)
88
88
  return _parse(text);
89
89
  }
@@ -0,0 +1,63 @@
1
+ import type { JSONObject } from '@ai-sdk/provider';
2
+ import { isJSONSerializable } from './is-json-serializable';
3
+ import type { Resolvable } from './resolve';
4
+
5
+ /**
6
+ * Serializes a model instance for workflow step boundaries.
7
+ * Returns the `modelId` plus the JSON-serializable config properties.
8
+ *
9
+ * Non-serializable values are omitted. As a special case, a
10
+ * function-valued `headers` property is resolved during serialization
11
+ * and included if the returned value is JSON-serializable.
12
+ *
13
+ * Used as the body of `static [WORKFLOW_SERIALIZE]` in provider models.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * static [WORKFLOW_SERIALIZE](model: MyLanguageModel) {
18
+ * return serializeModelOptions({
19
+ * modelId: model.modelId,
20
+ * config: model.config,
21
+ * });
22
+ * }
23
+ * ```
24
+ */
25
+ export function serializeModelOptions<
26
+ CONFIG extends {
27
+ headers?: Resolvable<Record<string, string | undefined>>;
28
+ },
29
+ >(options: {
30
+ modelId: string;
31
+ config: CONFIG;
32
+ }): {
33
+ modelId: string;
34
+ config: JSONObject;
35
+ } {
36
+ const serializableConfig: JSONObject = {};
37
+ for (const [key, value] of Object.entries(options.config)) {
38
+ if (key === 'headers') {
39
+ const resolvedHeaders = resolveSync(value);
40
+ if (isJSONSerializable(resolvedHeaders)) {
41
+ serializableConfig[key] = resolvedHeaders;
42
+ }
43
+ } else if (isJSONSerializable(value)) {
44
+ serializableConfig[key] = value;
45
+ }
46
+ }
47
+ return { modelId: options.modelId, config: serializableConfig };
48
+ }
49
+
50
+ function resolveSync<T>(value: Resolvable<T>): T {
51
+ let next: unknown = value;
52
+ if (typeof value === 'function') {
53
+ next = (value as () => unknown)();
54
+ }
55
+
56
+ // the serialization for workflows currently only supports synchronous values
57
+ // TODO introduce SerializationError
58
+ if (next instanceof Promise) {
59
+ throw new Error('Promise returned from resolveSync');
60
+ }
61
+
62
+ return next as T;
63
+ }
@@ -0,0 +1,241 @@
1
+ import {
2
+ InvalidResponseDataError,
3
+ type LanguageModelV4StreamPart,
4
+ type SharedV4ProviderMetadata,
5
+ } from '@ai-sdk/provider';
6
+ import { generateId as defaultGenerateId } from './generate-id';
7
+ import { isParsableJson } from './parse-json';
8
+
9
+ /**
10
+ * Minimal interface for a streaming tool call delta from an OpenAI-compatible API.
11
+ */
12
+ export interface StreamingToolCallDelta {
13
+ index?: number | null;
14
+ id?: string | null;
15
+ type?: string | null;
16
+ function?: {
17
+ name?: string | null;
18
+ arguments?: string | null;
19
+ } | null;
20
+ }
21
+
22
+ export interface StreamingToolCallTrackerOptions<
23
+ DELTA extends StreamingToolCallDelta = StreamingToolCallDelta,
24
+ > {
25
+ /**
26
+ * ID generator function for tool call IDs.
27
+ * Defaults to the standard generateId.
28
+ */
29
+ generateId?: () => string;
30
+
31
+ /**
32
+ * How to validate the `type` field on new tool call deltas.
33
+ * - `'none'`: no validation (default)
34
+ * - `'if-present'`: throw if type is present and not `'function'`
35
+ * - `'required'`: throw if type is not exactly `'function'`
36
+ */
37
+ typeValidation?: 'none' | 'if-present' | 'required';
38
+
39
+ /**
40
+ * Extract provider-specific metadata from a tool call delta.
41
+ * Called once when a new tool call is detected.
42
+ * The returned metadata is stored on the tool call and passed to
43
+ * `buildToolCallProviderMetadata` when the tool call is finalized.
44
+ */
45
+ extractMetadata?: (delta: DELTA) => SharedV4ProviderMetadata | undefined;
46
+
47
+ /**
48
+ * Build the `providerMetadata` object for a `tool-call` event.
49
+ * Receives the metadata previously extracted via `extractMetadata`.
50
+ * If `undefined` is returned, no `providerMetadata` is included in the event.
51
+ */
52
+ buildToolCallProviderMetadata?: (
53
+ metadata: SharedV4ProviderMetadata | undefined,
54
+ ) => SharedV4ProviderMetadata | undefined;
55
+ }
56
+
57
+ interface TrackedToolCall {
58
+ id: string;
59
+ type: 'function';
60
+ function: { name: string; arguments: string };
61
+ hasFinished: boolean;
62
+ metadata?: SharedV4ProviderMetadata;
63
+ }
64
+
65
+ type StreamingToolCallTrackerController = Pick<
66
+ TransformStreamDefaultController<LanguageModelV4StreamPart>,
67
+ 'enqueue'
68
+ >;
69
+
70
+ /**
71
+ * Tracks streaming tool call state across multiple deltas from an
72
+ * OpenAI-compatible chat completion stream. Handles argument accumulation,
73
+ * emits tool-input-start/delta/end and tool-call events, and finalizes
74
+ * unfinished tool calls on flush.
75
+ *
76
+ * Used by openai, openai-compatible, groq, deepseek, and alibaba providers.
77
+ */
78
+ export class StreamingToolCallTracker<
79
+ DELTA extends StreamingToolCallDelta = StreamingToolCallDelta,
80
+ > {
81
+ private toolCalls: TrackedToolCall[] = [];
82
+ private readonly controller: StreamingToolCallTrackerController;
83
+ private readonly _generateId: () => string;
84
+ private readonly typeValidation: 'none' | 'if-present' | 'required';
85
+ private readonly extractMetadata?: (
86
+ delta: DELTA,
87
+ ) => SharedV4ProviderMetadata | undefined;
88
+ private readonly buildToolCallProviderMetadata?: (
89
+ metadata: SharedV4ProviderMetadata | undefined,
90
+ ) => SharedV4ProviderMetadata | undefined;
91
+
92
+ constructor(
93
+ controller: StreamingToolCallTrackerController,
94
+ options: StreamingToolCallTrackerOptions<DELTA> = {},
95
+ ) {
96
+ this.controller = controller;
97
+ this._generateId = options.generateId ?? defaultGenerateId;
98
+ this.typeValidation = options.typeValidation ?? 'none';
99
+ this.extractMetadata = options.extractMetadata;
100
+ this.buildToolCallProviderMetadata = options.buildToolCallProviderMetadata;
101
+ }
102
+
103
+ /**
104
+ * Process a tool call delta from a streaming response chunk.
105
+ * Emits tool-input-start, tool-input-delta, tool-input-end, and tool-call
106
+ * events as appropriate.
107
+ */
108
+ processDelta(toolCallDelta: DELTA): void {
109
+ const index = toolCallDelta.index ?? this.toolCalls.length;
110
+
111
+ if (this.toolCalls[index] == null) {
112
+ this.processNewToolCall(index, toolCallDelta);
113
+ } else {
114
+ this.processExistingToolCall(index, toolCallDelta);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Finalize any unfinished tool calls. Should be called during the stream's
120
+ * flush handler to ensure all tool calls are properly completed.
121
+ */
122
+ flush(): void {
123
+ for (const toolCall of this.toolCalls) {
124
+ if (!toolCall.hasFinished) {
125
+ this.finishToolCall(toolCall);
126
+ }
127
+ }
128
+ }
129
+
130
+ private processNewToolCall(index: number, toolCallDelta: DELTA): void {
131
+ if (this.typeValidation === 'required') {
132
+ if (toolCallDelta.type !== 'function') {
133
+ throw new InvalidResponseDataError({
134
+ data: toolCallDelta,
135
+ message: `Expected 'function' type.`,
136
+ });
137
+ }
138
+ } else if (this.typeValidation === 'if-present') {
139
+ if (toolCallDelta.type != null && toolCallDelta.type !== 'function') {
140
+ throw new InvalidResponseDataError({
141
+ data: toolCallDelta,
142
+ message: `Expected 'function' type.`,
143
+ });
144
+ }
145
+ }
146
+
147
+ if (toolCallDelta.id == null) {
148
+ throw new InvalidResponseDataError({
149
+ data: toolCallDelta,
150
+ message: `Expected 'id' to be a string.`,
151
+ });
152
+ }
153
+
154
+ if (toolCallDelta.function?.name == null) {
155
+ throw new InvalidResponseDataError({
156
+ data: toolCallDelta,
157
+ message: `Expected 'function.name' to be a string.`,
158
+ });
159
+ }
160
+
161
+ this.controller.enqueue({
162
+ type: 'tool-input-start',
163
+ id: toolCallDelta.id,
164
+ toolName: toolCallDelta.function.name,
165
+ });
166
+
167
+ const metadata = this.extractMetadata?.(toolCallDelta);
168
+
169
+ this.toolCalls[index] = {
170
+ id: toolCallDelta.id,
171
+ type: 'function',
172
+ function: {
173
+ name: toolCallDelta.function.name,
174
+ arguments: toolCallDelta.function.arguments ?? '',
175
+ },
176
+ hasFinished: false,
177
+ metadata,
178
+ };
179
+
180
+ const toolCall = this.toolCalls[index];
181
+
182
+ // Emit initial delta if arguments already present
183
+ if (toolCall.function.arguments.length > 0) {
184
+ this.controller.enqueue({
185
+ type: 'tool-input-delta',
186
+ id: toolCall.id,
187
+ delta: toolCall.function.arguments,
188
+ });
189
+ }
190
+
191
+ // Check if tool call is complete
192
+ // (some providers send the full tool call in one chunk)
193
+ if (isParsableJson(toolCall.function.arguments)) {
194
+ this.finishToolCall(toolCall);
195
+ }
196
+ }
197
+
198
+ private processExistingToolCall(index: number, toolCallDelta: DELTA): void {
199
+ const toolCall = this.toolCalls[index];
200
+
201
+ if (toolCall.hasFinished) {
202
+ return;
203
+ }
204
+
205
+ if (toolCallDelta.function?.arguments != null) {
206
+ toolCall.function.arguments += toolCallDelta.function.arguments;
207
+
208
+ this.controller.enqueue({
209
+ type: 'tool-input-delta',
210
+ id: toolCall.id,
211
+ delta: toolCallDelta.function.arguments,
212
+ });
213
+ }
214
+
215
+ // Check if tool call is complete
216
+ if (isParsableJson(toolCall.function.arguments)) {
217
+ this.finishToolCall(toolCall);
218
+ }
219
+ }
220
+
221
+ private finishToolCall(toolCall: TrackedToolCall): void {
222
+ this.controller.enqueue({
223
+ type: 'tool-input-end',
224
+ id: toolCall.id,
225
+ });
226
+
227
+ const providerMetadata = this.buildToolCallProviderMetadata?.(
228
+ toolCall.metadata,
229
+ );
230
+
231
+ this.controller.enqueue({
232
+ type: 'tool-call',
233
+ toolCallId: toolCall.id ?? this._generateId(),
234
+ toolName: toolCall.function.name,
235
+ input: toolCall.function.arguments,
236
+ ...(providerMetadata ? { providerMetadata } : {}),
237
+ });
238
+
239
+ toolCall.hasFinished = true;
240
+ }
241
+ }
@@ -3,7 +3,7 @@ import { convertReadableStreamToArray } from './convert-readable-stream-to-array
3
3
  export async function convertResponseStreamToArray(
4
4
  response: Response,
5
5
  ): Promise<string[]> {
6
- return convertReadableStreamToArray(
6
+ return await convertReadableStreamToArray(
7
7
  response.body!.pipeThrough(new TextDecoderStream()),
8
8
  );
9
9
  }