ai 7.0.106 → 7.0.107
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 +20 -0
- package/dist/index.js +186 -57
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +12 -1
- package/dist/internal/index.js +144 -35
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/19-tool-search.mdx +4 -2
- package/docs/07-reference/01-ai-sdk-core/23-tool-search.mdx +3 -2
- package/package.json +11 -11
- package/src/generate-speech/generate-speech.ts +44 -4
- package/src/generate-text/generate-text.ts +27 -15
- package/src/generate-text/invoke-tool-callbacks-from-stream.ts +68 -16
- package/src/generate-text/stream-text.ts +1 -1
- package/src/generate-video/generate-video.ts +61 -10
- package/src/middleware/wrap-provider.ts +2 -0
- package/src/test/mock-server-response.ts +19 -3
- package/src/text-stream/pipe-text-stream-to-response.ts +3 -5
- package/src/ui/http-chat-transport.ts +12 -2
- package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +1 -1
- package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +2 -1
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +1 -3
- package/src/util/data-url.ts +13 -1
- package/src/util/write-to-server-response.ts +7 -3
|
@@ -10,8 +10,10 @@ definition into its initial context. Register tools with `deferLoading: true`;
|
|
|
10
10
|
search matches their names and descriptions and makes them available on the
|
|
11
11
|
**next model step**.
|
|
12
12
|
|
|
13
|
-
Use it with `generateText`, `streamText`,
|
|
14
|
-
arguments; the model supplies a search
|
|
13
|
+
Use it with `generateText`, `streamText`, `ToolLoopAgent`, or `WorkflowAgent` from
|
|
14
|
+
`@ai-sdk/workflow`. The factory takes no arguments; the model supplies a search
|
|
15
|
+
query. `WorkflowAgent` supports direct tool calling; the other APIs also support
|
|
16
|
+
cache-preserving code mode.
|
|
15
17
|
|
|
16
18
|
## Direct Tool Calling
|
|
17
19
|
|
|
@@ -7,8 +7,9 @@ description: Search deferred tools and load their definitions on demand for dire
|
|
|
7
7
|
|
|
8
8
|
Creates a tool that searches the surrounding generation's deferred tools by name
|
|
9
9
|
and description. The factory takes no arguments. Use it with `generateText`,
|
|
10
|
-
`streamText`,
|
|
11
|
-
|
|
10
|
+
`streamText`, `ToolLoopAgent`, or `WorkflowAgent` from `@ai-sdk/workflow`.
|
|
11
|
+
`WorkflowAgent` supports direct tool calling. The other APIs also support code
|
|
12
|
+
mode configured with `toolDiscovery: 'conversation'`.
|
|
12
13
|
|
|
13
14
|
```ts
|
|
14
15
|
import { toolSearch } from 'ai';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.107",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.87",
|
|
46
46
|
"@ai-sdk/provider": "4.0.17",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.45"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@ai-sdk/amazon-bedrock": "5.0.
|
|
51
|
-
"@ai-sdk/deepseek": "3.0.
|
|
52
|
-
"@ai-sdk/google": "4.0.
|
|
53
|
-
"@ai-sdk/groq": "4.0.
|
|
54
|
-
"@ai-sdk/huggingface": "2.0.
|
|
55
|
-
"@ai-sdk/moonshotai": "3.0.
|
|
56
|
-
"@ai-sdk/openai": "4.0.
|
|
50
|
+
"@ai-sdk/amazon-bedrock": "5.0.88",
|
|
51
|
+
"@ai-sdk/deepseek": "3.0.49",
|
|
52
|
+
"@ai-sdk/google": "4.0.76",
|
|
53
|
+
"@ai-sdk/groq": "4.0.46",
|
|
54
|
+
"@ai-sdk/huggingface": "2.0.53",
|
|
55
|
+
"@ai-sdk/moonshotai": "3.0.54",
|
|
56
|
+
"@ai-sdk/openai": "4.0.71",
|
|
57
57
|
"@ai-sdk/test-server": "2.0.1",
|
|
58
|
-
"@ai-sdk/xai": "5.0.
|
|
58
|
+
"@ai-sdk/xai": "5.0.4",
|
|
59
59
|
"@edge-runtime/vm": "^5.0.0",
|
|
60
60
|
"@smithy/eventstream-codec": "^4.3.3",
|
|
61
61
|
"@smithy/util-utf8": "^4.3.3",
|
|
@@ -155,14 +155,19 @@ export async function generateSpeech({
|
|
|
155
155
|
model: resolvedModel.modelId,
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
const detectedMediaType = detectMediaType({
|
|
159
|
+
data: result.audio,
|
|
160
|
+
topLevelType: 'audio',
|
|
161
|
+
});
|
|
162
|
+
|
|
158
163
|
return new DefaultSpeechResult({
|
|
159
164
|
audio: new DefaultGeneratedAudioFile({
|
|
160
165
|
data: result.audio,
|
|
161
166
|
mediaType:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
detectedMediaType ??
|
|
168
|
+
getResponseAudioMediaType(result.response.headers) ??
|
|
169
|
+
getOutputFormatMediaType(outputFormat) ??
|
|
170
|
+
'audio/mp3',
|
|
166
171
|
}),
|
|
167
172
|
warnings: result.warnings,
|
|
168
173
|
responses: [result.response],
|
|
@@ -170,6 +175,41 @@ export async function generateSpeech({
|
|
|
170
175
|
});
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
function getResponseAudioMediaType(
|
|
179
|
+
headers: Record<string, string> | undefined,
|
|
180
|
+
): string | undefined {
|
|
181
|
+
const mediaType = Object.entries(headers ?? {}).find(
|
|
182
|
+
([name]) => name.toLowerCase() === 'content-type',
|
|
183
|
+
)?.[1];
|
|
184
|
+
|
|
185
|
+
if (mediaType == null) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const normalizedMediaType = mediaType.split(';', 1)[0].trim().toLowerCase();
|
|
190
|
+
|
|
191
|
+
if (normalizedMediaType.length === 0) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return normalizedMediaType.startsWith('audio/')
|
|
196
|
+
? normalizedMediaType
|
|
197
|
+
: undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getOutputFormatMediaType(outputFormat: string | undefined) {
|
|
201
|
+
if (outputFormat == null) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const normalizedOutputFormat = outputFormat.trim().toLowerCase();
|
|
206
|
+
|
|
207
|
+
return normalizedOutputFormat === 'pcm' ||
|
|
208
|
+
normalizedOutputFormat === 'audio/pcm'
|
|
209
|
+
? 'audio/pcm'
|
|
210
|
+
: undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
173
213
|
class DefaultSpeechResult implements SpeechResult {
|
|
174
214
|
readonly audio: GeneratedAudioFile;
|
|
175
215
|
readonly warnings: Array<Warning>;
|
|
@@ -118,6 +118,7 @@ import type {
|
|
|
118
118
|
OnToolExecutionEndCallback,
|
|
119
119
|
OnToolExecutionStartCallback,
|
|
120
120
|
} from './tool-execution-events';
|
|
121
|
+
import { validateToolContext } from './validate-tool-context';
|
|
121
122
|
import type { ToolInputRefinement } from './tool-input-refinement';
|
|
122
123
|
import type { ToolOrder } from './tool-order';
|
|
123
124
|
import type { ToolOutput } from './tool-output';
|
|
@@ -1183,23 +1184,34 @@ export async function generateText<
|
|
|
1183
1184
|
continue;
|
|
1184
1185
|
}
|
|
1185
1186
|
|
|
1186
|
-
if (
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1187
|
+
if (
|
|
1188
|
+
tool.onInputStart != null ||
|
|
1189
|
+
tool.onInputAvailable != null
|
|
1190
|
+
) {
|
|
1191
|
+
const context = await validateToolContext({
|
|
1192
|
+
toolName: toolCall.toolName,
|
|
1193
|
+
context: getOwn(toolsContext, toolCall.toolName),
|
|
1194
|
+
contextSchema: tool.contextSchema,
|
|
1192
1195
|
});
|
|
1193
|
-
}
|
|
1194
1196
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1197
|
+
if (tool.onInputStart != null) {
|
|
1198
|
+
await tool.onInputStart({
|
|
1199
|
+
toolCallId: toolCall.toolCallId,
|
|
1200
|
+
messages: stepMessages,
|
|
1201
|
+
abortSignal: mergedAbortSignal,
|
|
1202
|
+
context,
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if (tool.onInputAvailable != null) {
|
|
1207
|
+
await tool.onInputAvailable({
|
|
1208
|
+
input: toolCall.input,
|
|
1209
|
+
toolCallId: toolCall.toolCallId,
|
|
1210
|
+
messages: stepMessages,
|
|
1211
|
+
abortSignal: mergedAbortSignal,
|
|
1212
|
+
context,
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1203
1215
|
}
|
|
1204
1216
|
|
|
1205
1217
|
const toolApprovalStatus = await resolveToolApproval({
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
InferToolSetContext,
|
|
3
|
+
ModelMessage,
|
|
4
|
+
ToolSet,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
2
6
|
import { createIdMap } from '../util/create-id-map';
|
|
3
7
|
import { getOwn } from '../util/get-own';
|
|
4
8
|
import type { LanguageModelStreamPart } from './stream-language-model-call';
|
|
@@ -6,30 +10,61 @@ import {
|
|
|
6
10
|
isStreamRetryAttemptBoundaryPart,
|
|
7
11
|
type StreamRetryAttemptBoundaryPart,
|
|
8
12
|
} from './stream-retry-attempt-boundary';
|
|
13
|
+
import { validateToolContext } from './validate-tool-context';
|
|
9
14
|
|
|
10
15
|
type ToolCallbackStreamPart<TOOLS extends ToolSet> =
|
|
11
16
|
| LanguageModelStreamPart<TOOLS>
|
|
12
17
|
| StreamRetryAttemptBoundaryPart;
|
|
13
18
|
|
|
14
|
-
export function invokeToolCallbacksFromStream<
|
|
15
|
-
TOOLS extends ToolSet,
|
|
16
|
-
RUNTIME_CONTEXT extends Context,
|
|
17
|
-
>({
|
|
19
|
+
export function invokeToolCallbacksFromStream<TOOLS extends ToolSet>({
|
|
18
20
|
stream,
|
|
19
21
|
tools,
|
|
20
22
|
stepInputMessages,
|
|
21
23
|
abortSignal,
|
|
22
|
-
|
|
24
|
+
toolsContext,
|
|
23
25
|
}: {
|
|
24
26
|
stream: ReadableStream<ToolCallbackStreamPart<TOOLS>>;
|
|
25
27
|
tools: TOOLS | undefined;
|
|
26
28
|
stepInputMessages: Array<ModelMessage>;
|
|
27
29
|
abortSignal: AbortSignal | undefined;
|
|
28
|
-
|
|
30
|
+
toolsContext: InferToolSetContext<TOOLS>;
|
|
29
31
|
}): ReadableStream<ToolCallbackStreamPart<TOOLS>> {
|
|
30
32
|
if (tools == null) return stream;
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
let ongoingToolCalls: Record<
|
|
35
|
+
string,
|
|
36
|
+
{
|
|
37
|
+
toolName: string;
|
|
38
|
+
validatedContext: Promise<unknown> | undefined;
|
|
39
|
+
}
|
|
40
|
+
> = createIdMap();
|
|
41
|
+
|
|
42
|
+
const getValidatedContext = ({
|
|
43
|
+
toolCallId,
|
|
44
|
+
toolName,
|
|
45
|
+
}: {
|
|
46
|
+
toolCallId: string;
|
|
47
|
+
toolName: string;
|
|
48
|
+
}): Promise<unknown> => {
|
|
49
|
+
const ongoingToolCall = ongoingToolCalls[toolCallId];
|
|
50
|
+
|
|
51
|
+
if (ongoingToolCall?.validatedContext != null) {
|
|
52
|
+
return ongoingToolCall.validatedContext;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tool = getOwn(tools, toolName);
|
|
56
|
+
const validatedContext = validateToolContext({
|
|
57
|
+
toolName,
|
|
58
|
+
context: getOwn(toolsContext, toolName),
|
|
59
|
+
contextSchema: tool?.contextSchema,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (ongoingToolCall != null) {
|
|
63
|
+
ongoingToolCall.validatedContext = validatedContext;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return validatedContext;
|
|
67
|
+
};
|
|
33
68
|
|
|
34
69
|
return stream.pipeThrough(
|
|
35
70
|
new TransformStream({
|
|
@@ -37,12 +72,16 @@ export function invokeToolCallbacksFromStream<
|
|
|
37
72
|
controller.enqueue(chunk);
|
|
38
73
|
|
|
39
74
|
if (isStreamRetryAttemptBoundaryPart(chunk)) {
|
|
75
|
+
ongoingToolCalls = createIdMap();
|
|
40
76
|
return;
|
|
41
77
|
}
|
|
42
78
|
|
|
43
79
|
switch (chunk.type) {
|
|
44
80
|
case 'tool-input-start': {
|
|
45
|
-
|
|
81
|
+
ongoingToolCalls[chunk.id] = {
|
|
82
|
+
toolName: chunk.toolName,
|
|
83
|
+
validatedContext: undefined,
|
|
84
|
+
};
|
|
46
85
|
|
|
47
86
|
const tool = getOwn(tools, chunk.toolName);
|
|
48
87
|
if (tool?.onInputStart != null) {
|
|
@@ -50,7 +89,10 @@ export function invokeToolCallbacksFromStream<
|
|
|
50
89
|
toolCallId: chunk.id,
|
|
51
90
|
messages: stepInputMessages,
|
|
52
91
|
abortSignal,
|
|
53
|
-
context:
|
|
92
|
+
context: await getValidatedContext({
|
|
93
|
+
toolCallId: chunk.id,
|
|
94
|
+
toolName: chunk.toolName,
|
|
95
|
+
}),
|
|
54
96
|
});
|
|
55
97
|
}
|
|
56
98
|
|
|
@@ -58,7 +100,7 @@ export function invokeToolCallbacksFromStream<
|
|
|
58
100
|
}
|
|
59
101
|
|
|
60
102
|
case 'tool-input-delta': {
|
|
61
|
-
const toolName =
|
|
103
|
+
const toolName = ongoingToolCalls[chunk.id]?.toolName;
|
|
62
104
|
const tool = getOwn(tools, toolName);
|
|
63
105
|
|
|
64
106
|
if (tool?.onInputDelta != null) {
|
|
@@ -67,7 +109,10 @@ export function invokeToolCallbacksFromStream<
|
|
|
67
109
|
toolCallId: chunk.id,
|
|
68
110
|
messages: stepInputMessages,
|
|
69
111
|
abortSignal,
|
|
70
|
-
context:
|
|
112
|
+
context: await getValidatedContext({
|
|
113
|
+
toolCallId: chunk.id,
|
|
114
|
+
toolName,
|
|
115
|
+
}),
|
|
71
116
|
});
|
|
72
117
|
}
|
|
73
118
|
|
|
@@ -75,19 +120,26 @@ export function invokeToolCallbacksFromStream<
|
|
|
75
120
|
}
|
|
76
121
|
|
|
77
122
|
case 'tool-call': {
|
|
78
|
-
const toolName =
|
|
123
|
+
const toolName = ongoingToolCalls[chunk.toolCallId]?.toolName;
|
|
79
124
|
const tool = getOwn(tools, toolName);
|
|
80
125
|
|
|
81
|
-
delete ongoingToolCallToolNames[chunk.toolCallId];
|
|
82
|
-
|
|
83
126
|
if (!chunk.invalid && tool?.onInputAvailable != null) {
|
|
127
|
+
const validatedContext = getValidatedContext({
|
|
128
|
+
toolCallId: chunk.toolCallId,
|
|
129
|
+
toolName,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
delete ongoingToolCalls[chunk.toolCallId];
|
|
133
|
+
|
|
84
134
|
await tool.onInputAvailable({
|
|
85
135
|
input: chunk.input,
|
|
86
136
|
toolCallId: chunk.toolCallId,
|
|
87
137
|
messages: stepInputMessages,
|
|
88
138
|
abortSignal,
|
|
89
|
-
context:
|
|
139
|
+
context: await validatedContext,
|
|
90
140
|
});
|
|
141
|
+
} else {
|
|
142
|
+
delete ongoingToolCalls[chunk.toolCallId];
|
|
91
143
|
}
|
|
92
144
|
}
|
|
93
145
|
}
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
Experimental_VideoModelV4CallOptions,
|
|
4
4
|
Experimental_VideoModelV4File,
|
|
5
5
|
Experimental_VideoModelV4Result,
|
|
6
|
+
Experimental_VideoModelV4OperationStatusResult,
|
|
6
7
|
Experimental_VideoModelV4OperationWebhook,
|
|
7
8
|
Experimental_VideoModelV4FrameImage,
|
|
8
9
|
Experimental_VideoModelV4FrameType,
|
|
@@ -287,7 +288,7 @@ export async function experimental_generateVideo({
|
|
|
287
288
|
`ai/${VERSION}`,
|
|
288
289
|
);
|
|
289
290
|
|
|
290
|
-
const { retry } = prepareRetries({
|
|
291
|
+
const { maxRetries, retry } = prepareRetries({
|
|
291
292
|
maxRetries: maxRetriesArg,
|
|
292
293
|
abortSignal,
|
|
293
294
|
});
|
|
@@ -363,6 +364,7 @@ export async function experimental_generateVideo({
|
|
|
363
364
|
callOptions,
|
|
364
365
|
poll,
|
|
365
366
|
webhook,
|
|
367
|
+
maxRetries,
|
|
366
368
|
retry,
|
|
367
369
|
});
|
|
368
370
|
}
|
|
@@ -477,12 +479,14 @@ async function executeStartStatusFlow({
|
|
|
477
479
|
callOptions,
|
|
478
480
|
poll: pollConfig,
|
|
479
481
|
webhook: webhookFactory,
|
|
482
|
+
maxRetries,
|
|
480
483
|
retry,
|
|
481
484
|
}: {
|
|
482
485
|
model: Experimental_VideoModelV4;
|
|
483
486
|
callOptions: Experimental_VideoModelV4CallOptions;
|
|
484
487
|
poll?: GenerateVideoPollOptions;
|
|
485
488
|
webhook?: GenerateVideoWebhookFactory;
|
|
489
|
+
maxRetries: number;
|
|
486
490
|
retry: <OUTPUT>(fn: () => PromiseLike<OUTPUT>) => PromiseLike<OUTPUT>;
|
|
487
491
|
}): Promise<Experimental_VideoModelV4Result> {
|
|
488
492
|
// 1. If webhook and provider supports it, set up the webhook
|
|
@@ -538,6 +542,9 @@ async function executeStartStatusFlow({
|
|
|
538
542
|
const timeoutMs = pollConfig?.timeoutMs ?? 600_000;
|
|
539
543
|
const delay = pollConfig?.delay ?? defaultDelay;
|
|
540
544
|
const startTime = Date.now();
|
|
545
|
+
const pollingTimeoutError = new Error(
|
|
546
|
+
`Video generation timed out after ${timeoutMs}ms.`,
|
|
547
|
+
);
|
|
541
548
|
|
|
542
549
|
if (webhookReceived != null) {
|
|
543
550
|
// 3a. Webhook flow: wait for webhook, then get final status
|
|
@@ -554,23 +561,67 @@ async function executeStartStatusFlow({
|
|
|
554
561
|
// 3b. Polling flow (also used when webhooks are not supported)
|
|
555
562
|
const elapsedMs = Date.now() - startTime;
|
|
556
563
|
if (elapsedMs >= timeoutMs) {
|
|
557
|
-
throw
|
|
564
|
+
throw pollingTimeoutError;
|
|
558
565
|
}
|
|
559
566
|
await delay(Math.min(intervalMs, timeoutMs - elapsedMs), {
|
|
560
567
|
abortSignal: callOptions.abortSignal,
|
|
561
568
|
});
|
|
562
569
|
if (Date.now() - startTime >= timeoutMs) {
|
|
563
|
-
throw
|
|
570
|
+
throw pollingTimeoutError;
|
|
564
571
|
}
|
|
565
572
|
}
|
|
566
573
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
+
let statusResult: Experimental_VideoModelV4OperationStatusResult;
|
|
575
|
+
if (webhookReceived != null) {
|
|
576
|
+
statusResult = await retry(() =>
|
|
577
|
+
model.doStatus!({
|
|
578
|
+
operation: startResult.operation,
|
|
579
|
+
abortSignal: callOptions.abortSignal,
|
|
580
|
+
headers: callOptions.headers,
|
|
581
|
+
}),
|
|
582
|
+
);
|
|
583
|
+
} else {
|
|
584
|
+
const statusTimeoutController = new AbortController();
|
|
585
|
+
const statusAbortSignal = mergeAbortSignals(
|
|
586
|
+
callOptions.abortSignal,
|
|
587
|
+
statusTimeoutController.signal,
|
|
588
|
+
);
|
|
589
|
+
const statusTimeoutId = setTimeout(
|
|
590
|
+
() => statusTimeoutController.abort(pollingTimeoutError),
|
|
591
|
+
timeoutMs - (Date.now() - startTime),
|
|
592
|
+
);
|
|
593
|
+
const statusTimeoutPromise = new Promise<never>((_, reject) => {
|
|
594
|
+
statusTimeoutController.signal.addEventListener(
|
|
595
|
+
'abort',
|
|
596
|
+
() => reject(pollingTimeoutError),
|
|
597
|
+
{ once: true },
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
const { retry: statusRetry } = prepareRetries({
|
|
601
|
+
maxRetries,
|
|
602
|
+
abortSignal: statusAbortSignal,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
statusResult = await Promise.race([
|
|
607
|
+
statusRetry(() =>
|
|
608
|
+
model.doStatus!({
|
|
609
|
+
operation: startResult.operation,
|
|
610
|
+
abortSignal: statusAbortSignal,
|
|
611
|
+
headers: callOptions.headers,
|
|
612
|
+
}),
|
|
613
|
+
),
|
|
614
|
+
statusTimeoutPromise,
|
|
615
|
+
]);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
if (statusTimeoutController.signal.aborted) {
|
|
618
|
+
throw pollingTimeoutError;
|
|
619
|
+
}
|
|
620
|
+
throw error;
|
|
621
|
+
} finally {
|
|
622
|
+
clearTimeout(statusTimeoutId);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
574
625
|
|
|
575
626
|
if (statusResult.status === 'error') {
|
|
576
627
|
throw new Error(statusResult.error);
|
|
@@ -47,5 +47,7 @@ export function wrapProvider({
|
|
|
47
47
|
transcriptionModel: providerV4.transcriptionModel,
|
|
48
48
|
speechModel: providerV4.speechModel,
|
|
49
49
|
rerankingModel: providerV4.rerankingModel,
|
|
50
|
+
...(providerV4.files != null ? { files: providerV4.files } : {}),
|
|
51
|
+
...(providerV4.skills != null ? { skills: providerV4.skills } : {}),
|
|
50
52
|
};
|
|
51
53
|
}
|
|
@@ -3,7 +3,7 @@ import type { ServerResponse } from 'node:http';
|
|
|
3
3
|
|
|
4
4
|
class MockServerResponse extends EventEmitter {
|
|
5
5
|
writtenChunks: any[] = [];
|
|
6
|
-
headers: Record<string, string> = {};
|
|
6
|
+
headers: Record<string, string | string[]> = {};
|
|
7
7
|
statusCode = 0;
|
|
8
8
|
statusMessage = '';
|
|
9
9
|
ended = false;
|
|
@@ -17,6 +17,20 @@ class MockServerResponse extends EventEmitter {
|
|
|
17
17
|
this.ended = true;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
setHeaders(headers: Headers): void {
|
|
21
|
+
this.headers = {};
|
|
22
|
+
|
|
23
|
+
for (const [key, value] of headers.entries()) {
|
|
24
|
+
const existingValue = this.headers[key];
|
|
25
|
+
this.headers[key] =
|
|
26
|
+
existingValue == null
|
|
27
|
+
? value
|
|
28
|
+
: Array.isArray(existingValue)
|
|
29
|
+
? [...existingValue, value]
|
|
30
|
+
: [existingValue, value];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
writeHead(
|
|
21
35
|
statusCode: number,
|
|
22
36
|
arg2: string | Record<string, string>,
|
|
@@ -26,8 +40,10 @@ class MockServerResponse extends EventEmitter {
|
|
|
26
40
|
|
|
27
41
|
if (typeof arg2 === 'string') {
|
|
28
42
|
this.statusMessage = arg2;
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
if (arg3 != null) {
|
|
44
|
+
this.headers = arg3;
|
|
45
|
+
}
|
|
46
|
+
} else if (arg2 != null) {
|
|
31
47
|
this.statusMessage = '';
|
|
32
48
|
this.headers = arg2;
|
|
33
49
|
}
|
|
@@ -29,11 +29,9 @@ export function pipeTextStreamToResponse({
|
|
|
29
29
|
response,
|
|
30
30
|
status,
|
|
31
31
|
statusText,
|
|
32
|
-
headers:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}).entries(),
|
|
36
|
-
),
|
|
32
|
+
headers: prepareHeaders(headers, {
|
|
33
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
34
|
+
}),
|
|
37
35
|
stream: stream.pipeThrough(new TextEncoderStream()),
|
|
38
36
|
});
|
|
39
37
|
}
|
|
@@ -10,6 +10,14 @@ import type { ChatTransport } from './chat-transport';
|
|
|
10
10
|
import { createUIApiCallError } from './create-ui-api-call-error';
|
|
11
11
|
import type { UIMessage } from './ui-messages';
|
|
12
12
|
|
|
13
|
+
function appendPathToUrl(url: string, path: string): string {
|
|
14
|
+
const queryOrFragmentStart = url.search(/[?#]/);
|
|
15
|
+
|
|
16
|
+
return queryOrFragmentStart === -1
|
|
17
|
+
? `${url}${path}`
|
|
18
|
+
: `${url.slice(0, queryOrFragmentStart)}${path}${url.slice(queryOrFragmentStart)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
export type PrepareSendMessagesRequest<UI_MESSAGE extends UIMessage> = (
|
|
14
22
|
options: {
|
|
15
23
|
id: string;
|
|
@@ -193,7 +201,7 @@ export abstract class HttpChatTransport<
|
|
|
193
201
|
const response = await fetch(api, {
|
|
194
202
|
method: 'POST',
|
|
195
203
|
headers: {
|
|
196
|
-
'
|
|
204
|
+
'content-type': 'application/json',
|
|
197
205
|
...headers,
|
|
198
206
|
},
|
|
199
207
|
body: JSON.stringify(body),
|
|
@@ -239,7 +247,9 @@ export abstract class HttpChatTransport<
|
|
|
239
247
|
requestMetadata: options.metadata,
|
|
240
248
|
});
|
|
241
249
|
|
|
242
|
-
const api =
|
|
250
|
+
const api =
|
|
251
|
+
preparedRequest?.api ??
|
|
252
|
+
appendPathToUrl(this.api, `/${options.chatId}/stream`);
|
|
243
253
|
const headers =
|
|
244
254
|
preparedRequest?.headers !== undefined
|
|
245
255
|
? normalizeHeaders(preparedRequest.headers)
|
|
@@ -34,7 +34,7 @@ export function lastAssistantMessageIsCompleteWithApprovalResponses({
|
|
|
34
34
|
// all tool approvals must have a response
|
|
35
35
|
lastStepToolInvocations.every(
|
|
36
36
|
part =>
|
|
37
|
-
part.state === 'output-available' ||
|
|
37
|
+
(part.state === 'output-available' && part.preliminary !== true) ||
|
|
38
38
|
part.state === 'output-error' ||
|
|
39
39
|
part.state === 'output-denied' ||
|
|
40
40
|
part.state === 'approval-responded',
|
|
@@ -32,7 +32,8 @@ export function lastAssistantMessageIsCompleteWithToolCalls({
|
|
|
32
32
|
lastStepToolInvocations.length > 0 &&
|
|
33
33
|
lastStepToolInvocations.every(
|
|
34
34
|
part =>
|
|
35
|
-
part.state === 'output-available'
|
|
35
|
+
(part.state === 'output-available' && part.preliminary !== true) ||
|
|
36
|
+
part.state === 'output-error',
|
|
36
37
|
)
|
|
37
38
|
);
|
|
38
39
|
}
|
|
@@ -44,9 +44,7 @@ export function pipeUIMessageStreamToResponse({
|
|
|
44
44
|
response,
|
|
45
45
|
status,
|
|
46
46
|
statusText,
|
|
47
|
-
headers:
|
|
48
|
-
prepareHeaders(headers, UI_MESSAGE_STREAM_HEADERS).entries(),
|
|
49
|
-
),
|
|
47
|
+
headers: prepareHeaders(headers, UI_MESSAGE_STREAM_HEADERS),
|
|
50
48
|
stream: sseStream.pipeThrough(new TextEncoderStream()),
|
|
51
49
|
});
|
|
52
50
|
}
|
package/src/util/data-url.ts
CHANGED
|
@@ -13,6 +13,10 @@ const { atob } = globalThis;
|
|
|
13
13
|
export function getTextFromDataUrl(dataUrl: string): string {
|
|
14
14
|
const [header, base64Content] = dataUrl.split(',');
|
|
15
15
|
const mediaType = header.split(';')[0].split(':')[1];
|
|
16
|
+
const charsetMatch = /(?:^|;)\s*charset\s*=\s*(?:"([^"]+)"|([^;\s]+))/i.exec(
|
|
17
|
+
header,
|
|
18
|
+
);
|
|
19
|
+
const charset = charsetMatch?.[1] ?? charsetMatch?.[2];
|
|
16
20
|
|
|
17
21
|
if (mediaType == null || base64Content == null) {
|
|
18
22
|
throw new InvalidArgumentError({
|
|
@@ -23,7 +27,15 @@ export function getTextFromDataUrl(dataUrl: string): string {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
try {
|
|
26
|
-
|
|
30
|
+
const byteString = atob(base64Content);
|
|
31
|
+
|
|
32
|
+
if (charset == null) {
|
|
33
|
+
return byteString;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new TextDecoder(charset).decode(
|
|
37
|
+
Uint8Array.from(byteString, byte => byte.codePointAt(0)!),
|
|
38
|
+
);
|
|
27
39
|
} catch {
|
|
28
40
|
throw new InvalidArgumentError({
|
|
29
41
|
parameter: 'dataUrl',
|
|
@@ -17,14 +17,18 @@ export function writeToServerResponse({
|
|
|
17
17
|
response: ServerResponse;
|
|
18
18
|
status?: number;
|
|
19
19
|
statusText?: string;
|
|
20
|
-
headers?:
|
|
20
|
+
headers?: Headers;
|
|
21
21
|
stream: ReadableStream<Uint8Array>;
|
|
22
22
|
}): Promise<void> {
|
|
23
23
|
const statusCode = status ?? 200;
|
|
24
|
+
if (headers != null) {
|
|
25
|
+
response.setHeaders(headers);
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
if (statusText !== undefined) {
|
|
25
|
-
response.writeHead(statusCode, statusText
|
|
29
|
+
response.writeHead(statusCode, statusText);
|
|
26
30
|
} else {
|
|
27
|
-
response.writeHead(statusCode
|
|
31
|
+
response.writeHead(statusCode);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const reader = stream.getReader();
|