ai 7.0.110 → 7.0.112

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 (34) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.d.ts +163 -41
  3. package/dist/index.js +262 -73
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +141 -2
  6. package/dist/internal/index.js +33 -4
  7. package/dist/internal/index.js.map +1 -1
  8. package/docs/03-ai-sdk-core/40-middleware.mdx +92 -7
  9. package/docs/03-ai-sdk-core/60-telemetry.mdx +56 -3
  10. package/docs/03-ai-sdk-core/65-lifecycle-callbacks.mdx +124 -2
  11. package/docs/06-advanced/02-stopping-streams.mdx +8 -0
  12. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +3 -3
  13. package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +16 -9
  14. package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +9 -3
  15. package/package.json +12 -12
  16. package/src/batch/batch.ts +18 -3
  17. package/src/evaluate/evaluate-events.ts +131 -0
  18. package/src/evaluate/evaluate.ts +145 -40
  19. package/src/evaluate/index.ts +6 -0
  20. package/src/evaluate/restricted-telemetry-dispatcher.ts +46 -0
  21. package/src/generate-text/convert-language-model-content.ts +15 -6
  22. package/src/generate-text/generate-text.ts +7 -2
  23. package/src/generate-text/prune-messages.ts +3 -1
  24. package/src/generate-text/resolve-generated-file-data.ts +41 -0
  25. package/src/generate-text/stream-language-model-call.ts +5 -4
  26. package/src/prompt/convert-to-language-model-prompt.ts +12 -2
  27. package/src/prompt/file-part-data.ts +11 -1
  28. package/src/telemetry/create-telemetry-dispatcher.ts +12 -0
  29. package/src/telemetry/telemetry.ts +34 -0
  30. package/src/telemetry/tracing-channel.ts +2 -1
  31. package/src/ui/validate-ui-messages.ts +2 -2
  32. package/src/ui-message-stream/handle-ui-message-stream-finish.ts +5 -3
  33. package/src/ui-message-stream/ui-message-stream-on-end-callback.ts +8 -0
  34. package/src/ui-message-stream/ui-message-stream-outcome.ts +4 -0
