@tanstack/ai-client 0.22.1 → 0.23.0
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 +15 -1
- package/dist/esm/audio-recorder.js +190 -213
- package/dist/esm/audio-recorder.js.map +1 -1
- package/dist/esm/chat-client.d.ts +172 -3
- package/dist/esm/chat-client.js +1656 -1386
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/cleared-stream-tracker.d.ts +23 -0
- package/dist/esm/cleared-stream-tracker.js +97 -0
- package/dist/esm/cleared-stream-tracker.js.map +1 -0
- package/dist/esm/client-persistor.d.ts +25 -12
- package/dist/esm/client-persistor.js +260 -235
- package/dist/esm/client-persistor.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +231 -10
- package/dist/esm/connection-adapters.js +989 -574
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +1 -0
- package/dist/esm/devtools-noop.js +79 -139
- package/dist/esm/devtools-noop.js.map +1 -1
- package/dist/esm/devtools.d.ts +31 -1
- package/dist/esm/devtools.js +977 -1127
- package/dist/esm/devtools.js.map +1 -1
- package/dist/esm/events.js +224 -226
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.d.ts +145 -2
- package/dist/esm/generation-client.js +659 -321
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-reconstruct.d.ts +21 -0
- package/dist/esm/generation-reconstruct.js +85 -0
- package/dist/esm/generation-reconstruct.js.map +1 -0
- package/dist/esm/generation-types.d.ts +289 -3
- package/dist/esm/generation-types.js +356 -13
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +9 -4
- package/dist/esm/index.js +7 -39
- package/dist/esm/interrupt-manager.d.ts +77 -0
- package/dist/esm/interrupt-manager.js +787 -0
- package/dist/esm/interrupt-manager.js.map +1 -0
- package/dist/esm/mcp-app-bridge.js +56 -64
- package/dist/esm/mcp-app-bridge.js.map +1 -1
- package/dist/esm/realtime-client.js +366 -440
- package/dist/esm/realtime-client.js.map +1 -1
- package/dist/esm/response-stream.js +19 -26
- package/dist/esm/response-stream.js.map +1 -1
- package/dist/esm/sse-parser.js +44 -47
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/sse-utils.js +8 -9
- package/dist/esm/sse-utils.js.map +1 -1
- package/dist/esm/storage-adapters.d.ts +62 -0
- package/dist/esm/storage-adapters.js +174 -0
- package/dist/esm/storage-adapters.js.map +1 -0
- package/dist/esm/types.d.ts +212 -10
- package/dist/esm/types.js +38 -7
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +113 -2
- package/dist/esm/video-generation-client.js +665 -379
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +7 -7
- package/src/chat-client.ts +1079 -61
- package/src/cleared-stream-tracker.ts +151 -0
- package/src/client-persistor.ts +102 -33
- package/src/connection-adapters.ts +1185 -142
- package/src/devtools-noop.ts +4 -3
- package/src/devtools.ts +121 -3
- package/src/generation-client.ts +563 -13
- package/src/generation-reconstruct.ts +121 -0
- package/src/generation-types.ts +727 -3
- package/src/index.ts +56 -1
- package/src/interrupt-manager.ts +1440 -0
- package/src/storage-adapters.ts +242 -0
- package/src/types.ts +301 -9
- package/src/video-generation-client.ts +479 -13
- package/dist/esm/index.js.map +0 -1
package/dist/esm/devtools.js
CHANGED
|
@@ -1,1178 +1,1028 @@
|
|
|
1
|
-
import { emitAIDevtoolsEvent, aiEventClient, createAIDevtoolsEventEnvelope } from "@tanstack/ai-event-client";
|
|
2
|
-
import { convertSchemaToJsonSchema } from "@tanstack/ai/client";
|
|
3
1
|
import { DefaultChatClientEventEmitter } from "./events.js";
|
|
2
|
+
import { convertSchemaToJsonSchema } from "@tanstack/ai/client";
|
|
3
|
+
import { aiEventClient, createAIDevtoolsEventEnvelope, emitAIDevtoolsEvent } from "@tanstack/ai-event-client";
|
|
4
|
+
//#region src/devtools.ts
|
|
4
5
|
function createAIDevtoolsGenerationPreview(input) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (input.outputKind === "text") {
|
|
15
|
-
return textPreviewFromResult(input.result);
|
|
16
|
-
}
|
|
17
|
-
if (input.result === null || input.result === void 0) {
|
|
18
|
-
return { kind: "empty" };
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
kind: "structured",
|
|
22
|
-
value: input.result
|
|
23
|
-
};
|
|
6
|
+
if (input.outputKind === "image") return imagePreviewFromResult(input.result);
|
|
7
|
+
if (input.outputKind === "audio") return audioPreviewFromResult(input.result);
|
|
8
|
+
if (input.outputKind === "video") return videoPreviewFromResult(input.result, input.videoStatus);
|
|
9
|
+
if (input.outputKind === "text") return textPreviewFromResult(input.result);
|
|
10
|
+
if (input.result === null || input.result === void 0) return { kind: "empty" };
|
|
11
|
+
return {
|
|
12
|
+
kind: "structured",
|
|
13
|
+
value: input.result
|
|
14
|
+
};
|
|
24
15
|
}
|
|
25
16
|
function imagePreviewFromResult(result) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
17
|
+
const record = asRecord(result);
|
|
18
|
+
const items = (Array.isArray(record?.images) ? record.images : []).map((image) => mediaItemFromSource(image, "image/png")).filter(isGenerationMediaItem);
|
|
19
|
+
if (items.length === 0 && result !== null && result !== void 0) {
|
|
20
|
+
const directItem = mediaItemFromSource(result, "image/png");
|
|
21
|
+
if (directItem) items.push(directItem);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
kind: "image",
|
|
25
|
+
items
|
|
26
|
+
};
|
|
36
27
|
}
|
|
37
28
|
function audioPreviewFromResult(result) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { kind: "audio", items };
|
|
29
|
+
const record = asRecord(result);
|
|
30
|
+
const audio = record?.audio;
|
|
31
|
+
const resultContentType = stringField(record, "contentType");
|
|
32
|
+
const format = stringField(record, "format");
|
|
33
|
+
const mimeType = resultContentType ?? mimeTypeFromAudioFormat(format);
|
|
34
|
+
const items = [];
|
|
35
|
+
const directItem = typeof audio === "string" ? base64MediaItem(audio, mimeType, {
|
|
36
|
+
format,
|
|
37
|
+
duration: numberField(record, "duration")
|
|
38
|
+
}) : mediaItemFromSource(audio, mimeType, { format });
|
|
39
|
+
if (directItem) items.push(directItem);
|
|
40
|
+
return {
|
|
41
|
+
kind: "audio",
|
|
42
|
+
items
|
|
43
|
+
};
|
|
54
44
|
}
|
|
55
45
|
function videoPreviewFromResult(result, videoStatus) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
const resultRecord = asRecord(result);
|
|
47
|
+
const statusRecord = asRecord(videoStatus);
|
|
48
|
+
const item = mediaItemFromSource(result, "video/mp4") ?? mediaItemFromSource(videoStatus, "video/mp4");
|
|
49
|
+
const items = item ? [item] : [];
|
|
50
|
+
const job = videoJobFromStatus(statusRecord ?? resultRecord);
|
|
51
|
+
return {
|
|
52
|
+
kind: "video",
|
|
53
|
+
items,
|
|
54
|
+
...job ? { job } : {}
|
|
55
|
+
};
|
|
66
56
|
}
|
|
67
57
|
function textPreviewFromResult(result) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
58
|
+
const record = asRecord(result);
|
|
59
|
+
const text = stringField(record, "text") ?? stringField(record, "summary") ?? stringField(record, "content") ?? (typeof result === "string" ? result : void 0);
|
|
60
|
+
if (text !== void 0) return {
|
|
61
|
+
kind: "text",
|
|
62
|
+
text
|
|
63
|
+
};
|
|
64
|
+
if (result === null || result === void 0) return { kind: "empty" };
|
|
65
|
+
return {
|
|
66
|
+
kind: "structured",
|
|
67
|
+
value: result
|
|
68
|
+
};
|
|
80
69
|
}
|
|
81
70
|
function videoJobFromStatus(record) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
const jobId = stringField(record, "jobId");
|
|
72
|
+
if (!jobId) return void 0;
|
|
73
|
+
return {
|
|
74
|
+
jobId,
|
|
75
|
+
...stringField(record, "status") ? { status: stringField(record, "status") } : {},
|
|
76
|
+
...numberField(record, "progress") !== void 0 ? { progress: numberField(record, "progress") } : {},
|
|
77
|
+
...stringField(record, "error") ? { error: stringField(record, "error") } : {}
|
|
78
|
+
};
|
|
90
79
|
}
|
|
91
80
|
function mediaItemFromSource(value, defaultMimeType, extras = {}) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
duration
|
|
112
|
-
});
|
|
81
|
+
const record = asRecord(value);
|
|
82
|
+
if (!record) return void 0;
|
|
83
|
+
const explicitContentType = stringField(record, "contentType") ?? stringField(record, "mimeType");
|
|
84
|
+
const duration = numberField(record, "duration") ?? extras.duration;
|
|
85
|
+
const format = stringField(record, "format") ?? extras.format;
|
|
86
|
+
const url = stringField(record, "url");
|
|
87
|
+
if (url) return {
|
|
88
|
+
src: url,
|
|
89
|
+
sourceType: "url",
|
|
90
|
+
...explicitContentType ? { mimeType: explicitContentType } : {},
|
|
91
|
+
...format ? { format } : {},
|
|
92
|
+
...duration !== void 0 ? { duration } : {}
|
|
93
|
+
};
|
|
94
|
+
const b64Json = stringField(record, "b64Json");
|
|
95
|
+
if (!b64Json) return void 0;
|
|
96
|
+
return base64MediaItem(b64Json, explicitContentType ?? defaultMimeType, {
|
|
97
|
+
format,
|
|
98
|
+
duration
|
|
99
|
+
});
|
|
113
100
|
}
|
|
114
101
|
function base64MediaItem(value, mimeType, extras = {}) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
102
|
+
return {
|
|
103
|
+
src: value.startsWith("data:") ? value : `data:${mimeType ?? "application/octet-stream"};base64,${value}`,
|
|
104
|
+
sourceType: "base64",
|
|
105
|
+
...mimeType ? { mimeType } : {},
|
|
106
|
+
...extras.format ? { format: extras.format } : {},
|
|
107
|
+
...extras.duration !== void 0 ? { duration: extras.duration } : {}
|
|
108
|
+
};
|
|
123
109
|
}
|
|
124
110
|
function mimeTypeFromAudioFormat(format) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
111
|
+
if (!format) return "audio/mpeg";
|
|
112
|
+
if (format === "mp3") return "audio/mpeg";
|
|
113
|
+
return `audio/${format}`;
|
|
128
114
|
}
|
|
129
115
|
function asRecord(value) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
return value;
|
|
116
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
117
|
+
return value;
|
|
134
118
|
}
|
|
135
119
|
function stringField(record, field) {
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
const value = record?.[field];
|
|
121
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
138
122
|
}
|
|
139
123
|
function numberField(record, field) {
|
|
140
|
-
|
|
141
|
-
|
|
124
|
+
const value = record?.[field];
|
|
125
|
+
return typeof value === "number" ? value : void 0;
|
|
142
126
|
}
|
|
143
127
|
function isGenerationMediaItem(value) {
|
|
144
|
-
|
|
128
|
+
return Boolean(value);
|
|
145
129
|
}
|
|
146
|
-
|
|
147
|
-
"tanstack.ai.devtools.activeBridgeByHookId"
|
|
148
|
-
);
|
|
130
|
+
var activeBridgeRegistryKey = Symbol.for("tanstack.ai.devtools.activeBridgeByHookId");
|
|
149
131
|
function getActiveBridgeRegistry() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
132
|
+
const global = globalThis;
|
|
133
|
+
const existing = global[activeBridgeRegistryKey];
|
|
134
|
+
if (existing) return existing;
|
|
135
|
+
const registry = /* @__PURE__ */ new Map();
|
|
136
|
+
global[activeBridgeRegistryKey] = registry;
|
|
137
|
+
return registry;
|
|
156
138
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
this.emitToolsRegistered();
|
|
323
|
-
this.emitSnapshot();
|
|
324
|
-
}
|
|
325
|
-
async handleToolFixtureApply(event) {
|
|
326
|
-
const fixture = event.payload;
|
|
327
|
-
if (!this.matchesFixtureTarget(fixture)) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
await this.options.applyToolFixture?.(fixture);
|
|
331
|
-
}
|
|
332
|
-
matchesFixtureTarget(fixture) {
|
|
333
|
-
if (!fixture.hookId && !fixture.threadId) {
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
if (fixture.hookId) {
|
|
337
|
-
return fixture.hookId === this.options.hookId;
|
|
338
|
-
}
|
|
339
|
-
if (fixture.threadId && (!this.options.threadId || fixture.threadId !== this.options.threadId)) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
createEnvelope(eventType, visibility = "client-state", context = {}) {
|
|
345
|
-
return createAIDevtoolsEventEnvelope({
|
|
346
|
-
eventType,
|
|
347
|
-
source: "client",
|
|
348
|
-
visibility,
|
|
349
|
-
clientId: this.options.clientId,
|
|
350
|
-
hookId: this.options.hookId,
|
|
351
|
-
correlationId: this.bridgeId,
|
|
352
|
-
...this.options.threadId ? { threadId: this.options.threadId } : {},
|
|
353
|
-
...context.runId ? { runId: context.runId } : {},
|
|
354
|
-
timestamp: Date.now()
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
createMetadataPayload() {
|
|
358
|
-
return {
|
|
359
|
-
hookId: this.options.hookId,
|
|
360
|
-
hookName: this.options.metadata.hookName,
|
|
361
|
-
...this.options.metadata.name ? { displayName: this.options.metadata.name } : {},
|
|
362
|
-
...this.options.metadata.outputKind ? { outputKind: this.options.metadata.outputKind } : {},
|
|
363
|
-
...this.options.metadata.framework ? { framework: this.options.metadata.framework } : {}
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
let bridgeIdSequence = 0;
|
|
139
|
+
var ClientDevtoolsBridge = class {
|
|
140
|
+
options;
|
|
141
|
+
bridgeId;
|
|
142
|
+
unsubscribers = [];
|
|
143
|
+
disposed = false;
|
|
144
|
+
superseded = false;
|
|
145
|
+
registered = false;
|
|
146
|
+
constructor(options) {
|
|
147
|
+
this.options = options;
|
|
148
|
+
this.bridgeId = createBridgeId(options.hookId);
|
|
149
|
+
}
|
|
150
|
+
emitRegistered() {
|
|
151
|
+
if (!this.prepareForMountEmit()) return;
|
|
152
|
+
this.registered = true;
|
|
153
|
+
emitAIDevtoolsEvent("hook:registered", {
|
|
154
|
+
...this.createEnvelope("hook:registered"),
|
|
155
|
+
...this.createMetadataPayload(),
|
|
156
|
+
lifecycle: "mounted"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
emitUpdated() {
|
|
160
|
+
if (!this.prepareForEmit()) return;
|
|
161
|
+
emitAIDevtoolsEvent("hook:updated", {
|
|
162
|
+
...this.createEnvelope("hook:updated"),
|
|
163
|
+
...this.createMetadataPayload(),
|
|
164
|
+
lifecycle: "active"
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
emitSnapshot() {
|
|
168
|
+
if (!this.prepareForEmit()) return;
|
|
169
|
+
emitAIDevtoolsEvent("hook:state-snapshot", {
|
|
170
|
+
...this.createEnvelope("hook:state-snapshot"),
|
|
171
|
+
...this.createMetadataPayload(),
|
|
172
|
+
state: this.options.getSnapshot()
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
emitToolsRegistered() {
|
|
176
|
+
if (!this.prepareForEmit()) return;
|
|
177
|
+
const tools = this.options.getTools ? Array.from(this.options.getTools()).map((tool) => ({
|
|
178
|
+
name: tool.name,
|
|
179
|
+
description: tool.description,
|
|
180
|
+
inputSchema: tool.inputSchema ? convertSchemaToJsonSchema(tool.inputSchema) : { type: "object" },
|
|
181
|
+
outputSchema: tool.outputSchema ? convertSchemaToJsonSchema(tool.outputSchema) : void 0,
|
|
182
|
+
needsApproval: tool.needsApproval,
|
|
183
|
+
metadata: tool.metadata
|
|
184
|
+
})) : [];
|
|
185
|
+
emitAIDevtoolsEvent("tools:registered", {
|
|
186
|
+
...this.createEnvelope("tools:registered"),
|
|
187
|
+
...this.createMetadataPayload(),
|
|
188
|
+
tools
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
emitRunLifecycle(eventType, runId, status, options = {}) {
|
|
192
|
+
if (!this.prepareForEmit()) return;
|
|
193
|
+
emitAIDevtoolsEvent(eventType, {
|
|
194
|
+
...this.createEnvelope(eventType, "client-state", { runId }),
|
|
195
|
+
runId,
|
|
196
|
+
status,
|
|
197
|
+
...options.error ? { error: options.error } : {}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
deactivate() {
|
|
201
|
+
const activeBridgeByHookId = getActiveBridgeRegistry();
|
|
202
|
+
if (activeBridgeByHookId.get(this.options.hookId) === this) activeBridgeByHookId.delete(this.options.hookId);
|
|
203
|
+
for (const unsubscribe of this.unsubscribers.splice(0)) unsubscribe();
|
|
204
|
+
}
|
|
205
|
+
supersede() {
|
|
206
|
+
if (this.disposed) return;
|
|
207
|
+
this.superseded = true;
|
|
208
|
+
this.disposed = true;
|
|
209
|
+
this.deactivate();
|
|
210
|
+
}
|
|
211
|
+
dispose() {
|
|
212
|
+
if (this.disposed) return;
|
|
213
|
+
this.disposed = true;
|
|
214
|
+
if (!this.registered) {
|
|
215
|
+
this.deactivate();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
emitAIDevtoolsEvent("hook:unregistered", {
|
|
219
|
+
...this.createEnvelope("hook:unregistered"),
|
|
220
|
+
...this.createMetadataPayload(),
|
|
221
|
+
reason: "disposed"
|
|
222
|
+
});
|
|
223
|
+
this.deactivate();
|
|
224
|
+
}
|
|
225
|
+
prepareForEmit() {
|
|
226
|
+
if (this.disposed || this.superseded) return false;
|
|
227
|
+
this.activate();
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
prepareForMountEmit() {
|
|
231
|
+
if (this.superseded) return false;
|
|
232
|
+
if (this.disposed) {
|
|
233
|
+
this.disposed = false;
|
|
234
|
+
this.registered = false;
|
|
235
|
+
}
|
|
236
|
+
this.activate();
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
activate() {
|
|
240
|
+
if (this.disposed) return;
|
|
241
|
+
const activeBridgeByHookId = getActiveBridgeRegistry();
|
|
242
|
+
const activeBridge = activeBridgeByHookId.get(this.options.hookId);
|
|
243
|
+
if (activeBridge && activeBridge !== this) if (typeof activeBridge.supersede === "function") activeBridge.supersede();
|
|
244
|
+
else activeBridge.deactivate();
|
|
245
|
+
activeBridgeByHookId.set(this.options.hookId, this);
|
|
246
|
+
if (this.unsubscribers.length > 0) return;
|
|
247
|
+
this.unsubscribers.push(aiEventClient.on("devtools:request-state", (event) => {
|
|
248
|
+
this.handleRequestState(event);
|
|
249
|
+
}));
|
|
250
|
+
if (this.options.applyToolFixture) this.unsubscribers.push(aiEventClient.on("devtools:tool-fixture:apply", (event) => {
|
|
251
|
+
this.handleToolFixtureApply(event);
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
handleRequestState(event) {
|
|
255
|
+
if (this.disposed || this.superseded) return;
|
|
256
|
+
const targetHookId = event.payload.targetHookId;
|
|
257
|
+
if (targetHookId && targetHookId !== this.options.hookId) return;
|
|
258
|
+
this.emitRegistered();
|
|
259
|
+
this.emitToolsRegistered();
|
|
260
|
+
this.emitSnapshot();
|
|
261
|
+
this.onReplayState();
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Extension hook for subclasses to replay any additional cached state on a
|
|
265
|
+
* `devtools:request-state` (i.e. when a panel opens). Called only after the
|
|
266
|
+
* base guards (disposed/superseded/targetHookId) pass. No-op by default.
|
|
267
|
+
*/
|
|
268
|
+
onReplayState() {}
|
|
269
|
+
async handleToolFixtureApply(event) {
|
|
270
|
+
const fixture = event.payload;
|
|
271
|
+
if (!this.matchesFixtureTarget(fixture)) return;
|
|
272
|
+
await this.options.applyToolFixture?.(fixture);
|
|
273
|
+
}
|
|
274
|
+
matchesFixtureTarget(fixture) {
|
|
275
|
+
if (!fixture.hookId && !fixture.threadId) return false;
|
|
276
|
+
if (fixture.hookId) return fixture.hookId === this.options.hookId;
|
|
277
|
+
if (fixture.threadId && (!this.options.threadId || fixture.threadId !== this.options.threadId)) return false;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
createEnvelope(eventType, visibility = "client-state", context = {}) {
|
|
281
|
+
return createAIDevtoolsEventEnvelope({
|
|
282
|
+
eventType,
|
|
283
|
+
source: "client",
|
|
284
|
+
visibility,
|
|
285
|
+
clientId: this.options.clientId,
|
|
286
|
+
hookId: this.options.hookId,
|
|
287
|
+
correlationId: this.bridgeId,
|
|
288
|
+
...this.options.threadId ? { threadId: this.options.threadId } : {},
|
|
289
|
+
...context.runId ? { runId: context.runId } : {},
|
|
290
|
+
timestamp: Date.now()
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
createMetadataPayload() {
|
|
294
|
+
return {
|
|
295
|
+
hookId: this.options.hookId,
|
|
296
|
+
hookName: this.options.metadata.hookName,
|
|
297
|
+
...this.options.metadata.name ? { displayName: this.options.metadata.name } : {},
|
|
298
|
+
...this.options.metadata.outputKind ? { outputKind: this.options.metadata.outputKind } : {},
|
|
299
|
+
...this.options.metadata.framework ? { framework: this.options.metadata.framework } : {}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
var bridgeIdSequence = 0;
|
|
368
304
|
function createBridgeId(hookId) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
bridgeIdSequence += 1;
|
|
374
|
-
return `bridge:${hookId}:${bridgeIdSequence}`;
|
|
375
|
-
}
|
|
376
|
-
class ChatDevtoolsBridge extends ClientDevtoolsBridge {
|
|
377
|
-
events;
|
|
378
|
-
chatOptions;
|
|
379
|
-
currentRunId = null;
|
|
380
|
-
currentRunThreadId = null;
|
|
381
|
-
currentStreamId = null;
|
|
382
|
-
lastStreamId = null;
|
|
383
|
-
lastRunEventContext;
|
|
384
|
-
constructor(options) {
|
|
385
|
-
super({
|
|
386
|
-
...options,
|
|
387
|
-
// Thunk defers `this.applyFixture` lookup until after `super` returns.
|
|
388
|
-
applyToolFixture: (fixture) => this.applyFixture(fixture)
|
|
389
|
-
});
|
|
390
|
-
this.chatOptions = options;
|
|
391
|
-
this.events = new ChatDevtoolsAwareEventEmitter(options.clientId, this);
|
|
392
|
-
}
|
|
393
|
-
// --- Stream / run context API -------------------------------------------
|
|
394
|
-
setCurrentStreamId(streamId) {
|
|
395
|
-
this.currentStreamId = streamId;
|
|
396
|
-
if (streamId) {
|
|
397
|
-
this.lastStreamId = streamId;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Called by the auto-attaching emitter every time it sees a non-empty
|
|
402
|
-
* streamId pass through. Lets devtools track the latest stream id
|
|
403
|
-
* without the chat client wiring it up explicitly.
|
|
404
|
-
*/
|
|
405
|
-
recordStreamId(streamId) {
|
|
406
|
-
if (streamId) this.lastStreamId = streamId;
|
|
407
|
-
}
|
|
408
|
-
mountWithTools(initialMessageCount) {
|
|
409
|
-
this.events.clientCreated(initialMessageCount);
|
|
410
|
-
this.emitRegistered();
|
|
411
|
-
this.emitToolsRegistered();
|
|
412
|
-
this.emitSnapshot();
|
|
413
|
-
}
|
|
414
|
-
notifyToolsChanged() {
|
|
415
|
-
this.emitToolsRegistered();
|
|
416
|
-
this.emitSnapshot();
|
|
417
|
-
}
|
|
418
|
-
getCurrentStreamId() {
|
|
419
|
-
return this.currentStreamId;
|
|
420
|
-
}
|
|
421
|
-
getLastStreamId() {
|
|
422
|
-
return this.lastStreamId;
|
|
423
|
-
}
|
|
424
|
-
resolveStreamId() {
|
|
425
|
-
return this.currentStreamId ?? this.lastStreamId ?? this.chatOptions.generateId("stream");
|
|
426
|
-
}
|
|
427
|
-
// Called when the chat client has just generated a runId for outbound emits;
|
|
428
|
-
// the matching RUN_STARTED chunk from the adapter lands later and
|
|
429
|
-
// observeChunk keeps the same context.
|
|
430
|
-
beginRun(runId, threadId) {
|
|
431
|
-
this.currentRunId = runId;
|
|
432
|
-
this.currentRunThreadId = threadId;
|
|
433
|
-
this.lastRunEventContext = { runId, threadId };
|
|
434
|
-
}
|
|
435
|
-
observeChunk(chunk) {
|
|
436
|
-
if (chunk.type === "RUN_STARTED") {
|
|
437
|
-
this.beginRun(chunk.runId, chunk.threadId);
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") {
|
|
441
|
-
const runId = chunk.type === "RUN_FINISHED" ? chunk.runId : chunk.runId;
|
|
442
|
-
if (!runId || runId === this.currentRunId) {
|
|
443
|
-
const context = this.getCurrentRunEventContext();
|
|
444
|
-
if (context) {
|
|
445
|
-
this.lastRunEventContext = context;
|
|
446
|
-
}
|
|
447
|
-
this.currentRunId = null;
|
|
448
|
-
this.currentRunThreadId = null;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
getCurrentRunEventContext() {
|
|
453
|
-
if (!this.currentRunId) return void 0;
|
|
454
|
-
return {
|
|
455
|
-
threadId: this.currentRunThreadId ?? this.chatOptions.threadId ?? "",
|
|
456
|
-
runId: this.currentRunId
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
getCurrentOrLastRunEventContext() {
|
|
460
|
-
return this.getCurrentRunEventContext() ?? this.lastRunEventContext;
|
|
461
|
-
}
|
|
462
|
-
findToolCallContext(toolCallId) {
|
|
463
|
-
const base = { toolCallId };
|
|
464
|
-
const runContext = this.getCurrentRunEventContext();
|
|
465
|
-
if (runContext) {
|
|
466
|
-
return {
|
|
467
|
-
threadId: runContext.threadId,
|
|
468
|
-
runId: runContext.runId,
|
|
469
|
-
toolCallId
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
if (this.chatOptions.threadId) {
|
|
473
|
-
return { threadId: this.chatOptions.threadId, toolCallId };
|
|
474
|
-
}
|
|
475
|
-
return base;
|
|
476
|
-
}
|
|
477
|
-
// --- Fixture replay ------------------------------------------------------
|
|
478
|
-
/**
|
|
479
|
-
* Entry point invoked when the devtools panel emits
|
|
480
|
-
* `devtools:tool-fixture:apply`. The chat client never calls this
|
|
481
|
-
* directly; it is wired through the base bridge's fixture subscription.
|
|
482
|
-
*/
|
|
483
|
-
async applyFixture(fixture) {
|
|
484
|
-
const messages = this.chatOptions.getMessages();
|
|
485
|
-
const threadId = fixture.threadId ?? this.chatOptions.threadId ?? "";
|
|
486
|
-
if (fixture.execute) {
|
|
487
|
-
await this.executeFixture(fixture, messages, threadId);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
const replay = this.createReplayMessageFromFixture(fixture, messages);
|
|
491
|
-
const { message, toolCallId } = replay;
|
|
492
|
-
const messageId = message.id;
|
|
493
|
-
this.events.messageAppended(message, void 0, {
|
|
494
|
-
threadId,
|
|
495
|
-
toolCallId,
|
|
496
|
-
...fixture.runId ? { runId: fixture.runId } : {}
|
|
497
|
-
});
|
|
498
|
-
this.chatOptions.setMessages([...messages, message]);
|
|
499
|
-
this.events.toolFixtureApplied({
|
|
500
|
-
hookId: this.chatOptions.hookId,
|
|
501
|
-
threadId,
|
|
502
|
-
...fixture.runId ? { runId: fixture.runId } : {},
|
|
503
|
-
toolName: fixture.toolName,
|
|
504
|
-
input: fixture.input,
|
|
505
|
-
output: fixture.output,
|
|
506
|
-
messageId,
|
|
507
|
-
toolCallId,
|
|
508
|
-
...fixture.execute !== void 0 ? { execute: fixture.execute } : {},
|
|
509
|
-
...fixture.message ? { message: fixture.message } : {},
|
|
510
|
-
...fixture.errorText ? { errorText: fixture.errorText } : {}
|
|
511
|
-
});
|
|
512
|
-
this.emitSnapshot();
|
|
513
|
-
}
|
|
514
|
-
async executeFixture(fixture, messages, threadId) {
|
|
515
|
-
const toolCallId = this.resolveFixtureToolCallId(
|
|
516
|
-
fixture.toolCallId,
|
|
517
|
-
messages
|
|
518
|
-
);
|
|
519
|
-
const messageId = this.resolveFixtureMessageId(fixture.messageId, messages);
|
|
520
|
-
const message = {
|
|
521
|
-
id: messageId,
|
|
522
|
-
role: "assistant",
|
|
523
|
-
parts: [
|
|
524
|
-
{
|
|
525
|
-
type: "tool-call",
|
|
526
|
-
id: toolCallId,
|
|
527
|
-
name: fixture.toolName,
|
|
528
|
-
arguments: stringifyFixtureValue(fixture.input),
|
|
529
|
-
input: fixture.input,
|
|
530
|
-
state: "input-complete"
|
|
531
|
-
}
|
|
532
|
-
],
|
|
533
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
534
|
-
};
|
|
535
|
-
this.events.messageAppended(message, void 0, {
|
|
536
|
-
threadId,
|
|
537
|
-
toolCallId,
|
|
538
|
-
...fixture.runId ? { runId: fixture.runId } : {}
|
|
539
|
-
});
|
|
540
|
-
this.chatOptions.setMessages([...messages, message]);
|
|
541
|
-
this.emitSnapshot();
|
|
542
|
-
const clientTool = this.findClientTool(fixture.toolName);
|
|
543
|
-
const executeFunc = clientTool?.execute;
|
|
544
|
-
if (!executeFunc) {
|
|
545
|
-
console.warn(
|
|
546
|
-
`[ai-devtools] tool fixture "${fixture.toolName}" requested execute=true but no client tool implementation is registered; replaying saved output instead.`
|
|
547
|
-
);
|
|
548
|
-
this.addToolResultForFixture({
|
|
549
|
-
fixture,
|
|
550
|
-
messageId,
|
|
551
|
-
toolCallId,
|
|
552
|
-
threadId,
|
|
553
|
-
output: fixture.output,
|
|
554
|
-
errorText: fixture.errorText
|
|
555
|
-
});
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
let output;
|
|
559
|
-
try {
|
|
560
|
-
output = await executeFunc(fixture.input);
|
|
561
|
-
} catch (error) {
|
|
562
|
-
console.error(
|
|
563
|
-
`[ai-devtools] tool fixture "${fixture.toolName}" execute threw`,
|
|
564
|
-
error
|
|
565
|
-
);
|
|
566
|
-
this.addToolResultForFixture({
|
|
567
|
-
fixture,
|
|
568
|
-
messageId,
|
|
569
|
-
toolCallId,
|
|
570
|
-
threadId,
|
|
571
|
-
output: null,
|
|
572
|
-
errorText: error instanceof Error ? `${error.name}: ${error.message}` : `Tool execution failed: ${String(error)}`
|
|
573
|
-
});
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
this.addToolResultForFixture({
|
|
577
|
-
fixture,
|
|
578
|
-
messageId,
|
|
579
|
-
toolCallId,
|
|
580
|
-
threadId,
|
|
581
|
-
output
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
addToolResultForFixture(input) {
|
|
585
|
-
const state = input.errorText ? "output-error" : "output-available";
|
|
586
|
-
this.events.toolResultAdded(
|
|
587
|
-
input.toolCallId,
|
|
588
|
-
input.fixture.toolName,
|
|
589
|
-
input.output,
|
|
590
|
-
state,
|
|
591
|
-
{
|
|
592
|
-
threadId: input.threadId,
|
|
593
|
-
...input.fixture.runId ? { runId: input.fixture.runId } : {},
|
|
594
|
-
toolCallId: input.toolCallId
|
|
595
|
-
}
|
|
596
|
-
);
|
|
597
|
-
this.chatOptions.addToolResult(
|
|
598
|
-
input.toolCallId,
|
|
599
|
-
input.output,
|
|
600
|
-
input.errorText
|
|
601
|
-
);
|
|
602
|
-
this.events.toolFixtureApplied({
|
|
603
|
-
hookId: this.chatOptions.hookId,
|
|
604
|
-
threadId: input.threadId,
|
|
605
|
-
...input.fixture.runId ? { runId: input.fixture.runId } : {},
|
|
606
|
-
toolName: input.fixture.toolName,
|
|
607
|
-
input: input.fixture.input,
|
|
608
|
-
output: input.output,
|
|
609
|
-
execute: true,
|
|
610
|
-
messageId: input.messageId,
|
|
611
|
-
toolCallId: input.toolCallId,
|
|
612
|
-
...input.errorText ? { errorText: input.errorText } : {}
|
|
613
|
-
});
|
|
614
|
-
this.emitSnapshot();
|
|
615
|
-
}
|
|
616
|
-
createReplayMessageFromFixture(fixture, messages) {
|
|
617
|
-
const cloned = this.cloneFixtureSourceMessage(fixture, messages);
|
|
618
|
-
if (cloned) return cloned;
|
|
619
|
-
const toolCallId = this.resolveFixtureToolCallId(
|
|
620
|
-
fixture.toolCallId,
|
|
621
|
-
messages
|
|
622
|
-
);
|
|
623
|
-
const messageId = this.resolveFixtureMessageId(fixture.messageId, messages);
|
|
624
|
-
const state = fixture.errorText ? "error" : "complete";
|
|
625
|
-
return {
|
|
626
|
-
toolCallId,
|
|
627
|
-
message: {
|
|
628
|
-
id: messageId,
|
|
629
|
-
role: "assistant",
|
|
630
|
-
parts: [
|
|
631
|
-
{
|
|
632
|
-
type: "tool-call",
|
|
633
|
-
id: toolCallId,
|
|
634
|
-
name: fixture.toolName,
|
|
635
|
-
arguments: stringifyFixtureValue(fixture.input),
|
|
636
|
-
input: fixture.input,
|
|
637
|
-
state: "input-complete",
|
|
638
|
-
output: fixture.output
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
type: "tool-result",
|
|
642
|
-
toolCallId,
|
|
643
|
-
content: stringifyFixtureValue(fixture.output),
|
|
644
|
-
state,
|
|
645
|
-
...fixture.errorText ? { error: fixture.errorText } : {}
|
|
646
|
-
}
|
|
647
|
-
],
|
|
648
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
cloneFixtureSourceMessage(fixture, messages) {
|
|
653
|
-
const sourceMessage = fixture.message;
|
|
654
|
-
if (!sourceMessage || !Array.isArray(sourceMessage.parts)) {
|
|
655
|
-
return void 0;
|
|
656
|
-
}
|
|
657
|
-
const toolCallIds = this.createFixtureToolCallIdMap(
|
|
658
|
-
sourceMessage.parts,
|
|
659
|
-
messages
|
|
660
|
-
);
|
|
661
|
-
const parts = sourceMessage.parts.map((part) => cloneFixtureMessagePart(part, toolCallIds)).filter((part) => Boolean(part));
|
|
662
|
-
const mappedFixtureToolCallId = fixture.toolCallId ? toolCallIds.get(fixture.toolCallId) : void 0;
|
|
663
|
-
hydrateToolCallOutputs(parts, {
|
|
664
|
-
...mappedFixtureToolCallId ? { mappedToolCallId: mappedFixtureToolCallId } : {},
|
|
665
|
-
output: fixture.output
|
|
666
|
-
});
|
|
667
|
-
if (parts.length === 0) return void 0;
|
|
668
|
-
const toolCallId = (fixture.toolCallId ? toolCallIds.get(fixture.toolCallId) : void 0) ?? firstToolCallId(parts);
|
|
669
|
-
if (!toolCallId) return void 0;
|
|
670
|
-
return {
|
|
671
|
-
toolCallId,
|
|
672
|
-
message: {
|
|
673
|
-
id: this.resolveFixtureMessageId(sourceMessage.id, messages),
|
|
674
|
-
role: sourceMessage.role,
|
|
675
|
-
parts,
|
|
676
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
createFixtureToolCallIdMap(parts, messages) {
|
|
681
|
-
const ids = /* @__PURE__ */ new Map();
|
|
682
|
-
for (const part of parts) {
|
|
683
|
-
if (!isRecord(part) || part.type !== "tool-call") continue;
|
|
684
|
-
if (typeof part.id !== "string") continue;
|
|
685
|
-
ids.set(part.id, this.resolveFixtureToolCallId(part.id, messages));
|
|
686
|
-
}
|
|
687
|
-
return ids;
|
|
688
|
-
}
|
|
689
|
-
resolveFixtureMessageId(messageId, messages) {
|
|
690
|
-
if (messageId && !messages.some((message) => message.id === messageId)) {
|
|
691
|
-
return messageId;
|
|
692
|
-
}
|
|
693
|
-
return this.chatOptions.generateId("fixture-msg");
|
|
694
|
-
}
|
|
695
|
-
resolveFixtureToolCallId(toolCallId, messages) {
|
|
696
|
-
if (toolCallId && !hasToolCallId(messages, toolCallId)) {
|
|
697
|
-
return toolCallId;
|
|
698
|
-
}
|
|
699
|
-
return this.chatOptions.generateId("fixture-tool-call");
|
|
700
|
-
}
|
|
701
|
-
findClientTool(name) {
|
|
702
|
-
const tools = this.chatOptions.getTools?.();
|
|
703
|
-
if (!tools) return void 0;
|
|
704
|
-
for (const tool of tools) {
|
|
705
|
-
if (tool.name === name) return tool;
|
|
706
|
-
}
|
|
707
|
-
return void 0;
|
|
708
|
-
}
|
|
305
|
+
const cryptoLike = globalThis.crypto;
|
|
306
|
+
if (cryptoLike?.randomUUID) return `bridge:${hookId}:${cryptoLike.randomUUID()}`;
|
|
307
|
+
bridgeIdSequence += 1;
|
|
308
|
+
return `bridge:${hookId}:${bridgeIdSequence}`;
|
|
709
309
|
}
|
|
310
|
+
var ChatDevtoolsBridge = class extends ClientDevtoolsBridge {
|
|
311
|
+
events;
|
|
312
|
+
chatOptions;
|
|
313
|
+
currentRunId = null;
|
|
314
|
+
currentRunThreadId = null;
|
|
315
|
+
currentStreamId = null;
|
|
316
|
+
lastStreamId = null;
|
|
317
|
+
lastRunEventContext;
|
|
318
|
+
/** Last transported `memory:state` value, replayed when a panel opens. */
|
|
319
|
+
lastMemoryStateValue = null;
|
|
320
|
+
constructor(options) {
|
|
321
|
+
super({
|
|
322
|
+
...options,
|
|
323
|
+
applyToolFixture: (fixture) => this.applyFixture(fixture)
|
|
324
|
+
});
|
|
325
|
+
this.chatOptions = options;
|
|
326
|
+
this.events = new ChatDevtoolsAwareEventEmitter(options.clientId, this);
|
|
327
|
+
}
|
|
328
|
+
setCurrentStreamId(streamId) {
|
|
329
|
+
this.currentStreamId = streamId;
|
|
330
|
+
if (streamId) this.lastStreamId = streamId;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Called by the auto-attaching emitter every time it sees a non-empty
|
|
334
|
+
* streamId pass through. Lets devtools track the latest stream id
|
|
335
|
+
* without the chat client wiring it up explicitly.
|
|
336
|
+
*/
|
|
337
|
+
recordStreamId(streamId) {
|
|
338
|
+
if (streamId) this.lastStreamId = streamId;
|
|
339
|
+
}
|
|
340
|
+
mountWithTools(initialMessageCount) {
|
|
341
|
+
this.events.clientCreated(initialMessageCount);
|
|
342
|
+
this.emitRegistered();
|
|
343
|
+
this.emitToolsRegistered();
|
|
344
|
+
this.emitSnapshot();
|
|
345
|
+
}
|
|
346
|
+
notifyToolsChanged() {
|
|
347
|
+
this.emitToolsRegistered();
|
|
348
|
+
this.emitSnapshot();
|
|
349
|
+
}
|
|
350
|
+
getCurrentStreamId() {
|
|
351
|
+
return this.currentStreamId;
|
|
352
|
+
}
|
|
353
|
+
getLastStreamId() {
|
|
354
|
+
return this.lastStreamId;
|
|
355
|
+
}
|
|
356
|
+
resolveStreamId() {
|
|
357
|
+
return this.currentStreamId ?? this.lastStreamId ?? this.chatOptions.generateId("stream");
|
|
358
|
+
}
|
|
359
|
+
beginRun(runId, threadId) {
|
|
360
|
+
this.currentRunId = runId;
|
|
361
|
+
this.currentRunThreadId = threadId;
|
|
362
|
+
this.lastRunEventContext = {
|
|
363
|
+
runId,
|
|
364
|
+
threadId
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
observeChunk(chunk) {
|
|
368
|
+
if (chunk.type === "RUN_STARTED") {
|
|
369
|
+
this.beginRun(chunk.runId, chunk.threadId);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") {
|
|
373
|
+
const runId = chunk.type === "RUN_FINISHED" ? chunk.runId : chunk.runId;
|
|
374
|
+
if (!runId || runId === this.currentRunId) {
|
|
375
|
+
const context = this.getCurrentRunEventContext();
|
|
376
|
+
if (context) this.lastRunEventContext = context;
|
|
377
|
+
this.currentRunId = null;
|
|
378
|
+
this.currentRunThreadId = null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Record a transported `memory:state` value (the server memory middleware's
|
|
384
|
+
* per-turn recall metrics + store snapshot). Called from the chat client's
|
|
385
|
+
* `onCustomEvent` handler — the designated path for CUSTOM stream events —
|
|
386
|
+
* NOT from `observeChunk`. Re-emits the browser `memory:*` events the devtools
|
|
387
|
+
* store consumes, and caches the value so a panel opened later can replay it
|
|
388
|
+
* (see {@link onReplayState}). Symmetric with the generation bridge's
|
|
389
|
+
* `recordResultChange` / `recordProgressChange`.
|
|
390
|
+
*/
|
|
391
|
+
recordMemoryState(rawValue) {
|
|
392
|
+
this.lastMemoryStateValue = rawValue;
|
|
393
|
+
this.emitMemoryState(rawValue);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Re-emit the browser-side `memory:*` events from a transported
|
|
397
|
+
* `memory:state` value. The devtools store consumes these to render the
|
|
398
|
+
* Memory tab (operations timeline + live contents). The recall pair mirrors
|
|
399
|
+
* the server middleware's own emits; the snapshot is included only when the
|
|
400
|
+
* adapter supports introspection.
|
|
401
|
+
*/
|
|
402
|
+
emitMemoryState(rawValue) {
|
|
403
|
+
if (!rawValue || typeof rawValue !== "object") return;
|
|
404
|
+
const value = rawValue;
|
|
405
|
+
const scope = value.scope;
|
|
406
|
+
const adapter = value.adapter;
|
|
407
|
+
if (!scope || typeof adapter !== "string") return;
|
|
408
|
+
const runContext = this.currentRunId ? { runId: this.currentRunId } : {};
|
|
409
|
+
emitAIDevtoolsEvent("memory:retrieve:started", {
|
|
410
|
+
...this.createEnvelope("memory:retrieve:started", "client-state", runContext),
|
|
411
|
+
scope,
|
|
412
|
+
adapter,
|
|
413
|
+
query: value.query ?? ""
|
|
414
|
+
});
|
|
415
|
+
if (value.recall) emitAIDevtoolsEvent("memory:retrieve:completed", {
|
|
416
|
+
...this.createEnvelope("memory:retrieve:completed", "client-state", runContext),
|
|
417
|
+
scope,
|
|
418
|
+
adapter,
|
|
419
|
+
fragmentCount: value.recall.fragmentCount ?? 0,
|
|
420
|
+
hasTools: Boolean(value.recall.hasTools),
|
|
421
|
+
systemPromptChars: value.recall.systemPromptChars ?? 0,
|
|
422
|
+
durationMs: value.recall.durationMs ?? 0
|
|
423
|
+
});
|
|
424
|
+
if (value.snapshot) emitAIDevtoolsEvent("memory:snapshot", {
|
|
425
|
+
...this.createEnvelope("memory:snapshot", "client-state", runContext),
|
|
426
|
+
scope,
|
|
427
|
+
adapter,
|
|
428
|
+
takenAt: value.snapshot.takenAt,
|
|
429
|
+
data: value.snapshot.data,
|
|
430
|
+
facts: value.snapshot.facts ?? []
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
onReplayState() {
|
|
434
|
+
if (this.lastMemoryStateValue != null) this.emitMemoryState(this.lastMemoryStateValue);
|
|
435
|
+
}
|
|
436
|
+
getCurrentRunEventContext() {
|
|
437
|
+
if (!this.currentRunId) return void 0;
|
|
438
|
+
return {
|
|
439
|
+
threadId: this.currentRunThreadId ?? this.chatOptions.threadId ?? "",
|
|
440
|
+
runId: this.currentRunId
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
getCurrentOrLastRunEventContext() {
|
|
444
|
+
return this.getCurrentRunEventContext() ?? this.lastRunEventContext;
|
|
445
|
+
}
|
|
446
|
+
findToolCallContext(toolCallId) {
|
|
447
|
+
const base = { toolCallId };
|
|
448
|
+
const runContext = this.getCurrentRunEventContext();
|
|
449
|
+
if (runContext) return {
|
|
450
|
+
threadId: runContext.threadId,
|
|
451
|
+
runId: runContext.runId,
|
|
452
|
+
toolCallId
|
|
453
|
+
};
|
|
454
|
+
if (this.chatOptions.threadId) return {
|
|
455
|
+
threadId: this.chatOptions.threadId,
|
|
456
|
+
toolCallId
|
|
457
|
+
};
|
|
458
|
+
return base;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Entry point invoked when the devtools panel emits
|
|
462
|
+
* `devtools:tool-fixture:apply`. The chat client never calls this
|
|
463
|
+
* directly; it is wired through the base bridge's fixture subscription.
|
|
464
|
+
*/
|
|
465
|
+
async applyFixture(fixture) {
|
|
466
|
+
const messages = this.chatOptions.getMessages();
|
|
467
|
+
const threadId = fixture.threadId ?? this.chatOptions.threadId ?? "";
|
|
468
|
+
if (fixture.execute) {
|
|
469
|
+
await this.executeFixture(fixture, messages, threadId);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const { message, toolCallId } = this.createReplayMessageFromFixture(fixture, messages);
|
|
473
|
+
const messageId = message.id;
|
|
474
|
+
this.events.messageAppended(message, void 0, {
|
|
475
|
+
threadId,
|
|
476
|
+
toolCallId,
|
|
477
|
+
...fixture.runId ? { runId: fixture.runId } : {}
|
|
478
|
+
});
|
|
479
|
+
this.chatOptions.setMessages([...messages, message]);
|
|
480
|
+
this.events.toolFixtureApplied({
|
|
481
|
+
hookId: this.chatOptions.hookId,
|
|
482
|
+
threadId,
|
|
483
|
+
...fixture.runId ? { runId: fixture.runId } : {},
|
|
484
|
+
toolName: fixture.toolName,
|
|
485
|
+
input: fixture.input,
|
|
486
|
+
output: fixture.output,
|
|
487
|
+
messageId,
|
|
488
|
+
toolCallId,
|
|
489
|
+
...fixture.execute !== void 0 ? { execute: fixture.execute } : {},
|
|
490
|
+
...fixture.message ? { message: fixture.message } : {},
|
|
491
|
+
...fixture.errorText ? { errorText: fixture.errorText } : {}
|
|
492
|
+
});
|
|
493
|
+
this.emitSnapshot();
|
|
494
|
+
}
|
|
495
|
+
async executeFixture(fixture, messages, threadId) {
|
|
496
|
+
const toolCallId = this.resolveFixtureToolCallId(fixture.toolCallId, messages);
|
|
497
|
+
const messageId = this.resolveFixtureMessageId(fixture.messageId, messages);
|
|
498
|
+
const message = {
|
|
499
|
+
id: messageId,
|
|
500
|
+
role: "assistant",
|
|
501
|
+
parts: [{
|
|
502
|
+
type: "tool-call",
|
|
503
|
+
id: toolCallId,
|
|
504
|
+
name: fixture.toolName,
|
|
505
|
+
arguments: stringifyFixtureValue(fixture.input),
|
|
506
|
+
input: fixture.input,
|
|
507
|
+
state: "input-complete"
|
|
508
|
+
}],
|
|
509
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
510
|
+
};
|
|
511
|
+
this.events.messageAppended(message, void 0, {
|
|
512
|
+
threadId,
|
|
513
|
+
toolCallId,
|
|
514
|
+
...fixture.runId ? { runId: fixture.runId } : {}
|
|
515
|
+
});
|
|
516
|
+
this.chatOptions.setMessages([...messages, message]);
|
|
517
|
+
this.emitSnapshot();
|
|
518
|
+
const executeFunc = this.findClientTool(fixture.toolName)?.execute;
|
|
519
|
+
if (!executeFunc) {
|
|
520
|
+
console.warn(`[ai-devtools] tool fixture "${fixture.toolName}" requested execute=true but no client tool implementation is registered; replaying saved output instead.`);
|
|
521
|
+
this.addToolResultForFixture({
|
|
522
|
+
fixture,
|
|
523
|
+
messageId,
|
|
524
|
+
toolCallId,
|
|
525
|
+
threadId,
|
|
526
|
+
output: fixture.output,
|
|
527
|
+
errorText: fixture.errorText
|
|
528
|
+
});
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
let output;
|
|
532
|
+
try {
|
|
533
|
+
output = await executeFunc(fixture.input);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error(`[ai-devtools] tool fixture "${fixture.toolName}" execute threw`, error);
|
|
536
|
+
this.addToolResultForFixture({
|
|
537
|
+
fixture,
|
|
538
|
+
messageId,
|
|
539
|
+
toolCallId,
|
|
540
|
+
threadId,
|
|
541
|
+
output: null,
|
|
542
|
+
errorText: error instanceof Error ? `${error.name}: ${error.message}` : `Tool execution failed: ${String(error)}`
|
|
543
|
+
});
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.addToolResultForFixture({
|
|
547
|
+
fixture,
|
|
548
|
+
messageId,
|
|
549
|
+
toolCallId,
|
|
550
|
+
threadId,
|
|
551
|
+
output
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
addToolResultForFixture(input) {
|
|
555
|
+
const state = input.errorText ? "output-error" : "output-available";
|
|
556
|
+
this.events.toolResultAdded(input.toolCallId, input.fixture.toolName, input.output, state, {
|
|
557
|
+
threadId: input.threadId,
|
|
558
|
+
...input.fixture.runId ? { runId: input.fixture.runId } : {},
|
|
559
|
+
toolCallId: input.toolCallId
|
|
560
|
+
});
|
|
561
|
+
this.chatOptions.addToolResult(input.toolCallId, input.output, input.errorText);
|
|
562
|
+
this.events.toolFixtureApplied({
|
|
563
|
+
hookId: this.chatOptions.hookId,
|
|
564
|
+
threadId: input.threadId,
|
|
565
|
+
...input.fixture.runId ? { runId: input.fixture.runId } : {},
|
|
566
|
+
toolName: input.fixture.toolName,
|
|
567
|
+
input: input.fixture.input,
|
|
568
|
+
output: input.output,
|
|
569
|
+
execute: true,
|
|
570
|
+
messageId: input.messageId,
|
|
571
|
+
toolCallId: input.toolCallId,
|
|
572
|
+
...input.errorText ? { errorText: input.errorText } : {}
|
|
573
|
+
});
|
|
574
|
+
this.emitSnapshot();
|
|
575
|
+
}
|
|
576
|
+
createReplayMessageFromFixture(fixture, messages) {
|
|
577
|
+
const cloned = this.cloneFixtureSourceMessage(fixture, messages);
|
|
578
|
+
if (cloned) return cloned;
|
|
579
|
+
const toolCallId = this.resolveFixtureToolCallId(fixture.toolCallId, messages);
|
|
580
|
+
const messageId = this.resolveFixtureMessageId(fixture.messageId, messages);
|
|
581
|
+
const state = fixture.errorText ? "error" : "complete";
|
|
582
|
+
return {
|
|
583
|
+
toolCallId,
|
|
584
|
+
message: {
|
|
585
|
+
id: messageId,
|
|
586
|
+
role: "assistant",
|
|
587
|
+
parts: [{
|
|
588
|
+
type: "tool-call",
|
|
589
|
+
id: toolCallId,
|
|
590
|
+
name: fixture.toolName,
|
|
591
|
+
arguments: stringifyFixtureValue(fixture.input),
|
|
592
|
+
input: fixture.input,
|
|
593
|
+
state: "input-complete",
|
|
594
|
+
output: fixture.output
|
|
595
|
+
}, {
|
|
596
|
+
type: "tool-result",
|
|
597
|
+
toolCallId,
|
|
598
|
+
content: stringifyFixtureValue(fixture.output),
|
|
599
|
+
state,
|
|
600
|
+
...fixture.errorText ? { error: fixture.errorText } : {}
|
|
601
|
+
}],
|
|
602
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
cloneFixtureSourceMessage(fixture, messages) {
|
|
607
|
+
const sourceMessage = fixture.message;
|
|
608
|
+
if (!sourceMessage || !Array.isArray(sourceMessage.parts)) return;
|
|
609
|
+
const toolCallIds = this.createFixtureToolCallIdMap(sourceMessage.parts, messages);
|
|
610
|
+
const parts = sourceMessage.parts.map((part) => cloneFixtureMessagePart(part, toolCallIds)).filter((part) => Boolean(part));
|
|
611
|
+
const mappedFixtureToolCallId = fixture.toolCallId ? toolCallIds.get(fixture.toolCallId) : void 0;
|
|
612
|
+
hydrateToolCallOutputs(parts, {
|
|
613
|
+
...mappedFixtureToolCallId ? { mappedToolCallId: mappedFixtureToolCallId } : {},
|
|
614
|
+
output: fixture.output
|
|
615
|
+
});
|
|
616
|
+
if (parts.length === 0) return void 0;
|
|
617
|
+
const toolCallId = (fixture.toolCallId ? toolCallIds.get(fixture.toolCallId) : void 0) ?? firstToolCallId(parts);
|
|
618
|
+
if (!toolCallId) return void 0;
|
|
619
|
+
return {
|
|
620
|
+
toolCallId,
|
|
621
|
+
message: {
|
|
622
|
+
id: this.resolveFixtureMessageId(sourceMessage.id, messages),
|
|
623
|
+
role: sourceMessage.role,
|
|
624
|
+
parts,
|
|
625
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
createFixtureToolCallIdMap(parts, messages) {
|
|
630
|
+
const ids = /* @__PURE__ */ new Map();
|
|
631
|
+
for (const part of parts) {
|
|
632
|
+
if (!isRecord(part) || part.type !== "tool-call") continue;
|
|
633
|
+
if (typeof part.id !== "string") continue;
|
|
634
|
+
ids.set(part.id, this.resolveFixtureToolCallId(part.id, messages));
|
|
635
|
+
}
|
|
636
|
+
return ids;
|
|
637
|
+
}
|
|
638
|
+
resolveFixtureMessageId(messageId, messages) {
|
|
639
|
+
if (messageId && !messages.some((message) => message.id === messageId)) return messageId;
|
|
640
|
+
return this.chatOptions.generateId("fixture-msg");
|
|
641
|
+
}
|
|
642
|
+
resolveFixtureToolCallId(toolCallId, messages) {
|
|
643
|
+
if (toolCallId && !hasToolCallId(messages, toolCallId)) return toolCallId;
|
|
644
|
+
return this.chatOptions.generateId("fixture-tool-call");
|
|
645
|
+
}
|
|
646
|
+
findClientTool(name) {
|
|
647
|
+
const tools = this.chatOptions.getTools?.();
|
|
648
|
+
if (!tools) return void 0;
|
|
649
|
+
for (const tool of tools) if (tool.name === name) return tool;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
710
652
|
function isRecord(value) {
|
|
711
|
-
|
|
653
|
+
return typeof value === "object" && value !== null;
|
|
712
654
|
}
|
|
713
655
|
function stringifyFixtureValue(value) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
return String(value);
|
|
726
|
-
}
|
|
656
|
+
if (typeof value === "string") return value;
|
|
657
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") return String(value);
|
|
658
|
+
try {
|
|
659
|
+
return JSON.stringify(value);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error("[ai-devtools] failed to JSON.stringify fixture value; falling back to String(). Tool call arguments may be malformed.", {
|
|
662
|
+
error,
|
|
663
|
+
value
|
|
664
|
+
});
|
|
665
|
+
return String(value);
|
|
666
|
+
}
|
|
727
667
|
}
|
|
728
668
|
function parseFixtureResultContent(content) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
669
|
+
try {
|
|
670
|
+
return JSON.parse(content);
|
|
671
|
+
} catch (error) {
|
|
672
|
+
console.error("[ai-devtools] failed to JSON.parse fixture result content; replaying as raw string. Fixture payload may be corrupted.", {
|
|
673
|
+
error,
|
|
674
|
+
content
|
|
675
|
+
});
|
|
676
|
+
return content;
|
|
677
|
+
}
|
|
738
678
|
}
|
|
739
679
|
function cloneFixtureMessagePart(part, toolCallIds) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if (part.type === "tool-result" && typeof part.toolCallId === "string") {
|
|
746
|
-
cloned.toolCallId = toolCallIds.get(part.toolCallId) ?? part.toolCallId;
|
|
747
|
-
}
|
|
748
|
-
return cloned;
|
|
680
|
+
if (!isRecord(part) || typeof part.type !== "string") return void 0;
|
|
681
|
+
const cloned = { ...part };
|
|
682
|
+
if (part.type === "tool-call" && typeof part.id === "string") cloned.id = toolCallIds.get(part.id) ?? part.id;
|
|
683
|
+
if (part.type === "tool-result" && typeof part.toolCallId === "string") cloned.toolCallId = toolCallIds.get(part.toolCallId) ?? part.toolCallId;
|
|
684
|
+
return cloned;
|
|
749
685
|
}
|
|
750
686
|
function firstToolCallId(parts) {
|
|
751
|
-
|
|
752
|
-
|
|
687
|
+
const toolCall = parts.find((part) => part.type === "tool-call");
|
|
688
|
+
return toolCall?.type === "tool-call" ? toolCall.id : void 0;
|
|
753
689
|
}
|
|
754
690
|
function hydrateToolCallOutputs(parts, fixtureOutput) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
if (fixtureOutput.mappedToolCallId && fixtureOutput.output !== void 0) {
|
|
765
|
-
const toolCall = parts.find(
|
|
766
|
-
(candidate) => candidate.type === "tool-call" && candidate.id === fixtureOutput.mappedToolCallId && candidate.output === void 0
|
|
767
|
-
);
|
|
768
|
-
if (toolCall) {
|
|
769
|
-
toolCall.output = fixtureOutput.output;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
691
|
+
for (const part of parts) {
|
|
692
|
+
if (part.type !== "tool-result") continue;
|
|
693
|
+
const toolCall = parts.find((candidate) => candidate.type === "tool-call" && candidate.id === part.toolCallId && candidate.output === void 0);
|
|
694
|
+
if (toolCall) toolCall.output = Array.isArray(part.content) ? part.content : parseFixtureResultContent(part.content);
|
|
695
|
+
}
|
|
696
|
+
if (fixtureOutput.mappedToolCallId && fixtureOutput.output !== void 0) {
|
|
697
|
+
const toolCall = parts.find((candidate) => candidate.type === "tool-call" && candidate.id === fixtureOutput.mappedToolCallId && candidate.output === void 0);
|
|
698
|
+
if (toolCall) toolCall.output = fixtureOutput.output;
|
|
699
|
+
}
|
|
772
700
|
}
|
|
773
701
|
function hasToolCallId(messages, toolCallId) {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
})
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
class GenerationDevtoolsBridge extends ClientDevtoolsBridge {
|
|
783
|
-
activeRunId = null;
|
|
784
|
-
activeRunStarted = false;
|
|
785
|
-
devtoolsRuns = [];
|
|
786
|
-
maxRuns;
|
|
787
|
-
getCoreState;
|
|
788
|
-
constructor(options) {
|
|
789
|
-
super({
|
|
790
|
-
...options,
|
|
791
|
-
getSnapshot: () => this.buildSnapshot()
|
|
792
|
-
});
|
|
793
|
-
this.maxRuns = options.maxRuns ?? 20;
|
|
794
|
-
this.getCoreState = options.getCoreState;
|
|
795
|
-
}
|
|
796
|
-
// --- Run lifecycle (called by GenerationClient) -----------------------
|
|
797
|
-
beginRun(input) {
|
|
798
|
-
const runId = this.generateRunId();
|
|
799
|
-
this.activeRunId = runId;
|
|
800
|
-
this.activeRunStarted = false;
|
|
801
|
-
this.upsertRun(runId, {
|
|
802
|
-
input,
|
|
803
|
-
result: null,
|
|
804
|
-
preview: this.createPreview(null),
|
|
805
|
-
progress: null,
|
|
806
|
-
status: "generating",
|
|
807
|
-
isLoading: true,
|
|
808
|
-
clearError: true
|
|
809
|
-
});
|
|
810
|
-
return runId;
|
|
811
|
-
}
|
|
812
|
-
ensureRunStarted(runId) {
|
|
813
|
-
if (this.activeRunStarted && this.activeRunId === runId) return;
|
|
814
|
-
if (!this.activeRunStarted && this.activeRunId && this.activeRunId !== runId) {
|
|
815
|
-
this.renameRun(this.activeRunId, runId);
|
|
816
|
-
}
|
|
817
|
-
this.activeRunId = runId;
|
|
818
|
-
this.activeRunStarted = true;
|
|
819
|
-
this.upsertRun(runId, {
|
|
820
|
-
status: "generating",
|
|
821
|
-
isLoading: true,
|
|
822
|
-
clearError: true
|
|
823
|
-
});
|
|
824
|
-
this.emitRunLifecycle("run:started", runId, "started");
|
|
825
|
-
this.emitState();
|
|
826
|
-
}
|
|
827
|
-
finishRun(runId, eventType, status, error) {
|
|
828
|
-
this.ensureRunStarted(runId);
|
|
829
|
-
const completedAt = Date.now();
|
|
830
|
-
const completedProgress = status === "completed" ? this.completeProgress() : this.getProgress();
|
|
831
|
-
const runStatus = status === "completed" ? "success" : status === "errored" ? "error" : "cancelled";
|
|
832
|
-
this.upsertRun(runId, {
|
|
833
|
-
status: runStatus,
|
|
834
|
-
isLoading: false,
|
|
835
|
-
progress: completedProgress,
|
|
836
|
-
completedAt,
|
|
837
|
-
...error ? { error } : { clearError: true }
|
|
838
|
-
});
|
|
839
|
-
if (this.activeRunId === runId) {
|
|
840
|
-
this.activeRunId = null;
|
|
841
|
-
}
|
|
842
|
-
this.activeRunStarted = false;
|
|
843
|
-
this.emitRunLifecycle(eventType, runId, status, {
|
|
844
|
-
...error ? { error } : {}
|
|
845
|
-
});
|
|
846
|
-
this.emitState();
|
|
847
|
-
}
|
|
848
|
-
getActiveRunId() {
|
|
849
|
-
return this.activeRunId;
|
|
850
|
-
}
|
|
851
|
-
/** Clear all per-run history. Called when the client `reset()`s. */
|
|
852
|
-
resetRuns() {
|
|
853
|
-
this.activeRunId = null;
|
|
854
|
-
this.activeRunStarted = false;
|
|
855
|
-
this.devtoolsRuns = [];
|
|
856
|
-
}
|
|
857
|
-
/** Record state changes from the client and emit the matching snapshot. */
|
|
858
|
-
recordResultChange() {
|
|
859
|
-
this.updateActiveRun({
|
|
860
|
-
result: this.getCoreState().result,
|
|
861
|
-
preview: this.createPreview(this.getCoreState().result),
|
|
862
|
-
clearError: true
|
|
863
|
-
});
|
|
864
|
-
this.emitState();
|
|
865
|
-
}
|
|
866
|
-
recordLoadingChange() {
|
|
867
|
-
this.updateActiveRun({ isLoading: this.getCoreState().isLoading });
|
|
868
|
-
this.emitState();
|
|
869
|
-
}
|
|
870
|
-
recordErrorChange(error) {
|
|
871
|
-
this.updateActiveRun(
|
|
872
|
-
error ? { error: error.message } : { clearError: true }
|
|
873
|
-
);
|
|
874
|
-
this.emitState();
|
|
875
|
-
}
|
|
876
|
-
recordStatusChange(status) {
|
|
877
|
-
this.updateActiveRun({ status });
|
|
878
|
-
this.emitState();
|
|
879
|
-
}
|
|
880
|
-
recordProgressChange() {
|
|
881
|
-
this.updateActiveRun({ progress: this.getCoreState().progress });
|
|
882
|
-
this.emitState();
|
|
883
|
-
}
|
|
884
|
-
/** Emit the latest snapshot without touching run state. */
|
|
885
|
-
emitState() {
|
|
886
|
-
this.emitUpdated();
|
|
887
|
-
this.emitSnapshot();
|
|
888
|
-
}
|
|
889
|
-
// --- Internal ---------------------------------------------------------
|
|
890
|
-
buildSnapshot() {
|
|
891
|
-
const core = this.getCoreState();
|
|
892
|
-
return {
|
|
893
|
-
input: core.input,
|
|
894
|
-
result: core.result,
|
|
895
|
-
preview: this.createPreview(core.result),
|
|
896
|
-
progress: core.progress,
|
|
897
|
-
status: core.status,
|
|
898
|
-
isLoading: core.isLoading,
|
|
899
|
-
activeRunId: this.activeRunId,
|
|
900
|
-
runs: this.devtoolsRuns,
|
|
901
|
-
...core.error ? { error: core.error } : {}
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
updateActiveRun(patch) {
|
|
905
|
-
if (!this.activeRunId) return;
|
|
906
|
-
this.upsertRun(this.activeRunId, patch);
|
|
907
|
-
}
|
|
908
|
-
upsertRun(runId, patch) {
|
|
909
|
-
const now = Date.now();
|
|
910
|
-
const index = this.devtoolsRuns.findIndex((run) => run.id === runId);
|
|
911
|
-
const existing = index >= 0 ? this.devtoolsRuns[index] : void 0;
|
|
912
|
-
const next = existing ? { ...existing } : {
|
|
913
|
-
id: runId,
|
|
914
|
-
input: this.getCoreState().input,
|
|
915
|
-
result: null,
|
|
916
|
-
preview: this.createPreview(null),
|
|
917
|
-
progress: null,
|
|
918
|
-
status: "idle",
|
|
919
|
-
isLoading: false,
|
|
920
|
-
startedAt: now,
|
|
921
|
-
updatedAt: now
|
|
922
|
-
};
|
|
923
|
-
if ("input" in patch) next.input = patch.input ?? null;
|
|
924
|
-
if ("result" in patch) next.result = patch.result ?? null;
|
|
925
|
-
if (patch.preview) next.preview = patch.preview;
|
|
926
|
-
if ("progress" in patch) next.progress = patch.progress ?? null;
|
|
927
|
-
if (patch.status) next.status = patch.status;
|
|
928
|
-
if ("isLoading" in patch) next.isLoading = patch.isLoading === true;
|
|
929
|
-
if (patch.completedAt !== void 0) next.completedAt = patch.completedAt;
|
|
930
|
-
if (patch.clearError) delete next.error;
|
|
931
|
-
if (patch.error !== void 0) next.error = patch.error;
|
|
932
|
-
next.updatedAt = now;
|
|
933
|
-
if (index >= 0) {
|
|
934
|
-
this.devtoolsRuns = this.devtoolsRuns.map(
|
|
935
|
-
(run) => run.id === runId ? next : run
|
|
936
|
-
);
|
|
937
|
-
} else {
|
|
938
|
-
this.devtoolsRuns = [...this.devtoolsRuns, next];
|
|
939
|
-
}
|
|
940
|
-
if (this.devtoolsRuns.length > this.maxRuns) {
|
|
941
|
-
this.devtoolsRuns = this.devtoolsRuns.slice(-this.maxRuns);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
renameRun(previousRunId, nextRunId) {
|
|
945
|
-
if (previousRunId === nextRunId) return;
|
|
946
|
-
const existing = this.devtoolsRuns.find((run) => run.id === previousRunId);
|
|
947
|
-
if (!existing) return;
|
|
948
|
-
const renamed = { ...existing, id: nextRunId, updatedAt: Date.now() };
|
|
949
|
-
this.devtoolsRuns = this.devtoolsRuns.filter((run) => run.id !== nextRunId).map((run) => run.id === previousRunId ? renamed : run);
|
|
950
|
-
}
|
|
951
|
-
getProgress() {
|
|
952
|
-
return this.getCoreState().progress;
|
|
953
|
-
}
|
|
954
|
-
completeProgress() {
|
|
955
|
-
const progress = this.getCoreState().progress;
|
|
956
|
-
if (!progress) return null;
|
|
957
|
-
return {
|
|
958
|
-
value: 100,
|
|
959
|
-
...progress.message ? { message: progress.message } : {}
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
createPreview(result) {
|
|
963
|
-
return createAIDevtoolsGenerationPreview({
|
|
964
|
-
outputKind: this.options.metadata.outputKind,
|
|
965
|
-
result
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
generateRunId() {
|
|
969
|
-
return `run-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
class VideoDevtoolsBridge extends GenerationDevtoolsBridge {
|
|
973
|
-
constructor(options) {
|
|
974
|
-
super(options);
|
|
975
|
-
}
|
|
976
|
-
recordJobIdChange() {
|
|
977
|
-
this.updateActiveRun({
|
|
978
|
-
jobId: this.getCoreState().jobId
|
|
979
|
-
});
|
|
980
|
-
this.emitState();
|
|
981
|
-
}
|
|
982
|
-
recordVideoStatusChange() {
|
|
983
|
-
const core = this.getCoreState();
|
|
984
|
-
this.updateActiveRun({
|
|
985
|
-
videoStatus: core.videoStatus,
|
|
986
|
-
preview: this.createVideoPreview(core.result, core.videoStatus)
|
|
987
|
-
});
|
|
988
|
-
this.emitState();
|
|
989
|
-
}
|
|
990
|
-
buildSnapshot() {
|
|
991
|
-
const core = this.getCoreState();
|
|
992
|
-
return {
|
|
993
|
-
input: core.input,
|
|
994
|
-
result: core.result,
|
|
995
|
-
preview: this.createVideoPreview(core.result, core.videoStatus),
|
|
996
|
-
progress: core.progress,
|
|
997
|
-
status: core.status,
|
|
998
|
-
isLoading: core.isLoading,
|
|
999
|
-
activeRunId: this.activeRunId,
|
|
1000
|
-
runs: this.devtoolsRuns,
|
|
1001
|
-
jobId: core.jobId,
|
|
1002
|
-
videoStatus: core.videoStatus,
|
|
1003
|
-
...core.error ? { error: core.error } : {}
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
upsertRun(runId, patch) {
|
|
1007
|
-
super.upsertRun(runId, patch);
|
|
1008
|
-
if (!("jobId" in patch || "videoStatus" in patch)) return;
|
|
1009
|
-
const index = this.devtoolsRuns.findIndex((run) => run.id === runId);
|
|
1010
|
-
if (index < 0) return;
|
|
1011
|
-
const target = this.devtoolsRuns[index];
|
|
1012
|
-
if (!target) return;
|
|
1013
|
-
const merged = { ...target };
|
|
1014
|
-
if ("jobId" in patch) merged.jobId = patch.jobId ?? null;
|
|
1015
|
-
if ("videoStatus" in patch) merged.videoStatus = patch.videoStatus ?? null;
|
|
1016
|
-
this.devtoolsRuns = this.devtoolsRuns.map(
|
|
1017
|
-
(run) => run.id === runId ? merged : run
|
|
1018
|
-
);
|
|
1019
|
-
}
|
|
1020
|
-
// Override so record* methods inherited from GenerationDevtoolsBridge
|
|
1021
|
-
// (e.g. recordResultChange) thread the latest videoStatus into the preview.
|
|
1022
|
-
createPreview(result) {
|
|
1023
|
-
const core = this.getCoreState();
|
|
1024
|
-
return this.createVideoPreview(result, core.videoStatus);
|
|
1025
|
-
}
|
|
1026
|
-
createVideoPreview(result, videoStatus) {
|
|
1027
|
-
return createAIDevtoolsGenerationPreview({
|
|
1028
|
-
outputKind: this.options.metadata.outputKind,
|
|
1029
|
-
result,
|
|
1030
|
-
videoStatus
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
class ChatDevtoolsAwareEventEmitter extends DefaultChatClientEventEmitter {
|
|
1035
|
-
constructor(clientId, helper) {
|
|
1036
|
-
super(clientId);
|
|
1037
|
-
this.helper = helper;
|
|
1038
|
-
}
|
|
1039
|
-
helper;
|
|
1040
|
-
afterEmit(streamId) {
|
|
1041
|
-
if (streamId) this.helper.recordStreamId(streamId);
|
|
1042
|
-
this.helper.emitSnapshot();
|
|
1043
|
-
}
|
|
1044
|
-
// -- methods with run context --------------------------------------------
|
|
1045
|
-
textUpdated(streamId, messageId, content, context) {
|
|
1046
|
-
super.textUpdated(
|
|
1047
|
-
streamId,
|
|
1048
|
-
messageId,
|
|
1049
|
-
content,
|
|
1050
|
-
context ?? this.helper.getCurrentRunEventContext()
|
|
1051
|
-
);
|
|
1052
|
-
this.afterEmit(streamId);
|
|
1053
|
-
}
|
|
1054
|
-
thinkingUpdated(streamId, messageId, content, delta, context) {
|
|
1055
|
-
super.thinkingUpdated(
|
|
1056
|
-
streamId,
|
|
1057
|
-
messageId,
|
|
1058
|
-
content,
|
|
1059
|
-
delta,
|
|
1060
|
-
context ?? this.helper.getCurrentRunEventContext()
|
|
1061
|
-
);
|
|
1062
|
-
this.afterEmit(streamId);
|
|
1063
|
-
}
|
|
1064
|
-
messageAppended(uiMessage, streamId, context) {
|
|
1065
|
-
super.messageAppended(
|
|
1066
|
-
uiMessage,
|
|
1067
|
-
streamId,
|
|
1068
|
-
context ?? this.helper.getCurrentRunEventContext()
|
|
1069
|
-
);
|
|
1070
|
-
this.afterEmit(streamId);
|
|
1071
|
-
}
|
|
1072
|
-
toolCallStateChanged(streamId, messageId, toolCallId, toolName, state, args, context) {
|
|
1073
|
-
super.toolCallStateChanged(
|
|
1074
|
-
streamId,
|
|
1075
|
-
messageId,
|
|
1076
|
-
toolCallId,
|
|
1077
|
-
toolName,
|
|
1078
|
-
state,
|
|
1079
|
-
args,
|
|
1080
|
-
context ?? this.helper.getCurrentRunEventContext()
|
|
1081
|
-
);
|
|
1082
|
-
this.afterEmit(streamId);
|
|
1083
|
-
}
|
|
1084
|
-
structuredOutputChanged(eventName, streamId, messageId, output, context) {
|
|
1085
|
-
super.structuredOutputChanged(
|
|
1086
|
-
eventName,
|
|
1087
|
-
streamId,
|
|
1088
|
-
messageId,
|
|
1089
|
-
output,
|
|
1090
|
-
context ?? this.helper.getCurrentOrLastRunEventContext()
|
|
1091
|
-
);
|
|
1092
|
-
this.afterEmit(streamId);
|
|
1093
|
-
}
|
|
1094
|
-
approvalRequested(streamId, messageId, toolCallId, toolName, input, approvalId, context) {
|
|
1095
|
-
super.approvalRequested(
|
|
1096
|
-
streamId,
|
|
1097
|
-
messageId,
|
|
1098
|
-
toolCallId,
|
|
1099
|
-
toolName,
|
|
1100
|
-
input,
|
|
1101
|
-
approvalId,
|
|
1102
|
-
context ?? this.helper.getCurrentOrLastRunEventContext()
|
|
1103
|
-
);
|
|
1104
|
-
this.afterEmit(streamId);
|
|
1105
|
-
}
|
|
1106
|
-
toolResultAdded(toolCallId, toolName, output, state, context) {
|
|
1107
|
-
super.toolResultAdded(
|
|
1108
|
-
toolCallId,
|
|
1109
|
-
toolName,
|
|
1110
|
-
output,
|
|
1111
|
-
state,
|
|
1112
|
-
context ?? this.helper.getCurrentRunEventContext()
|
|
1113
|
-
);
|
|
1114
|
-
this.afterEmit();
|
|
1115
|
-
}
|
|
1116
|
-
toolApprovalResponded(approvalId, toolCallId, approved, context) {
|
|
1117
|
-
super.toolApprovalResponded(
|
|
1118
|
-
approvalId,
|
|
1119
|
-
toolCallId,
|
|
1120
|
-
approved,
|
|
1121
|
-
context ?? this.helper.getCurrentRunEventContext()
|
|
1122
|
-
);
|
|
1123
|
-
this.afterEmit();
|
|
1124
|
-
}
|
|
1125
|
-
// -- methods without context (just auto-emit snapshot) -------------------
|
|
1126
|
-
clientCreated(initialMessageCount) {
|
|
1127
|
-
super.clientCreated(initialMessageCount);
|
|
1128
|
-
this.afterEmit();
|
|
1129
|
-
}
|
|
1130
|
-
loadingChanged(isLoading) {
|
|
1131
|
-
super.loadingChanged(isLoading);
|
|
1132
|
-
this.afterEmit();
|
|
1133
|
-
}
|
|
1134
|
-
errorChanged(error) {
|
|
1135
|
-
super.errorChanged(error);
|
|
1136
|
-
this.afterEmit();
|
|
1137
|
-
}
|
|
1138
|
-
reloaded(fromMessageIndex) {
|
|
1139
|
-
super.reloaded(fromMessageIndex);
|
|
1140
|
-
this.afterEmit();
|
|
1141
|
-
}
|
|
1142
|
-
stopped() {
|
|
1143
|
-
super.stopped();
|
|
1144
|
-
this.afterEmit();
|
|
1145
|
-
}
|
|
1146
|
-
messagesCleared() {
|
|
1147
|
-
super.messagesCleared();
|
|
1148
|
-
this.afterEmit();
|
|
1149
|
-
}
|
|
1150
|
-
messageSent(messageId, content) {
|
|
1151
|
-
super.messageSent(messageId, content);
|
|
1152
|
-
this.afterEmit();
|
|
1153
|
-
}
|
|
1154
|
-
toolFixtureApplied(fixture) {
|
|
1155
|
-
super.toolFixtureApplied(fixture);
|
|
1156
|
-
this.afterEmit();
|
|
1157
|
-
}
|
|
702
|
+
return messages.some((message) => message.parts.some((part) => {
|
|
703
|
+
if (part.type === "tool-call") return part.id === toolCallId;
|
|
704
|
+
if (part.type === "tool-result") return part.toolCallId === toolCallId;
|
|
705
|
+
return false;
|
|
706
|
+
}));
|
|
1158
707
|
}
|
|
708
|
+
var GenerationDevtoolsBridge = class extends ClientDevtoolsBridge {
|
|
709
|
+
activeRunId = null;
|
|
710
|
+
activeRunStarted = false;
|
|
711
|
+
devtoolsRuns = [];
|
|
712
|
+
maxRuns;
|
|
713
|
+
getCoreState;
|
|
714
|
+
constructor(options) {
|
|
715
|
+
super({
|
|
716
|
+
...options,
|
|
717
|
+
getSnapshot: () => this.buildSnapshot()
|
|
718
|
+
});
|
|
719
|
+
this.maxRuns = options.maxRuns ?? 20;
|
|
720
|
+
this.getCoreState = options.getCoreState;
|
|
721
|
+
}
|
|
722
|
+
beginRun(input) {
|
|
723
|
+
const runId = this.generateRunId();
|
|
724
|
+
this.activeRunId = runId;
|
|
725
|
+
this.activeRunStarted = false;
|
|
726
|
+
this.upsertRun(runId, {
|
|
727
|
+
input,
|
|
728
|
+
result: null,
|
|
729
|
+
preview: this.createPreview(null),
|
|
730
|
+
progress: null,
|
|
731
|
+
status: "generating",
|
|
732
|
+
isLoading: true,
|
|
733
|
+
clearError: true
|
|
734
|
+
});
|
|
735
|
+
return runId;
|
|
736
|
+
}
|
|
737
|
+
ensureRunStarted(runId) {
|
|
738
|
+
if (this.activeRunStarted && this.activeRunId === runId) return;
|
|
739
|
+
if (!this.activeRunStarted && this.activeRunId && this.activeRunId !== runId) this.renameRun(this.activeRunId, runId);
|
|
740
|
+
this.activeRunId = runId;
|
|
741
|
+
this.activeRunStarted = true;
|
|
742
|
+
this.upsertRun(runId, {
|
|
743
|
+
status: "generating",
|
|
744
|
+
isLoading: true,
|
|
745
|
+
clearError: true
|
|
746
|
+
});
|
|
747
|
+
this.emitRunLifecycle("run:started", runId, "started");
|
|
748
|
+
this.emitState();
|
|
749
|
+
}
|
|
750
|
+
finishRun(runId, eventType, status, error) {
|
|
751
|
+
this.ensureRunStarted(runId);
|
|
752
|
+
const completedAt = Date.now();
|
|
753
|
+
const completedProgress = status === "completed" ? this.completeProgress() : this.getProgress();
|
|
754
|
+
const runStatus = status === "completed" ? "success" : status === "errored" ? "error" : "cancelled";
|
|
755
|
+
this.upsertRun(runId, {
|
|
756
|
+
status: runStatus,
|
|
757
|
+
isLoading: false,
|
|
758
|
+
progress: completedProgress,
|
|
759
|
+
completedAt,
|
|
760
|
+
...error ? { error } : { clearError: true }
|
|
761
|
+
});
|
|
762
|
+
if (this.activeRunId === runId) this.activeRunId = null;
|
|
763
|
+
this.activeRunStarted = false;
|
|
764
|
+
this.emitRunLifecycle(eventType, runId, status, { ...error ? { error } : {} });
|
|
765
|
+
this.emitState();
|
|
766
|
+
}
|
|
767
|
+
getActiveRunId() {
|
|
768
|
+
return this.activeRunId;
|
|
769
|
+
}
|
|
770
|
+
/** Clear all per-run history. Called when the client `reset()`s. */
|
|
771
|
+
resetRuns() {
|
|
772
|
+
this.activeRunId = null;
|
|
773
|
+
this.activeRunStarted = false;
|
|
774
|
+
this.devtoolsRuns = [];
|
|
775
|
+
}
|
|
776
|
+
/** Record state changes from the client and emit the matching snapshot. */
|
|
777
|
+
recordResultChange() {
|
|
778
|
+
this.updateActiveRun({
|
|
779
|
+
result: this.getCoreState().result,
|
|
780
|
+
preview: this.createPreview(this.getCoreState().result),
|
|
781
|
+
clearError: true
|
|
782
|
+
});
|
|
783
|
+
this.emitState();
|
|
784
|
+
}
|
|
785
|
+
recordLoadingChange() {
|
|
786
|
+
this.updateActiveRun({ isLoading: this.getCoreState().isLoading });
|
|
787
|
+
this.emitState();
|
|
788
|
+
}
|
|
789
|
+
recordErrorChange(error) {
|
|
790
|
+
this.updateActiveRun(error ? { error: error.message } : { clearError: true });
|
|
791
|
+
this.emitState();
|
|
792
|
+
}
|
|
793
|
+
recordStatusChange(status) {
|
|
794
|
+
this.updateActiveRun({ status });
|
|
795
|
+
this.emitState();
|
|
796
|
+
}
|
|
797
|
+
recordProgressChange() {
|
|
798
|
+
this.updateActiveRun({ progress: this.getCoreState().progress });
|
|
799
|
+
this.emitState();
|
|
800
|
+
}
|
|
801
|
+
/** Emit the latest snapshot without touching run state. */
|
|
802
|
+
emitState() {
|
|
803
|
+
this.emitUpdated();
|
|
804
|
+
this.emitSnapshot();
|
|
805
|
+
}
|
|
806
|
+
buildSnapshot() {
|
|
807
|
+
const core = this.getCoreState();
|
|
808
|
+
return {
|
|
809
|
+
input: core.input,
|
|
810
|
+
result: core.result,
|
|
811
|
+
preview: this.createPreview(core.result),
|
|
812
|
+
progress: core.progress,
|
|
813
|
+
status: core.status,
|
|
814
|
+
isLoading: core.isLoading,
|
|
815
|
+
activeRunId: this.activeRunId,
|
|
816
|
+
runs: this.devtoolsRuns,
|
|
817
|
+
...core.error ? { error: core.error } : {}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
updateActiveRun(patch) {
|
|
821
|
+
if (!this.activeRunId) return;
|
|
822
|
+
this.upsertRun(this.activeRunId, patch);
|
|
823
|
+
}
|
|
824
|
+
upsertRun(runId, patch) {
|
|
825
|
+
const now = Date.now();
|
|
826
|
+
const index = this.devtoolsRuns.findIndex((run) => run.id === runId);
|
|
827
|
+
const existing = index >= 0 ? this.devtoolsRuns[index] : void 0;
|
|
828
|
+
const next = existing ? { ...existing } : {
|
|
829
|
+
id: runId,
|
|
830
|
+
input: this.getCoreState().input,
|
|
831
|
+
result: null,
|
|
832
|
+
preview: this.createPreview(null),
|
|
833
|
+
progress: null,
|
|
834
|
+
status: "idle",
|
|
835
|
+
isLoading: false,
|
|
836
|
+
startedAt: now,
|
|
837
|
+
updatedAt: now
|
|
838
|
+
};
|
|
839
|
+
if ("input" in patch) next.input = patch.input ?? null;
|
|
840
|
+
if ("result" in patch) next.result = patch.result ?? null;
|
|
841
|
+
if (patch.preview) next.preview = patch.preview;
|
|
842
|
+
if ("progress" in patch) next.progress = patch.progress ?? null;
|
|
843
|
+
if (patch.status) next.status = patch.status;
|
|
844
|
+
if ("isLoading" in patch) next.isLoading = patch.isLoading === true;
|
|
845
|
+
if (patch.completedAt !== void 0) next.completedAt = patch.completedAt;
|
|
846
|
+
if (patch.clearError) delete next.error;
|
|
847
|
+
if (patch.error !== void 0) next.error = patch.error;
|
|
848
|
+
next.updatedAt = now;
|
|
849
|
+
if (index >= 0) this.devtoolsRuns = this.devtoolsRuns.map((run) => run.id === runId ? next : run);
|
|
850
|
+
else this.devtoolsRuns = [...this.devtoolsRuns, next];
|
|
851
|
+
if (this.devtoolsRuns.length > this.maxRuns) this.devtoolsRuns = this.devtoolsRuns.slice(-this.maxRuns);
|
|
852
|
+
}
|
|
853
|
+
renameRun(previousRunId, nextRunId) {
|
|
854
|
+
if (previousRunId === nextRunId) return;
|
|
855
|
+
const existing = this.devtoolsRuns.find((run) => run.id === previousRunId);
|
|
856
|
+
if (!existing) return;
|
|
857
|
+
const renamed = {
|
|
858
|
+
...existing,
|
|
859
|
+
id: nextRunId,
|
|
860
|
+
updatedAt: Date.now()
|
|
861
|
+
};
|
|
862
|
+
this.devtoolsRuns = this.devtoolsRuns.filter((run) => run.id !== nextRunId).map((run) => run.id === previousRunId ? renamed : run);
|
|
863
|
+
}
|
|
864
|
+
getProgress() {
|
|
865
|
+
return this.getCoreState().progress;
|
|
866
|
+
}
|
|
867
|
+
completeProgress() {
|
|
868
|
+
const progress = this.getCoreState().progress;
|
|
869
|
+
if (!progress) return null;
|
|
870
|
+
return {
|
|
871
|
+
value: 100,
|
|
872
|
+
...progress.message ? { message: progress.message } : {}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
createPreview(result) {
|
|
876
|
+
return createAIDevtoolsGenerationPreview({
|
|
877
|
+
outputKind: this.options.metadata.outputKind,
|
|
878
|
+
result
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
generateRunId() {
|
|
882
|
+
return `run-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
var VideoDevtoolsBridge = class extends GenerationDevtoolsBridge {
|
|
886
|
+
constructor(options) {
|
|
887
|
+
super(options);
|
|
888
|
+
}
|
|
889
|
+
recordJobIdChange() {
|
|
890
|
+
this.updateActiveRun({ jobId: this.getCoreState().jobId });
|
|
891
|
+
this.emitState();
|
|
892
|
+
}
|
|
893
|
+
recordVideoStatusChange() {
|
|
894
|
+
const core = this.getCoreState();
|
|
895
|
+
this.updateActiveRun({
|
|
896
|
+
videoStatus: core.videoStatus,
|
|
897
|
+
preview: this.createVideoPreview(core.result, core.videoStatus)
|
|
898
|
+
});
|
|
899
|
+
this.emitState();
|
|
900
|
+
}
|
|
901
|
+
buildSnapshot() {
|
|
902
|
+
const core = this.getCoreState();
|
|
903
|
+
return {
|
|
904
|
+
input: core.input,
|
|
905
|
+
result: core.result,
|
|
906
|
+
preview: this.createVideoPreview(core.result, core.videoStatus),
|
|
907
|
+
progress: core.progress,
|
|
908
|
+
status: core.status,
|
|
909
|
+
isLoading: core.isLoading,
|
|
910
|
+
activeRunId: this.activeRunId,
|
|
911
|
+
runs: this.devtoolsRuns,
|
|
912
|
+
jobId: core.jobId,
|
|
913
|
+
videoStatus: core.videoStatus,
|
|
914
|
+
...core.error ? { error: core.error } : {}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
upsertRun(runId, patch) {
|
|
918
|
+
super.upsertRun(runId, patch);
|
|
919
|
+
if (!("jobId" in patch || "videoStatus" in patch)) return;
|
|
920
|
+
const index = this.devtoolsRuns.findIndex((run) => run.id === runId);
|
|
921
|
+
if (index < 0) return;
|
|
922
|
+
const target = this.devtoolsRuns[index];
|
|
923
|
+
if (!target) return;
|
|
924
|
+
const merged = { ...target };
|
|
925
|
+
if ("jobId" in patch) merged.jobId = patch.jobId ?? null;
|
|
926
|
+
if ("videoStatus" in patch) merged.videoStatus = patch.videoStatus ?? null;
|
|
927
|
+
this.devtoolsRuns = this.devtoolsRuns.map((run) => run.id === runId ? merged : run);
|
|
928
|
+
}
|
|
929
|
+
createPreview(result) {
|
|
930
|
+
const core = this.getCoreState();
|
|
931
|
+
return this.createVideoPreview(result, core.videoStatus);
|
|
932
|
+
}
|
|
933
|
+
createVideoPreview(result, videoStatus) {
|
|
934
|
+
return createAIDevtoolsGenerationPreview({
|
|
935
|
+
outputKind: this.options.metadata.outputKind,
|
|
936
|
+
result,
|
|
937
|
+
videoStatus
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
var ChatDevtoolsAwareEventEmitter = class extends DefaultChatClientEventEmitter {
|
|
942
|
+
helper;
|
|
943
|
+
constructor(clientId, helper) {
|
|
944
|
+
super(clientId);
|
|
945
|
+
this.helper = helper;
|
|
946
|
+
}
|
|
947
|
+
afterEmit(streamId) {
|
|
948
|
+
if (streamId) this.helper.recordStreamId(streamId);
|
|
949
|
+
this.helper.emitSnapshot();
|
|
950
|
+
}
|
|
951
|
+
textUpdated(streamId, messageId, content, context) {
|
|
952
|
+
super.textUpdated(streamId, messageId, content, context ?? this.helper.getCurrentRunEventContext());
|
|
953
|
+
this.afterEmit(streamId);
|
|
954
|
+
}
|
|
955
|
+
thinkingUpdated(streamId, messageId, content, delta, context) {
|
|
956
|
+
super.thinkingUpdated(streamId, messageId, content, delta, context ?? this.helper.getCurrentRunEventContext());
|
|
957
|
+
this.afterEmit(streamId);
|
|
958
|
+
}
|
|
959
|
+
messageAppended(uiMessage, streamId, context) {
|
|
960
|
+
super.messageAppended(uiMessage, streamId, context ?? this.helper.getCurrentRunEventContext());
|
|
961
|
+
this.afterEmit(streamId);
|
|
962
|
+
}
|
|
963
|
+
toolCallStateChanged(streamId, messageId, toolCallId, toolName, state, args, context) {
|
|
964
|
+
super.toolCallStateChanged(streamId, messageId, toolCallId, toolName, state, args, context ?? this.helper.getCurrentRunEventContext());
|
|
965
|
+
this.afterEmit(streamId);
|
|
966
|
+
}
|
|
967
|
+
structuredOutputChanged(eventName, streamId, messageId, output, context) {
|
|
968
|
+
super.structuredOutputChanged(eventName, streamId, messageId, output, context ?? this.helper.getCurrentOrLastRunEventContext());
|
|
969
|
+
this.afterEmit(streamId);
|
|
970
|
+
}
|
|
971
|
+
approvalRequested(streamId, messageId, toolCallId, toolName, input, approvalId, context) {
|
|
972
|
+
super.approvalRequested(streamId, messageId, toolCallId, toolName, input, approvalId, context ?? this.helper.getCurrentOrLastRunEventContext());
|
|
973
|
+
this.afterEmit(streamId);
|
|
974
|
+
}
|
|
975
|
+
toolResultAdded(toolCallId, toolName, output, state, context) {
|
|
976
|
+
super.toolResultAdded(toolCallId, toolName, output, state, context ?? this.helper.getCurrentRunEventContext());
|
|
977
|
+
this.afterEmit();
|
|
978
|
+
}
|
|
979
|
+
toolApprovalResponded(approvalId, toolCallId, approved, context) {
|
|
980
|
+
super.toolApprovalResponded(approvalId, toolCallId, approved, context ?? this.helper.getCurrentRunEventContext());
|
|
981
|
+
this.afterEmit();
|
|
982
|
+
}
|
|
983
|
+
clientCreated(initialMessageCount) {
|
|
984
|
+
super.clientCreated(initialMessageCount);
|
|
985
|
+
this.afterEmit();
|
|
986
|
+
}
|
|
987
|
+
loadingChanged(isLoading) {
|
|
988
|
+
super.loadingChanged(isLoading);
|
|
989
|
+
this.afterEmit();
|
|
990
|
+
}
|
|
991
|
+
errorChanged(error) {
|
|
992
|
+
super.errorChanged(error);
|
|
993
|
+
this.afterEmit();
|
|
994
|
+
}
|
|
995
|
+
reloaded(fromMessageIndex) {
|
|
996
|
+
super.reloaded(fromMessageIndex);
|
|
997
|
+
this.afterEmit();
|
|
998
|
+
}
|
|
999
|
+
stopped() {
|
|
1000
|
+
super.stopped();
|
|
1001
|
+
this.afterEmit();
|
|
1002
|
+
}
|
|
1003
|
+
messagesCleared() {
|
|
1004
|
+
super.messagesCleared();
|
|
1005
|
+
this.afterEmit();
|
|
1006
|
+
}
|
|
1007
|
+
messageSent(messageId, content) {
|
|
1008
|
+
super.messageSent(messageId, content);
|
|
1009
|
+
this.afterEmit();
|
|
1010
|
+
}
|
|
1011
|
+
toolFixtureApplied(fixture) {
|
|
1012
|
+
super.toolFixtureApplied(fixture);
|
|
1013
|
+
this.afterEmit();
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1159
1016
|
function createChatDevtoolsBridge(options) {
|
|
1160
|
-
|
|
1017
|
+
return new ChatDevtoolsBridge(options);
|
|
1161
1018
|
}
|
|
1162
1019
|
function createGenerationDevtoolsBridge(options) {
|
|
1163
|
-
|
|
1020
|
+
return new GenerationDevtoolsBridge(options);
|
|
1164
1021
|
}
|
|
1165
1022
|
function createVideoDevtoolsBridge(options) {
|
|
1166
|
-
|
|
1023
|
+
return new VideoDevtoolsBridge(options);
|
|
1167
1024
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
VideoDevtoolsBridge,
|
|
1173
|
-
createAIDevtoolsGenerationPreview,
|
|
1174
|
-
createChatDevtoolsBridge,
|
|
1175
|
-
createGenerationDevtoolsBridge,
|
|
1176
|
-
createVideoDevtoolsBridge
|
|
1177
|
-
};
|
|
1178
|
-
//# sourceMappingURL=devtools.js.map
|
|
1025
|
+
//#endregion
|
|
1026
|
+
export { ChatDevtoolsBridge, ClientDevtoolsBridge, GenerationDevtoolsBridge, VideoDevtoolsBridge, createAIDevtoolsGenerationPreview, createChatDevtoolsBridge, createGenerationDevtoolsBridge, createVideoDevtoolsBridge };
|
|
1027
|
+
|
|
1028
|
+
//# sourceMappingURL=devtools.js.map
|