@ai-sdk/workflow 1.0.0-beta.8 → 1.0.0-beta.94
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 +747 -0
- package/dist/index.d.mts +190 -112
- package/dist/index.mjs +855 -340
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -11
- package/src/create-language-model-tool-result-output.ts +85 -0
- package/src/do-stream-step.ts +46 -9
- package/src/index.ts +5 -3
- package/src/providers/mock.ts +8 -8
- package/src/serializable-schema.ts +5 -1
- package/src/stream-text-iterator.ts +150 -62
- package/src/test/agent-e2e-workflows.ts +89 -9
- package/src/to-ui-message-chunk.ts +13 -10
- package/src/workflow-agent.ts +1157 -606
- package/src/workflow-chat-transport.ts +17 -15
- package/src/telemetry.ts +0 -199
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
parseJsonEventStream,
|
|
3
|
+
uiMessageChunkSchema,
|
|
2
4
|
type ChatRequestOptions,
|
|
3
5
|
type ChatTransport,
|
|
4
6
|
type PrepareReconnectToStreamRequest,
|
|
5
7
|
type PrepareSendMessagesRequest,
|
|
6
|
-
parseJsonEventStream,
|
|
7
8
|
type UIMessage,
|
|
8
9
|
type UIMessageChunk,
|
|
9
|
-
uiMessageChunkSchema,
|
|
10
10
|
} from 'ai';
|
|
11
11
|
import {
|
|
12
12
|
convertAsyncIteratorToReadableStream,
|
|
@@ -216,7 +216,7 @@ export class WorkflowChatTransport<
|
|
|
216
216
|
: undefined;
|
|
217
217
|
|
|
218
218
|
const url = requestConfig?.api ?? this.api;
|
|
219
|
-
const
|
|
219
|
+
const response = await this.fetch(url, {
|
|
220
220
|
method: 'POST',
|
|
221
221
|
body: JSON.stringify(
|
|
222
222
|
requestConfig?.body ?? { messages, ...options.body },
|
|
@@ -226,13 +226,13 @@ export class WorkflowChatTransport<
|
|
|
226
226
|
signal: abortSignal,
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
-
if (!
|
|
229
|
+
if (!response.ok || !response.body) {
|
|
230
230
|
throw new Error(
|
|
231
|
-
`Failed to fetch chat: ${
|
|
231
|
+
`Failed to fetch chat: ${response.status} ${await response.text()}`,
|
|
232
232
|
);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
const workflowRunId =
|
|
235
|
+
const workflowRunId = response.headers.get('x-workflow-run-id');
|
|
236
236
|
if (!workflowRunId) {
|
|
237
237
|
throw new Error(
|
|
238
238
|
'Workflow run ID not found in "x-workflow-run-id" response header',
|
|
@@ -242,12 +242,12 @@ export class WorkflowChatTransport<
|
|
|
242
242
|
// Notify the caller that the chat POST request was sent.
|
|
243
243
|
// This is useful for tracking the chat history on the client
|
|
244
244
|
// side and allows for inspecting response headers.
|
|
245
|
-
await this.onChatSendMessage?.(
|
|
245
|
+
await this.onChatSendMessage?.(response, options);
|
|
246
246
|
|
|
247
247
|
// Flush the initial stream until the end or an error occurs
|
|
248
248
|
try {
|
|
249
249
|
const chunkStream = parseJsonEventStream({
|
|
250
|
-
stream:
|
|
250
|
+
stream: response.body,
|
|
251
251
|
schema: uiMessageChunkSchema,
|
|
252
252
|
});
|
|
253
253
|
for await (const chunk of createAsyncIterableStream(chunkStream)) {
|
|
@@ -292,8 +292,8 @@ export class WorkflowChatTransport<
|
|
|
292
292
|
async reconnectToStream(
|
|
293
293
|
options: ReconnectToStreamOptions & ChatRequestOptions,
|
|
294
294
|
): Promise<ReadableStream<UIMessageChunk> | null> {
|
|
295
|
-
const
|
|
296
|
-
return convertAsyncIteratorToReadableStream(
|
|
295
|
+
const reconnectIterator = this.reconnectToStreamIterator(options);
|
|
296
|
+
return convertAsyncIteratorToReadableStream(reconnectIterator);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
private async *reconnectToStreamIterator(
|
|
@@ -344,15 +344,15 @@ export class WorkflowChatTransport<
|
|
|
344
344
|
: chunkIndex;
|
|
345
345
|
|
|
346
346
|
const url = `${baseUrl}?startIndex=${startIndex}`;
|
|
347
|
-
const
|
|
347
|
+
const response = await this.fetch(url, {
|
|
348
348
|
headers: requestConfig?.headers,
|
|
349
349
|
credentials: requestConfig?.credentials,
|
|
350
350
|
signal: options.abortSignal,
|
|
351
351
|
});
|
|
352
352
|
|
|
353
|
-
if (!
|
|
353
|
+
if (!response.ok || !response.body) {
|
|
354
354
|
throw new Error(
|
|
355
|
-
`Failed to fetch chat: ${
|
|
355
|
+
`Failed to fetch chat: ${response.status} ${await response.text()}`,
|
|
356
356
|
);
|
|
357
357
|
}
|
|
358
358
|
|
|
@@ -365,7 +365,9 @@ export class WorkflowChatTransport<
|
|
|
365
365
|
// resume from (explicitStartIndex + chunks received).
|
|
366
366
|
chunkIndex = explicitStartIndex;
|
|
367
367
|
} else if (useExplicitStartIndex && explicitStartIndex < 0) {
|
|
368
|
-
const tailIndexHeader =
|
|
368
|
+
const tailIndexHeader = response.headers.get(
|
|
369
|
+
'x-workflow-stream-tail-index',
|
|
370
|
+
);
|
|
369
371
|
const tailIndex =
|
|
370
372
|
tailIndexHeader !== null ? parseInt(tailIndexHeader, 10) : NaN;
|
|
371
373
|
|
|
@@ -389,7 +391,7 @@ export class WorkflowChatTransport<
|
|
|
389
391
|
|
|
390
392
|
try {
|
|
391
393
|
const chunkStream = parseJsonEventStream({
|
|
392
|
-
stream:
|
|
394
|
+
stream: response.body,
|
|
393
395
|
schema: uiMessageChunkSchema,
|
|
394
396
|
});
|
|
395
397
|
for await (const chunk of createAsyncIterableStream(chunkStream)) {
|
package/src/telemetry.ts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import type { TelemetrySettings } from './workflow-agent.js';
|
|
2
|
-
|
|
3
|
-
// Minimal OTel type shims so we don't depend on @opentelemetry/api at compile time.
|
|
4
|
-
type Attributes = Record<string, unknown>;
|
|
5
|
-
|
|
6
|
-
type Span = {
|
|
7
|
-
setAttributes(attributes: Attributes): void;
|
|
8
|
-
setStatus(status: { code: number; message?: string }): void;
|
|
9
|
-
recordException(exception: {
|
|
10
|
-
name: string;
|
|
11
|
-
message: string;
|
|
12
|
-
stack?: string;
|
|
13
|
-
}): void;
|
|
14
|
-
end(): void;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type Context = unknown;
|
|
18
|
-
|
|
19
|
-
type Tracer = {
|
|
20
|
-
startActiveSpan<T>(
|
|
21
|
-
name: string,
|
|
22
|
-
options: Attributes,
|
|
23
|
-
fn: (span: Span) => T,
|
|
24
|
-
): T;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Full OTel API surface we use
|
|
28
|
-
interface OtelApi {
|
|
29
|
-
trace: {
|
|
30
|
-
getTracer(name: string): Tracer;
|
|
31
|
-
setSpan(context: Context, span: Span): Context;
|
|
32
|
-
};
|
|
33
|
-
context: {
|
|
34
|
-
active(): Context;
|
|
35
|
-
with<T>(ctx: Context, fn: () => T): T;
|
|
36
|
-
};
|
|
37
|
-
SpanStatusCode: { ERROR: number };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Lazy-loaded OTel API — self-initializes on first use (item 5)
|
|
41
|
-
let otelApi: OtelApi | null = null;
|
|
42
|
-
let otelLoadAttempted = false;
|
|
43
|
-
|
|
44
|
-
async function ensureOtelApi(): Promise<OtelApi | null> {
|
|
45
|
-
if (otelLoadAttempted) return otelApi;
|
|
46
|
-
otelLoadAttempted = true;
|
|
47
|
-
try {
|
|
48
|
-
// Dynamic import — @opentelemetry/api is an optional peer dependency.
|
|
49
|
-
// Use Function() to hide the import from bundlers that would fail at
|
|
50
|
-
// compile time when the package is absent.
|
|
51
|
-
otelApi = await (Function(
|
|
52
|
-
'return import("@opentelemetry/api")',
|
|
53
|
-
)() as Promise<OtelApi>);
|
|
54
|
-
} catch {
|
|
55
|
-
otelApi = null;
|
|
56
|
-
}
|
|
57
|
-
return otelApi;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Stateless tracer accessor matching AI SDK's `getTracer` pattern (item 5).
|
|
62
|
-
* Returns a no-op–equivalent `null` when telemetry is disabled, so callers
|
|
63
|
-
* don't need a separate init step.
|
|
64
|
-
*/
|
|
65
|
-
function getTracer(telemetry?: TelemetrySettings): Tracer | null {
|
|
66
|
-
if (!telemetry?.isEnabled || !otelApi) return null;
|
|
67
|
-
if (telemetry.tracer) return telemetry.tracer as Tracer;
|
|
68
|
-
return otelApi.trace.getTracer('ai');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ── Attribute helpers ──────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Assemble `operation.name` / `resource.name` following the AI SDK convention
|
|
75
|
-
* (items 1 + 2): separator is a **space**, not a dot.
|
|
76
|
-
*/
|
|
77
|
-
function assembleOperationName(
|
|
78
|
-
operationId: string,
|
|
79
|
-
telemetry?: TelemetrySettings,
|
|
80
|
-
): Attributes {
|
|
81
|
-
return {
|
|
82
|
-
'operation.name': `${operationId}${
|
|
83
|
-
telemetry?.functionId != null ? ` ${telemetry.functionId}` : ''
|
|
84
|
-
}`,
|
|
85
|
-
'resource.name': telemetry?.functionId,
|
|
86
|
-
'ai.operationId': operationId,
|
|
87
|
-
'ai.telemetry.functionId': telemetry?.functionId,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Build the full attribute bag for a span, merging operation name,
|
|
93
|
-
* caller-supplied attributes, and user-defined telemetry metadata.
|
|
94
|
-
*/
|
|
95
|
-
function buildAttributes(
|
|
96
|
-
operationId: string,
|
|
97
|
-
telemetry: TelemetrySettings | undefined,
|
|
98
|
-
extra?: Attributes,
|
|
99
|
-
): Attributes {
|
|
100
|
-
if (!telemetry?.isEnabled) return {};
|
|
101
|
-
|
|
102
|
-
const attrs: Attributes = {
|
|
103
|
-
...assembleOperationName(operationId, telemetry),
|
|
104
|
-
...extra,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
if (telemetry.metadata) {
|
|
108
|
-
for (const [key, value] of Object.entries(telemetry.metadata)) {
|
|
109
|
-
if (value != null) {
|
|
110
|
-
attrs[`ai.telemetry.metadata.${key}`] = value;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return attrs;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ── Error recording (item 3) ───────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Record an error on a span following the AI SDK pattern:
|
|
122
|
-
* `recordException` (with name / message / stack) + `setStatus`.
|
|
123
|
-
*/
|
|
124
|
-
function recordErrorOnSpan(span: Span, error: unknown): void {
|
|
125
|
-
if (error instanceof Error) {
|
|
126
|
-
span.recordException({
|
|
127
|
-
name: error.name,
|
|
128
|
-
message: error.message,
|
|
129
|
-
stack: error.stack,
|
|
130
|
-
});
|
|
131
|
-
span.setStatus({
|
|
132
|
-
code: otelApi?.SpanStatusCode.ERROR ?? 2,
|
|
133
|
-
message: error.message,
|
|
134
|
-
});
|
|
135
|
-
} else {
|
|
136
|
-
span.setStatus({ code: otelApi?.SpanStatusCode.ERROR ?? 2 });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ── Public API ─────────────────────────────────────────────────────────
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Record a span around an async function.
|
|
144
|
-
*
|
|
145
|
-
* Self-initialising: the first call lazily loads `@opentelemetry/api`.
|
|
146
|
-
* If telemetry is disabled or OTel is unavailable the `fn` runs without
|
|
147
|
-
* instrumentation (no-op fast path).
|
|
148
|
-
*
|
|
149
|
-
* Matches the AI SDK's `recordSpan`:
|
|
150
|
-
* - Uses `context.with()` for proper context propagation (item 4)
|
|
151
|
-
* - Calls `recordException` + `setStatus` on errors (item 3)
|
|
152
|
-
* - Uses space separator in `operation.name` (item 1)
|
|
153
|
-
* - Sets `resource.name` (item 2)
|
|
154
|
-
*/
|
|
155
|
-
export async function recordSpan<T>(options: {
|
|
156
|
-
name: string;
|
|
157
|
-
telemetry?: TelemetrySettings;
|
|
158
|
-
attributes?: Attributes;
|
|
159
|
-
fn: (span?: Span) => PromiseLike<T> | T;
|
|
160
|
-
}): Promise<T> {
|
|
161
|
-
// Self-initialise on first call (item 5)
|
|
162
|
-
if (!otelLoadAttempted) {
|
|
163
|
-
await ensureOtelApi();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const tracer = getTracer(options.telemetry);
|
|
167
|
-
if (!tracer || !otelApi) {
|
|
168
|
-
return options.fn(undefined);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const attrs = buildAttributes(
|
|
172
|
-
options.name,
|
|
173
|
-
options.telemetry,
|
|
174
|
-
options.attributes,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
return tracer.startActiveSpan(
|
|
178
|
-
options.name,
|
|
179
|
-
{ attributes: attrs },
|
|
180
|
-
async span => {
|
|
181
|
-
// Capture current context so nested spans parent correctly (item 4).
|
|
182
|
-
// otelApi is guaranteed non-null here (checked before startActiveSpan).
|
|
183
|
-
const ctx = otelApi!.context.active();
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const result = await otelApi!.context.with(ctx, () => options.fn(span));
|
|
187
|
-
span.end();
|
|
188
|
-
return result;
|
|
189
|
-
} catch (error) {
|
|
190
|
-
try {
|
|
191
|
-
recordErrorOnSpan(span, error);
|
|
192
|
-
} finally {
|
|
193
|
-
span.end();
|
|
194
|
-
}
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
);
|
|
199
|
-
}
|