ai 7.0.128 → 7.0.130
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 +46 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +173 -39
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +10 -2
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +68 -16
- package/dist/internal/index.js.map +1 -1
- package/docs/02-foundations/06-provider-options.mdx +11 -10
- package/docs/03-agents/02-building-agents.mdx +16 -11
- package/docs/03-agents/07-workflow-agent.mdx +3 -3
- package/docs/03-ai-sdk-core/17-runtime-and-tool-context.mdx +18 -14
- package/docs/03-ai-sdk-core/25-settings.mdx +3 -2
- package/docs/03-ai-sdk-core/26-reasoning.mdx +12 -5
- package/docs/03-ai-sdk-core/32-decisions.mdx +20 -20
- package/docs/03-ai-sdk-core/35-image-generation.mdx +2 -0
- package/docs/04-ai-sdk-ui/21-error-handling.mdx +28 -3
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +3 -3
- package/docs/07-reference/01-ai-sdk-core/14-decide.mdx +2 -1
- package/docs/07-reference/05-ai-sdk-errors/ai-decision-refusal-error.mdx +33 -0
- package/package.json +15 -15
- package/src/decide/decide.ts +11 -0
- package/src/decide/validate-decision.ts +1 -0
- package/src/error/index.ts +1 -0
- package/src/generate-object/generate-object.ts +10 -0
- package/src/generate-object/stream-object.ts +10 -0
- package/src/generate-speech/index.ts +13 -1
- package/src/generate-text/generate-text.ts +3 -1
- package/src/generate-text/stream-text.ts +22 -5
- package/src/generate-text/tool-approval-signature.ts +7 -4
- package/src/global.ts +2 -1
- package/src/logger/deprecations.ts +72 -0
- package/src/logger/log-warnings.ts +25 -3
- package/src/model/as-language-model-v3.ts +4 -0
- package/src/prompt/convert-to-language-model-prompt.ts +7 -1
- package/src/realtime/encode-realtime-frame.ts +7 -2
- package/src/transcribe/index.ts +13 -1
- package/src/types/index.ts +1 -0
- package/src/types/language-model.ts +9 -1
- package/src/types/usage.ts +21 -0
- package/src/ui/chat.ts +16 -8
- package/src/ui/convert-to-model-messages.ts +40 -8
- package/src/ui/http-chat-transport.ts +5 -3
- package/src/ui/process-text-stream.ts +5 -1
- package/src/ui/text-stream-chat-transport.ts +3 -1
- package/src/ui/ui-messages.ts +8 -0
- package/src/ui/unavailable-tool.ts +17 -0
- package/src/ui/validate-ui-messages.ts +10 -2
|
@@ -1099,7 +1099,9 @@ export async function generateText<
|
|
|
1099
1099
|
> = {};
|
|
1100
1100
|
const blockedToolCallIds = new Set<string>();
|
|
1101
1101
|
|
|
1102
|
-
const generatedFileDataCache
|
|
1102
|
+
const generatedFileDataCache: Parameters<
|
|
1103
|
+
typeof convertLanguageModelContent
|
|
1104
|
+
>[0]['generatedFileDataCache'] = new WeakMap();
|
|
1103
1105
|
const modelCallContent = await convertLanguageModelContent({
|
|
1104
1106
|
content: currentModelResponse.content,
|
|
1105
1107
|
toolCalls: stepToolCalls,
|
|
@@ -1849,16 +1849,30 @@ class DefaultStreamTextResult<
|
|
|
1849
1849
|
|
|
1850
1850
|
// resilient stream that handles abort signals and errors:
|
|
1851
1851
|
const reader = stitchableStream.stream.getReader();
|
|
1852
|
+
const cancelOnAbort = () => {
|
|
1853
|
+
// Result promises must settle before any potentially stalled callback.
|
|
1854
|
+
this.rejectResultPromises(abortSignal?.reason);
|
|
1855
|
+
// Cancelling the reader releases a pending read immediately, even when
|
|
1856
|
+
// the provider body or its cancellation promise does not settle.
|
|
1857
|
+
void reader.cancel(abortSignal?.reason).catch(() => {});
|
|
1858
|
+
};
|
|
1859
|
+
const removeAbortListener = () =>
|
|
1860
|
+
abortSignal?.removeEventListener('abort', cancelOnAbort);
|
|
1852
1861
|
let stream = new ReadableStream<InternalTextStreamPart<TOOLS>>({
|
|
1853
1862
|
async start(controller) {
|
|
1854
1863
|
// send start event:
|
|
1855
1864
|
controller.enqueue({ type: 'start' });
|
|
1865
|
+
abortSignal?.addEventListener('abort', cancelOnAbort, { once: true });
|
|
1866
|
+
if (abortSignal?.aborted) {
|
|
1867
|
+
cancelOnAbort();
|
|
1868
|
+
}
|
|
1856
1869
|
},
|
|
1857
1870
|
|
|
1858
1871
|
async pull(controller) {
|
|
1859
1872
|
// abort handling:
|
|
1860
1873
|
async function abort() {
|
|
1861
1874
|
isAborted = true;
|
|
1875
|
+
removeAbortListener();
|
|
1862
1876
|
|
|
1863
1877
|
await notify({
|
|
1864
1878
|
event: {
|
|
@@ -1885,18 +1899,20 @@ class DefaultStreamTextResult<
|
|
|
1885
1899
|
try {
|
|
1886
1900
|
const { done, value } = await reader.read();
|
|
1887
1901
|
|
|
1888
|
-
if (
|
|
1889
|
-
|
|
1902
|
+
if (abortSignal?.aborted) {
|
|
1903
|
+
await abort();
|
|
1890
1904
|
return;
|
|
1891
1905
|
}
|
|
1892
1906
|
|
|
1893
|
-
if (
|
|
1894
|
-
|
|
1907
|
+
if (done) {
|
|
1908
|
+
removeAbortListener();
|
|
1909
|
+
controller.close();
|
|
1895
1910
|
return;
|
|
1896
1911
|
}
|
|
1897
1912
|
|
|
1898
1913
|
controller.enqueue(value);
|
|
1899
1914
|
} catch (error) {
|
|
1915
|
+
removeAbortListener();
|
|
1900
1916
|
if (isAbortError(error) && abortSignal?.aborted) {
|
|
1901
1917
|
await abort();
|
|
1902
1918
|
} else {
|
|
@@ -1907,7 +1923,8 @@ class DefaultStreamTextResult<
|
|
|
1907
1923
|
},
|
|
1908
1924
|
|
|
1909
1925
|
cancel(reason) {
|
|
1910
|
-
|
|
1926
|
+
removeAbortListener();
|
|
1927
|
+
return reader.cancel(reason);
|
|
1911
1928
|
},
|
|
1912
1929
|
});
|
|
1913
1930
|
|
|
@@ -3,12 +3,15 @@ import { hashCanonical, toBase64url } from '../util/canonical-hash';
|
|
|
3
3
|
|
|
4
4
|
const encoder = new TextEncoder();
|
|
5
5
|
|
|
6
|
-
function fromBase64url(str: string)
|
|
6
|
+
function fromBase64url(str: string) {
|
|
7
7
|
return convertBase64ToUint8Array(str);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async function importKey(secret: string | Uint8Array): Promise<CryptoKey> {
|
|
11
|
-
const keyData =
|
|
11
|
+
const keyData =
|
|
12
|
+
typeof secret === 'string'
|
|
13
|
+
? encoder.encode(secret)
|
|
14
|
+
: (secret as Uint8Array<ArrayBuffer>);
|
|
12
15
|
return crypto.subtle.importKey(
|
|
13
16
|
'raw',
|
|
14
17
|
keyData,
|
|
@@ -26,7 +29,7 @@ function buildPayload(
|
|
|
26
29
|
toolCallId: string,
|
|
27
30
|
toolName: string,
|
|
28
31
|
inputDigest: string,
|
|
29
|
-
)
|
|
32
|
+
) {
|
|
30
33
|
return encoder.encode(
|
|
31
34
|
JSON.stringify([
|
|
32
35
|
'ai-sdk-tool-approval-v1',
|
|
@@ -49,7 +52,7 @@ function buildLegacyPayload(
|
|
|
49
52
|
toolCallId: string,
|
|
50
53
|
toolName: string,
|
|
51
54
|
inputDigest: string,
|
|
52
|
-
)
|
|
55
|
+
) {
|
|
53
56
|
return encoder.encode(
|
|
54
57
|
`${approvalId}\n${toolCallId}\n${toolName}\n${inputDigest}`,
|
|
55
58
|
);
|
package/src/global.ts
CHANGED
|
@@ -18,7 +18,8 @@ declare global {
|
|
|
18
18
|
/**
|
|
19
19
|
* The warning logger to use for the AI SDK.
|
|
20
20
|
*
|
|
21
|
-
* If not set,
|
|
21
|
+
* If not set, warnings use process.emitWarning when available, otherwise
|
|
22
|
+
* console.warn. Deprecations are emitted once per code by the default logger.
|
|
22
23
|
*
|
|
23
24
|
* If set to false, no warnings are logged.
|
|
24
25
|
*/
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// These codes are stable identifiers. Do not rename or reuse an existing code.
|
|
2
|
+
const deprecationCodes = new Map<string, string>([
|
|
3
|
+
['generateObject', 'AISDK_DEP_GENERATE_OBJECT'],
|
|
4
|
+
['streamObject', 'AISDK_DEP_STREAM_OBJECT'],
|
|
5
|
+
['experimental_generateSpeech', 'AISDK_DEP_EXPERIMENTAL_GENERATE_SPEECH'],
|
|
6
|
+
['experimental_transcribe', 'AISDK_DEP_EXPERIMENTAL_TRANSCRIBE'],
|
|
7
|
+
['"image" content part', 'AISDK_DEP_IMAGE_CONTENT_PART'],
|
|
8
|
+
[
|
|
9
|
+
'"tool-result" content of type "file-data"',
|
|
10
|
+
'AISDK_DEP_TOOL_RESULT_FILE_DATA',
|
|
11
|
+
],
|
|
12
|
+
[
|
|
13
|
+
'"tool-result" content of type "file-url"',
|
|
14
|
+
'AISDK_DEP_TOOL_RESULT_FILE_URL',
|
|
15
|
+
],
|
|
16
|
+
['"tool-result" content of type "file-id"', 'AISDK_DEP_TOOL_RESULT_FILE_ID'],
|
|
17
|
+
[
|
|
18
|
+
'"tool-result" content of type "file-reference"',
|
|
19
|
+
'AISDK_DEP_TOOL_RESULT_FILE_REFERENCE',
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
'"tool-result" content of type "image-data"',
|
|
23
|
+
'AISDK_DEP_TOOL_RESULT_IMAGE_DATA',
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
'"tool-result" content of type "image-url"',
|
|
27
|
+
'AISDK_DEP_TOOL_RESULT_IMAGE_URL',
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
'"tool-result" content of type "image-file-id"',
|
|
31
|
+
'AISDK_DEP_TOOL_RESULT_IMAGE_FILE_ID',
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
'"tool-result" content of type "image-file-reference"',
|
|
35
|
+
'AISDK_DEP_TOOL_RESULT_IMAGE_FILE_REFERENCE',
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
'rawInput in output-error UI message parts',
|
|
39
|
+
'AISDK_DEP_UI_MESSAGE_RAW_INPUT',
|
|
40
|
+
],
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Escape punctuation and UTF-16 code units without losing case or collapsing
|
|
45
|
+
* distinct settings. Underscores are escaped too, so `__` separates code parts.
|
|
46
|
+
*/
|
|
47
|
+
function encodeCodePart(value: string): string {
|
|
48
|
+
return value.replace(
|
|
49
|
+
/[^a-zA-Z0-9]/g,
|
|
50
|
+
character =>
|
|
51
|
+
`_${character.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getDeprecationCode({
|
|
56
|
+
setting,
|
|
57
|
+
provider,
|
|
58
|
+
}: {
|
|
59
|
+
setting: string;
|
|
60
|
+
provider?: string;
|
|
61
|
+
}): string {
|
|
62
|
+
if (provider == null) {
|
|
63
|
+
return (
|
|
64
|
+
deprecationCodes.get(setting) ??
|
|
65
|
+
`AISDK_DEP_SETTING_${encodeCodePart(setting)}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Provider codes are scoped to the provider, but not to a particular model or
|
|
70
|
+
// message. This works for third-party providers without changing their spec.
|
|
71
|
+
return `AISDK_DEP_PROVIDER_${encodeCodePart(provider)}__${encodeCodePart(setting)}`;
|
|
72
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Warning } from '../types';
|
|
2
|
+
import { getDeprecationCode } from './deprecations';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A function for logging warnings.
|
|
@@ -87,21 +88,24 @@ export const FIRST_WARNING_INFO_MESSAGE =
|
|
|
87
88
|
'AI SDK Warning System: To turn off warning logging, set the AI_SDK_LOG_WARNINGS global to false.';
|
|
88
89
|
|
|
89
90
|
let hasLoggedBefore = false;
|
|
91
|
+
const emittedDeprecationCodes = new Set<string>();
|
|
90
92
|
|
|
91
93
|
function emitWarning({
|
|
92
94
|
message,
|
|
93
95
|
type,
|
|
96
|
+
code,
|
|
94
97
|
}: {
|
|
95
98
|
message: string;
|
|
96
99
|
type: 'DeprecationWarning' | 'Warning';
|
|
100
|
+
code?: string;
|
|
97
101
|
}) {
|
|
98
102
|
if (
|
|
99
103
|
typeof process !== 'undefined' &&
|
|
100
104
|
typeof process.emitWarning === 'function'
|
|
101
105
|
) {
|
|
102
|
-
process.emitWarning(message, { type });
|
|
106
|
+
process.emitWarning(message, code == null ? { type } : { type, code });
|
|
103
107
|
} else {
|
|
104
|
-
console.warn(message);
|
|
108
|
+
console.warn(code == null ? message : `[${code}] ${message}`);
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -111,7 +115,9 @@ function emitWarning({
|
|
|
111
115
|
* The behavior can be customized via the `AI_SDK_LOG_WARNINGS` global variable:
|
|
112
116
|
* - If set to `false`, warnings are suppressed.
|
|
113
117
|
* - If set to a function, that function is called with the warnings.
|
|
114
|
-
* - Otherwise, warnings
|
|
118
|
+
* - Otherwise, warnings use `process.emitWarning` when available, falling back
|
|
119
|
+
* to `console.warn`. Deprecations have stable codes and are emitted once per
|
|
120
|
+
* code by the default logger. Custom loggers receive every warning.
|
|
115
121
|
*
|
|
116
122
|
* @param options - The options containing warnings and context.
|
|
117
123
|
* @param options.warnings - The warnings to log.
|
|
@@ -148,6 +154,20 @@ export const logWarnings: LogWarningsFunction = options => {
|
|
|
148
154
|
|
|
149
155
|
// default behavior: log warnings via process.emitWarning if available, otherwise console.warn
|
|
150
156
|
for (const warning of options.warnings) {
|
|
157
|
+
const code =
|
|
158
|
+
warning.type === 'deprecated'
|
|
159
|
+
? getDeprecationCode({
|
|
160
|
+
setting: warning.setting,
|
|
161
|
+
provider: options.provider,
|
|
162
|
+
})
|
|
163
|
+
: undefined;
|
|
164
|
+
if (code != null) {
|
|
165
|
+
if (emittedDeprecationCodes.has(code)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
emittedDeprecationCodes.add(code);
|
|
169
|
+
}
|
|
170
|
+
|
|
151
171
|
const message = formatWarning({
|
|
152
172
|
warning,
|
|
153
173
|
provider: options.provider,
|
|
@@ -156,6 +176,7 @@ export const logWarnings: LogWarningsFunction = options => {
|
|
|
156
176
|
emitWarning({
|
|
157
177
|
message,
|
|
158
178
|
type: warning.type === 'deprecated' ? 'DeprecationWarning' : 'Warning',
|
|
179
|
+
code,
|
|
159
180
|
});
|
|
160
181
|
}
|
|
161
182
|
};
|
|
@@ -165,4 +186,5 @@ export const logWarnings: LogWarningsFunction = options => {
|
|
|
165
186
|
*/
|
|
166
187
|
export const resetLogWarningsState = () => {
|
|
167
188
|
hasLoggedBefore = false;
|
|
189
|
+
emittedDeprecationCodes.clear();
|
|
168
190
|
};
|
|
@@ -99,5 +99,9 @@ function convertV2UsageToV3(usage: LanguageModelV2Usage): LanguageModelV3Usage {
|
|
|
99
99
|
text: undefined,
|
|
100
100
|
reasoning: usage.reasoningTokens,
|
|
101
101
|
},
|
|
102
|
+
raw:
|
|
103
|
+
usage.totalTokens == null
|
|
104
|
+
? undefined
|
|
105
|
+
: { totalTokens: usage.totalTokens },
|
|
102
106
|
};
|
|
103
107
|
}
|
|
@@ -123,7 +123,13 @@ export async function convertToLanguageModelPrompt({
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
lastCombinedMessage.content.push(...message.content);
|
|
126
|
-
lastCombinedMessage.providerOptions =
|
|
126
|
+
lastCombinedMessage.providerOptions =
|
|
127
|
+
lastContentPart == null
|
|
128
|
+
? mergeObjects(
|
|
129
|
+
lastCombinedMessage.providerOptions,
|
|
130
|
+
message.providerOptions,
|
|
131
|
+
)
|
|
132
|
+
: message.providerOptions;
|
|
127
133
|
} else {
|
|
128
134
|
combinedMessages.push(message);
|
|
129
135
|
}
|
|
@@ -3,14 +3,19 @@ export const REALTIME_MAX_FRAME_BYTES = 128 * 1024;
|
|
|
3
3
|
export const REALTIME_MAX_BUFFERED_BYTES = 128 * 1024;
|
|
4
4
|
|
|
5
5
|
export type EncodedRealtimeFrame = {
|
|
6
|
-
data: string | ArrayBuffer | ArrayBufferView | Blob;
|
|
6
|
+
data: string | ArrayBuffer | ArrayBufferView<ArrayBuffer> | Blob;
|
|
7
7
|
byteLength: number;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
/** Serialize once, measuring the exact payload passed to the browser. */
|
|
11
11
|
export function encodeRealtimeFrame(value: unknown): EncodedRealtimeFrame {
|
|
12
|
-
if (value instanceof ArrayBuffer
|
|
12
|
+
if (value instanceof ArrayBuffer)
|
|
13
13
|
return { data: value, byteLength: value.byteLength };
|
|
14
|
+
if (ArrayBuffer.isView(value))
|
|
15
|
+
return {
|
|
16
|
+
data: value as ArrayBufferView<ArrayBuffer>,
|
|
17
|
+
byteLength: value.byteLength,
|
|
18
|
+
};
|
|
14
19
|
if (value instanceof Blob) return { data: value, byteLength: value.size };
|
|
15
20
|
const data = typeof value === 'string' ? value : JSON.stringify(value);
|
|
16
21
|
if (data === undefined)
|
package/src/transcribe/index.ts
CHANGED
|
@@ -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 =
|
|
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
|
/**
|
package/src/types/index.ts
CHANGED
|
@@ -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 =
|
|
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.
|
package/src/types/usage.ts
CHANGED
|
@@ -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)
|
|
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;
|
|
@@ -1010,13 +1015,16 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
1010
1015
|
|
|
1011
1016
|
isError = true;
|
|
1012
1017
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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';
|
|
1020
1028
|
}
|
|
1021
1029
|
|
|
1022
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
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
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
|
|
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) {
|
|
@@ -17,7 +17,9 @@ export class TextStreamChatTransport<
|
|
|
17
17
|
stream: ReadableStream<Uint8Array<ArrayBufferLike>>,
|
|
18
18
|
): ReadableStream<UIMessageChunk> {
|
|
19
19
|
return transformTextToUiMessageStream({
|
|
20
|
-
stream: stream.pipeThrough(
|
|
20
|
+
stream: stream.pipeThrough(
|
|
21
|
+
new TextDecoderStream() as ReadableWritablePair<string, Uint8Array>,
|
|
22
|
+
),
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
25
|
}
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -421,6 +421,14 @@ export type ToolUIPart<TOOLS extends UITools = UITools> = ValueOf<{
|
|
|
421
421
|
export type DynamicToolUIPart = {
|
|
422
422
|
type: 'dynamic-tool';
|
|
423
423
|
|
|
424
|
+
/**
|
|
425
|
+
* `false` when a static tool part was normalized to a dynamic tool part
|
|
426
|
+
* because its current tool schema was unavailable or incompatible.
|
|
427
|
+
*
|
|
428
|
+
* This preserves its static origin across persistence boundaries.
|
|
429
|
+
*/
|
|
430
|
+
dynamic?: false;
|
|
431
|
+
|
|
424
432
|
/**
|
|
425
433
|
* Name of the tool that is being called.
|
|
426
434
|
*/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DynamicToolUIPart, ToolUIPart } from './ui-messages';
|
|
2
|
+
|
|
3
|
+
export function markToolPartAsUnavailable(
|
|
4
|
+
part: DynamicToolUIPart,
|
|
5
|
+
): DynamicToolUIPart {
|
|
6
|
+
// Preserve the static tool's provenance across persistence boundaries so
|
|
7
|
+
// model-message conversion never falls back to its unfiltered raw output.
|
|
8
|
+
part.dynamic = false;
|
|
9
|
+
|
|
10
|
+
return part;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isToolPartFromUnavailableTool(
|
|
14
|
+
part: ToolUIPart | DynamicToolUIPart,
|
|
15
|
+
): boolean {
|
|
16
|
+
return part.type === 'dynamic-tool' && part.dynamic === false;
|
|
17
|
+
}
|