ai 7.0.127 → 7.0.129

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 (100) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/index.d.ts +230 -155
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +386 -209
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +288 -70
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +407 -76
  9. package/dist/internal/index.js.map +1 -1
  10. package/dist/test/index.d.ts +25 -17
  11. package/dist/test/index.d.ts.map +1 -1
  12. package/dist/test/index.js +27 -21
  13. package/dist/test/index.js.map +1 -1
  14. package/docs/02-foundations/06-provider-options.mdx +11 -10
  15. package/docs/02-getting-started/03-nextjs-pages-router.mdx +1 -1
  16. package/docs/02-getting-started/07-expo.mdx +1 -1
  17. package/docs/03-agents/02-building-agents.mdx +16 -11
  18. package/docs/03-agents/07-workflow-agent.mdx +83 -15
  19. package/docs/03-ai-sdk-core/16-mcp-tools.mdx +44 -0
  20. package/docs/03-ai-sdk-core/17-runtime-and-tool-context.mdx +18 -14
  21. package/docs/03-ai-sdk-core/25-settings.mdx +3 -2
  22. package/docs/03-ai-sdk-core/26-reasoning.mdx +12 -5
  23. package/docs/03-ai-sdk-core/{32-evaluation.mdx → 32-decisions.mdx} +64 -67
  24. package/docs/03-ai-sdk-core/35-image-generation.mdx +3 -1
  25. package/docs/03-ai-sdk-core/45-provider-management.mdx +8 -8
  26. package/docs/03-ai-sdk-core/60-telemetry.mdx +40 -31
  27. package/docs/03-ai-sdk-core/65-lifecycle-callbacks.mdx +21 -22
  28. package/docs/03-ai-sdk-harnesses/08-terminal-ui.mdx +2 -2
  29. package/docs/04-ai-sdk-ui/21-error-handling.mdx +28 -3
  30. package/docs/06-advanced/09-multistep-interfaces.mdx +1 -1
  31. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +2 -2
  32. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +2 -2
  33. package/docs/07-reference/01-ai-sdk-core/14-decide.mdx +74 -0
  34. package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +58 -4
  35. package/docs/07-reference/01-ai-sdk-core/40-provider-registry.mdx +11 -11
  36. package/docs/07-reference/01-ai-sdk-core/42-custom-provider.mdx +9 -9
  37. package/docs/07-reference/01-ai-sdk-core/66-extract-reasoning-middleware.mdx +24 -8
  38. package/docs/07-reference/02-ai-sdk-ui/32-prune-messages.mdx +1 -1
  39. package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +1 -0
  40. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +39 -2
  41. package/docs/07-reference/05-ai-sdk-errors/ai-decision-unsupported-question-type-error.mdx +31 -0
  42. package/docs/07-reference/05-ai-sdk-errors/ai-no-such-model-error.mdx +3 -3
  43. package/docs/07-reference/05-ai-sdk-errors/ai-no-such-provider-error.mdx +3 -3
  44. package/docs/08-migration-guides/23-migration-guide-7-0.mdx +1 -1
  45. package/docs/08-migration-guides/29-migration-guide-4-0.mdx +1 -1
  46. package/docs/09-troubleshooting/05-tool-invocation-missing-result.mdx +2 -2
  47. package/package.json +15 -15
  48. package/src/decide/decide-events.ts +129 -0
  49. package/src/{evaluate/evaluate.ts → decide/decide.ts} +36 -38
  50. package/src/decide/decision-provider.ts +11 -0
  51. package/src/decide/decision-result.ts +39 -0
  52. package/src/decide/index.ts +64 -0
  53. package/src/{evaluate → decide}/restricted-telemetry-dispatcher.ts +7 -7
  54. package/src/{evaluate/validate-evaluation.ts → decide/validate-decision.ts} +9 -9
  55. package/src/error/index.ts +10 -1
  56. package/src/generate-object/generate-object.ts +10 -0
  57. package/src/generate-object/stream-object.ts +10 -0
  58. package/src/generate-speech/generated-audio-file.ts +5 -4
  59. package/src/generate-speech/index.ts +13 -1
  60. package/src/generate-text/default-generate-text-result.ts +127 -0
  61. package/src/generate-text/generate-text.ts +5 -123
  62. package/src/generate-text/tool-approval-signature.ts +7 -4
  63. package/src/global.ts +3 -2
  64. package/src/index.ts +1 -1
  65. package/src/logger/deprecations.ts +72 -0
  66. package/src/logger/log-warnings.ts +25 -3
  67. package/src/middleware/extract-reasoning-middleware.ts +25 -6
  68. package/src/model/as-language-model-v3.ts +4 -0
  69. package/src/model/resolve-model.ts +45 -14
  70. package/src/prompt/convert-to-language-model-prompt.ts +7 -1
  71. package/src/realtime/encode-realtime-frame.ts +7 -2
  72. package/src/registry/custom-provider.ts +38 -26
  73. package/src/registry/index.ts +13 -1
  74. package/src/registry/no-such-provider-error.ts +2 -0
  75. package/src/registry/provider-registry.ts +56 -29
  76. package/src/telemetry/create-telemetry-dispatcher.ts +17 -5
  77. package/src/telemetry/telemetry.ts +35 -19
  78. package/src/telemetry/tracing-channel.ts +1 -1
  79. package/src/test/decision-mock-model-v4.ts +35 -0
  80. package/src/transcribe/index.ts +13 -1
  81. package/src/transcribe/transcribe.ts +1 -1
  82. package/src/types/index.ts +1 -0
  83. package/src/types/language-model.ts +9 -1
  84. package/src/types/usage.ts +21 -0
  85. package/src/ui/chat.ts +25 -9
  86. package/src/ui/convert-to-model-messages.ts +40 -8
  87. package/src/ui/http-chat-transport.ts +5 -3
  88. package/src/ui/process-text-stream.ts +5 -1
  89. package/src/ui/process-ui-message-stream.ts +21 -0
  90. package/src/ui/text-stream-chat-transport.ts +3 -1
  91. package/src/ui/ui-messages.ts +8 -0
  92. package/src/ui/unavailable-tool.ts +17 -0
  93. package/src/ui/validate-ui-messages.ts +10 -2
  94. package/docs/07-reference/01-ai-sdk-core/14-evaluate.mdx +0 -74
  95. package/docs/07-reference/05-ai-sdk-errors/ai-evaluation-unsupported-question-type-error.mdx +0 -31
  96. package/src/evaluate/evaluate-events.ts +0 -131
  97. package/src/evaluate/evaluation-provider.ts +0 -6
  98. package/src/evaluate/evaluation-result.ts +0 -39
  99. package/src/evaluate/index.ts +0 -13
  100. package/src/test/evaluation-mock-model-v4.ts +0 -27