@@ -0,0 +1,131 @@
1
+ import type {
2
+ Experimental_EvaluationModelV4CallOptions as EvaluationModelV4CallOptions,
3
+ Experimental_EvaluationModelV4Result as EvaluationModelV4Result,
4
+ } from '@ai-sdk/provider';
5
+ import type { Context, ProviderOptions } from '@ai-sdk/provider-utils';
6
+ import type { EvaluationQuestion, EvaluationResult } from './evaluation-result';
7
+
8
+ /**
9
+ * Event passed to the `onStart` callback for evaluation operations.
10
+ *
11
+ * Called when the operation begins, before the evaluation model is called.
12
+ */
13
+ export type EvaluateStartEvent<RUNTIME_CONTEXT extends Context = Context> = {
14
+ /** User-defined runtime context. */
15
+ readonly runtimeContext: RUNTIME_CONTEXT;
16
+
17
+ /** Unique identifier for this evaluation call, used to correlate events. */
18
+ readonly callId: string;
19
+
20
+ /** Identifies the operation type (`ai.evaluate`). */
21
+ readonly operationId: 'ai.evaluate';
22
+
23
+ /** The provider identifier. */
24
+ readonly provider: string;
25
+
26
+ /** The evaluation model identifier. */
27
+ readonly modelId: string;
28
+
29
+ /** The shared state being evaluated. */
30
+ readonly state: EvaluationModelV4CallOptions['state'];
31
+
32
+ /** The questions being evaluated against the shared state. */
33
+ readonly questions: Readonly<Record<string, EvaluationQuestion>>;
34
+
35
+ /** Maximum number of retries for the evaluation model call. */
36
+ readonly maxRetries: number;
37
+
38
+ /** Additional HTTP headers sent with the request. */
39
+ readonly headers: Record<string, string> | undefined;
40
+
41
+ /** Additional provider-specific options. */
42
+ readonly providerOptions: ProviderOptions;
43
+ };
44
+
45
+ /**
46
+ * Event passed to the `onEnd` callback for evaluation operations.
47
+ *
48
+ * Called when the operation completes successfully.
49
+ */
50
+ export type EvaluateEndEvent<RUNTIME_CONTEXT extends Context = Context> =
51
+ EvaluateStartEvent<RUNTIME_CONTEXT> & {
52
+ /** Exactly one typed answer per question ID. */
53
+ readonly answers: EvaluationResult<
54
+ Record<string, EvaluationQuestion>
55
+ >['answers'];
56
+
57
+ /** Token usage for the evaluation operation. */
58
+ readonly usage: EvaluationResult<
59
+ Record<string, EvaluationQuestion>
60
+ >['usage'];
61
+
62
+ /** Warnings from the evaluation model. */
63
+ readonly warnings: EvaluationResult<
64
+ Record<string, EvaluationQuestion>
65
+ >['warnings'];
66
+
67
+ /** Provider-declared decimal precision for probabilities and scores. */
68
+ readonly rounding: EvaluationResult<
69
+ Record<string, EvaluationQuestion>
70
+ >['rounding'];
71
+
72
+ /** Optional provider-specific metadata. */
73
+ readonly providerMetadata: EvaluationResult<
74
+ Record<string, EvaluationQuestion>
75
+ >['providerMetadata'];
76
+
77
+ /** Response metadata, including the resolved model ID and timestamp. */
78
+ readonly response: EvaluationResult<
79
+ Record<string, EvaluationQuestion>
80
+ >['response'];
81
+ };
82
+
83
+ /**
84
+ * Event fired when the evaluation model call begins.
85
+ *
86
+ * The logical model call includes any provider retries.
87
+ */
88
+ export type EvaluationModelCallStartEvent = {
89
+ /** Unique identifier for the outer evaluation call. */
90
+ readonly callId: string;
91
+
92
+ /** Identifies the inner operation (`ai.evaluate.doEvaluate`). */
93
+ readonly operationId: 'ai.evaluate.doEvaluate';
94
+
95
+ /** The provider identifier. */
96
+ readonly provider: string;
97
+
98
+ /** The evaluation model identifier. */
99
+ readonly modelId: string;
100
+
101
+ /** The shared state being evaluated. */
102
+ readonly state: EvaluationModelV4CallOptions['state'];
103
+
104
+ /** The questions being evaluated against the shared state. */
105
+ readonly questions: Readonly<Record<string, EvaluationQuestion>>;
106
+ };
107
+
108
+ /**
109
+ * Event fired after the evaluation model response has been validated.
110
+ *
111
+ * Contains the result of the logical model call, including any retries.
112
+ */
113
+ export type EvaluationModelCallEndEvent = EvaluationModelCallStartEvent & {
114
+ /** Exactly one answer per question ID. */
115
+ readonly answers: EvaluationModelV4Result['answers'];
116
+
117
+ /** Token usage reported by the evaluation model. */
118
+ readonly usage?: EvaluationModelV4Result['usage'];
119
+
120
+ /** Warnings from the evaluation model. */
121
+ readonly warnings: EvaluationModelV4Result['warnings'];
122
+
123
+ /** Provider-declared decimal precision for probabilities and scores. */
124
+ readonly rounding?: EvaluationModelV4Result['rounding'];
125
+
126
+ /** Optional provider-specific metadata. */
127
+ readonly providerMetadata?: EvaluationModelV4Result['providerMetadata'];
128
+
129
+ /** Optional raw response metadata from the provider. */
130
+ readonly response?: EvaluationModelV4Result['response'];
131
+ };
@@ -3,26 +3,40 @@ import {
3
3
  type Experimental_EvaluationModelV4CallOptions as EvaluationModelV4CallOptions,
4
4
  } from '@ai-sdk/provider';
5
5
  import {
6
+ createIdGenerator,
6
7
  withUserAgentSuffix,
8
+ type Context,
7
9
  type ProviderOptions,
8
10
  } from '@ai-sdk/provider-utils';
9
11
  import { resolveEvaluationModel } from '../model/resolve-model';
10
12
  import { logWarnings } from '../logger/log-warnings';
13
+ import { filterIncludedContext } from '../telemetry/filter-included-context';
14
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
15
+ import type { Callback } from '../util/callback';
16
+ import { notify } from '../util/notify';
11
17
  import { prepareRetries } from '../util/prepare-retries';
12
18
  import { VERSION } from '../version';
19
+ import type { EvaluateEndEvent, EvaluateStartEvent } from './evaluate-events';
13
20
  import type {
14
21
  EvaluationModel,
15
22
  EvaluationQuestion,
16
23
  EvaluationResult,
17
24
  } from './evaluation-result';
