ai 7.0.161 → 7.0.163
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 +79 -0
- package/dist/index.d.ts +210 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +551 -114
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +87 -10
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +221 -26
- package/dist/internal/index.js.map +1 -1
- package/dist/test/index.d.ts +3 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +2 -1
- package/dist/test/index.js.map +1 -1
- package/docs/02-foundations/02-providers-and-models.mdx +2 -0
- package/docs/03-agents/04-loop-control.mdx +34 -0
- package/docs/03-agents/06-memory.mdx +42 -0
- package/docs/03-agents/06-tool-approvals.mdx +2 -0
- package/docs/03-ai-sdk-core/05-generating-text.mdx +1 -0
- package/docs/03-ai-sdk-core/36-realtime.mdx +74 -1
- package/docs/03-ai-sdk-core/36-transcription.mdx +51 -4
- package/docs/03-ai-sdk-core/40-middleware.mdx +7 -2
- package/docs/03-ai-sdk-core/60-telemetry.mdx +85 -4
- package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +13 -0
- package/docs/06-advanced/04-caching.mdx +13 -4
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +33 -28
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +58 -1
- package/docs/07-reference/01-ai-sdk-core/11-transcribe.mdx +17 -2
- package/docs/07-reference/01-ai-sdk-core/13-generate-video.mdx +8 -1
- package/docs/07-reference/01-ai-sdk-core/31-ui-message.mdx +23 -0
- package/docs/07-reference/02-ai-sdk-ui/05-use-realtime.mdx +37 -8
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -49
- package/docs/08-migration-guides/23-migration-guide-7-0.mdx +36 -34
- package/package.json +12 -12
- package/src/generate-object/generate-object.ts +2 -1
- package/src/generate-object/stream-object.ts +7 -3
- package/src/generate-text/generate-text.ts +23 -3
- package/src/generate-text/is-output-chunk.ts +1 -0
- package/src/generate-text/language-model-events.ts +3 -0
- package/src/generate-text/output.ts +90 -37
- package/src/generate-text/smooth-stream.ts +5 -0
- package/src/generate-text/steering.ts +5 -4
- package/src/generate-text/stream-language-model-call.ts +16 -1
- package/src/generate-text/stream-text-result.ts +29 -1
- package/src/generate-text/stream-text.ts +25 -1
- package/src/generate-text/to-response-messages.ts +6 -2
- package/src/generate-text/validate-tool-approvals.ts +21 -3
- package/src/generate-video/generate-video-result.ts +11 -2
- package/src/generate-video/generate-video.ts +26 -2
- package/src/model/as-transcription-model-v4.ts +25 -0
- package/src/prompt/convert-to-language-model-prompt.ts +86 -14
- package/src/realtime/index.ts +3 -0
- package/src/realtime/realtime-attempt.ts +1 -0
- package/src/realtime/realtime-session.ts +142 -21
- package/src/telemetry/create-telemetry-dispatcher.ts +8 -1
- package/src/telemetry/filter-telemetry-event.ts +165 -0
- package/src/telemetry/get-request-origin.ts +25 -0
- package/src/telemetry/index.ts +6 -1
- package/src/telemetry/telemetry.ts +62 -0
- package/src/telemetry/tracing-channel-publisher.ts +19 -4
- package/src/test/mock-transcription-model-v4.ts +4 -0
- package/src/transcribe/transcribe-result.ts +20 -0
- package/src/transcribe/transcribe.ts +107 -20
- package/src/transcribe/transcription-events.ts +28 -8
- package/src/types/language-model-request-metadata.ts +3 -0
- package/src/types/realtime-model.ts +9 -0
- package/src/ui/convert-to-model-messages.ts +36 -28
- package/src/ui/index.ts +1 -0
- package/src/ui/process-ui-message-stream.ts +10 -0
- package/src/ui/ui-messages.ts +20 -0
- package/src/ui/validate-ui-messages.ts +21 -0
- package/src/ui-message-stream/to-ui-message-chunk.ts +72 -1
- package/src/ui-message-stream/ui-message-chunks.ts +31 -0
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from './tracing-channel-publisher';
|
|
19
19
|
import { getGlobalTelemetryIntegrations } from './telemetry-registry';
|
|
20
20
|
import type { TelemetryOptions } from './telemetry-options';
|
|
21
|
+
import { filterTelemetryEvent } from './filter-telemetry-event';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* The subset of `TelemetryDispatcher` keys whose values are Callback callbacks.
|
|
@@ -100,7 +101,7 @@ function augmentEvent<EVENT>(
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
return augmentedEvent;
|
|
104
|
+
return filterTelemetryEvent(augmentedEvent, telemetry);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/**
|
|
@@ -141,6 +142,9 @@ export function createTelemetryDispatcher({
|
|
|
141
142
|
includeRuntimeContext: telemetry?.includeRuntimeContext,
|
|
142
143
|
includeToolsContext: telemetry?.includeToolsContext,
|
|
143
144
|
};
|
|
145
|
+
// Raw operation results may contain both inputs and outputs.
|
|
146
|
+
const recordResult =
|
|
147
|
+
telemetry?.recordInputs !== false && telemetry?.recordOutputs !== false;
|
|
144
148
|
|
|
145
149
|
const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>(
|
|
146
150
|
key: KEY,
|
|
@@ -199,6 +203,7 @@ export function createTelemetryDispatcher({
|
|
|
199
203
|
event: augmentEvent(event, telemetryMetadata, true),
|
|
200
204
|
},
|
|
201
205
|
execute,
|
|
206
|
+
recordResult,
|
|
202
207
|
),
|
|
203
208
|
|
|
204
209
|
startTracingChannelContext: ({ type, event, completion }) =>
|
|
@@ -208,6 +213,7 @@ export function createTelemetryDispatcher({
|
|
|
208
213
|
event: augmentEvent(event, telemetryMetadata, true),
|
|
209
214
|
},
|
|
210
215
|
completion,
|
|
216
|
+
recordResult,
|
|
211
217
|
}),
|
|
212
218
|
|
|
213
219
|
onStart: mergeTelemetryCallback('onStart'),
|
|
@@ -276,6 +282,7 @@ export function createTelemetryDispatcher({
|
|
|
276
282
|
return await runWithTracingChannelSpan(
|
|
277
283
|
{ type: 'languageModelCall', event: augmentedEvent },
|
|
278
284
|
wrappedExecute,
|
|
285
|
+
recordResult,
|
|
279
286
|
);
|
|
280
287
|
},
|
|
281
288
|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { TelemetryOptions } from './telemetry-options';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters SDK-owned content fields on a copy. Only descend into SDK event
|
|
5
|
+
* containers; user context has its own inclusion policy.
|
|
6
|
+
*/
|
|
7
|
+
export function filterTelemetryEvent<EVENT>(
|
|
8
|
+
event: EVENT,
|
|
9
|
+
telemetry: Pick<TelemetryOptions, 'recordInputs' | 'recordOutputs'>,
|
|
10
|
+
): EVENT {
|
|
11
|
+
const hideInputs = telemetry.recordInputs === false;
|
|
12
|
+
const hideOutputs = telemetry.recordOutputs === false;
|
|
13
|
+
if (
|
|
14
|
+
(!hideInputs && !hideOutputs) ||
|
|
15
|
+
event == null ||
|
|
16
|
+
typeof event !== 'object'
|
|
17
|
+
) {
|
|
18
|
+
return event;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const filtered = Object.assign(
|
|
22
|
+
Object.create(Object.getPrototypeOf(event)),
|
|
23
|
+
event,
|
|
24
|
+
) as Record<string, unknown>;
|
|
25
|
+
const replace = (fields: Record<string, unknown>) => {
|
|
26
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
27
|
+
if (Object.hasOwn(event, key)) {
|
|
28
|
+
filtered[key] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isSpeech = filtered.operationId === 'ai.generateSpeech';
|
|
34
|
+
if (hideInputs) {
|
|
35
|
+
replace({
|
|
36
|
+
instructions: undefined,
|
|
37
|
+
system: undefined,
|
|
38
|
+
prompt: undefined,
|
|
39
|
+
messages: [],
|
|
40
|
+
promptMessages: [],
|
|
41
|
+
experimental_steeringMessages: [],
|
|
42
|
+
tools: undefined,
|
|
43
|
+
stepTools: undefined,
|
|
44
|
+
schema: undefined,
|
|
45
|
+
schemaName: undefined,
|
|
46
|
+
schemaDescription: undefined,
|
|
47
|
+
...(typeof filtered.output === 'object' ? { output: undefined } : {}),
|
|
48
|
+
providerOptions: {},
|
|
49
|
+
value: '',
|
|
50
|
+
values: [],
|
|
51
|
+
query: '',
|
|
52
|
+
documents: [],
|
|
53
|
+
state: Array.isArray(filtered.state)
|
|
54
|
+
? []
|
|
55
|
+
: typeof filtered.state === 'string'
|
|
56
|
+
? ''
|
|
57
|
+
: {},
|
|
58
|
+
questions: {},
|
|
59
|
+
...(isSpeech ? { text: '' } : {}),
|
|
60
|
+
});
|
|
61
|
+
if (filtered.toolCall != null) {
|
|
62
|
+
filtered.toolCall = { ...filtered.toolCall, input: undefined };
|
|
63
|
+
}
|
|
64
|
+
if (filtered.toolOutput != null) {
|
|
65
|
+
filtered.toolOutput = { ...filtered.toolOutput, input: undefined };
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(filtered.ranking)) {
|
|
68
|
+
filtered.ranking = filtered.ranking.map(entry =>
|
|
69
|
+
'document' in entry
|
|
70
|
+
? { ...entry, document: typeof entry.document === 'string' ? '' : {} }
|
|
71
|
+
: entry,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (hideOutputs) {
|
|
77
|
+
replace({
|
|
78
|
+
content: [],
|
|
79
|
+
...(!isSpeech ? { text: '' } : {}),
|
|
80
|
+
reasoning: Array.isArray(filtered.reasoning) ? [] : undefined,
|
|
81
|
+
reasoningText: undefined,
|
|
82
|
+
files: [],
|
|
83
|
+
sources: [],
|
|
84
|
+
toolCalls: [],
|
|
85
|
+
staticToolCalls: [],
|
|
86
|
+
dynamicToolCalls: [],
|
|
87
|
+
toolResults: [],
|
|
88
|
+
staticToolResults: [],
|
|
89
|
+
dynamicToolResults: [],
|
|
90
|
+
responseMessages: [],
|
|
91
|
+
object: undefined,
|
|
92
|
+
objectText: '',
|
|
93
|
+
embedding: [],
|
|
94
|
+
embeddings: [],
|
|
95
|
+
ranking: [],
|
|
96
|
+
answers: {},
|
|
97
|
+
segments: [],
|
|
98
|
+
});
|
|
99
|
+
if (filtered.toolOutput != null) {
|
|
100
|
+
filtered.toolOutput = {
|
|
101
|
+
...filtered.toolOutput,
|
|
102
|
+
output: undefined,
|
|
103
|
+
error: undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Raw provider payloads and errors can contain either inputs or outputs.
|
|
109
|
+
replace({
|
|
110
|
+
providerMetadata: undefined,
|
|
111
|
+
error:
|
|
112
|
+
filtered.error == null
|
|
113
|
+
? filtered.error
|
|
114
|
+
: new Error('AI SDK operation failed'),
|
|
115
|
+
});
|
|
116
|
+
for (const key of ['toolCall', 'toolOutput'] as const) {
|
|
117
|
+
const toolEvent = filtered[key];
|
|
118
|
+
if (toolEvent != null && typeof toolEvent === 'object') {
|
|
119
|
+
filtered[key] = {
|
|
120
|
+
...toolEvent,
|
|
121
|
+
providerMetadata: undefined,
|
|
122
|
+
...('error' in toolEvent
|
|
123
|
+
? {
|
|
124
|
+
error:
|
|
125
|
+
toolEvent.error == null
|
|
126
|
+
? toolEvent.error
|
|
127
|
+
: new Error('AI SDK operation failed'),
|
|
128
|
+
}
|
|
129
|
+
: {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const key of ['request', 'response'] as const) {
|
|
134
|
+
const filterMetadata = (metadata: unknown) => {
|
|
135
|
+
if (metadata == null || typeof metadata !== 'object') {
|
|
136
|
+
return metadata;
|
|
137
|
+
}
|
|
138
|
+
const filteredMetadata: Record<string, unknown> = { ...metadata };
|
|
139
|
+
delete filteredMetadata.body;
|
|
140
|
+
if (key === 'request' ? hideInputs : hideOutputs) {
|
|
141
|
+
if ('messages' in metadata) filteredMetadata.messages = [];
|
|
142
|
+
if ('instructions' in metadata)
|
|
143
|
+
filteredMetadata.instructions = undefined;
|
|
144
|
+
}
|
|
145
|
+
return filteredMetadata;
|
|
146
|
+
};
|
|
147
|
+
const metadata = filtered[key];
|
|
148
|
+
if (metadata != null) {
|
|
149
|
+
filtered[key] = Array.isArray(metadata)
|
|
150
|
+
? metadata.map(filterMetadata)
|
|
151
|
+
: filterMetadata(metadata);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (Array.isArray(filtered.steps)) {
|
|
156
|
+
filtered.steps = filtered.steps.map(step =>
|
|
157
|
+
filterTelemetryEvent(step, telemetry),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (Object.hasOwn(event, 'finalStep')) {
|
|
161
|
+
filtered.finalStep = filterTelemetryEvent(filtered.finalStep, telemetry);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return filtered as EVENT;
|
|
165
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LanguageModelV4GenerateResult } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
/** Remove credentials, paths, queries, and fragments before emitting telemetry. */
|
|
4
|
+
export function getRequestOrigin(url: string | undefined): string | undefined {
|
|
5
|
+
if (url == null) return undefined;
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const endpoint = new URL(url);
|
|
9
|
+
return endpoint.protocol === 'http:' || endpoint.protocol === 'https:'
|
|
10
|
+
? endpoint.origin
|
|
11
|
+
: undefined;
|
|
12
|
+
} catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Preserve request metadata while keeping unknown or sensitive URL fields out of results. */
|
|
18
|
+
export function sanitizeRequestMetadata(
|
|
19
|
+
request: LanguageModelV4GenerateResult['request'],
|
|
20
|
+
): LanguageModelV4GenerateResult['request'] {
|
|
21
|
+
if (request == null || !('url' in request)) return request;
|
|
22
|
+
const { url, ...rest } = request;
|
|
23
|
+
const origin = getRequestOrigin(url);
|
|
24
|
+
return { ...rest, ...(origin != null ? { url: origin } : {}) };
|
|
25
|
+
}
|
package/src/telemetry/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export type { TelemetryOptions } from './telemetry-options';
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
DurableTelemetryContext,
|
|
4
|
+
DurableTelemetryEventContext,
|
|
5
|
+
InferTelemetryEvent,
|
|
6
|
+
Telemetry,
|
|
7
|
+
} from './telemetry';
|
|
3
8
|
export { registerTelemetry } from './telemetry-registry';
|
|
4
9
|
export {
|
|
5
10
|
AI_SDK_TELEMETRY_TRACING_CHANNEL,
|
|
@@ -56,6 +56,43 @@ import type { TelemetryOptions } from '../telemetry/telemetry-options';
|
|
|
56
56
|
import type { TelemetryTracingEventType } from './tracing-channel';
|
|
57
57
|
import type { TracingChannelContext } from './tracing-channel-publisher';
|
|
58
58
|
|
|
59
|
+
/** Serializable tracing identity shared by the processes of a durable run. */
|
|
60
|
+
export type DurableTelemetryContext = {
|
|
61
|
+
traceId: string;
|
|
62
|
+
rootSpanId: string;
|
|
63
|
+
/** The integration's root-span data, reused when the run is reported. */
|
|
64
|
+
rootSpanName?: string;
|
|
65
|
+
rootAttributes?: Record<
|
|
66
|
+
string,
|
|
67
|
+
| string
|
|
68
|
+
| number
|
|
69
|
+
| boolean
|
|
70
|
+
| Array<string | null | undefined>
|
|
71
|
+
| Array<number | null | undefined>
|
|
72
|
+
| Array<boolean | null | undefined>
|
|
73
|
+
| undefined
|
|
74
|
+
>;
|
|
75
|
+
traceFlags: number;
|
|
76
|
+
traceState?: string;
|
|
77
|
+
samplingDecision: 0 | 1 | 2;
|
|
78
|
+
parentSpanContext?: {
|
|
79
|
+
traceId: string;
|
|
80
|
+
spanId: string;
|
|
81
|
+
traceFlags: number;
|
|
82
|
+
traceState?: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Identifies live model events and the run events reported after completion. */
|
|
87
|
+
export type DurableTelemetryEventContext = {
|
|
88
|
+
run: DurableTelemetryContext;
|
|
89
|
+
stepNumber?: number;
|
|
90
|
+
phase: 'live' | 'replay';
|
|
91
|
+
/** Context has already been filtered before crossing the step boundary. */
|
|
92
|
+
runtimeContext?: Record<string, unknown>;
|
|
93
|
+
headers?: Record<string, string | undefined>;
|
|
94
|
+
};
|
|
95
|
+
|
|
59
96
|
export type InferTelemetryEvent<EVENT> = EVENT &
|
|
60
97
|
Omit<
|
|
61
98
|
TelemetryOptions,
|
|
@@ -66,6 +103,16 @@ export type InferTelemetryEvent<EVENT> = EVENT &
|
|
|
66
103
|
* Absent in older releases.
|
|
67
104
|
*/
|
|
68
105
|
readonly aiVersion?: string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Time at which the event occurred, in milliseconds since the Unix epoch.
|
|
109
|
+
*
|
|
110
|
+
* Set when events are reported after they happened, for example when a
|
|
111
|
+
* durable workflow reports a run that spanned several processes.
|
|
112
|
+
* Integrations should use it instead of the current time when present.
|
|
113
|
+
*/
|
|
114
|
+
readonly timestamp?: number;
|
|
115
|
+
readonly durableContext?: DurableTelemetryEventContext;
|
|
69
116
|
};
|
|
70
117
|
|
|
71
118
|
type BivariantCallback<EVENT> = {
|
|
@@ -142,6 +189,21 @@ export interface TelemetryDispatcher {
|
|
|
142
189
|
* Methods can be sync or return a PromiseLike.
|
|
143
190
|
*/
|
|
144
191
|
export interface Telemetry {
|
|
192
|
+
/**
|
|
193
|
+
* Capture a serializable tracing identity before a durable run's model calls.
|
|
194
|
+
* Return undefined to preserve live per-model operations when the integration
|
|
195
|
+
* cannot reconstruct a run span across processes. Integrations without this
|
|
196
|
+
* hook receive the recorded lifecycle after completion.
|
|
197
|
+
*/
|
|
198
|
+
experimental_durable?: {
|
|
199
|
+
captureContext: (
|
|
200
|
+
event: InferTelemetryEvent<GenerateTextStartEvent>,
|
|
201
|
+
) =>
|
|
202
|
+
| DurableTelemetryContext
|
|
203
|
+
| undefined
|
|
204
|
+
| PromiseLike<DurableTelemetryContext | undefined>;
|
|
205
|
+
};
|
|
206
|
+
|
|
145
207
|
/**
|
|
146
208
|
* Called when an operation begins. Fired for text generation
|
|
147
209
|
* (generateText/streamText), object generation (generateObject/streamObject),
|
|
@@ -66,6 +66,7 @@ function loadBuiltinModule<T>(id: string): T | undefined {
|
|
|
66
66
|
export async function runWithTracingChannelSpan<T>(
|
|
67
67
|
message: TelemetryTracingChannelMessage,
|
|
68
68
|
execute: () => PromiseLike<T>,
|
|
69
|
+
recordResult = true,
|
|
69
70
|
): Promise<T> {
|
|
70
71
|
const diagnosticsChannel = await loadDiagnosticsChannel();
|
|
71
72
|
const tracingChannel = diagnosticsChannel?.tracingChannel?.(
|
|
@@ -102,11 +103,21 @@ export async function runWithTracingChannelSpan<T>(
|
|
|
102
103
|
},
|
|
103
104
|
);
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
// tracePromise publishes its resolved value on the channel. Keep the
|
|
107
|
+
// application's result separately when content recording is disabled.
|
|
108
|
+
return recordResult
|
|
109
|
+
? executePromise
|
|
110
|
+
: executePromise.then(
|
|
111
|
+
() => undefined,
|
|
112
|
+
() => {
|
|
113
|
+
throw new Error('AI SDK operation failed');
|
|
114
|
+
},
|
|
115
|
+
);
|
|
106
116
|
};
|
|
107
117
|
|
|
108
118
|
try {
|
|
109
|
-
|
|
119
|
+
await tracingChannel.tracePromise(tracedExecute, message);
|
|
120
|
+
return executionResult as T;
|
|
110
121
|
} catch {
|
|
111
122
|
if (hasExecutionError) {
|
|
112
123
|
throw executionError;
|
|
@@ -131,9 +142,11 @@ export async function runWithTracingChannelSpan<T>(
|
|
|
131
142
|
export function openTelemetryChannelSpanContext({
|
|
132
143
|
message,
|
|
133
144
|
completion,
|
|
145
|
+
recordResult = true,
|
|
134
146
|
}: {
|
|
135
147
|
message: TelemetryTracingChannelMessage;
|
|
136
148
|
completion: PromiseLike<unknown>;
|
|
149
|
+
recordResult?: boolean;
|
|
137
150
|
}): TracingChannelContext | undefined {
|
|
138
151
|
if (!isNodeRuntime()) {
|
|
139
152
|
Promise.resolve(completion).catch(() => {});
|
|
@@ -186,11 +199,13 @@ export function openTelemetryChannelSpanContext({
|
|
|
186
199
|
asyncEndPublished = true;
|
|
187
200
|
|
|
188
201
|
if (error !== undefined) {
|
|
189
|
-
context.error =
|
|
202
|
+
context.error = recordResult
|
|
203
|
+
? error
|
|
204
|
+
: new Error('AI SDK operation failed');
|
|
190
205
|
safePublish(() => tracingChannel.error.publish(context));
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
if (result !== undefined) {
|
|
208
|
+
if (recordResult && result !== undefined) {
|
|
194
209
|
context.result = result;
|
|
195
210
|
}
|
|
196
211
|
|
|
@@ -8,21 +8,25 @@ export class MockTranscriptionModelV4 implements TranscriptionModelV4 {
|
|
|
8
8
|
|
|
9
9
|
doGenerate: TranscriptionModelV4['doGenerate'];
|
|
10
10
|
doStream: TranscriptionModelV4['doStream'];
|
|
11
|
+
doGenerateFromUrl: TranscriptionModelV4['doGenerateFromUrl'];
|
|
11
12
|
|
|
12
13
|
constructor({
|
|
13
14
|
provider = 'mock-provider',
|
|
14
15
|
modelId = 'mock-model-id',
|
|
15
16
|
doGenerate = notImplemented,
|
|
16
17
|
doStream,
|
|
18
|
+
doGenerateFromUrl,
|
|
17
19
|
}: {
|
|
18
20
|
provider?: TranscriptionModelV4['provider'];
|
|
19
21
|
modelId?: TranscriptionModelV4['modelId'];
|
|
20
22
|
doGenerate?: TranscriptionModelV4['doGenerate'];
|
|
21
23
|
doStream?: TranscriptionModelV4['doStream'];
|
|
24
|
+
doGenerateFromUrl?: TranscriptionModelV4['doGenerateFromUrl'];
|
|
22
25
|
} = {}) {
|
|
23
26
|
this.provider = provider;
|
|
24
27
|
this.modelId = modelId;
|
|
25
28
|
this.doGenerate = doGenerate;
|
|
26
29
|
this.doStream = doStream;
|
|
30
|
+
this.doGenerateFromUrl = doGenerateFromUrl;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -15,6 +15,7 @@ export interface TranscriptionResult {
|
|
|
15
15
|
/**
|
|
16
16
|
* Array of transcript segments with timing information.
|
|
17
17
|
* Each segment represents a portion of the transcribed text with start and end times.
|
|
18
|
+
* Segments retain the provider's behavior and may represent individual words.
|
|
18
19
|
*/
|
|
19
20
|
readonly segments: Array<{
|
|
20
21
|
/**
|
|
@@ -31,6 +32,25 @@ export interface TranscriptionResult {
|
|
|
31
32
|
readonly endSecond: number;
|
|
32
33
|
}>;
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Word-level timings returned by the provider, whether explicitly requested
|
|
37
|
+
* or returned by default. Undefined when the provider does not return them.
|
|
38
|
+
*/
|
|
39
|
+
readonly words?: Array<{
|
|
40
|
+
/**
|
|
41
|
+
* The text of this word.
|
|
42
|
+
*/
|
|
43
|
+
readonly text: string;
|
|
44
|
+
/**
|
|
45
|
+
* The start time of this word in seconds.
|
|
46
|
+
*/
|
|
47
|
+
readonly startSecond: number;
|
|
48
|
+
/**
|
|
49
|
+
* The end time of this word in seconds.
|
|
50
|
+
*/
|
|
51
|
+
readonly endSecond: number;
|
|
52
|
+
}>;
|
|
53
|
+
|
|
34
54
|
/**
|
|
35
55
|
* The detected language of the audio content, as an ISO-639-1 code (e.g., 'en' for English).
|
|
36
56
|
* May be undefined if the language couldn't be detected.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
JSONObject,
|
|
3
|
+
TranscriptionModelV4TimestampGranularity,
|
|
4
|
+
} from '@ai-sdk/provider';
|
|
2
5
|
import {
|
|
3
6
|
createIdGenerator,
|
|
4
7
|
detectMediaType,
|
|
8
|
+
getTopLevelMediaType,
|
|
9
|
+
isFullMediaType,
|
|
5
10
|
withUserAgentSuffix,
|
|
6
11
|
type ProviderOptions,
|
|
7
12
|
} from '@ai-sdk/provider-utils';
|
|
@@ -49,6 +54,7 @@ export async function transcribe({
|
|
|
49
54
|
model,
|
|
50
55
|
audio,
|
|
51
56
|
providerOptions = {},
|
|
57
|
+
timestampGranularities,
|
|
52
58
|
maxRetries: maxRetriesArg,
|
|
53
59
|
abortSignal,
|
|
54
60
|
headers,
|
|
@@ -82,6 +88,16 @@ export async function transcribe({
|
|
|
82
88
|
*/
|
|
83
89
|
providerOptions?: ProviderOptions;
|
|
84
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Word and segment timings to request.
|
|
93
|
+
*
|
|
94
|
+
* Supported adapters map this onto the provider's native control when the
|
|
95
|
+
* matching provider option is omitted. An explicit provider option wins.
|
|
96
|
+
* Supported adapters warn when they cannot return a requested granularity.
|
|
97
|
+
* An empty array requests no shared timing granularity; provider defaults may vary.
|
|
98
|
+
*/
|
|
99
|
+
timestampGranularities?: Array<TranscriptionModelV4TimestampGranularity>;
|
|
100
|
+
|
|
85
101
|
/**
|
|
86
102
|
* Maximum number of retries per transcript model call. Set to 0 to disable retries.
|
|
87
103
|
*
|
|
@@ -107,6 +123,8 @@ export async function transcribe({
|
|
|
107
123
|
|
|
108
124
|
/**
|
|
109
125
|
* Custom download function for fetching audio from URLs.
|
|
126
|
+
* Providing this option forces local downloading even when the provider
|
|
127
|
+
* supports native URL transcription.
|
|
110
128
|
* Use `createDownload()` from `ai` to create a download function with custom size limits.
|
|
111
129
|
*
|
|
112
130
|
* @default createDownload() (2 GiB limit)
|
|
@@ -164,6 +182,7 @@ export async function transcribe({
|
|
|
164
182
|
maxRetries,
|
|
165
183
|
headers,
|
|
166
184
|
providerOptions,
|
|
185
|
+
timestampGranularities,
|
|
167
186
|
});
|
|
168
187
|
|
|
169
188
|
const executeWithStartEvent = async <T>({
|
|
@@ -188,14 +207,22 @@ export async function transcribe({
|
|
|
188
207
|
|
|
189
208
|
let preparationError: unknown;
|
|
190
209
|
let preparationFailed = false;
|
|
191
|
-
let audioData: Uint8Array;
|
|
210
|
+
let audioData: Uint8Array | URL;
|
|
192
211
|
let downloadedMediaType: string | undefined;
|
|
193
212
|
|
|
194
213
|
try {
|
|
195
214
|
if (audio instanceof URL) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
215
|
+
if (
|
|
216
|
+
resolvedModel.doGenerateFromUrl != null &&
|
|
217
|
+
downloadFn === defaultDownload &&
|
|
218
|
+
(audio.protocol === 'https:' || audio.protocol === 'http:')
|
|
219
|
+
) {
|
|
220
|
+
audioData = audio;
|
|
221
|
+
} else {
|
|
222
|
+
const downloadResult = await downloadFn({ url: audio, abortSignal });
|
|
223
|
+
audioData = downloadResult.data;
|
|
224
|
+
downloadedMediaType = downloadResult.mediaType;
|
|
225
|
+
}
|
|
199
226
|
} else {
|
|
200
227
|
audioData = convertDataContentToUint8Array(audio);
|
|
201
228
|
}
|
|
@@ -224,15 +251,18 @@ export async function transcribe({
|
|
|
224
251
|
}
|
|
225
252
|
}
|
|
226
253
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
254
|
+
let mediaType =
|
|
255
|
+
audioData instanceof URL
|
|
256
|
+
? undefined
|
|
257
|
+
: (getAudioMediaType(downloadedMediaType) ??
|
|
258
|
+
detectMediaType({
|
|
259
|
+
data: audioData,
|
|
260
|
+
topLevelType: 'audio',
|
|
261
|
+
}) ??
|
|
262
|
+
'audio/wav');
|
|
263
|
+
let byteLength = audioData instanceof URL ? undefined : audioData.byteLength;
|
|
234
264
|
const startEvent = createStartEvent({
|
|
235
|
-
byteLength
|
|
265
|
+
byteLength,
|
|
236
266
|
mediaType,
|
|
237
267
|
});
|
|
238
268
|
|
|
@@ -240,15 +270,41 @@ export async function transcribe({
|
|
|
240
270
|
return await executeWithStartEvent({
|
|
241
271
|
startEvent,
|
|
242
272
|
execute: async () => {
|
|
243
|
-
const result = await retry(() =>
|
|
244
|
-
|
|
245
|
-
audio: audioData,
|
|
273
|
+
const result = await retry(async () => {
|
|
274
|
+
const callOptions = {
|
|
246
275
|
abortSignal,
|
|
247
276
|
headers: headersWithUserAgent,
|
|
248
277
|
providerOptions,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
278
|
+
...(timestampGranularities != null
|
|
279
|
+
? { timestampGranularities }
|
|
280
|
+
: {}),
|
|
281
|
+
};
|
|
282
|
+
if (audioData instanceof URL) {
|
|
283
|
+
const nativeResult = await resolvedModel.doGenerateFromUrl?.({
|
|
284
|
+
...callOptions,
|
|
285
|
+
audio: audioData,
|
|
286
|
+
});
|
|
287
|
+
if (nativeResult != null) return nativeResult;
|
|
288
|
+
|
|
289
|
+
// A capable provider can decline an unsupported URL (for example,
|
|
290
|
+
// a redirect). Keep the existing secure download and file path.
|
|
291
|
+
const downloaded = await downloadFn({
|
|
292
|
+
url: audioData,
|
|
293
|
+
abortSignal,
|
|
294
|
+
});
|
|
295
|
+
audioData = downloaded.data;
|
|
296
|
+
mediaType =
|
|
297
|
+
getAudioMediaType(downloaded.mediaType) ??
|
|
298
|
+
detectMediaType({ data: audioData, topLevelType: 'audio' }) ??
|
|
299
|
+
'audio/wav';
|
|
300
|
+
byteLength = audioData.byteLength;
|
|
301
|
+
}
|
|
302
|
+
return resolvedModel.doGenerate({
|
|
303
|
+
...callOptions,
|
|
304
|
+
audio: audioData,
|
|
305
|
+
mediaType: mediaType ?? 'audio/wav',
|
|
306
|
+
});
|
|
307
|
+
});
|
|
252
308
|
|
|
253
309
|
logWarnings({
|
|
254
310
|
warnings: result.warnings,
|
|
@@ -268,11 +324,12 @@ export async function transcribe({
|
|
|
268
324
|
provider: resolvedModel.provider,
|
|
269
325
|
modelId: resolvedModel.modelId,
|
|
270
326
|
audio: {
|
|
271
|
-
byteLength
|
|
327
|
+
byteLength,
|
|
272
328
|
mediaType,
|
|
273
329
|
},
|
|
274
330
|
text: result.text,
|
|
275
331
|
segments: result.segments,
|
|
332
|
+
words: result.words,
|
|
276
333
|
language: result.language,
|
|
277
334
|
durationInSeconds: result.durationInSeconds,
|
|
278
335
|
usage: result.usage,
|
|
@@ -289,6 +346,7 @@ export async function transcribe({
|
|
|
289
346
|
return new DefaultTranscriptionResult({
|
|
290
347
|
text: result.text,
|
|
291
348
|
segments: result.segments,
|
|
349
|
+
words: result.words,
|
|
292
350
|
language: result.language,
|
|
293
351
|
durationInSeconds: result.durationInSeconds,
|
|
294
352
|
warnings: result.warnings,
|
|
@@ -303,6 +361,24 @@ export async function transcribe({
|
|
|
303
361
|
}
|
|
304
362
|
}
|
|
305
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Normalizes a downloaded `Content-Type` header value and returns it only
|
|
366
|
+
* when it is a full `audio/<subtype>` media type. Generic or mislabeled types
|
|
367
|
+
* (e.g. `application/octet-stream`, `binary/octet-stream`, `text/plain`)
|
|
368
|
+
* return `undefined` so that the media type is detected from the audio bytes
|
|
369
|
+
* instead.
|
|
370
|
+
*/
|
|
371
|
+
function getAudioMediaType(
|
|
372
|
+
contentType: string | undefined,
|
|
373
|
+
): string | undefined {
|
|
374
|
+
const mediaType = contentType?.split(';')[0].trim().toLowerCase();
|
|
375
|
+
return mediaType != null &&
|
|
376
|
+
getTopLevelMediaType(mediaType) === 'audio' &&
|
|
377
|
+
isFullMediaType(mediaType)
|
|
378
|
+
? mediaType
|
|
379
|
+
: undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
306
382
|
class DefaultTranscriptionResult implements TranscriptionResult {
|
|
307
383
|
readonly text: string;
|
|
308
384
|
readonly segments: Array<{
|
|
@@ -310,6 +386,11 @@ class DefaultTranscriptionResult implements TranscriptionResult {
|
|
|
310
386
|
startSecond: number;
|
|
311
387
|
endSecond: number;
|
|
312
388
|
}>;
|
|
389
|
+
readonly words?: Array<{
|
|
390
|
+
text: string;
|
|
391
|
+
startSecond: number;
|
|
392
|
+
endSecond: number;
|
|
393
|
+
}>;
|
|
313
394
|
readonly language: string | undefined;
|
|
314
395
|
readonly durationInSeconds: number | undefined;
|
|
315
396
|
readonly warnings: Array<Warning>;
|
|
@@ -323,6 +404,11 @@ class DefaultTranscriptionResult implements TranscriptionResult {
|
|
|
323
404
|
startSecond: number;
|
|
324
405
|
endSecond: number;
|
|
325
406
|
}>;
|
|
407
|
+
words?: Array<{
|
|
408
|
+
text: string;
|
|
409
|
+
startSecond: number;
|
|
410
|
+
endSecond: number;
|
|
411
|
+
}>;
|
|
326
412
|
language: string | undefined;
|
|
327
413
|
durationInSeconds: number | undefined;
|
|
328
414
|
warnings: Array<Warning>;
|
|
@@ -331,6 +417,7 @@ class DefaultTranscriptionResult implements TranscriptionResult {
|
|
|
331
417
|
}) {
|
|
332
418
|
this.text = options.text;
|
|
333
419
|
this.segments = options.segments;
|
|
420
|
+
this.words = options.words;
|
|
334
421
|
this.language = options.language;
|
|
335
422
|
this.durationInSeconds = options.durationInSeconds;
|
|
336
423
|
this.warnings = options.warnings;
|