@@ -1,5 +1,5 @@
1
1
  import {
2
- type Experimental_EvaluationModelV4 as EvaluationModelV4,
2
+ type Experimental_DecisionModelV4 as DecisionModelV4,
3
3
  type EmbeddingModelV4,
4
4
  type Experimental_VideoModelV3,
5
5
  type Experimental_VideoModelV4,
@@ -14,8 +14,12 @@ import {
14
14
  type SpeechModelV4,
15
15
  type TranscriptionModelV4,
16
16
  } from '@ai-sdk/provider';
17
- import type { EvaluationProvider } from '../evaluate/evaluation-provider';
18
- import { resolveEvaluationModel } from '../model/resolve-model';
17
+ import type { DecisionModel } from '../decide/decision-result';
18
+ import type { DecisionProvider } from '../decide/decision-provider';
19
+ import {
20
+ resolveDecisionModel,
21
+ asEvaluationModel,
22
+ } from '../model/resolve-model';
19
23
  import { wrapImageModel } from '../middleware/wrap-image-model';
20
24
  import { wrapLanguageModel } from '../middleware/wrap-language-model';
21
25
  import { asProviderV4 } from '../model/as-provider-v4';
@@ -38,7 +42,7 @@ type RegistryModelType =
38
42
  | 'speechModel'
39
43
  | 'rerankingModel'
40
44
  | 'videoModel'
41
- | 'evaluationModel';
45
+ | 'decisionModel';
42
46
 
43
47
  type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
44
48
  videoModel: (...args: infer ARGS) => unknown;
@@ -46,28 +50,40 @@ type ProviderVideoModelIdentifier<PROVIDER> = PROVIDER extends {
46
50
  ? ExtractLiteralUnion<ARGS[0]>
47
51
  : never;
48
52
 
49
- type ProviderEvaluationModelIdentifier<PROVIDER> = PROVIDER extends {
50
- evaluationModel: (...args: infer ARGS) => unknown;
53
+ type ProviderDecisionModelIdentifier<PROVIDER> = PROVIDER extends {
54
+ decisionModel: (...args: infer ARGS) => unknown;
51
55
  }
52
56
  ? ExtractLiteralUnion<ARGS[0]>
53
- : never;
57
+ : PROVIDER extends { evaluationModel: (...args: infer ARGS) => unknown }
58
+ ? ExtractLiteralUnion<ARGS[0]>
59
+ : never;
54
60
 
55
- /** Registry with experimental evaluation access, separate from the stable interface. */
56
- export type EvaluationProviderRegistry<
61
+ /** Registry with experimental decision access, separate from the stable interface. */
62
+ export type DecisionProviderRegistry<
57
63
  PROVIDERS extends Record<string, ProviderV4 | ProviderV3> = Record<
58
64
  string,
59
65
  ProviderV4 | ProviderV3
60
66
  >,
61
67
  SEPARATOR extends string = ':',
62
68
  > = ProviderRegistryProvider<PROVIDERS, SEPARATOR> & {
69
+ decisionModel<KEY extends keyof PROVIDERS>(
70
+ id: KEY extends string
71
+ ? `${KEY & string}${SEPARATOR}${ProviderDecisionModelIdentifier<PROVIDERS[KEY]>}`
72
+ : never,
73
+ ): DecisionModelV4;
74
+ decisionModel<KEY extends keyof PROVIDERS>(
75
+ id: KEY extends string ? `${KEY & string}${SEPARATOR}${string}` : never,
76
+ ): DecisionModelV4;
77
+ /** @deprecated Use `decisionModel` instead. */
63
78
  evaluationModel<KEY extends keyof PROVIDERS>(
64
79
  id: KEY extends string
65
- ? `${KEY & string}${SEPARATOR}${ProviderEvaluationModelIdentifier<PROVIDERS[KEY]>}`
80
+ ? `${KEY & string}${SEPARATOR}${ProviderDecisionModelIdentifier<PROVIDERS[KEY]>}`
66
81
  : never,
67
- ): EvaluationModelV4;
82
+ ): DecisionModelV4 & { doEvaluate: DecisionModelV4['doDecide'] };
83
+ /** @deprecated Use `decisionModel` instead. */
68
84
  evaluationModel<KEY extends keyof PROVIDERS>(
69
85
  id: KEY extends string ? `${KEY & string}${SEPARATOR}${string}` : never,
70
- ): EvaluationModelV4;
86
+ ): DecisionModelV4 & { doEvaluate: DecisionModelV4['doDecide'] };
71
87
  };
72
88
 
73
89
  export interface ProviderRegistryProvider<
@@ -178,7 +194,7 @@ export function createProviderRegistry<
178
194
  | LanguageModelMiddleware[];
179
195
  imageModelMiddleware?: ImageModelMiddleware | ImageModelMiddleware[];
180
196
  } = {},