25
+ import { createRestrictedTelemetryDispatcher } from './restricted-telemetry-dispatcher';
18
26
  import {
19
27
  validateEvaluationInput,
20
28
  validateEvaluationAnswers,
21
29
  } from './validate-evaluation';
22
30
 
31
+ const originalGenerateCallId = createIdGenerator({
32
+ prefix: 'call',
33
+ size: 24,
34
+ });
35
+
23
36
  /** Evaluate typed questions against one shared state. Experimental. */
24
37
  export async function evaluate<
25
38
  const QUESTIONS extends Record<string, EvaluationQuestion>,
39
+ RUNTIME_CONTEXT extends Context = Context,
26
40
  >({
27
41
  model: modelArg,
28
42
  state,
@@ -31,6 +45,11 @@ export async function evaluate<
31
45
  abortSignal,
32
46
  headers,
33
47
  providerOptions = {},
48
+ telemetry,
49
+ runtimeContext = {} as RUNTIME_CONTEXT,
50
+ onStart,
51
+ onEnd,
52
+ _internal: { generateCallId = originalGenerateCallId } = {},
34
53
  }: {
35
54
  /** An evaluation model instance or an ID resolved by the configured default provider. */
36
55
  model: EvaluationModel;
@@ -41,6 +60,18 @@ export async function evaluate<
41
60
  abortSignal?: AbortSignal;
42
61
  headers?: Record<string, string>;
43
62
  providerOptions?: ProviderOptions;
63
+ /** Optional telemetry configuration. */
64
+ telemetry?: TelemetryOptions<RUNTIME_CONTEXT>;
65
+ /** User-defined runtime context. Treat runtime context as immutable. */
66
+ runtimeContext?: RUNTIME_CONTEXT;
67
+ /** Called when the evaluate operation begins. */
68
+ onStart?: Callback<EvaluateStartEvent<RUNTIME_CONTEXT>>;
69
+ /** Called when the evaluate operation completes. */
70
+ onEnd?: Callback<EvaluateEndEvent<RUNTIME_CONTEXT>>;
71
+ /** Internal. For test use only. May change without notice. */
72
+ _internal?: {
73
+ generateCallId?: () => string;
74
+ };
44
75
  }): Promise<EvaluationResult<QUESTIONS>> {
45
76
  const model = resolveEvaluationModel(modelArg);
46
77
 
@@ -57,50 +88,124 @@ export async function evaluate<
57
88
  }
58
89
  }
59
90
 
