ai 7.0.102 → 7.0.104

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 (60) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/index.d.ts +90 -6
  3. package/dist/index.js +900 -299
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/index.d.ts +1 -0
  6. package/dist/internal/index.js +5 -1
  7. package/dist/internal/index.js.map +1 -1
  8. package/dist/test/index.d.ts +16 -2
  9. package/dist/test/index.js +17 -0
  10. package/dist/test/index.js.map +1 -1
  11. package/docs/03-ai-sdk-core/18-code-mode.mdx +50 -0
  12. package/docs/03-ai-sdk-core/19-tool-search.mdx +80 -0
  13. package/docs/03-ai-sdk-core/32-evaluation.mdx +255 -0
  14. package/docs/03-ai-sdk-core/42-batch.mdx +1 -2
  15. package/docs/03-ai-sdk-core/45-provider-management.mdx +10 -0
  16. package/docs/03-ai-sdk-core/index.mdx +6 -0
  17. package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +35 -0
  18. package/docs/06-advanced/11-secure-url-fetching.mdx +8 -2
  19. package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +67 -0
  20. package/docs/07-reference/01-ai-sdk-core/20-tool.mdx +7 -0
  21. package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +7 -0
  22. package/docs/07-reference/01-ai-sdk-core/23-tool-search.mdx +75 -0
  23. package/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx +12 -0
  24. package/docs/07-reference/01-ai-sdk-core/33-safe-validate-ui-messages.mdx +12 -0
  25. package/docs/07-reference/01-ai-sdk-core/40-provider-registry.mdx +17 -0
  26. package/docs/07-reference/01-ai-sdk-core/42-custom-provider.mdx +16 -0
  27. package/docs/07-reference/01-ai-sdk-core/index.mdx +6 -0
  28. package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +12 -0
  29. package/docs/07-reference/05-ai-sdk-errors/ai-evaluation-unsupported-question-type-error.mdx +31 -0
  30. package/docs/07-reference/05-ai-sdk-errors/ai-no-such-model-error.mdx +4 -0
  31. package/docs/07-reference/05-ai-sdk-errors/ai-no-such-provider-error.mdx +4 -0
  32. package/package.json +12 -12
  33. package/src/error/index.ts +1 -0
  34. package/src/evaluate/evaluate.ts +106 -0
  35. package/src/evaluate/evaluation-provider.ts +6 -0
  36. package/src/evaluate/evaluation-result.ts +39 -0
  37. package/src/evaluate/index.ts +7 -0
  38. package/src/evaluate/validate-evaluation.ts +298 -0
  39. package/src/generate-text/generate-text.ts +25 -13
  40. package/src/generate-text/stream-text.ts +15 -2
  41. package/src/generate-text/tool-caller-configuration.ts +59 -5
  42. package/src/global.ts +1 -0
  43. package/src/index.ts +2 -0
  44. package/src/model/resolve-model.ts +55 -10
  45. package/src/prompt/prepare-tools.ts +1 -1
  46. package/src/realtime/browser-realtime-transport.ts +12 -1
  47. package/src/realtime/realtime-event-channel.ts +4 -0
  48. package/src/realtime/realtime-session.ts +9 -4
  49. package/src/registry/custom-provider.ts +37 -0
  50. package/src/registry/index.ts +2 -0
  51. package/src/registry/no-such-provider-error.ts +2 -1
  52. package/src/registry/provider-registry.ts +65 -6
  53. package/src/test/evaluation-mock-model-v4.ts +27 -0
  54. package/src/tool-search/prepare-tool-search.ts +143 -0
  55. package/src/tool-search/tool-search.ts +59 -0
  56. package/src/ui/convert-to-model-messages.ts +3 -0
  57. package/src/ui/process-ui-message-stream.ts +4 -0
  58. package/src/ui/ui-messages.ts +5 -1
  59. package/src/ui/validate-ui-messages.ts +3 -0
  60. package/src/ui/warn-if-ui-message-has-deprecated-raw-input.ts +36 -0
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  experimental_getToolCaller,
3
3
  type Experimental_ToolCallerTool,
