ai 7.0.147 → 7.0.149
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.
- package/CHANGELOG.md +31 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -32
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +2 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +4 -1
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +7 -0
- package/docs/03-ai-sdk-core/18-code-mode.mdx +3 -1
- package/docs/03-ai-sdk-core/18-tools.mdx +19 -0
- package/docs/03-ai-sdk-core/36-transcription.mdx +40 -39
- package/docs/03-ai-sdk-core/37-speech.mdx +1 -0
- package/docs/03-ai-sdk-core/60-telemetry.mdx +25 -2
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +32 -0
- package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +1 -1
- package/docs/06-advanced/11-secure-url-fetching.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +7 -7
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +1 -1
- package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +1 -1
- package/docs/07-reference/05-ai-sdk-errors/ai-tool-choice-violation-error.mdx +12 -7
- package/package.json +12 -12
- package/src/error/tool-choice-violation-error.ts +6 -4
- package/src/generate-text/collect-tool-approvals.ts +10 -0
- package/src/generate-text/generate-text.ts +24 -12
- package/src/generate-text/stream-language-model-call.ts +37 -12
- package/src/generate-text/stream-text.ts +4 -1
- package/src/types/language-model.ts +2 -0
- package/src/ui/chat.ts +40 -12
- package/src/ui-message-stream/ui-message-chunks.ts +1 -0
|
@@ -448,6 +448,12 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
448
448
|
let previousOutputChunkTimestampMs: number | undefined;
|
|
449
449
|
const timeBetweenOutputChunksMs: number[] = [];
|
|
450
450
|
|
|
451
|
+
// Tool choice none rejects tool calls when the model call finishes.
|
|
452
|
+
// Withhold tool parts so that consumers (e.g. client-side tools) cannot
|
|
453
|
+
// act on them before the rejection, and skip user callbacks that would
|
|
454
|
+
// repair or refine them.
|
|
455
|
+
const withholdToolParts = toolChoice.type === 'none';
|
|
456
|
+
|
|
451
457
|
return new TransformStream<
|
|
452
458
|
LanguageModelV4StreamPart,
|
|
453
459
|
LanguageModelStreamPart<TOOLS>
|
|
@@ -467,6 +473,15 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
467
473
|
previousOutputChunkTimestampMs = outputChunkTimestampMs;
|
|
468
474
|
}
|
|
469
475
|
|
|
476
|
+
if (
|
|
477
|
+
withholdToolParts &&
|
|
478
|
+
(chunk.type === 'tool-input-start' ||
|
|
479
|
+
chunk.type === 'tool-input-delta' ||
|
|
480
|
+
chunk.type === 'tool-input-end')
|
|
481
|
+
) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
470
485
|
switch (chunk.type) {
|
|
471
486
|
case 'error':
|
|
472
487
|
controller.enqueue({
|
|
@@ -655,17 +670,18 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
655
670
|
});
|
|
656
671
|
|
|
657
672
|
const enforcedToolChoice =
|
|
658
|
-
toolChoice.type === '
|
|
659
|
-
? toolChoice
|
|
660
|
-
: undefined;
|
|
673
|
+
toolChoice.type === 'auto' ? undefined : toolChoice;
|
|
661
674
|
|
|
662
675
|
const toolChoiceViolationError =
|
|
663
676
|
enforcedToolChoice != null &&
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
677
|
+
(enforcedToolChoice.type === 'none'
|
|
678
|
+
? toolCallsByToolCallId.size > 0
|
|
679
|
+
: chunk.finishReason.unified !== 'interrupted' &&
|
|
680
|
+
![...toolCallsByToolCallId.values()].some(
|
|
681
|
+
toolCall =>
|
|
682
|
+
enforcedToolChoice.type === 'required' ||
|
|
683
|
+
toolCall.toolName === enforcedToolChoice.toolName,
|
|
684
|
+
))
|
|
669
685
|
? new ToolChoiceViolationError({
|
|
670
686
|
toolChoice: enforcedToolChoice,
|
|
671
687
|
finishReason: chunk.finishReason.unified,
|
|
@@ -708,17 +724,22 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
708
724
|
const toolCall = await parseToolCall({
|
|
709
725
|
toolCall: chunk,
|
|
710
726
|
tools,
|
|
711
|
-
repairToolCall,
|
|
712
|
-
refineToolInput,
|
|
727
|
+
repairToolCall: withholdToolParts ? undefined : repairToolCall,
|
|
728
|
+
refineToolInput: withholdToolParts ? undefined : refineToolInput,
|
|
713
729
|
instructions,
|
|
714
730
|
messages,
|
|
715
731
|
abortSignal,
|
|
716
732
|
});
|
|
717
733
|
|
|
718
734
|
toolCallsByToolCallId.set(toolCall.toolCallId, toolCall);
|
|
719
|
-
controller.enqueue(toolCall);
|
|
720
735
|
if (collectContent) modelCallContent.push(toolCall);
|
|
721
736
|
|
|
737
|
+
if (withholdToolParts) {
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
controller.enqueue(toolCall);
|
|
742
|
+
|
|
722
743
|
if (toolCall.invalid) {
|
|
723
744
|
if (!toolCall.providerExecuted) {
|
|
724
745
|
controller.enqueue({
|
|
@@ -746,6 +767,10 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
746
767
|
case 'tool-approval-request': {
|
|
747
768
|
if (collectContent) rawModelCallContent.push(chunk);
|
|
748
769
|
|
|
770
|
+
if (withholdToolParts) {
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
|
|
749
774
|
const toolCall = toolCallsByToolCallId.get(chunk.toolCallId);
|
|
750
775
|
|
|
751
776
|
if (toolCall == null) {
|
|
@@ -808,7 +833,7 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
808
833
|
: {}),
|
|
809
834
|
} as TypedToolResult<TOOLS>);
|
|
810
835
|
|
|
811
|
-
controller.enqueue(toolResultPart);
|
|
836
|
+
if (!withholdToolParts) controller.enqueue(toolResultPart);
|
|
812
837
|
if (collectContent) modelCallContent.push(toolResultPart);
|
|
813
838
|
|
|
814
839
|
break;
|
|
@@ -2465,7 +2465,7 @@ class DefaultStreamTextResult<
|
|
|
2465
2465
|
activeTools: prepareStepResult?.activeTools ?? activeTools,
|
|
2466
2466
|
});
|
|
2467
2467
|
const {
|
|
2468
|
-
executionTools
|
|
2468
|
+
executionTools,
|
|
2469
2469
|
modelTools: stepModelTools,
|
|
2470
2470
|
toolCallerMessages,
|
|
2471
2471
|
} = prepareToolsForToolCallers({
|
|
@@ -2495,6 +2495,8 @@ class DefaultStreamTextResult<
|
|
|
2495
2495
|
const stepToolChoice = prepareToolChoice({
|
|
2496
2496
|
toolChoice: prepareStepResult?.toolChoice ?? toolChoice,
|
|
2497
2497
|
});
|
|
2498
|
+
const stepExecutionTools =
|
|
2499
|
+
stepToolChoice.type === 'none' ? undefined : executionTools;
|
|
2498
2500
|
|
|
2499
2501
|
const stepMessages = appendToolCallerMessages({
|
|
2500
2502
|
messages: prepareStepResult?.messages ?? stepInputMessages,
|
|
@@ -3215,6 +3217,7 @@ class DefaultStreamTextResult<
|
|
|
3215
3217
|
cleanupStepTimeouts();
|
|
3216
3218
|
|
|
3217
3219
|
if (
|
|
3220
|
+
stepFinishReason !== 'interrupted' &&
|
|
3218
3221
|
// Continue only after all client tool calls have been executed or denied,
|
|
3219
3222
|
// and if there are client results or pending deferred provider results.
|
|
3220
3223
|
clientToolCalls.length ===
|
|
@@ -70,6 +70,7 @@ export type LanguageModel =
|
|
|
70
70
|
* - `content-filter`: content filter violation stopped the model
|
|
71
71
|
* - `tool-calls`: model triggered tool calls
|
|
72
72
|
* - `error`: model stopped because of an error
|
|
73
|
+
* - `interrupted`: provider stream ended without a completion signal; output may be incomplete
|
|
73
74
|
* - `other`: model stopped for other reasons
|
|
74
75
|
*/
|
|
75
76
|
export type FinishReason =
|
|
@@ -78,6 +79,7 @@ export type FinishReason =
|
|
|
78
79
|
| 'content-filter'
|
|
79
80
|
| 'tool-calls'
|
|
80
81
|
| 'error'
|
|
82
|
+
| 'interrupted'
|
|
81
83
|
| 'other';
|
|
82
84
|
|
|
83
85
|
/**
|
package/src/ui/chat.ts
CHANGED
|
@@ -274,6 +274,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
274
274
|
|
|
275
275
|
private pendingMessagePreparations = new Set<AbortController>();
|
|
276
276
|
private pendingApprovalMessageId: string | undefined;
|
|
277
|
+
// Keep interruption state after activeResponse is cleared so late tool updates
|
|
278
|
+
// cannot automatically continue an interrupted message.
|
|
279
|
+
private interruptedMessageIds = new Set<string>();
|
|
277
280
|
private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined;
|
|
278
281
|
private activeResumeRequest: ActiveResumeRequest | undefined = undefined;
|
|
279
282
|
private resumableStreamState:
|
|
@@ -638,7 +641,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
638
641
|
this.status !== 'submitted' &&
|
|
639
642
|
this.sendAutomaticallyWhen
|
|
640
643
|
) {
|
|
641
|
-
const shouldSend = await this.shouldSendAutomatically(
|
|
644
|
+
const shouldSend = await this.shouldSendAutomatically(
|
|
645
|
+
messages[messageIndex].id,
|
|
646
|
+
);
|
|
642
647
|
|
|
643
648
|
if (shouldSend) {
|
|
644
649
|
// no await to avoid deadlocking
|
|
@@ -735,7 +740,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
735
740
|
this.status !== 'submitted' &&
|
|
736
741
|
this.sendAutomaticallyWhen
|
|
737
742
|
) {
|
|
738
|
-
const shouldSend = await this.shouldSendAutomatically(
|
|
743
|
+
const shouldSend = await this.shouldSendAutomatically(
|
|
744
|
+
messages[messageIndex].id,
|
|
745
|
+
);
|
|
739
746
|
|
|
740
747
|
if (shouldSend) {
|
|
741
748
|
// no await to avoid deadlocking
|
|
@@ -794,19 +801,29 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
794
801
|
this.transport.close?.();
|
|
795
802
|
};
|
|
796
803
|
|
|
797
|
-
private async shouldSendAutomatically(
|
|
798
|
-
|
|
804
|
+
private async shouldSendAutomatically(
|
|
805
|
+
messageId = this.lastMessage?.id,
|
|
806
|
+
): Promise<boolean> {
|
|
807
|
+
const isInterrupted = (id: string | undefined) =>
|
|
808
|
+
id != null && this.interruptedMessageIds.has(id);
|
|
799
809
|
|
|
800
|
-
|
|
810
|
+
if (
|
|
811
|
+
!this.sendAutomaticallyWhen ||
|
|
812
|
+
isInterrupted(messageId) ||
|
|
813
|
+
isInterrupted(this.lastMessage?.id)
|
|
814
|
+
) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const result = await this.sendAutomaticallyWhen({
|
|
801
819
|
messages: this.state.messages,
|
|
802
820
|
});
|
|
803
821
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return result as boolean;
|
|
822
|
+
return (
|
|
823
|
+
result &&
|
|
824
|
+
!isInterrupted(messageId) &&
|
|
825
|
+
!isInterrupted(this.lastMessage?.id)
|
|
826
|
+
);
|
|
810
827
|
}
|
|
811
828
|
|
|
812
829
|
private async runAutomaticRequest(
|
|
@@ -1182,6 +1199,13 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
1182
1199
|
} finally {
|
|
1183
1200
|
try {
|
|
1184
1201
|
if (activeResponse) {
|
|
1202
|
+
const { message, finishReason } = activeResponse.state;
|
|
1203
|
+
if (finishReason === 'interrupted') {
|
|
1204
|
+
this.interruptedMessageIds.add(message.id);
|
|
1205
|
+
} else if (!isAbort && !isError) {
|
|
1206
|
+
this.interruptedMessageIds.delete(message.id);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1185
1209
|
this.onFinish?.({
|
|
1186
1210
|
message: activeResponse.state.message,
|
|
1187
1211
|
messages: this.state.messages,
|
|
@@ -1201,7 +1225,11 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
1201
1225
|
}
|
|
1202
1226
|
|
|
1203
1227
|
// automatically send the message if the sendAutomaticallyWhen function returns true
|
|
1204
|
-
if (
|
|
1228
|
+
if (
|
|
1229
|
+
!isAbort &&
|
|
1230
|
+
!isError &&
|
|
1231
|
+
activeResponse?.state.finishReason !== 'interrupted'
|
|
1232
|
+
) {
|
|
1205
1233
|
await this.runAutomaticRequest(() =>
|
|
1206
1234
|
this.makeRequest({
|
|
1207
1235
|
trigger: 'submit-message',
|