60
- const { retry } = prepareRetries({ maxRetries, abortSignal });
61
- const result = await retry(() => {
62
- abortSignal?.throwIfAborted();
63
- return model.doEvaluate({
64
- state,
65
- questions,
66
- abortSignal,
67
- headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
68
- providerOptions,
69
- });
91
+ const callId = generateCallId();
92
+ const { maxRetries: resolvedMaxRetries, retry } = prepareRetries({
93
+ maxRetries,
94
+ abortSignal,
70
95
  });
71
-
72
- abortSignal?.throwIfAborted();
73
- validateEvaluationAnswers({
74
- questions,
75
- answers: result.answers,
76
- rounding: result.rounding,
96
+ const telemetryDispatcher = createRestrictedTelemetryDispatcher({
97
+ telemetry,
77
98
  });
78
- logWarnings({
79
- warnings: result.warnings,
99
+ const runInTracingChannelSpan =
100
+ telemetryDispatcher.runInTracingChannelSpan ??
101
+ (async <T>({ execute }: { execute: () => PromiseLike<T> }) =>
102
+ await execute());
103
+ const startEvent = {
104
+ callId,
105
+ operationId: 'ai.evaluate' as const,
106
+ runtimeContext,
80
107
  provider: model.provider,
81
- model: model.modelId,
82
- });
108
+ modelId: model.modelId,
109
+ state,
110
+ questions,
111
+ maxRetries: resolvedMaxRetries,
112
+ headers,
113
+ providerOptions,
114
+ };
115
+ const tracingStartEvent = {
116
+ ...startEvent,
117
+ runtimeContext: filterIncludedContext({
118
+ context: runtimeContext,
119
+ includeContext: telemetry?.includeRuntimeContext,
120
+ }),
121
+ };
83
122
 
84
- const inputTokens = result.usage?.inputTokens;
85
- const outputTokens = result.usage?.outputTokens;
123
+ return await runInTracingChannelSpan({
124
+ type: 'experimental_evaluate',
125
+ event: tracingStartEvent,
126
+ execute: async () => {
127
+ await notify({
128
+ event: startEvent,
129
+ callbacks: [onStart, telemetryDispatcher.onStart],
130
+ });
86
131
 
87
- return {
88
- answers: result.answers as EvaluationResult<QUESTIONS>['answers'],
89
- usage: {
90
- inputTokens,
91
- outputTokens,
92
- totalTokens:
93
- inputTokens != null && outputTokens != null
94
- ? inputTokens + outputTokens
95
- : undefined,
96
- },
97
- warnings: result.warnings,
98
- rounding: result.rounding,
99
- providerMetadata: result.providerMetadata,
100
- response: {
101
- ...result.response,
102
- timestamp: result.response?.timestamp ?? new Date(),
103
- modelId: result.response?.modelId ?? model.modelId,
132
+ try {
133
+ const modelCallEvent = {
134
+ callId,
135
+ operationId: 'ai.evaluate.doEvaluate' as const,
136
+ provider: model.provider,
137
+ modelId: model.modelId,
138
+ state,
139
+ questions,
140
+ };
141
+ await notify({
142
+ event: modelCallEvent,
143
+ callbacks: [
144
+ telemetryDispatcher.experimental_onEvaluationModelCallStart,
145
+ ],
146
+ });
147
+ const result = await retry(async () => {
148
+ abortSignal?.throwIfAborted();
149
+ return await model.doEvaluate({
150
+ state,
151
+ questions,
152
+ abortSignal,
153
+ headers: withUserAgentSuffix(headers ?? {}, `ai/${VERSION}`),
154
+ providerOptions,
155
+ });
156
+ });
157
+
158
+ abortSignal?.throwIfAborted();
159
+ validateEvaluationAnswers({
160
+ questions,
161
+ answers: result.answers,
162
+ rounding: result.rounding,
163
+ });
164
+ await notify({
165
+ event: { ...modelCallEvent, ...result },
166
+ callbacks: [
167
+ telemetryDispatcher.experimental_onEvaluationModelCallEnd,
168
+ ],
169
+ });
170
+
171
+ logWarnings({
172
+ warnings: result.warnings,
173
+ provider: model.provider,
174
+ model: model.modelId,
175
+ });
176
+
177
+ const inputTokens = result.usage?.inputTokens;
178
+ const outputTokens = result.usage?.outputTokens;
179
+ const evaluationResult: EvaluationResult<QUESTIONS> = {
180
+ answers: result.answers as EvaluationResult<QUESTIONS>['answers'],
181
+ usage: {
182
+ inputTokens,
183
+ outputTokens,
184
+ totalTokens:
185
+ inputTokens != null && outputTokens != null
186
+ ? inputTokens + outputTokens
187
+ : undefined,
188
+ },
189
+ warnings: result.warnings,
190
+ rounding: result.rounding,
191
+ providerMetadata: result.providerMetadata,
192
+ response: {
193
+ ...result.response,
194
+ timestamp: result.response?.timestamp ?? new Date(),
195
+ modelId: result.response?.modelId ?? model.modelId,
196
+ },
197
+ };
198
+
199
+ await notify({
200
+ event: { ...startEvent, ...evaluationResult },
201
+ callbacks: [onEnd, telemetryDispatcher.onEnd],
202
+ });
203
+
204
+ return evaluationResult;
205
+ } catch (error) {
206
+ await telemetryDispatcher.onError?.({ callId, error });
207
+ throw error;
208
+ }
104
209
  },
105
- };
210
+ });
106
211
  }
@@ -1,4 +1,10 @@
1
1
  export { evaluate as experimental_evaluate } from './evaluate';
2
+ export type {
3
+ EvaluateStartEvent as Experimental_EvaluateStartEvent,
4
+ EvaluateEndEvent as Experimental_EvaluateEndEvent,
5
+ EvaluationModelCallStartEvent as Experimental_EvaluationModelCallStartEvent,
6
+ EvaluationModelCallEndEvent as Experimental_EvaluationModelCallEndEvent,
7
+ } from './evaluate-events';
2
8
  export type {
3
9
  EvaluationModel as Experimental_EvaluationModel,
4
10
  EvaluationQuestion as Experimental_EvaluationQuestion,
@@ -0,0 +1,46 @@
1
+ import type { Context } from '@ai-sdk/provider-utils';
2
+ import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
3
+ import { filterIncludedContext } from '../telemetry/filter-included-context';
4
+ import type { TelemetryDispatcher } from '../telemetry/telemetry';
5
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
6
+ import type { Callback } from '../util/callback';
7
+ import type { EvaluateEndEvent, EvaluateStartEvent } from './evaluate-events';
8
+
9
+ export function createRestrictedTelemetryDispatcher<
10
+ RUNTIME_CONTEXT extends Context,
11
+ >({
12
+ telemetry,
13
+ }: {
14
+ telemetry?: TelemetryOptions<RUNTIME_CONTEXT>;
15
+ }): Omit<
16
+ TelemetryDispatcher,
17
+ | 'onStart'
18
+ | 'onEnd'
19
+ | 'experimental_onEvaluateStart'
20
+ | 'experimental_onEvaluateEnd'
21
+ > & {
22
+ onStart: Callback<EvaluateStartEvent<RUNTIME_CONTEXT>>;
23
+ onEnd: Callback<EvaluateEndEvent<RUNTIME_CONTEXT>>;
24
+ } {
25
+ const dispatcher = createTelemetryDispatcher({ telemetry });
26
+
27
+ return {
28
+ ...dispatcher,
29
+ onStart: event =>
30
+ dispatcher.experimental_onEvaluateStart?.({
31
+ ...event,
32
+ runtimeContext: filterIncludedContext({
33
+ context: event.runtimeContext,
34
+ includeContext: telemetry?.includeRuntimeContext,
35
+ }),
36
+ }),
37
+ onEnd: event =>
38
+ dispatcher.experimental_onEvaluateEnd?.({
39
+ ...event,
40
+ runtimeContext: filterIncludedContext({
41
+ context: event.runtimeContext,
42
+ includeContext: telemetry?.includeRuntimeContext,
43
+ }),
44
+ }),
45
+ };
46
+ }
@@ -4,6 +4,10 @@ import { ToolCallNotFoundForApprovalError } from '../error/tool-call-not-found-f
4
4
  import { getOwn } from '../util/get-own';
5
5
  import type { ContentPart } from './content-part';
6
6
  import { DefaultGeneratedFile } from './generated-file';
7
+ import {
8
+ resolveGeneratedFileData,
9
+ type GeneratedFileDataCache,
10
+ } from './resolve-generated-file-data';
7
11
  import type { ToolApprovalRequestOutput } from './tool-approval-request-output';
8
12
  import type { ToolApprovalResponseOutput } from './tool-approval-response-output';
9
13
  import type { TypedToolCall } from './tool-call';
@@ -11,13 +15,15 @@ import type { TypedToolError } from './tool-error';
11
15
  import type { ToolOutput } from './tool-output';
12
16
  import type { TypedToolResult } from './tool-result';
13
17
 
14
- export function convertLanguageModelContent<TOOLS extends ToolSet>({
18
+ export async function convertLanguageModelContent<TOOLS extends ToolSet>({
15
19
  content,
16
20
  toolCalls,
17
21
  toolOutputs,
18
22
  toolApprovalRequests,
19
23
  toolApprovalResponses,
20
24
  tools,
25
+ abortSignal,
26
+ generatedFileDataCache,
21
27
  }: {
22
28
  content: Array<LanguageModelV4Content>;
23
29
  toolCalls: Array<TypedToolCall<TOOLS>>;
@@ -25,7 +31,9 @@ export function convertLanguageModelContent<TOOLS extends ToolSet>({
25
31
  toolApprovalRequests: Array<ToolApprovalRequestOutput<TOOLS>>;
26
32
  toolApprovalResponses: Array<ToolApprovalResponseOutput<TOOLS>>;
27
33
  tools: TOOLS | undefined;
28
- }): Array<ContentPart<TOOLS>> {
34
+ abortSignal?: AbortSignal;
35
+ generatedFileDataCache?: GeneratedFileDataCache;
36
+ }): Promise<Array<ContentPart<TOOLS>>> {
29
37
  const contentParts: Array<ContentPart<TOOLS>> = [];
30
38
  const toolOutputsWithApprovalResponses: Array<ToolOutput<TOOLS>> = [];
31
39
  const toolOutputsWithoutApprovalResponses: Array<ToolOutput<TOOLS>> = [];
@@ -49,10 +57,11 @@ export function convertLanguageModelContent<TOOLS extends ToolSet>({
49
57
  contentParts.push({
50
58
  type: part.type as 'file' | 'reasoning-file',
51
59
  file: new DefaultGeneratedFile({
52
- data:
53
- part.data.type === 'data'
54
- ? part.data.data
55
- : part.data.url.toString(),
60
+ data: await resolveGeneratedFileData({
61
+ data: part.data,
62
+ abortSignal,
63
+ cache: generatedFileDataCache,
64
+ }),
56
65
  mediaType: part.mediaType,
57
66
  }),
58
67
  ...(part.providerMetadata != null
@@ -1099,13 +1099,16 @@ export async function generateText<
1099
1099
  > = {};
1100
1100
  const blockedToolCallIds = new Set<string>();
1101
1101
 
1102
- const modelCallContent = convertLanguageModelContent({
1102
+ const generatedFileDataCache = new WeakMap();
1103
+ const modelCallContent = await convertLanguageModelContent({
1103
1104
  content: currentModelResponse.content,
1104
1105
  toolCalls: stepToolCalls,
1105
1106
  toolOutputs: [],
1106
1107
  toolApprovalRequests: [],
1107
1108
  toolApprovalResponses: [],
1108
1109
  tools,
1110
+ abortSignal: mergedAbortSignal,
1111
+ generatedFileDataCache,
1109
1112
  });
1110
1113
 
1111
1114
  await notify({
@@ -1426,13 +1429,15 @@ export async function generateText<
1426
1429
  }
1427
1430
 
1428
1431
  // content:
1429
- const stepContent = convertLanguageModelContent({
1432
+ const stepContent = await convertLanguageModelContent({
1430
1433
  content: currentModelResponse.content,
1431
1434
  toolCalls: stepToolCalls,
1432
1435
  toolOutputs: clientToolOutputs,
1433
1436
  toolApprovalRequests: Object.values(toolApprovalRequests),
1434
1437
  toolApprovalResponses,
1435
1438
  tools,
1439
+ abortSignal: mergedAbortSignal,
1440
+ generatedFileDataCache,
1436
1441
  });
1437
1442
 
1438
1443
  const stepResponseMessages = await toResponseMessages({
@@ -47,7 +47,9 @@ export function pruneMessages({
47
47
 
48
48
  return {
49
49
  ...message,
50
- content: message.content.filter(part => part.type !== 'reasoning'),
50
+ content: message.content.filter(
51
+ part => part.type !== 'reasoning' && part.type !== 'reasoning-file',
52
+ ),
51
53
  };
52
54
  });
53
55
  }
@@ -0,0 +1,41 @@
1
+ import type { LanguageModelV4Content } from '@ai-sdk/provider';
2
+ import { download } from '../util/download/download';
3
+
4
+ type GeneratedFileData = Extract<
5
+ LanguageModelV4Content,
6
+ { type: 'file' | 'reasoning-file' }
7
+ >['data'];
8
+
9
+ export type GeneratedFileDataCache = WeakMap<
10
+ GeneratedFileData,
11
+ string | Uint8Array
12
+ >;
13
+
14
+ export async function resolveGeneratedFileData({
15
+ data,
16
+ abortSignal,
17
+ cache,
18
+ }: {
19
+ data: GeneratedFileData;
20
+ abortSignal?: AbortSignal;
21
+ cache?: GeneratedFileDataCache;
22
+ }): Promise<string | Uint8Array> {
23
+ if (data.type === 'data') {
24
+ return data.data;
25
+ }
26
+
27
+ const cachedData = cache?.get(data);
28
+ if (cachedData != null) {
29
+ return cachedData;
30
+ }
31
+
32
+ const downloadedData = (
33
+ await download({
34
+ url: data.url,
35
+ abortSignal,
36
+ })
37
+ ).data;
38
+
39
+ cache?.set(data, downloadedData);
40
+ return downloadedData;
41
+ }
@@ -53,6 +53,7 @@ import type {
53
53
  } from './language-model-events';
54
54
  import type { Output } from './output';
55
55
  import { parseToolCall } from './parse-tool-call';
56
+ import { resolveGeneratedFileData } from './resolve-generated-file-data';
56
57
  import type {
57
58
  TextStreamFilePart,
58
59
  TextStreamPart,
@@ -565,10 +566,10 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
565
566
  case 'file':
566
567
  case 'reasoning-file': {
567
568
  const file = new DefaultGeneratedFileWithType({
568
- data:
569
- chunk.data.type === 'data'
570
- ? chunk.data.data
571
- : chunk.data.url.toString(),
569
+ data: await resolveGeneratedFileData({
570
+ data: chunk.data,
571
+ abortSignal,
572
+ }),
572
573
  mediaType: chunk.mediaType,
573
574
  });
574
575
 
@@ -661,6 +661,7 @@ export function mapToolResultOutput({
661
661
  }
662
662
  case 'file-url': {
663
663
  const mediaType = item.mediaType ?? getMediaTypeFromUrl(item.url);
664
+ const url = new URL(item.url);
664
665
  let message = `The "file-url" type for tool result content is deprecated. Use the "file" type with mediaType and { type: 'url', url } instead.`;
665
666
  if (!item.mediaType) {
666
667
  const inferenceSuffix =
@@ -676,7 +677,11 @@ export function mapToolResultOutput({
676
677
  });
677
678
  return {
678
679
  type: 'file' as const,
679
- data: { type: 'url' as const, url: new URL(item.url) },
680
+ data: {
681
+ type: 'url' as const,
682
+ url,
683
+ ...(url.toString() !== item.url ? { originalUrl: item.url } : {}),
684
+ },
680
685
  mediaType,
681
686
  providerOptions: item.providerOptions,
682
687
  };
@@ -732,6 +737,7 @@ export function mapToolResultOutput({
732
737
  };
733
738
  }
734
739
  case 'image-url': {
740
+ const url = new URL(item.url);
735
741
  warnings.push({
736
742
  type: 'deprecated',
737
743
  setting: '"tool-result" content of type "image-url"',
@@ -739,7 +745,11 @@ export function mapToolResultOutput({
739
745
  });
740
746
  return {
741
747
  type: 'file' as const,
742
- data: { type: 'url' as const, url: new URL(item.url) },
748
+ data: {
749
+ type: 'url' as const,
750
+ url,
751
+ ...(url.toString() !== item.url ? { originalUrl: item.url } : {}),
752
+ },
743
753
  mediaType: 'image',
744
754
  providerOptions: item.providerOptions,
745
755
  };
@@ -41,6 +41,16 @@ function convertUrlToFilePartData(url: URL): ConvertResult {
41
41
  return { data: { type: 'url', url }, mediaType: undefined };
42
42
  }
43
43
 
44
+ function convertUrlStringToFilePartData(content: string): ConvertResult {
45
+ const result = convertUrlToFilePartData(new URL(content));
46
+
47
+ if (result.data.type === 'url' && result.data.url.toString() !== content) {
48
+ result.data.originalUrl = content;
49
+ }
50
+
51
+ return result;
52
+ }
53
+
44
54
  function convertInlineDataToFilePartData(content: DataContent): ConvertResult {
45
55
  if (content instanceof Uint8Array) {
46
56
  return { data: { type: 'data', data: content }, mediaType: undefined };
@@ -108,7 +118,7 @@ export function convertToLanguageModelV4FilePart(
108
118
 
109
119
  if (typeof content === 'string') {
110
120
  try {
111
- return convertUrlToFilePartData(new URL(content));
121
+ return convertUrlStringToFilePartData(content);
112
122
  } catch {
113
123
  return convertInlineDataToFilePartData(content);
114
124
  }
@@ -163,6 +163,18 @@ export function createTelemetryDispatcher({
163
163
  onEmbedEnd: mergeTelemetryCallback('onEmbedEnd'),
164
164
  onRerankStart: mergeTelemetryCallback('onRerankStart'),
165
165
  onRerankEnd: mergeTelemetryCallback('onRerankEnd'),
166
+ experimental_onEvaluateStart: mergeTelemetryCallback(
167
+ 'experimental_onEvaluateStart',
168
+ ),
169
+ experimental_onEvaluationModelCallStart: mergeTelemetryCallback(
170
+ 'experimental_onEvaluationModelCallStart',
171
+ ),
172
+ experimental_onEvaluationModelCallEnd: mergeTelemetryCallback(
173
+ 'experimental_onEvaluationModelCallEnd',
174
+ ),
175
+ experimental_onEvaluateEnd: mergeTelemetryCallback(
176
+ 'experimental_onEvaluateEnd',
177
+ ),
166
178
  onEnd: mergeTelemetryCallback('onEnd'),
167
179
  onAbort: mergeTelemetryCallback('onAbort'),
168
180
  onError: mergeTelemetryCallback('onError'),