@ai-sdk/devtools 1.0.0-beta.2 → 1.0.0-beta.31
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/README.md +40 -9
- package/dist/client/assets/index-BVPj3Knk.css +1 -0
- package/dist/client/assets/index-DKzNaLaQ.js +190 -0
- package/dist/client/index.html +2 -2
- package/dist/index.d.ts +21 -3
- package/dist/index.js +298 -6
- package/dist/viewer/server.js +106 -10
- package/package.json +44 -20
- package/src/db.ts +75 -7
- package/src/index.ts +1 -0
- package/src/integration.ts +447 -0
- package/src/middleware.ts +13 -13
- package/src/viewer/client/app.tsx +97 -1878
- package/src/viewer/client/components/message-components.tsx +342 -0
- package/src/viewer/client/components/output-components.tsx +145 -0
- package/src/viewer/client/components/shared-components.tsx +695 -0
- package/src/viewer/client/components/step-card.tsx +472 -0
- package/src/viewer/client/components/trace-timeline.tsx +529 -0
- package/src/viewer/client/components/ui/badge.tsx +0 -1
- package/src/viewer/client/components/ui/button.tsx +1 -2
- package/src/viewer/client/styles.css +16 -8
- package/src/viewer/client/types.ts +183 -0
- package/src/viewer/client/utils.ts +711 -0
- package/src/viewer/server.ts +90 -10
- package/dist/client/assets/index-BkyOIjbt.css +0 -1
- package/dist/client/assets/index-DmPGHSLs.js +0 -185
package/src/db.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const DEVTOOLS_DB_DIR = '.devtools';
|
|
5
|
+
const DEVTOOLS_DB_FILE = 'generations.json';
|
|
6
|
+
// Cap how many bytes we read from a remote database file. Guards against a
|
|
7
|
+
// synchronous hang / OOM if the file is enormous.
|
|
8
|
+
const MAX_DB_BYTES = 100 * 1024 * 1024; // 100 MB
|
|
9
|
+
const DB_DIR = path.join(process.cwd(), DEVTOOLS_DB_DIR);
|
|
10
|
+
const DB_PATH = path.join(DB_DIR, DEVTOOLS_DB_FILE);
|
|
6
11
|
const DEVTOOLS_PORT = process.env.AI_SDK_DEVTOOLS_PORT
|
|
7
12
|
? parseInt(process.env.AI_SDK_DEVTOOLS_PORT)
|
|
8
13
|
: 4983;
|
|
@@ -26,7 +31,7 @@ export const notifyServerAsync = async (
|
|
|
26
31
|
await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
|
|
27
32
|
method: 'POST',
|
|
28
33
|
headers: { 'Content-Type': 'application/json' },
|
|
29
|
-
body: JSON.stringify({ event, timestamp: Date.now() }),
|
|
34
|
+
body: JSON.stringify({ event, timestamp: Date.now(), dbPath: DB_PATH }),
|
|
30
35
|
});
|
|
31
36
|
} catch {
|
|
32
37
|
// Ignore errors - server might not be running
|
|
@@ -36,6 +41,9 @@ export const notifyServerAsync = async (
|
|
|
36
41
|
export interface Run {
|
|
37
42
|
id: string;
|
|
38
43
|
started_at: string;
|
|
44
|
+
parent_run_id: string | null;
|
|
45
|
+
parent_step_id: string | null;
|
|
46
|
+
function_id: string | null;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export interface Step {
|
|
@@ -137,15 +145,69 @@ const saveDb = (db: Database): void => {
|
|
|
137
145
|
writeDb(db);
|
|
138
146
|
};
|
|
139
147
|
|
|
148
|
+
const normalizeDevtoolsDbPath = (dbPath: string): string | undefined => {
|
|
149
|
+
const resolvedPath = path.resolve(dbPath);
|
|
150
|
+
const dbDir = path.dirname(resolvedPath);
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
path.basename(resolvedPath) !== DEVTOOLS_DB_FILE ||
|
|
154
|
+
path.basename(dbDir) !== DEVTOOLS_DB_DIR
|
|
155
|
+
) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return resolvedPath;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const validateRemoteDbPath = (dbPath: unknown): string | undefined => {
|
|
163
|
+
if (typeof dbPath !== 'string') {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const normalizedPath = normalizeDevtoolsDbPath(dbPath);
|
|
168
|
+
if (!normalizedPath) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const stats = fs.statSync(normalizedPath);
|
|
174
|
+
if (!stats.isFile() || stats.size > MAX_DB_BYTES) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const realPath = fs.realpathSync(normalizedPath);
|
|
179
|
+
return normalizeDevtoolsDbPath(realPath);
|
|
180
|
+
} catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
140
185
|
/**
|
|
141
186
|
* Reload the database from disk.
|
|
142
|
-
* Used by the viewer server to pick up changes made by the middleware.
|
|
187
|
+
* Used by the viewer server to pick up changes made by the middleware/integration.
|
|
188
|
+
* When a valid remote dbPath is provided (from a notify POST), reads from that
|
|
189
|
+
* path instead of the local CWD-based path, so the viewer works regardless of
|
|
190
|
+
* where it was started.
|
|
143
191
|
*/
|
|
144
|
-
export const reloadDb = async (): Promise<void> => {
|
|
192
|
+
export const reloadDb = async (remoteDbPath?: string): Promise<void> => {
|
|
193
|
+
const validatedRemoteDbPath = validateRemoteDbPath(remoteDbPath);
|
|
194
|
+
if (validatedRemoteDbPath) {
|
|
195
|
+
try {
|
|
196
|
+
const content = fs.readFileSync(validatedRemoteDbPath, 'utf-8');
|
|
197
|
+
dbCache = JSON.parse(content);
|
|
198
|
+
return;
|
|
199
|
+
} catch {
|
|
200
|
+
// Fall through to default
|
|
201
|
+
}
|
|
202
|
+
}
|
|
145
203
|
dbCache = readDb();
|
|
146
204
|
};
|
|
147
205
|
|
|
148
|
-
export const createRun = async (
|
|
206
|
+
export const createRun = async (
|
|
207
|
+
id: string,
|
|
208
|
+
parent?: { runId: string; stepId: string },
|
|
209
|
+
functionId?: string,
|
|
210
|
+
): Promise<Run> => {
|
|
149
211
|
const db = getDb();
|
|
150
212
|
const started_at = new Date().toISOString();
|
|
151
213
|
|
|
@@ -155,7 +217,13 @@ export const createRun = async (id: string): Promise<Run> => {
|
|
|
155
217
|
return existing;
|
|
156
218
|
}
|
|
157
219
|
|
|
158
|
-
const run: Run = {
|
|
220
|
+
const run: Run = {
|
|
221
|
+
id,
|
|
222
|
+
started_at,
|
|
223
|
+
parent_run_id: parent?.runId ?? null,
|
|
224
|
+
parent_step_id: parent?.stepId ?? null,
|
|
225
|
+
function_id: functionId ?? null,
|
|
226
|
+
};
|
|
159
227
|
db.runs.push(run);
|
|
160
228
|
saveDb(db);
|
|
161
229
|
notifyServer('run');
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GenerateTextStartEvent,
|
|
3
|
+
GenerateTextStepStartEvent,
|
|
4
|
+
GenerateTextStepEndEvent,
|
|
5
|
+
GenerateObjectStartEvent,
|
|
6
|
+
GenerateObjectStepStartEvent,
|
|
7
|
+
GenerateObjectStepEndEvent,
|
|
8
|
+
Telemetry,
|
|
9
|
+
ToolSet,
|
|
10
|
+
} from 'ai';
|
|
11
|
+
import {
|
|
12
|
+
createRun,
|
|
13
|
+
createStep,
|
|
14
|
+
updateStepResult,
|
|
15
|
+
notifyServerAsync,
|
|
16
|
+
} from './db.js';
|
|
17
|
+
|
|
18
|
+
type OperationType = 'generate' | 'stream';
|
|
19
|
+
|
|
20
|
+
interface StepState {
|
|
21
|
+
stepId: string;
|
|
22
|
+
startTime: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CallState {
|
|
26
|
+
runId: string;
|
|
27
|
+
operationType: OperationType;
|
|
28
|
+
functionId: string | undefined;
|
|
29
|
+
settings: Record<string, unknown>;
|
|
30
|
+
stepStates: Map<number, StepState>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const activeSteps = new Map<string, StepState>();
|
|
34
|
+
|
|
35
|
+
let signalHandlersRegistered = false;
|
|
36
|
+
const registerSignalHandlers = () => {
|
|
37
|
+
if (signalHandlersRegistered) return;
|
|
38
|
+
signalHandlersRegistered = true;
|
|
39
|
+
|
|
40
|
+
const cleanup = async () => {
|
|
41
|
+
if (activeSteps.size === 0) return;
|
|
42
|
+
|
|
43
|
+
const promises = Array.from(activeSteps.entries()).map(
|
|
44
|
+
async ([stepId, data]) => {
|
|
45
|
+
const durationMs = Date.now() - data.startTime;
|
|
46
|
+
await updateStepResult(stepId, {
|
|
47
|
+
duration_ms: durationMs,
|
|
48
|
+
output: null,
|
|
49
|
+
usage: null,
|
|
50
|
+
error: 'Request aborted',
|
|
51
|
+
raw_request: null,
|
|
52
|
+
raw_response: null,
|
|
53
|
+
raw_chunks: null,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
await Promise.all(promises);
|
|
58
|
+
await notifyServerAsync('step-update');
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
process.on('SIGINT', () => {
|
|
62
|
+
cleanup().then(() => process.exit(130));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
process.on('SIGTERM', () => {
|
|
66
|
+
cleanup().then(() => process.exit(143));
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const generateRunId = (): string => {
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const timestamp = now
|
|
73
|
+
.toISOString()
|
|
74
|
+
.replace(/[-:T.Z]/g, '')
|
|
75
|
+
.slice(0, 17);
|
|
76
|
+
const uniqueId = crypto.randomUUID().slice(0, 8);
|
|
77
|
+
return `${timestamp}-${uniqueId}`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function getOperationType(operationId: string): OperationType {
|
|
81
|
+
if (operationId === 'ai.streamText' || operationId === 'ai.streamObject') {
|
|
82
|
+
return 'stream';
|
|
83
|
+
}
|
|
84
|
+
return 'generate';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a devtools telemetry integration that logs all AI SDK operations
|
|
89
|
+
* to the devtools viewer.
|
|
90
|
+
*
|
|
91
|
+
* Usage:
|
|
92
|
+
* ```ts
|
|
93
|
+
* import { registerTelemetry } from 'ai';
|
|
94
|
+
* import { DevToolsTelemetry } from '@ai-sdk/devtools';
|
|
95
|
+
*
|
|
96
|
+
* registerTelemetry(DevToolsTelemetry());
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* Telemetry is enabled by default — no need to set `telemetry`
|
|
100
|
+
* unless you want to configure `functionId`, `recordInputs`, or `recordOutputs`.
|
|
101
|
+
*/
|
|
102
|
+
export function DevToolsTelemetry(): Telemetry {
|
|
103
|
+
if (process.env.NODE_ENV === 'production') {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'@ai-sdk/devtools should not be used in production. ' +
|
|
106
|
+
'Remove DevToolsTelemetry from your telemetry configuration for production builds.',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
registerSignalHandlers();
|
|
111
|
+
|
|
112
|
+
const callStates = new Map<string, CallState>();
|
|
113
|
+
|
|
114
|
+
// When executeTool runs a tool's execute function, any nested generateText/
|
|
115
|
+
// streamText call inside that tool gets its own callId. We track the nesting
|
|
116
|
+
// so that the inner call's run can be linked to the parent.
|
|
117
|
+
//
|
|
118
|
+
// We use a per-toolCallId Map instead of a single variable so that parallel
|
|
119
|
+
// tool calls
|
|
120
|
+
const toolContextMap = new Map<
|
|
121
|
+
string,
|
|
122
|
+
{ parentCallId: string; parentToolCallId: string }
|
|
123
|
+
>();
|
|
124
|
+
let currentToolCallId: string | null = null;
|
|
125
|
+
|
|
126
|
+
function resolveParentInfo(): { runId: string; stepId: string } | undefined {
|
|
127
|
+
if (!currentToolCallId) return undefined;
|
|
128
|
+
|
|
129
|
+
const toolCallContext = toolContextMap.get(currentToolCallId);
|
|
130
|
+
if (!toolCallContext) return undefined;
|
|
131
|
+
|
|
132
|
+
const parentState = callStates.get(toolCallContext.parentCallId);
|
|
133
|
+
if (!parentState) return undefined;
|
|
134
|
+
|
|
135
|
+
// Find the step that is currently executing the tool call.
|
|
136
|
+
// This is the most recent (highest step number) step in the parent call.
|
|
137
|
+
let latestStepId: string | undefined;
|
|
138
|
+
let latestStepNumber = -1;
|
|
139
|
+
for (const [stepNumber, stepState] of parentState.stepStates) {
|
|
140
|
+
if (stepNumber > latestStepNumber) {
|
|
141
|
+
latestStepNumber = stepNumber;
|
|
142
|
+
latestStepId = stepState.stepId;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!latestStepId) return undefined;
|
|
147
|
+
|
|
148
|
+
return { runId: parentState.runId, stepId: latestStepId };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getOrCreateCallState(
|
|
152
|
+
callId: string,
|
|
153
|
+
operationId: string,
|
|
154
|
+
event: {
|
|
155
|
+
functionId?: string | undefined;
|
|
156
|
+
maxOutputTokens?: number | undefined;
|
|
157
|
+
temperature?: number | undefined;
|
|
158
|
+
topP?: number | undefined;
|
|
159
|
+
topK?: number | undefined;
|
|
160
|
+
presencePenalty?: number | undefined;
|
|
161
|
+
frequencyPenalty?: number | undefined;
|
|
162
|
+
seed?: number | undefined;
|
|
163
|
+
},
|
|
164
|
+
): CallState {
|
|
165
|
+
let state = callStates.get(callId);
|
|
166
|
+
if (state) return state;
|
|
167
|
+
|
|
168
|
+
state = {
|
|
169
|
+
runId: generateRunId(),
|
|
170
|
+
operationType: getOperationType(operationId),
|
|
171
|
+
functionId: event.functionId,
|
|
172
|
+
settings: {
|
|
173
|
+
maxOutputTokens: event.maxOutputTokens,
|
|
174
|
+
temperature: event.temperature,
|
|
175
|
+
topP: event.topP,
|
|
176
|
+
topK: event.topK,
|
|
177
|
+
presencePenalty: event.presencePenalty,
|
|
178
|
+
frequencyPenalty: event.frequencyPenalty,
|
|
179
|
+
seed: event.seed,
|
|
180
|
+
},
|
|
181
|
+
stepStates: new Map(),
|
|
182
|
+
};
|
|
183
|
+
callStates.set(callId, state);
|
|
184
|
+
return state;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const integration: Telemetry = {
|
|
188
|
+
onStart: async event => {
|
|
189
|
+
const operationId = (event as { operationId: string }).operationId;
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
operationId === 'ai.embed' ||
|
|
193
|
+
operationId === 'ai.embedMany' ||
|
|
194
|
+
operationId === 'ai.rerank'
|
|
195
|
+
) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const startEvent = event as (
|
|
200
|
+
| GenerateTextStartEvent<ToolSet>
|
|
201
|
+
| GenerateObjectStartEvent
|
|
202
|
+
) & {
|
|
203
|
+
functionId?: string | undefined;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const parentInfo = resolveParentInfo();
|
|
207
|
+
|
|
208
|
+
const state = getOrCreateCallState(
|
|
209
|
+
startEvent.callId,
|
|
210
|
+
operationId,
|
|
211
|
+
startEvent,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
await createRun(state.runId, parentInfo, state.functionId);
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
onStepStart: async event => {
|
|
218
|
+
const stepStartEvent = event as GenerateTextStepStartEvent<ToolSet> & {
|
|
219
|
+
promptMessages?: unknown[];
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const state = callStates.get(stepStartEvent.callId);
|
|
223
|
+
if (!state) return;
|
|
224
|
+
|
|
225
|
+
const stepId = crypto.randomUUID();
|
|
226
|
+
const startTime = Date.now();
|
|
227
|
+
|
|
228
|
+
const stepState: StepState = {
|
|
229
|
+
stepId,
|
|
230
|
+
startTime,
|
|
231
|
+
};
|
|
232
|
+
const stepNumber = stepStartEvent.steps.length;
|
|
233
|
+
state.stepStates.set(stepNumber, stepState);
|
|
234
|
+
activeSteps.set(stepId, stepState);
|
|
235
|
+
|
|
236
|
+
const prompt = stepStartEvent.promptMessages ?? stepStartEvent.messages;
|
|
237
|
+
|
|
238
|
+
await createStep({
|
|
239
|
+
id: stepId,
|
|
240
|
+
run_id: state.runId,
|
|
241
|
+
step_number: stepNumber + 1,
|
|
242
|
+
type: state.operationType,
|
|
243
|
+
model_id: stepStartEvent.modelId,
|
|
244
|
+
provider: stepStartEvent.provider ?? null,
|
|
245
|
+
started_at: new Date().toISOString(),
|
|
246
|
+
input: JSON.stringify({
|
|
247
|
+
prompt,
|
|
248
|
+
tools: stepStartEvent.tools
|
|
249
|
+
? Object.entries(stepStartEvent.tools).map(([name, tool]) => ({
|
|
250
|
+
name,
|
|
251
|
+
description: (tool as { description?: string }).description,
|
|
252
|
+
parameters: (tool as { parameters?: unknown }).parameters,
|
|
253
|
+
}))
|
|
254
|
+
: undefined,
|
|
255
|
+
toolChoice: stepStartEvent.toolChoice,
|
|
256
|
+
maxOutputTokens: state.settings.maxOutputTokens,
|
|
257
|
+
temperature: state.settings.temperature,
|
|
258
|
+
topP: state.settings.topP,
|
|
259
|
+
topK: state.settings.topK,
|
|
260
|
+
presencePenalty: state.settings.presencePenalty,
|
|
261
|
+
frequencyPenalty: state.settings.frequencyPenalty,
|
|
262
|
+
seed: state.settings.seed,
|
|
263
|
+
}),
|
|
264
|
+
provider_options: stepStartEvent.providerOptions
|
|
265
|
+
? JSON.stringify(stepStartEvent.providerOptions)
|
|
266
|
+
: null,
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
onObjectStepStart: async event => {
|
|
271
|
+
const stepStartEvent = event as GenerateObjectStepStartEvent & {
|
|
272
|
+
promptMessages?: unknown[];
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const state = callStates.get(stepStartEvent.callId);
|
|
276
|
+
if (!state) return;
|
|
277
|
+
|
|
278
|
+
const stepId = crypto.randomUUID();
|
|
279
|
+
const startTime = Date.now();
|
|
280
|
+
|
|
281
|
+
const stepState: StepState = {
|
|
282
|
+
stepId,
|
|
283
|
+
startTime,
|
|
284
|
+
};
|
|
285
|
+
state.stepStates.set(stepStartEvent.stepNumber, stepState);
|
|
286
|
+
activeSteps.set(stepId, stepState);
|
|
287
|
+
|
|
288
|
+
await createStep({
|
|
289
|
+
id: stepId,
|
|
290
|
+
run_id: state.runId,
|
|
291
|
+
step_number: stepStartEvent.stepNumber + 1,
|
|
292
|
+
type: state.operationType,
|
|
293
|
+
model_id: stepStartEvent.modelId,
|
|
294
|
+
provider: stepStartEvent.provider ?? null,
|
|
295
|
+
started_at: new Date().toISOString(),
|
|
296
|
+
input: JSON.stringify({
|
|
297
|
+
prompt: stepStartEvent.promptMessages,
|
|
298
|
+
maxOutputTokens: state.settings.maxOutputTokens,
|
|
299
|
+
temperature: state.settings.temperature,
|
|
300
|
+
topP: state.settings.topP,
|
|
301
|
+
topK: state.settings.topK,
|
|
302
|
+
presencePenalty: state.settings.presencePenalty,
|
|
303
|
+
frequencyPenalty: state.settings.frequencyPenalty,
|
|
304
|
+
seed: state.settings.seed,
|
|
305
|
+
}),
|
|
306
|
+
provider_options: stepStartEvent.providerOptions
|
|
307
|
+
? JSON.stringify(stepStartEvent.providerOptions)
|
|
308
|
+
: null,
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
onStepEnd: async event => {
|
|
313
|
+
const stepResult = event as GenerateTextStepEndEvent<ToolSet>;
|
|
314
|
+
|
|
315
|
+
const state = callStates.get(stepResult.callId);
|
|
316
|
+
if (!state) return;
|
|
317
|
+
|
|
318
|
+
const stepState = state.stepStates.get(stepResult.stepNumber);
|
|
319
|
+
if (!stepState) return;
|
|
320
|
+
|
|
321
|
+
activeSteps.delete(stepState.stepId);
|
|
322
|
+
|
|
323
|
+
const durationMs = Date.now() - stepState.startTime;
|
|
324
|
+
|
|
325
|
+
const output = {
|
|
326
|
+
content: stepResult.content,
|
|
327
|
+
finishReason: stepResult.finishReason,
|
|
328
|
+
response: {
|
|
329
|
+
id: stepResult.response.id,
|
|
330
|
+
modelId: stepResult.response.modelId,
|
|
331
|
+
timestamp: stepResult.response.timestamp,
|
|
332
|
+
messages: stepResult.response.messages,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
await updateStepResult(stepState.stepId, {
|
|
337
|
+
duration_ms: durationMs,
|
|
338
|
+
output: JSON.stringify(output),
|
|
339
|
+
usage: stepResult.usage ? JSON.stringify(stepResult.usage) : null,
|
|
340
|
+
error: null,
|
|
341
|
+
raw_request: stepResult.request?.body
|
|
342
|
+
? JSON.stringify(stepResult.request.body)
|
|
343
|
+
: null,
|
|
344
|
+
raw_response: stepResult.response?.body
|
|
345
|
+
? JSON.stringify(stepResult.response.body)
|
|
346
|
+
: null,
|
|
347
|
+
raw_chunks: null,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
state.stepStates.delete(stepResult.stepNumber);
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
onObjectStepEnd: async event => {
|
|
354
|
+
const stepResult = event as GenerateObjectStepEndEvent;
|
|
355
|
+
|
|
356
|
+
const state = callStates.get(stepResult.callId);
|
|
357
|
+
if (!state) return;
|
|
358
|
+
|
|
359
|
+
const stepState = state.stepStates.get(stepResult.stepNumber);
|
|
360
|
+
if (!stepState) return;
|
|
361
|
+
|
|
362
|
+
activeSteps.delete(stepState.stepId);
|
|
363
|
+
|
|
364
|
+
const durationMs = Date.now() - stepState.startTime;
|
|
365
|
+
|
|
366
|
+
const output = {
|
|
367
|
+
finishReason: stepResult.finishReason,
|
|
368
|
+
objectText: stepResult.objectText,
|
|
369
|
+
response: {
|
|
370
|
+
id: stepResult.response.id,
|
|
371
|
+
modelId: stepResult.response.modelId,
|
|
372
|
+
timestamp: stepResult.response.timestamp,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
await updateStepResult(stepState.stepId, {
|
|
377
|
+
duration_ms: durationMs,
|
|
378
|
+
output: JSON.stringify(output),
|
|
379
|
+
usage: stepResult.usage ? JSON.stringify(stepResult.usage) : null,
|
|
380
|
+
error: null,
|
|
381
|
+
raw_request: stepResult.request?.body
|
|
382
|
+
? JSON.stringify(stepResult.request.body)
|
|
383
|
+
: null,
|
|
384
|
+
raw_response: stepResult.response?.body
|
|
385
|
+
? JSON.stringify(stepResult.response.body)
|
|
386
|
+
: null,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
state.stepStates.delete(stepResult.stepNumber);
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
onEnd: async event => {
|
|
393
|
+
const endEvent = event as { callId: string };
|
|
394
|
+
callStates.delete(endEvent.callId);
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
onError: async error => {
|
|
398
|
+
const errorObj = error as
|
|
399
|
+
| { callId?: string; error?: unknown }
|
|
400
|
+
| undefined;
|
|
401
|
+
const callId = errorObj?.callId;
|
|
402
|
+
if (!callId) return;
|
|
403
|
+
|
|
404
|
+
const state = callStates.get(callId);
|
|
405
|
+
if (!state) return;
|
|
406
|
+
|
|
407
|
+
const cause = errorObj?.error ?? error;
|
|
408
|
+
const errorMessage =
|
|
409
|
+
cause instanceof Error ? cause.message : String(cause);
|
|
410
|
+
|
|
411
|
+
for (const [, stepState] of state.stepStates) {
|
|
412
|
+
activeSteps.delete(stepState.stepId);
|
|
413
|
+
const durationMs = Date.now() - stepState.startTime;
|
|
414
|
+
await updateStepResult(stepState.stepId, {
|
|
415
|
+
duration_ms: durationMs,
|
|
416
|
+
output: null,
|
|
417
|
+
usage: null,
|
|
418
|
+
error: errorMessage,
|
|
419
|
+
raw_request: null,
|
|
420
|
+
raw_response: null,
|
|
421
|
+
raw_chunks: null,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
callStates.delete(callId);
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
executeTool: async ({ callId, toolCallId, execute }) => {
|
|
429
|
+
toolContextMap.set(toolCallId, {
|
|
430
|
+
parentCallId: callId,
|
|
431
|
+
parentToolCallId: toolCallId,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const previousToolCallId = currentToolCallId;
|
|
435
|
+
currentToolCallId = toolCallId;
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
return await execute();
|
|
439
|
+
} finally {
|
|
440
|
+
currentToolCallId = previousToolCallId;
|
|
441
|
+
toolContextMap.delete(toolCallId);
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
return integration;
|
|
447
|
+
}
|
package/src/middleware.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type {
|
|
2
|
+
LanguageModelV4FinishReason,
|
|
3
|
+
LanguageModelV4Usage,
|
|
4
|
+
LanguageModelV4Middleware,
|
|
5
|
+
LanguageModelV4StreamPart,
|
|
6
6
|
} from '@ai-sdk/provider';
|
|
7
7
|
import {
|
|
8
8
|
createRun,
|
|
@@ -97,7 +97,7 @@ const generateRunId = (): string => {
|
|
|
97
97
|
* });
|
|
98
98
|
* ```
|
|
99
99
|
*/
|
|
100
|
-
export const devToolsMiddleware = ():
|
|
100
|
+
export const devToolsMiddleware = (): LanguageModelV4Middleware => {
|
|
101
101
|
if (process.env.NODE_ENV === 'production') {
|
|
102
102
|
throw new Error(
|
|
103
103
|
'@ai-sdk/devtools should not be used in production. ' +
|
|
@@ -125,7 +125,7 @@ export const devToolsMiddleware = (): LanguageModelV3Middleware => {
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
return {
|
|
128
|
-
specificationVersion: '
|
|
128
|
+
specificationVersion: 'v4',
|
|
129
129
|
|
|
130
130
|
wrapGenerate: async ({ doGenerate, params, model }) => {
|
|
131
131
|
const startTime = Date.now();
|
|
@@ -242,9 +242,9 @@ export const devToolsMiddleware = (): LanguageModelV3Middleware => {
|
|
|
242
242
|
const collectedOutput: {
|
|
243
243
|
textParts: Array<{ id: string; text: string }>;
|
|
244
244
|
reasoningParts: Array<{ id: string; text: string }>;
|
|
245
|
-
toolCalls:
|
|
246
|
-
finishReason?:
|
|
247
|
-
usage?:
|
|
245
|
+
toolCalls: LanguageModelV4StreamPart[];
|
|
246
|
+
finishReason?: LanguageModelV4FinishReason;
|
|
247
|
+
usage?: LanguageModelV4Usage;
|
|
248
248
|
} = {
|
|
249
249
|
textParts: [],
|
|
250
250
|
reasoningParts: [],
|
|
@@ -253,7 +253,7 @@ export const devToolsMiddleware = (): LanguageModelV3Middleware => {
|
|
|
253
253
|
|
|
254
254
|
const currentText: Map<string, string> = new Map();
|
|
255
255
|
const currentReasoning: Map<string, string> = new Map();
|
|
256
|
-
const fullStreamChunks:
|
|
256
|
+
const fullStreamChunks: LanguageModelV4StreamPart[] = [];
|
|
257
257
|
const rawChunks: unknown[] = [];
|
|
258
258
|
|
|
259
259
|
// Track this step for cleanup on process exit
|
|
@@ -266,8 +266,8 @@ export const devToolsMiddleware = (): LanguageModelV3Middleware => {
|
|
|
266
266
|
});
|
|
267
267
|
|
|
268
268
|
const transformStream = new TransformStream<
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
LanguageModelV4StreamPart,
|
|
270
|
+
LanguageModelV4StreamPart
|
|
271
271
|
>({
|
|
272
272
|
transform(chunk, controller) {
|
|
273
273
|
// Separate raw provider chunks from other stream chunks
|