181
- ): EvaluationProviderRegistry<PROVIDERS, SEPARATOR> {
197
+ ): DecisionProviderRegistry<PROVIDERS, SEPARATOR> {
182
198
  const registry = new DefaultProviderRegistry<PROVIDERS, SEPARATOR>({
183
199
  separator,
184
200
  languageModelMiddleware,
@@ -206,12 +222,12 @@ class DefaultProviderRegistry<
206
222
  >
207
223
  implements
208
224
  ProviderRegistryProvider<PROVIDERS, SEPARATOR>,
209
- EvaluationProviderRegistry<PROVIDERS, SEPARATOR>
225
+ DecisionProviderRegistry<PROVIDERS, SEPARATOR>
210
226
  {
211
227
  private providers: Partial<
212
228
  Record<
213
229
  keyof PROVIDERS,
214
- ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider
230
+ ProviderV4 & ProviderWithOptionalVideoModel & DecisionProvider
215
231
  >
216
232
  > = Object.create(null);
217
233
  private separator: SEPARATOR;
@@ -248,9 +264,10 @@ class DefaultProviderRegistry<
248
264
  provider as ProviderWithOptionalVideoModel
249
265
  ).videoModel?.bind(provider);
250
266
 
251
- const evaluationModel = (
252
- provider as EvaluationProvider
253
- ).evaluationModel?.bind(provider);
267
+ const decisionProvider = provider as DecisionProvider;
268
+ const decisionModel = (
269
+ decisionProvider.decisionModel ?? decisionProvider.evaluationModel
270
+ )?.bind(provider);
254
271
 
255
272
  const registeredProvider =
256
273
  videoModel == null
@@ -261,9 +278,9 @@ class DefaultProviderRegistry<
261
278
  asVideoModelV4(videoModel(modelId)),
262
279
  });
263
280
 
264
- // Keep v4 instances intact. Adapted providers need the original evaluation receiver.
265
- if (registeredProvider !== provider && evaluationModel != null) {
266
- Object.assign(registeredProvider, { evaluationModel });
281
+ // Keep v4 instances intact. Adapted providers need the original decision receiver.
282
+ if (registeredProvider !== provider && decisionModel != null) {
283
+ Object.assign(registeredProvider, { decisionModel });
267
284
  }
268
285
  this.providers[id] = registeredProvider;
269
286
  }
@@ -271,7 +288,7 @@ class DefaultProviderRegistry<
271
288
  private getProvider(
272
289
  id: string,
273
290
  modelType: RegistryModelType,
274
- ): ProviderV4 & ProviderWithOptionalVideoModel & EvaluationProvider {
291
+ ): ProviderV4 & ProviderWithOptionalVideoModel & DecisionProvider {
275
292
  const provider = this.providers[id as keyof PROVIDERS];
276
293
 
277
294
  if (provider == null) {
@@ -427,18 +444,28 @@ class DefaultProviderRegistry<
427
444
  return asVideoModelV4(model);
428
445
  }
429
446
 
430
- evaluationModel<KEY extends keyof PROVIDERS>(
447
+ decisionModel<KEY extends keyof PROVIDERS>(
431
448
  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);
449
+ ): DecisionModelV4 {
450
+ const [providerId, modelId] = this.splitId(id, 'decisionModel');
451
+ const provider = this.getProvider(providerId, 'decisionModel');
452
+ const factory:
453
+ | ((modelId: string) => Exclude<DecisionModel, string>)
454
+ | undefined = provider.decisionModel ?? provider.evaluationModel;
455
+ const model = factory?.call(provider, modelId);
436
456
 
437
457
  if (model == null) {
438
- throw new NoSuchModelError({ modelId: id, modelType: 'evaluationModel' });
458
+ throw new NoSuchModelError({ modelId: id, modelType: 'decisionModel' });
439
459
  }
440
460
 
441
- return resolveEvaluationModel(model);
461
+ return resolveDecisionModel(model);
462
+ }
463
+
464
+ /** @deprecated Use `decisionModel` instead. */
465
+ evaluationModel<KEY extends keyof PROVIDERS>(
466
+ id: `${KEY & string}${SEPARATOR}${string}`,
467
+ ) {
468
+ return asEvaluationModel(this.decisionModel(id));
442
469
  }
443
470
 
444
471
  files<KEY extends keyof PROVIDERS>(id: KEY & string): FilesV4 {
@@ -142,10 +142,18 @@ export function createTelemetryDispatcher({
142
142
 
143
143
  const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>(
144
144
  key: KEY,
145
+ deprecatedKey?: keyof Telemetry,
145
146
  ): Callback<TelemetryEvent<KEY>> | undefined => {
146
147
  const integrationCallbacks = (
147
148
  integrations
148
- .map(integration => integration[key]?.bind(integration))
149
+ .map(integration => {
150
+ const callback =
151
+ integration[key] ??
152
+ (deprecatedKey == null ? undefined : integration[deprecatedKey]);
153
+ return typeof callback === 'function'
154
+ ? callback.bind(integration)
155
+ : undefined;
156
+ })
149
157
  .filter(Boolean) as Array<
150
158
  Callback<InferTelemetryEvent<TelemetryEvent<KEY>>>
151
159
  >
@@ -222,16 +230,20 @@ export function createTelemetryDispatcher({
222
230
  onEmbedEnd: mergeTelemetryCallback('onEmbedEnd'),
223
231
  onRerankStart: mergeTelemetryCallback('onRerankStart'),
224
232
  onRerankEnd: mergeTelemetryCallback('onRerankEnd'),
225
- experimental_onEvaluateStart: mergeTelemetryCallback(
233
+ experimental_onDecideStart: mergeTelemetryCallback(
234
+ 'experimental_onDecideStart',
226
235
  'experimental_onEvaluateStart',
227
236
  ),
228
- experimental_onEvaluationModelCallStart: mergeTelemetryCallback(
237
+ experimental_onDecisionModelCallStart: mergeTelemetryCallback(
238
+ 'experimental_onDecisionModelCallStart',
229
239
  'experimental_onEvaluationModelCallStart',
230
240
  ),
231
- experimental_onEvaluationModelCallEnd: mergeTelemetryCallback(
241
+ experimental_onDecisionModelCallEnd: mergeTelemetryCallback(
242
+ 'experimental_onDecisionModelCallEnd',
232
243
  'experimental_onEvaluationModelCallEnd',
233
244
  ),
234
- experimental_onEvaluateEnd: mergeTelemetryCallback(
245
+ experimental_onDecideEnd: mergeTelemetryCallback(
246
+ 'experimental_onDecideEnd',
235
247
  'experimental_onEvaluateEnd',
236
248
  ),
237
249
  experimental_onStreamTranscriptionStart: mergeTelemetryCallback(
@@ -6,11 +6,11 @@ import type {
6
6
  EmbeddingModelCallStartEvent,
7
7
  } from '../embed/embed-events';
8
8
  import type {
9
- EvaluateEndEvent,
10
- EvaluateStartEvent,
11
- EvaluationModelCallEndEvent,
12
- EvaluationModelCallStartEvent,
13
- } from '../evaluate/evaluate-events';
9
+ DecideEndEvent,
10
+ DecideStartEvent,
11
+ DecisionModelCallEndEvent,
12
+ DecisionModelCallStartEvent,
13
+ } from '../decide/decide-events';
14
14
  import type {
15
15
  GenerateObjectEndEvent,
16
16
  GenerateObjectStartEvent,
@@ -116,10 +116,10 @@ export interface TelemetryDispatcher {
116
116
  onEmbedEnd?: Callback<EmbeddingModelCallEndEvent>;
117
117
  onRerankStart?: Callback<RerankingModelCallStartEvent>;
118
118
  onRerankEnd?: Callback<RerankingModelCallEndEvent>;
119
- experimental_onEvaluateStart?: Callback<EvaluateStartEvent>;
120
- experimental_onEvaluationModelCallStart?: Callback<EvaluationModelCallStartEvent>;
121
- experimental_onEvaluationModelCallEnd?: Callback<EvaluationModelCallEndEvent>;
122
- experimental_onEvaluateEnd?: Callback<EvaluateEndEvent>;
119
+ experimental_onDecideStart?: Callback<DecideStartEvent>;
120
+ experimental_onDecisionModelCallStart?: Callback<DecisionModelCallStartEvent>;
121
+ experimental_onDecisionModelCallEnd?: Callback<DecisionModelCallEndEvent>;
122
+ experimental_onDecideEnd?: Callback<DecideEndEvent>;
123
123
  experimental_onStreamTranscriptionStart?: Callback<StreamTranscriptionStartEvent>;
124
124
  experimental_onStreamTranscriptionEnd?: Callback<StreamTranscriptionEndEvent>;
125
125
  onEnd?: Callback<OperationEndEvent>;
@@ -245,29 +245,45 @@ export interface Telemetry {
245
245
  */
246
246
  onRerankEnd?: Callback<InferTelemetryEvent<RerankingModelCallEndEvent>>;
247
247
 
248
- /** Called when an experimental evaluation operation begins. */
248
+ /** @deprecated Use `experimental_onDecideStart` instead. */
249
249
  experimental_onEvaluateStart?: Callback<
250
- InferTelemetryEvent<EvaluateStartEvent>
250
+ InferTelemetryEvent<DecideStartEvent>
251
251
  >;
252
252
 
253
+ /** @deprecated Use `experimental_onDecisionModelCallStart` instead. */
254
+ experimental_onEvaluationModelCallStart?: Callback<
255
+ InferTelemetryEvent<DecisionModelCallStartEvent>
256
+ >;
257
+
258
+ /** @deprecated Use `experimental_onDecisionModelCallEnd` instead. */
259
+ experimental_onEvaluationModelCallEnd?: Callback<
260
+ InferTelemetryEvent<DecisionModelCallEndEvent>
261
+ >;
262
+
263
+ /** @deprecated Use `experimental_onDecideEnd` instead. */
264
+ experimental_onEvaluateEnd?: Callback<InferTelemetryEvent<DecideEndEvent>>;
265
+
266
+ /** Called when an experimental decision operation begins. */
267
+ experimental_onDecideStart?: Callback<InferTelemetryEvent<DecideStartEvent>>;
268
+
253
269
  /**
254
- * Called immediately before an experimental evaluation model call begins.
270
+ * Called immediately before an experimental decision model call begins.
255
271
  * The logical model call includes any provider retries.
256
272
  */
257
- experimental_onEvaluationModelCallStart?: Callback<
258
- InferTelemetryEvent<EvaluationModelCallStartEvent>
273
+ experimental_onDecisionModelCallStart?: Callback<
274
+ InferTelemetryEvent<DecisionModelCallStartEvent>
259
275
  >;
260
276
 
261
277
  /**
262
- * Called after an experimental evaluation model response has been validated.
278
+ * Called after an experimental decision model response has been validated.
263
279
  * The logical model call includes any provider retries.
264
280
  */
265
- experimental_onEvaluationModelCallEnd?: Callback<
266
- InferTelemetryEvent<EvaluationModelCallEndEvent>
281
+ experimental_onDecisionModelCallEnd?: Callback<
282
+ InferTelemetryEvent<DecisionModelCallEndEvent>
267
283
  >;
268
284
 
269
- /** Called when an experimental evaluation operation completes. */
270
- experimental_onEvaluateEnd?: Callback<InferTelemetryEvent<EvaluateEndEvent>>;
285
+ /** Called when an experimental decision operation completes. */
286
+ experimental_onDecideEnd?: Callback<InferTelemetryEvent<DecideEndEvent>>;
271
287
 
272
288
  /** Called when an experimental streaming transcription operation begins. */
273
289
  experimental_onStreamTranscriptionStart?: Callback<
@@ -12,7 +12,7 @@ export type TelemetryTracingEventType =
12
12
  | 'generateSpeech'
13
13
  | 'transcribe'
14
14
  | 'streamTranscribe'
15
- | 'experimental_evaluate';
15
+ | 'experimental_decide';
16
16
 
17
17
  export type TelemetryTracingChannelMessage<EVENT = unknown> = {
18
18
  readonly type: TelemetryTracingEventType;
@@ -0,0 +1,35 @@
1
+ import type { Experimental_DecisionModelV4 as DecisionModelV4 } from '@ai-sdk/provider';
2
+ import { notImplemented } from './not-implemented';
3
+
4
+ export class DecisionMockModelV4 implements DecisionModelV4 {
5
+ readonly specificationVersion = 'v4';
6
+ readonly provider: string;
7
+ readonly modelId: string;
8
+ readonly supportedQuestionTypes: DecisionModelV4['supportedQuestionTypes'];
9
+ doDecide: DecisionModelV4['doDecide'];
10
+
11
+ /** @deprecated Use `doDecide` instead. */
12
+ doEvaluate(options: Parameters<DecisionModelV4['doDecide']>[0]) {
13
+ return this.doDecide(options);
14
+ }
15
+
16
+ constructor({
17
+ provider = 'mock-provider',
18
+ modelId = 'mock-model-id',
19
+ supportedQuestionTypes = ['choice', 'score', 'boolean'],
20
+ doDecide,
21
+ doEvaluate,
22
+ }: {
23
+ provider?: string;
24
+ modelId?: string;
25
+ supportedQuestionTypes?: DecisionModelV4['supportedQuestionTypes'];
26
+ doDecide?: DecisionModelV4['doDecide'];
27
+ /** @deprecated Use `doDecide` instead. */
28
+ doEvaluate?: DecisionModelV4['doDecide'];
29
+ } = {}) {
30
+ this.provider = provider;
31
+ this.modelId = modelId;
32
+ this.supportedQuestionTypes = supportedQuestionTypes;
33
+ this.doDecide = doDecide ?? doEvaluate ?? notImplemented;
34
+ }
35
+ }
@@ -1,5 +1,6 @@
1
1
  import type { TranscriptionResult } from './transcribe-result';
2
2
  import { transcribe } from './transcribe';
3
+ import { logWarnings } from '../logger/log-warnings';
3
4
 
4
5
  export { transcribe } from './transcribe';
5
6
  export { streamTranscribe as experimental_streamTranscribe } from './stream-transcribe';
@@ -20,7 +21,18 @@ export type {
20
21
  /**
21
22
  * @deprecated Use `transcribe` instead.
22
23
  */
23
- const experimental_transcribe = transcribe;
24
+ const experimental_transcribe: typeof transcribe = options => {
25
+ logWarnings({
26
+ warnings: [
27
+ {
28
+ type: 'deprecated',
29
+ setting: 'experimental_transcribe',
30
+ message: 'Use transcribe instead.',
31
+ },
32
+ ],
33
+ });
34
+ return transcribe(options);
35
+ };
24
36
  export { experimental_transcribe };
25
37
 
26
38
  /**
@@ -256,7 +256,7 @@ export async function transcribe({
256
256
  model: resolvedModel.modelId,
257
257
  });
258
258
 
259
- if (!result.text) {
259
+ if (result.text == null) {
260
260
  throw new NoTranscriptGeneratedError({
261
261
  responses: [result.response],
262
262
  });
@@ -7,6 +7,7 @@ export type { ImageModelResponseMetadata } from './image-model-response-metadata
7
7
  export type { JSONValue } from './json-value';
8
8
  export type {
9
9
  CallWarning,
10
+ CallWarningRegistry,
10
11
  FinishReason,
11
12
  LanguageModel,
12
13
  ToolChoice,
@@ -80,11 +80,19 @@ export type FinishReason =
80
80
  | 'error'
81
81
  | 'other';
82
82
 
83
+ /**
84
+ * Registry for call warning variants. Packages that expose additional warning
85
+ * protocols can augment this interface.
86
+ */
87
+ export interface CallWarningRegistry {
88
+ sharedV4: SharedV4Warning;
89
+ }
90
+
83
91
  /**
84
92
  * Warning from the model provider for this call. The call will proceed, but e.g.
85
93
  * some settings might not be supported, which can lead to suboptimal results.
86
94
  */
87
- export type CallWarning = SharedV4Warning;
95
+ export type CallWarning = CallWarningRegistry[keyof CallWarningRegistry];
88
96
 
89
97
  /**
90
98
  * A source that has been used as input to generate the response.
@@ -123,6 +123,12 @@ export function addLanguageModelUsage(
123
123
  usage1: LanguageModelUsage,
124
124
  usage2: LanguageModelUsage,
125
125
  ): LanguageModelUsage {
126
+ const raw = isNullLanguageModelUsage(usage1)
127
+ ? usage2.raw
128
+ : isNullLanguageModelUsage(usage2)
129
+ ? usage1.raw
130
+ : undefined;
131
+
126
132
  return {
127
133
  inputTokens: addTokenCounts(usage1.inputTokens, usage2.inputTokens),
128
134
  inputTokenDetails: {
@@ -151,9 +157,24 @@ export function addLanguageModelUsage(
151
157
  ),
152
158
  },
153
159
  totalTokens: addTokenCounts(usage1.totalTokens, usage2.totalTokens),
160
+ ...(raw == null ? {} : { raw }),
154
161
  };
155
162
  }
156
163
 
164
+ function isNullLanguageModelUsage(usage: LanguageModelUsage): boolean {
165
+ return (
166
+ usage.inputTokens == null &&
167
+ usage.inputTokenDetails?.noCacheTokens == null &&
168
+ usage.inputTokenDetails?.cacheReadTokens == null &&
169
+ usage.inputTokenDetails?.cacheWriteTokens == null &&
170
+ usage.outputTokens == null &&
171
+ usage.outputTokenDetails?.textTokens == null &&
172
+ usage.outputTokenDetails?.reasoningTokens == null &&
173
+ usage.totalTokens == null &&
174
+ usage.raw == null
175
+ );
176
+ }
177
+
157
178
  function addTokenCounts(
158
179
  tokenCount1: number | undefined,
159
180
  tokenCount2: number | undefined,
package/src/ui/chat.ts CHANGED
@@ -315,7 +315,12 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
315
315
  status: ChatStatus;
316
316
  error?: Error;
317
317
  }) {
318
- if (this.status === status) return;
318
+ if (this.status === status) {
319
+ if (this.error !== error) {
320
+ this.state.error = error;
321
+ }
322
+ return;
323
+ }
319
324
 
320
325
  this.state.status = status;
321
326
  this.state.error = error;
@@ -849,11 +854,18 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
849
854
  responseMessageIndex === -1
850
855
  ? lastMessage
851
856
  : this.state.messages[responseMessageIndex];
857
+ // the continued stream can start with either
858
+ // 1) input deltas
859
+ // 2) or the result of an answered tool approval request
860
+ // Keep the tool part so in case 2, those chunks can find their tool call
852
861
  const resumableResponseMessage =
853
862
  trigger === 'resume-stream' &&
854
863
  responseMessage?.role === 'assistant' &&
855
864
  responseMessage.parts.some(
856
- part => isToolUIPart(part) && part.state === 'input-streaming',
865
+ part =>
866
+ isToolUIPart(part) &&
867
+ (part.state === 'input-streaming' ||
868
+ part.state === 'approval-responded'),
857
869
  )
858
870
  ? this.state.snapshot(responseMessage)
859
871
  : undefined;
@@ -952,6 +964,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
952
964
  await consumeStream({
953
965
  stream: processUIMessageStream({
954
966
  stream,
967
+ resetStateOnMessageIdChange: trigger === 'resume-stream',
955
968
  onToolCall: this.onToolCall,
956
969
  onData: this.onData,
957
970
  messageMetadataSchema: this.messageMetadataSchema,
@@ -1002,13 +1015,16 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
1002
1015
 
1003
1016
  isError = true;
1004
1017
 
1005
- // Network errors such as disconnected, timeout, etc.
1006
- if (
1007
- err instanceof TypeError &&
1008
- (err.message.toLowerCase().includes('fetch') ||
1009
- err.message.toLowerCase().includes('network'))
1010
- ) {
1011
- isDisconnect = true;
1018
+ if (err instanceof TypeError) {
1019
+ const message = err.message.toLowerCase();
1020
+
1021
+ isDisconnect =
1022
+ // Chromium request failures; Node.js fetch failures.
1023
+ message.includes('fetch') ||
1024
+ // Firefox request failures; Chromium response-body failures.
1025
+ message.includes('network') ||
1026
+ // Safari/WebKit request and response-body failures.
1027
+ message === 'load failed';
1012
1028
  }
1013
1029
 
1014
1030
  if (isDisconnect) {
@@ -33,6 +33,7 @@ import {
33
33
  type ToolUIPart,
34
34
  type UIMessage,
35
35
  } from './ui-messages';
36
+ import { isToolPartFromUnavailableTool } from './unavailable-tool';
36
37
  import { warnIfUIMessageHasDeprecatedRawInput } from './warn-if-ui-message-has-deprecated-raw-input';
37
38
  /**
38
39
  * Converts an array of UI messages from useChat into an array of ModelMessages that can be used
@@ -85,6 +86,39 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
85
86
  }));
86
87
  }
87
88
 
89
+ async function createModelOutput({
90
+ toolPart,
91
+ toolName,
92
+ output,
93
+ errorMode,
94
+ }: {
95
+ toolPart: ToolUIPart<InferUIMessageTools<UI_MESSAGE>> | DynamicToolUIPart;
96
+ toolName: string;
97
+ output: unknown;
98
+ errorMode: 'none' | 'text' | 'json';
99
+ }) {
100
+ const tool = getOwn(options?.tools, toolName);
101
+
102
+ if (
103
+ errorMode === 'none' &&
104
+ tool == null &&
105
+ isToolPartFromUnavailableTool(toolPart)
106
+ ) {
107
+ return {
108
+ type: 'text' as const,
109
+ value: 'Tool output omitted because the tool is no longer available.',
110
+ };
111
+ }
112
+
113
+ return createToolModelOutput({
114
+ toolCallId: toolPart.toolCallId,
115
+ input: toolPart.input,
116
+ output,
117
+ tool,
118
+ errorMode,
119
+ });
120
+ }
121
+
88
122
  for (const message of messages) {
89
123
  switch (message.role) {
90
124
  case 'system': {
@@ -284,14 +318,13 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
284
318
  type: 'tool-result',
285
319
  toolCallId: part.toolCallId,
286
320
  toolName,
287
- output: await createToolModelOutput({
288
- toolCallId: part.toolCallId,
289
- input: part.input,
321
+ output: await createModelOutput({
322
+ toolPart: part,
323
+ toolName,
290
324
  output:
291
325
  part.state === 'output-error'
292
326
  ? part.errorText
293
327
  : part.output,
294
- tool: getOwn(options?.tools, toolName),
295
328
  errorMode:
296
329
  part.state === 'output-error' ? 'json' : 'none',
297
330
  }),
@@ -403,14 +436,13 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
403
436
  type: 'tool-result',
404
437
  toolCallId: toolPart.toolCallId,
405
438
  toolName,
406
- output: await createToolModelOutput({
407
- toolCallId: toolPart.toolCallId,
408
- input: toolPart.input,
439
+ output: await createModelOutput({
440
+ toolPart,
441
+ toolName,
409
442
  output:
410
443
  toolPart.state === 'output-error'
411
444
  ? toolPart.errorText
412
445
  : toolPart.output,
413
- tool: getOwn(options?.tools, toolName),
414
446
  errorMode:
415
447
  toolPart.state === 'output-error' ? 'text' : 'none',
416
448
  }),
@@ -13,10 +13,12 @@ import type { UIMessage } from './ui-messages';
13
13
 
14
14
  function appendPathToUrl(url: string, path: string): string {
15
15
  const queryOrFragmentStart = url.search(/[?#]/);
16
+ const urlPath =
17
+ queryOrFragmentStart === -1 ? url : url.slice(0, queryOrFragmentStart);
18
+ const suffix =
19
+ queryOrFragmentStart === -1 ? '' : url.slice(queryOrFragmentStart);
16
20
 
17
- return queryOrFragmentStart === -1
18
- ? `${url}${path}`
19
- : `${url.slice(0, queryOrFragmentStart)}${path}${url.slice(queryOrFragmentStart)}`;
21
+ return `${urlPath.endsWith('/') && path.startsWith('/') ? urlPath.slice(0, -1) : urlPath}${path}${suffix}`;
20
22
  }
21
23
 
22
24
  export type PrepareSendMessagesRequest<UI_MESSAGE extends UIMessage> = (
@@ -5,7 +5,11 @@ export async function processTextStream({
5
5
  stream: ReadableStream<Uint8Array>;
6
6
  onTextPart: (chunk: string) => Promise<void> | void;
7
7
  }): Promise<void> {
8
- const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
8
+ const reader = stream
9
+ .pipeThrough(
10
+ new TextDecoderStream() as ReadableWritablePair<string, Uint8Array>,
11
+ )
12
+ .getReader();
9
13
  while (true) {
10
14
  const { done, value } = await reader.read();
11
15
  if (done) {