4
+ type ModelMessage,
4
5
  type Tool,
5
6
  type ToolSet,
7
+ type UserModelMessage,
6
8
  } from '@ai-sdk/provider-utils';
7
9
  import { InvalidArgumentError } from '../error/invalid-argument-error';
8
10
 
9
- const DIRECT_TOOL_CALL = 'AI_SDK_DIRECT_TOOL_CALL';
11
+ export const DIRECT_TOOL_CALL = 'AI_SDK_DIRECT_TOOL_CALL';
10
12
 
11
13
  type ToolCallerName<TOOLS extends ToolSet> = {
12
14
  [NAME in keyof TOOLS]: TOOLS[NAME] extends Experimental_ToolCallerTool
@@ -86,15 +88,28 @@ export function prepareToolsForToolCallers({
86
88
  }): {
87
89
  executionTools: ToolSet | undefined;
88
90
  modelTools: ToolSet | undefined;
91
+ toolCallerMessages: UserModelMessage[];
89
92
  } {
90
93
  if (tools == null || toolCallers == null) {
91
- return { executionTools: tools, modelTools: tools };
94
+ return {
95
+ executionTools: tools,
96
+ modelTools: tools,
97
+ toolCallerMessages: [],
98
+ };
92
99
  }
93
100
 
101
+ // Tools available to the runtime for execution.
94
102
  const executionTools: ToolSet = { ...tools };
103
+
104
+ // Tools exposed directly to the model.
95
105
  const modelTools: ToolSet = { ...tools };
106
+
107
+ // Tools routed through each local caller.
96
108
  const localToolsByCaller = new Map<string, ToolSet>();
97
109
 
110
+ // User messages describing caller-accessible tools.
111
+ const toolCallerMessages: UserModelMessage[] = [];
112
+
98
113
  for (const [toolName, callerNames] of Object.entries(toolCallers)) {
99
114
  const tool = executionTools[toolName];
100
115
  if (tool == null) {
@@ -146,13 +161,52 @@ export function prepareToolsForToolCallers({
146
161
  continue;
147
162
  }
148
163
 
149
- const boundCaller = caller.bind(localToolsByCaller.get(callerName) ?? {});
164
+ const callerTools = localToolsByCaller.get(callerName) ?? {};
165
+ const boundCaller = caller.bind(callerTools);
150
166
  executionTools[callerName] = boundCaller;
151
167
 
152
168
  if (Object.prototype.hasOwnProperty.call(modelTools, callerName)) {
153
- modelTools[callerName] = boundCaller;
169
+ if (caller.prepareModelMessage == null) {
170
+ modelTools[callerName] = boundCaller;
171
+ } else {
172
+ const content = caller.prepareModelMessage(callerTools);
173
+ if (content != null) {
174
+ toolCallerMessages.push({ role: 'user', content });
175
+ }
176
+ }
154
177
  }
155
178
  }
156
179
 
157
- return { executionTools, modelTools };
180
+ return { executionTools, modelTools, toolCallerMessages };
181
+ }
182
+
183
+ export function appendToolCallerMessages({
184
+ messages,
185
+ toolCallerMessages,
186
+ }: {
187
+ messages: ModelMessage[];
188
+ toolCallerMessages: UserModelMessage[];
189
+ }): ModelMessage[] {
190
+ if (toolCallerMessages.length === 0) {
191
+ return messages;
192
+ }
193
+
194
+ const latestUserText = messages.findLast(
195
+ message => message.role === 'user' && typeof message.content === 'string',
196
+ )?.content;
197
+ const existingUserText = new Set(
198
+ latestUserText == null ? [] : [latestUserText],
199
+ );
200
+ const additions = toolCallerMessages.filter(message => {
201
+ if (
202
+ typeof message.content !== 'string' ||
203
+ existingUserText.has(message.content)
204
+ ) {
205
+ return false;
206
+ }
207
+ existingUserText.add(message.content);
208
+ return true;
209
+ });
210
+
211
+ return additions.length === 0 ? messages : [...messages, ...additions];
158
212
  }
package/src/global.ts CHANGED
@@ -9,6 +9,7 @@ declare global {
9
9
  * String model ids are resolved to the default provider and model id.
10
10
  *
11
11
  * If not set, the default provider is the Vercel AI gateway provider.
12
+ * Experimental evaluation requires an explicitly configured evaluation-capable provider.
12
13
  *
13
14
  * @see https://ai-sdk.dev/docs/ai-sdk-core/provider-management#global-provider-configuration
14
15
  */
package/src/index.ts CHANGED
@@ -41,6 +41,7 @@ export * from './agent';
41
41
  export * from './batch';
42
42
  export * from './embed';
43
43
  export * from './error';
44
+ export * from './evaluate';
44
45
  export * from './generate-image';
45
46
  export * from './generate-object';
46
47
  export * from './generate-speech';
@@ -54,6 +55,7 @@ export * from './registry';
54
55
  export * from './rerank';
55
56
  export * from './telemetry';
56
57
  export * from './text-stream';
58
+ export { toolSearch } from './tool-search/tool-search';
57
59
  export * from './transcribe';
58
60
  export * from './translate';
59
61
  export * from './types';
@@ -1,15 +1,19 @@
1
1
  import { gateway } from '@ai-sdk/gateway';
2
- import type {
3
- EmbeddingModelV4,
4
- Experimental_SpeechTranslationModelV4,
5
- Experimental_VideoModelV4,
6
- ImageModelV4,
7
- LanguageModelV4,
8
- ProviderV4,
9
- RerankingModelV4,
10
- SpeechModelV4,
11
- TranscriptionModelV4,
2
+ import {
3
+ NoSuchModelError,
4
+ type Experimental_EvaluationModelV4 as EvaluationModelV4,
5
+ type EmbeddingModelV4,
6
+ type Experimental_SpeechTranslationModelV4,
7
+ type Experimental_VideoModelV4,
8
+ type ImageModelV4,
9
+ type LanguageModelV4,
10
+ type ProviderV4,
11
+ type RerankingModelV4,
12
+ type SpeechModelV4,
13
+ type TranscriptionModelV4,
12
14
  } from '@ai-sdk/provider';
15
+ import type { EvaluationModel } from '../evaluate/evaluation-result';
16
+ import type { EvaluationProvider } from '../evaluate/evaluation-provider';
13
17
  import { UnsupportedModelVersionError } from '../error';
14
18
  import type { EmbeddingModel } from '../types/embedding-model';
15
19
  import type { LanguageModel } from '../types/language-model';
@@ -213,6 +217,47 @@ export function resolveRerankingModel(model: RerankingModel): RerankingModelV4 {
213
217
  return asRerankingModelV4(model);
214
218
  }
215
219
 
220
+ export function resolveEvaluationModel(
221
+ model: EvaluationModel,
222
+ ): EvaluationModelV4 {
223
+ if (typeof model === 'string') {
224
+ // Use the original provider so experimental methods and their receiver survive.
225
+ // Evaluation does not default to Gateway.
226
+ const provider = globalThis.AI_SDK_DEFAULT_PROVIDER as
227
+ | EvaluationProvider
228
+ | undefined;
229
+
230
+ if (typeof provider?.evaluationModel !== 'function') {
231
+ throw new NoSuchModelError({
232
+ modelId: model,
233
+ modelType: 'evaluationModel',
234
+ message:
235
+ 'The default provider does not support evaluation models. ' +
236
+ 'Pass an evaluation model instance or configure AI_SDK_DEFAULT_PROVIDER with an evaluationModel method.',
237
+ });
238
+ }
239
+
240
+ const resolvedModel = provider.evaluationModel(model);
241
+ if (resolvedModel == null) {
242
+ throw new NoSuchModelError({
243
+ modelId: model,
244
+ modelType: 'evaluationModel',
245
+ });
246
+ }
247
+ model = resolvedModel;
248
+ }
249
+
250
+ if (model.specificationVersion !== 'v4') {
251
+ throw new UnsupportedModelVersionError({
252
+ version: model.specificationVersion,
253
+ provider: model.provider,
254
+ modelId: model.modelId,
255
+ });
256
+ }
257
+
258
+ return model;
259
+ }
260
+
216
261
  function getGlobalProvider(): ProviderV4 {
217
262
  const provider = globalThis.AI_SDK_DEFAULT_PROVIDER ?? gateway;
218
263
  return asProviderV4(provider);
@@ -105,7 +105,7 @@ function orderToolEntries<TOOLS extends ToolSet>({
105
105
  return [...orderedTools, ...unorderedTools];
106
106
  }
107
107
 
108
- function resolveToolDescription<TOOLS extends ToolSet>({
108
+ export function resolveToolDescription<TOOLS extends ToolSet>({
109
109
  tool,
110
110
  toolName,
111
111
  toolsContext,
@@ -169,9 +169,20 @@ export class BrowserRealtimeTransport {
169
169
  if (this.ws === ws) {
170
170
  this.ws = null;
171
171
  const closeError = getCloseError(event) ?? connectionError;
172
+ const reportCloseErrorImmediately =
173
+ closeError != null && !codec.hasPendingIncoming;
172
174
  codec.stopWriting();
173
175
  this.notifyClosing();
174
176
  if (this.epoch !== epoch) return;
177
+ const drain = codec.finish();
178
+ if (reportCloseErrorImmediately) {
179
+ try {
180
+ this.onError(closeError);
181
+ } catch {
182
+ /* Application callbacks cannot interrupt teardown. */
183
+ }
184
+ if (this.epoch !== epoch) return;
185
+ }
175
186
  const complete = () => {
176
187
  if (this.epoch !== epoch) return;
177
188
  clearTimeout(this.drainTimer);
@@ -184,7 +195,7 @@ export class BrowserRealtimeTransport {
184
195
  }
185
196
  };
186
197
  this.drainTimer = setTimeout(complete, 1_000);
187
- void this.awaitDrain(codec.finish(), complete);
198
+ void this.awaitDrain(drain, complete);
188
199
  }
189
200
  };
190
201
  }
@@ -34,6 +34,10 @@ export class RealtimeEventChannel {
34
34
  options.model.parseServerEvent.bind(options.model);
35
35
  }
36
36
 
37
+ get hasPendingIncoming(): boolean {
38
+ return this.incomingCount > 0;
39
+ }
40
+
37
41
  dispose(): void {
38
42
  this.active = false;
39
43
  }
@@ -206,6 +206,7 @@ export abstract class AbstractRealtimeSession {
206
206
  if (current())
207
207
  this.fail(new Error('Realtime session startup timed out'));
208
208
  });
209
+ const reportedTransportErrors = new WeakSet<Error>();
209
210
  const callbacks = {
210
211
  model,
211
212
  onEvent: async (event: RealtimeServerEvent) => {
@@ -221,7 +222,10 @@ export abstract class AbstractRealtimeSession {
221
222
  }
222
223
  },
223
224
  onError: (error: Error) => {
224
- if (current()) void this.reportError(error, attempt);
225
+ if (current()) {
226
+ reportedTransportErrors.add(error);
227
+ void this.reportError(error, attempt);
228
+ }
225
229
  },
226
230
  onFatalError: (error: Error, drain?: Promise<void>) => {
227
231
  if (current()) this.fail(error, drain);
@@ -244,7 +248,8 @@ export abstract class AbstractRealtimeSession {
244
248
  const finalizationConfirmed =
245
249
  model.capabilities?.finalization === 'session-close' &&
246
250
  this.state.session?.finalization === 'confirmed';
247
- if (error != null && !finalizationConfirmed) this.fail(error);
251
+ if (error != null && !finalizationConfirmed)
252
+ this.fail(error, undefined, !reportedTransportErrors.has(error));
248
253
  else if (!attempt.ready && !finalizationConfirmed)
249
254
  this.fail(
250
255
  new Error('Realtime connection closed before becoming ready'),
@@ -348,7 +353,7 @@ export abstract class AbstractRealtimeSession {
348
353
  }
349
354
  }
350
355
 
351
- private fail(error: unknown, drain?: Promise<void>): void {
356
+ private fail(error: unknown, drain?: Promise<void>, report = true): void {
352
357
  const attempt = this.attempt;
353
358
  if (attempt == null || !attempt.active || attempt.cause != null) return;
354
359
  attempt.cause = error instanceof Error ? error : new Error(String(error));
@@ -359,7 +364,7 @@ export abstract class AbstractRealtimeSession {
359
364
  if (this.attempt !== attempt || !attempt.active) return;
360
365
  if (drain == null) this.disconnect();
361
366
  else void this.drainAttempt(attempt, drain);
362
- void this.reportError(attempt.cause, attempt);
367
+ if (report) void this.reportError(attempt.cause, attempt);
363
368
  }
364
369
 
365
370
  private async drainAttempt(
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Experimental_EvaluationModelV4 as EvaluationModelV4,
2
3
  type EmbeddingModelV4,
3
4
  type Experimental_VideoModelV4,
4
5
  type FilesV4,
@@ -13,6 +14,8 @@ import {
13
14
  type SpeechModelV4,
14
15
  type TranscriptionModelV4,
15
16
  } from '@ai-sdk/provider';
17
+ import type { EvaluationModel } from '../evaluate/evaluation-result';
18
+ import type { EvaluationProvider } from '../evaluate/evaluation-provider';
16
19
  import { asProviderV4 } from '../model/as-provider-v4';
17
20
  import {
18
21
  resolveEmbeddingModel,
@@ -22,6 +25,7 @@ import {
22
25
  resolveSpeechModel,
23
26
  resolveTranscriptionModel,
24
27
  resolveVideoModel,
28
+ resolveEvaluationModel,
25
29
  } from '../model/resolve-model';
26
30
  import type { EmbeddingModel } from '../types/embedding-model';
27
31
  import type { ImageModel } from '../types/image-model';
@@ -42,6 +46,7 @@ import type { VideoModel } from '../types/video-model';
42
46
  * @param {Record<string, SpeechModel>} [options.speechModels] - A record of speech models, where keys are model IDs and values are speech model instances.
43
47
  * @param {Record<string, RerankingModel>} [options.rerankingModels] - A record of reranking models, where keys are model IDs and values are reranking model instances.
44
48
  * @param {Record<string, VideoModel>} [options.videoModels] - A record of video models, where keys are model IDs and values are video model instances.
49
+ * @param {Record<string, EvaluationModel>} [options.evaluationModels] - Experimental evaluation models or default-provider model IDs, keyed by alias.
45
50
  * @param {FilesV4} [options.files] - A files interface for uploading files.
46
51
  * @param {SkillsV4} [options.skills] - A skills interface for uploading skills.
47
52
  * @param {ProviderV2 | ProviderV3 | ProviderV4} [options.fallbackProvider] - An optional fallback provider to use when a requested model is not found in the custom provider.
@@ -60,6 +65,10 @@ export function customProvider<
60
65
  FILES extends FilesV4 | undefined = undefined,
61
66
  SKILLS extends SkillsV4 | undefined = undefined,
62
67
  FALLBACK extends ProviderV2 | ProviderV3 | ProviderV4 | undefined = undefined,
68
+ EVALUATION_MODELS extends Record<string, EvaluationModel> = Record<
69
+ string,
70
+ EvaluationModel
71
+ >,
63
72
  >({
64
73
  languageModels,
65
74
  embeddingModels,
@@ -68,6 +77,7 @@ export function customProvider<
68
77
  speechModels,
69
78
  rerankingModels,
70
79
  videoModels,
80
+ evaluationModels,
71
81
  files,
72
82
  skills,
73
83
  fallbackProvider: fallbackProviderArg,
@@ -79,6 +89,7 @@ export function customProvider<
79
89
  speechModels?: SPEECH_MODELS;
80
90
  rerankingModels?: RERANKING_MODELS;
81
91
  videoModels?: VIDEO_MODELS;
92
+ evaluationModels?: EVALUATION_MODELS;
82
93
  files?: FILES;
83
94
  skills?: SKILLS;
84
95
  fallbackProvider?: FALLBACK;
@@ -92,6 +103,9 @@ export function customProvider<
92
103
  rerankingModel(modelId: ExtractModelId<RERANKING_MODELS>): RerankingModelV4;
93
104
  speechModel(modelId: ExtractModelId<SPEECH_MODELS>): SpeechModelV4;
94
105
  videoModel(modelId: ExtractModelId<VIDEO_MODELS>): Experimental_VideoModelV4;
106
+ evaluationModel(
107
+ modelId: ExtractModelId<EVALUATION_MODELS>,
108
+ ): EvaluationModelV4;
95
109
  } & (FILES extends FilesV4
96
110
  ? { files(): FilesV4 }
97
111
  : [FALLBACK] extends [{ files: () => FilesV4 }]
@@ -117,6 +131,9 @@ export function customProvider<
117
131
  videoModel(
118
132
  modelId: ExtractModelId<VIDEO_MODELS>,
119
133
  ): Experimental_VideoModelV4;
134
+ evaluationModel(
135
+ modelId: ExtractModelId<EVALUATION_MODELS>,
136
+ ): EvaluationModelV4;
120
137
  } = {
121
138
  specificationVersion: 'v4',
122
139
  languageModel(modelId: ExtractModelId<LANGUAGE_MODELS>): LanguageModelV4 {
@@ -203,6 +220,26 @@ export function customProvider<
203
220
 
204
221
  throw new NoSuchModelError({ modelId, modelType: 'rerankingModel' });
205
222
  },
223
+ evaluationModel(
224
+ modelId: ExtractModelId<EVALUATION_MODELS>,
225
+ ): EvaluationModelV4 {
226
+ if (
227
+ evaluationModels != null &&
228
+ Object.hasOwn(evaluationModels, modelId)
229
+ ) {
230
+ return resolveEvaluationModel(evaluationModels[modelId]);
231
+ }
232
+
233
+ const provider = fallbackProviderArg as EvaluationProvider | undefined;
234
+ if (typeof provider?.evaluationModel === 'function') {
235
+ const model = provider.evaluationModel(modelId);
236
+ if (model != null) {
237
+ return resolveEvaluationModel(model);
238
+ }
239
+ }
240
+
241
+ throw new NoSuchModelError({ modelId, modelType: 'evaluationModel' });
242
+ },
206
243
  videoModel(
207
244
  modelId: ExtractModelId<VIDEO_MODELS>,
208
245
  ): Experimental_VideoModelV4 {
@@ -5,3 +5,5 @@ export {
5
5
  experimental_createProviderRegistry,
6
6
  } from './provider-registry';
7
7
  export type { ProviderRegistryProvider } from './provider-registry';
8
+
9
+ export type { EvaluationProviderRegistry as Experimental_EvaluationProviderRegistry } from './provider-registry';
@@ -25,7 +25,8 @@ export class NoSuchProviderError extends NoSuchModelError {
25
25
  | 'transcriptionModel'
26
26
  | 'speechModel'
27
27
  | 'rerankingModel'
28
- | 'videoModel';
28
+ | 'videoModel'
29
+ | 'evaluationModel';
29
30
  providerId: string;
30
31
  availableProviders: string[];
31
32
  message?: string;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Experimental_EvaluationModelV4 as EvaluationModelV4,
2
3
  type EmbeddingModelV4,
3
4
  type Experimental_VideoModelV3,
4
5
  type Experimental_VideoModelV4,
@@ -13,6 +14,8 @@ import {
13
14
  type SpeechModelV4,
14
15
  type TranscriptionModelV4,
15
16
  } from '@ai-sdk/provider';
17
+ import type { EvaluationProvider } from '../evaluate/evaluation-provider';
18
+ import { resolveEvaluationModel } from '../model/resolve-model';
16
19
  import { wrapImageModel } from '../middleware/wrap-image-model';
17
20
  import { wrapLanguageModel } from '../middleware/wrap-language-model';
18
21
  import { asProviderV4 } from '../model/as-provider-v4';
@@ -34,7 +37,8 @@ type RegistryModelType =
34
37
  | 'transcriptionModel'
35
38
  | 'speechModel'
36
39
  | 'rerankingModel'
37
- | 'videoModel';
40
+ | 'videoModel'
41
+ | 'evaluationModel';
38
42
 
39
43
  type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
40
44
  videoModel: (...args: infer ARGS) => unknown;
@@ -42,6 +46,30 @@ type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
42
46
  ? ExtractLiteralUnion<ARGS[0]>
43
47
  : never;
44
48
 
49
+ type ProviderEvaluationModelIdentifier<PROVIDER> = PROVIDER extends {
50
+ evaluationModel: (...args: infer ARGS) => unknown;
51
+ }
52
+ ? ExtractLiteralUnion<ARGS[0]>
53
+ : never;
54
+
55
+ /** Registry with experimental evaluation access, separate from the stable interface. */
56
+ export type EvaluationProviderRegistry<
57
+ PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
58
+ string,
59
+ ProviderV4 | ProviderV3
60
+ >,
61
+ SEPARATOR extends string = ':',
62
+ > = ProviderRegistryProvider<PROVIDERS, SEPARATOR> & {
63
+ evaluationModel<KEY extends keyof PROVIDERS>(
64
+ id: KEY extends string
65
+ ? `${KEY & string}${SEPARATOR}${ProviderEvaluationModelIdentifier<PROVIDERS[KEY]>}`
66
+ : never,
67
+ ): EvaluationModelV4;
68
+ evaluationModel<KEY extends keyof PROVIDERS>(
69
+ id: KEY extends string ? `${KEY & string}${SEPARATOR}${string}` : never,
70
+ ): EvaluationModelV4;
71
+ };
72
+
45
73
  export interface ProviderRegistryProvider<
46
74
  PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
47
75
  string,
@@ -150,7 +178,7 @@ export function createProviderRegistry<
150
178
  | LanguageModelMiddleware[];
151
179
  imageModelMiddleware?: ImageModelMiddleware | ImageModelMiddleware[];
152
180
  } = {},
153
- ): ProviderRegistryProvider<PROVIDERS, SEPARATOR> {
181
+ ): EvaluationProviderRegistry<PROVIDERS, SEPARATOR> {
154
182
  const registry = new DefaultProviderRegistry<PROVIDERS, SEPARATOR>({
155
183
  separator,
156
184
  languageModelMiddleware,
@@ -175,9 +203,16 @@ export const experimental_createProviderRegistry = createProviderRegistry;
175
203
  class DefaultProviderRegistry<
176
204
  PROVIDERS extends Record<string, ProviderV4 | ProviderV3>,
177
205
  SEPARATOR extends string,
178
- > implements ProviderRegistryProvider<PROVIDERS, SEPARATOR> {
206
+ >
207
+ implements
208
+ ProviderRegistryProvider<PROVIDERS, SEPARATOR>,
209
+ EvaluationProviderRegistry<PROVIDERS, SEPARATOR>
210
+ {
179
211
  private providers: Partial<
180
- Record<keyof PROVIDERS, ProviderV4 & ProviderWithOptionalVideoModel>
212
+ Record<
213
+ keyof PROVIDERS,
214
+ ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider
215
+ >
181
216
  > = {};
182
217
  private separator: SEPARATOR;
183
218
  private languageModelMiddleware?:
@@ -213,7 +248,11 @@ class DefaultProviderRegistry<
213
248
  provider as ProviderWithOptionalVideoModel
214
249
  ).videoModel?.bind(provider);
215
250
 
216
- this.providers[id] =
251
+ const evaluationModel = (
252
+ provider as EvaluationProvider
253
+ ).evaluationModel?.bind(provider);
254
+
255
+ const registeredProvider =
217
256
  videoModel == null
218
257
  ? providerV4
219
258
  : Object.assign(Object.create(Object.getPrototypeOf(providerV4)), {
@@ -221,12 +260,18 @@ class DefaultProviderRegistry<
221
260
  videoModel: (modelId: string) =>
222
261
  asVideoModelV4(videoModel(modelId)),
223
262
  });
263
+
264
+ // Keep v4 instances intact. Adapted providers need the original evaluation receiver.
265
+ if (registeredProvider !== provider && evaluationModel != null) {
266
+ Object.assign(registeredProvider, { evaluationModel });
267
+ }
268
+ this.providers[id] = registeredProvider;
224
269
  }
225
270
 
226
271
  private getProvider(
227
272
  id: string,
228
273
  modelType: RegistryModelType,
229
- ): ProviderV4 & ProviderWithOptionalVideoModel {
274
+ ): ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider {
230
275
  const provider = this.providers[id as keyof PROVIDERS];
231
276
 
232
277
  if (provider == null) {
@@ -382,6 +427,20 @@ class DefaultProviderRegistry<
382
427
  return asVideoModelV4(model);
383
428
  }
384
429
 
430
+ evaluationModel<KEY extends keyof PROVIDERS>(
431
+ id: `${KEY & string}${SEPARATOR}${string}`,
432
+ ): EvaluationModelV4 {
433
+ const [providerId, modelId] = this.splitId(id, 'evaluationModel');
434
+ const provider = this.getProvider(providerId, 'evaluationModel');
435
+ const model = provider.evaluationModel?.(modelId);
436
+
437
+ if (model == null) {
438
+ throw new NoSuchModelError({ modelId: id, modelType: 'evaluationModel' });
439
+ }
440
+
441
+ return resolveEvaluationModel(model);
442
+ }
443
+
385
444
  files<KEY extends keyof PROVIDERS>(id: KEY & string): FilesV4 {
386
445
  const provider = this.getProvider(id, 'languageModel');
387
446
  const files = provider.files?.();
@@ -0,0 +1,27 @@
1
+ import type { Experimental_EvaluationModelV4 as EvaluationModelV4 } from '@ai-sdk/provider';
2
+ import { notImplemented } from './not-implemented';
3
+
4
+ export class EvaluationMockModelV4 implements EvaluationModelV4 {
5
+ readonly specificationVersion = 'v4';
6
+ readonly provider: string;
7
+ readonly modelId: string;
8
+ readonly supportedQuestionTypes: EvaluationModelV4['supportedQuestionTypes'];
9
+ doEvaluate: EvaluationModelV4['doEvaluate'];
10
+
11
+ constructor({
12
+ provider = 'mock-provider',
13
+ modelId = 'mock-model-id',
14
+ supportedQuestionTypes = ['choice', 'score', 'boolean'],
15
+ doEvaluate = notImplemented,
16
+ }: {
17
+ provider?: string;
18
+ modelId?: string;
19
+ supportedQuestionTypes?: EvaluationModelV4['supportedQuestionTypes'];
20
+ doEvaluate?: EvaluationModelV4['doEvaluate'];
21
+ } = {}) {
22
+ this.provider = provider;
23
+ this.modelId = modelId;
24
+ this.supportedQuestionTypes = supportedQuestionTypes;
25
+ this.doEvaluate = doEvaluate;
26
+ }
27
+ }