@rozenite/network-activity-plugin 1.5.1 → 1.6.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.
@@ -1,6 +1,928 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { useRozeniteDevToolsClient } from "@rozenite/plugin-bridge";
3
- import { g as getOverridesRegistry, a as getResponseBody, c as createNetworkInspectorsConfiguration, D as DEFAULT_CONFIG, v as validateConfig, i as isHttpEvent, b as isWebSocketEvent, d as isSSEEvent } from "./boot-recording.js";
3
+ import { s as safeStringify, g as getResponseBody, a as getOverridesRegistry, c as createNetworkInspectorsConfiguration, D as DEFAULT_CONFIG, v as validateConfig, i as isHttpEvent, b as isWebSocketEvent, d as isSSEEvent } from "./boot-recording.js";
4
+ import { useRozenitePluginAgentTool } from "@rozenite/agent-bridge";
5
+ const DEFAULT_PAGE_LIMIT = 20;
6
+ const MAX_PAGE_LIMIT = 100;
7
+ const HTTP_BUFFER_CAPACITY = 500;
8
+ const REALTIME_BUFFER_CAPACITY = 200;
9
+ const createInitialState = () => ({
10
+ isRecording: false,
11
+ generation: 0,
12
+ httpOrder: [],
13
+ httpRecords: /* @__PURE__ */ new Map(),
14
+ httpTotalRecorded: 0,
15
+ httpEvictedCount: 0,
16
+ httpTruncated: false,
17
+ realtimeOrder: [],
18
+ realtimeRecords: /* @__PURE__ */ new Map(),
19
+ realtimeTotalRecorded: 0,
20
+ realtimeEvictedCount: 0,
21
+ realtimeTruncated: false,
22
+ nextMessageId: 1,
23
+ recordingMetadata: {
24
+ enabledInspectors: {
25
+ http: true,
26
+ websocket: true,
27
+ sse: true
28
+ }
29
+ }
30
+ });
31
+ const getLimit = (value) => {
32
+ if (typeof value !== "number" || !Number.isInteger(value) || !Number.isFinite(value) || value < 1) {
33
+ return DEFAULT_PAGE_LIMIT;
34
+ }
35
+ return Math.min(value, MAX_PAGE_LIMIT);
36
+ };
37
+ const encodeCursor = (scope, index) => {
38
+ return `${scope}:${index}`;
39
+ };
40
+ const decodeCursor = (cursor, scope) => {
41
+ const [cursorScope, rawIndex] = cursor.split(":", 2);
42
+ if (cursorScope !== scope || !rawIndex) {
43
+ throw new Error(
44
+ "Cursor does not match the requested listing. Run the command again."
45
+ );
46
+ }
47
+ const index = Number(rawIndex);
48
+ if (!Number.isInteger(index) || index < 0) {
49
+ throw new Error("Cursor is invalid. Run the command again.");
50
+ }
51
+ return index;
52
+ };
53
+ const paginate = (rows, scope, limit, cursor) => {
54
+ const startIndex = cursor ? decodeCursor(cursor, scope) : 0;
55
+ const endIndex = Math.min(startIndex + limit, rows.length);
56
+ const hasMore = endIndex < rows.length;
57
+ return {
58
+ items: rows.slice(startIndex, endIndex),
59
+ page: {
60
+ limit,
61
+ hasMore,
62
+ ...hasMore ? { nextCursor: encodeCursor(scope, endIndex) } : {}
63
+ }
64
+ };
65
+ };
66
+ const serializeRequestBody = (requestId, postData) => {
67
+ if (!postData) {
68
+ return {
69
+ requestId,
70
+ available: false,
71
+ reason: "No request body is available for this request."
72
+ };
73
+ }
74
+ if (postData.type === "text") {
75
+ return {
76
+ requestId,
77
+ available: true,
78
+ body: postData.value,
79
+ base64Encoded: false
80
+ };
81
+ }
82
+ return {
83
+ requestId,
84
+ available: true,
85
+ body: safeStringify(postData),
86
+ base64Encoded: false
87
+ };
88
+ };
89
+ const createHttpSummary = (record) => ({
90
+ requestId: record.requestId,
91
+ method: record.request.method,
92
+ url: record.request.url,
93
+ status: record.response?.status ?? null,
94
+ type: record.resourceType,
95
+ startTimeMs: record.startTimeMs,
96
+ endTimeMs: record.endTimeMs ?? null,
97
+ durationMs: record.durationMs ?? null,
98
+ transferSize: record.size ?? null,
99
+ encodedDataLength: record.response?.size ?? null,
100
+ outcome: record.status === "failed" ? "failed" : record.status === "finished" ? "success" : "in-flight"
101
+ });
102
+ const getRealtimeSummary = (record) => {
103
+ if (record.kind === "websocket") {
104
+ return {
105
+ requestId: record.requestId,
106
+ kind: record.kind,
107
+ url: record.url,
108
+ status: record.status,
109
+ startedAt: record.startedAt,
110
+ endedAt: record.endedAt ?? null,
111
+ durationMs: record.durationMs ?? null,
112
+ messageCount: record.messages.length,
113
+ error: record.error ?? null,
114
+ closeCode: record.closeCode ?? null
115
+ };
116
+ }
117
+ return {
118
+ requestId: record.requestId,
119
+ kind: record.kind,
120
+ url: record.request?.url ?? record.response?.url ?? null,
121
+ status: record.status,
122
+ startedAt: record.startedAt,
123
+ endedAt: record.endedAt ?? null,
124
+ durationMs: record.durationMs ?? null,
125
+ messageCount: record.messages.length,
126
+ error: record.error ?? null,
127
+ httpStatus: record.response?.status ?? null
128
+ };
129
+ };
130
+ const trimMap = (order, records, capacity) => {
131
+ let evicted = 0;
132
+ while (order.length > capacity) {
133
+ const oldestId = order.shift();
134
+ if (!oldestId) {
135
+ break;
136
+ }
137
+ records.delete(oldestId);
138
+ evicted += 1;
139
+ }
140
+ return evicted;
141
+ };
142
+ const createNetworkActivityAgentState = () => {
143
+ const state = createInitialState();
144
+ const getStatus = () => ({
145
+ recording: {
146
+ isRecording: state.isRecording,
147
+ startedAt: state.startedAt ?? null,
148
+ stoppedAt: state.stoppedAt ?? null,
149
+ httpRequestCount: state.httpOrder.length,
150
+ realtimeConnectionCount: state.realtimeOrder.length,
151
+ http: {
152
+ totalRecorded: state.httpTotalRecorded,
153
+ evictedCount: state.httpEvictedCount,
154
+ truncated: state.httpTruncated,
155
+ capacity: HTTP_BUFFER_CAPACITY
156
+ },
157
+ realtime: {
158
+ totalRecorded: state.realtimeTotalRecorded,
159
+ evictedCount: state.realtimeEvictedCount,
160
+ truncated: state.realtimeTruncated,
161
+ capacity: REALTIME_BUFFER_CAPACITY
162
+ },
163
+ generation: state.generation,
164
+ enabledInspectors: state.recordingMetadata.enabledInspectors
165
+ }
166
+ });
167
+ const ensureHttpRecord = (requestId, fallback) => {
168
+ const existing = state.httpRecords.get(requestId);
169
+ if (existing) {
170
+ return existing;
171
+ }
172
+ const record = {
173
+ requestId,
174
+ request: fallback?.request || {
175
+ url: "",
176
+ method: "GET",
177
+ headers: {}
178
+ },
179
+ resourceType: fallback?.resourceType || "Other",
180
+ initiator: fallback?.initiator || { type: "other" },
181
+ startTimeMs: fallback?.startTimeMs ?? Date.now(),
182
+ status: fallback?.status || "pending"
183
+ };
184
+ state.httpRecords.set(requestId, record);
185
+ state.httpOrder.push(requestId);
186
+ state.httpTotalRecorded += 1;
187
+ const evicted = trimMap(
188
+ state.httpOrder,
189
+ state.httpRecords,
190
+ HTTP_BUFFER_CAPACITY
191
+ );
192
+ if (evicted > 0) {
193
+ state.httpEvictedCount += evicted;
194
+ state.httpTruncated = true;
195
+ }
196
+ return record;
197
+ };
198
+ const ensureRealtimeRecord = (requestId, createRecord) => {
199
+ const existing = state.realtimeRecords.get(requestId);
200
+ if (existing) {
201
+ return existing;
202
+ }
203
+ const record = createRecord();
204
+ state.realtimeRecords.set(requestId, record);
205
+ state.realtimeOrder.push(requestId);
206
+ state.realtimeTotalRecorded += 1;
207
+ const evicted = trimMap(
208
+ state.realtimeOrder,
209
+ state.realtimeRecords,
210
+ REALTIME_BUFFER_CAPACITY
211
+ );
212
+ if (evicted > 0) {
213
+ state.realtimeEvictedCount += evicted;
214
+ state.realtimeTruncated = true;
215
+ }
216
+ return record;
217
+ };
218
+ const nextMessageId = (prefix) => {
219
+ const id = `${prefix}-${state.nextMessageId}`;
220
+ state.nextMessageId += 1;
221
+ return id;
222
+ };
223
+ return {
224
+ startRecording(metadata) {
225
+ state.isRecording = true;
226
+ state.startedAt = Date.now();
227
+ state.stoppedAt = void 0;
228
+ state.generation += 1;
229
+ state.httpOrder = [];
230
+ state.httpRecords.clear();
231
+ state.httpTotalRecorded = 0;
232
+ state.httpEvictedCount = 0;
233
+ state.httpTruncated = false;
234
+ state.realtimeOrder = [];
235
+ state.realtimeRecords.clear();
236
+ state.realtimeTotalRecorded = 0;
237
+ state.realtimeEvictedCount = 0;
238
+ state.realtimeTruncated = false;
239
+ state.recordingMetadata = {
240
+ enabledInspectors: {
241
+ ...state.recordingMetadata.enabledInspectors,
242
+ ...metadata?.enabledInspectors
243
+ }
244
+ };
245
+ return getStatus();
246
+ },
247
+ stopRecording() {
248
+ if (!state.isRecording) {
249
+ throw new Error("No active network recording for this plugin session");
250
+ }
251
+ state.isRecording = false;
252
+ state.stoppedAt = Date.now();
253
+ return getStatus();
254
+ },
255
+ getStatus,
256
+ getHttpRecord(requestId) {
257
+ return state.httpRecords.get(requestId) || null;
258
+ },
259
+ getRealtimeRecord(requestId) {
260
+ return state.realtimeRecords.get(requestId) || null;
261
+ },
262
+ listRequests(input) {
263
+ const limit = getLimit(input.limit);
264
+ const rows = state.httpOrder.map((requestId) => state.httpRecords.get(requestId)).filter((record) => !!record).reverse().map(createHttpSummary);
265
+ return {
266
+ ...getStatus(),
267
+ ...paginate(rows, `http-${state.generation}`, limit, input.cursor)
268
+ };
269
+ },
270
+ listRealtimeConnections(input) {
271
+ const limit = getLimit(input.limit);
272
+ const rows = state.realtimeOrder.map((requestId) => state.realtimeRecords.get(requestId)).filter((record) => !!record).reverse().map(getRealtimeSummary);
273
+ return {
274
+ ...getStatus(),
275
+ ...paginate(rows, `realtime-${state.generation}`, limit, input.cursor)
276
+ };
277
+ },
278
+ getRequestDetails(requestId) {
279
+ const record = state.httpRecords.get(requestId);
280
+ if (!record) {
281
+ throw new Error(`Unknown request "${requestId}"`);
282
+ }
283
+ return {
284
+ ...getStatus(),
285
+ request: {
286
+ requestId: record.requestId,
287
+ method: record.request.method,
288
+ url: record.request.url,
289
+ type: record.resourceType,
290
+ initiator: record.initiator,
291
+ startTimeMs: record.startTimeMs,
292
+ endTimeMs: record.endTimeMs ?? null,
293
+ durationMs: record.durationMs ?? null,
294
+ request: record.request,
295
+ response: record.response ?? null,
296
+ loadingFinished: record.status === "finished",
297
+ loadingFailed: record.status === "failed",
298
+ failureText: record.error ?? null,
299
+ canceled: record.canceled ?? false,
300
+ progress: record.progress ? {
301
+ loaded: record.progress.loaded,
302
+ total: record.progress.total,
303
+ lengthComputable: record.progress.lengthComputable
304
+ } : null,
305
+ ttfb: record.ttfb ?? null,
306
+ size: record.size ?? null
307
+ }
308
+ };
309
+ },
310
+ getRealtimeConnectionDetails(requestId) {
311
+ const record = state.realtimeRecords.get(requestId);
312
+ if (!record) {
313
+ throw new Error(`Unknown realtime connection "${requestId}"`);
314
+ }
315
+ return {
316
+ ...getStatus(),
317
+ connection: record.kind === "websocket" ? {
318
+ requestId: record.requestId,
319
+ kind: record.kind,
320
+ url: record.url,
321
+ socketId: record.socketId,
322
+ status: record.status,
323
+ startedAt: record.startedAt,
324
+ endedAt: record.endedAt ?? null,
325
+ durationMs: record.durationMs ?? null,
326
+ protocols: record.protocols ?? null,
327
+ options: record.options ?? [],
328
+ error: record.error ?? null,
329
+ closeCode: record.closeCode ?? null,
330
+ closeReason: record.closeReason ?? null,
331
+ messages: record.messages
332
+ } : {
333
+ requestId: record.requestId,
334
+ kind: record.kind,
335
+ status: record.status,
336
+ startedAt: record.startedAt,
337
+ endedAt: record.endedAt ?? null,
338
+ durationMs: record.durationMs ?? null,
339
+ request: record.request ?? null,
340
+ response: record.response ?? null,
341
+ initiator: record.initiator ?? null,
342
+ resourceType: record.resourceType ?? null,
343
+ error: record.error ?? null,
344
+ messages: record.messages
345
+ }
346
+ };
347
+ },
348
+ getRequestBody(requestId) {
349
+ const record = state.httpRecords.get(requestId);
350
+ if (!record) {
351
+ throw new Error(`Unknown request "${requestId}"`);
352
+ }
353
+ return serializeRequestBody(requestId, record.request.postData);
354
+ },
355
+ onRequestSent(event) {
356
+ if (!state.isRecording) {
357
+ return;
358
+ }
359
+ const record = ensureHttpRecord(event.requestId, {
360
+ request: event.request,
361
+ resourceType: event.type,
362
+ initiator: event.initiator,
363
+ startTimeMs: event.timestamp,
364
+ status: "pending"
365
+ });
366
+ record.request = event.request;
367
+ record.resourceType = event.type;
368
+ record.initiator = event.initiator;
369
+ record.startTimeMs = event.timestamp;
370
+ record.status = "pending";
371
+ record.response = void 0;
372
+ record.endTimeMs = void 0;
373
+ record.durationMs = void 0;
374
+ record.size = void 0;
375
+ record.ttfb = void 0;
376
+ record.error = void 0;
377
+ record.canceled = void 0;
378
+ record.progress = void 0;
379
+ },
380
+ onRequestProgress(event) {
381
+ if (!state.isRecording) {
382
+ return;
383
+ }
384
+ const record = ensureHttpRecord(event.requestId);
385
+ record.progress = event;
386
+ record.status = "loading";
387
+ },
388
+ onResponseReceived(event) {
389
+ if (!state.isRecording) {
390
+ return;
391
+ }
392
+ const record = ensureHttpRecord(event.requestId);
393
+ record.response = event.response;
394
+ record.status = "loading";
395
+ },
396
+ onRequestCompleted(event) {
397
+ if (!state.isRecording) {
398
+ return;
399
+ }
400
+ const record = ensureHttpRecord(event.requestId);
401
+ record.status = "finished";
402
+ record.endTimeMs = event.timestamp;
403
+ record.durationMs = event.duration;
404
+ record.size = event.size;
405
+ record.ttfb = event.ttfb;
406
+ },
407
+ onRequestFailed(event) {
408
+ if (!state.isRecording) {
409
+ return;
410
+ }
411
+ const record = ensureHttpRecord(event.requestId);
412
+ record.status = "failed";
413
+ record.endTimeMs = event.timestamp;
414
+ record.error = event.error;
415
+ record.canceled = event.canceled;
416
+ },
417
+ onWebSocketConnect(event) {
418
+ if (!state.isRecording) {
419
+ return;
420
+ }
421
+ ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
422
+ requestId: `ws-${event.socketId}`,
423
+ kind: "websocket",
424
+ url: event.url,
425
+ socketId: event.socketId,
426
+ status: "connecting",
427
+ startedAt: event.timestamp,
428
+ protocols: event.protocols,
429
+ options: event.options,
430
+ messages: []
431
+ }));
432
+ },
433
+ onWebSocketOpen(event) {
434
+ if (!state.isRecording) {
435
+ return;
436
+ }
437
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
438
+ requestId: `ws-${event.socketId}`,
439
+ kind: "websocket",
440
+ url: event.url,
441
+ socketId: event.socketId,
442
+ status: "connecting",
443
+ startedAt: event.timestamp,
444
+ messages: []
445
+ }));
446
+ record.status = "open";
447
+ },
448
+ onWebSocketClose(event) {
449
+ if (!state.isRecording) {
450
+ return;
451
+ }
452
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
453
+ requestId: `ws-${event.socketId}`,
454
+ kind: "websocket",
455
+ url: event.url,
456
+ socketId: event.socketId,
457
+ status: "connecting",
458
+ startedAt: event.timestamp,
459
+ messages: []
460
+ }));
461
+ record.status = "closed";
462
+ record.endedAt = event.timestamp;
463
+ record.durationMs = event.timestamp - record.startedAt;
464
+ record.closeCode = event.code;
465
+ record.closeReason = event.reason;
466
+ },
467
+ onWebSocketMessageSent(event) {
468
+ if (!state.isRecording) {
469
+ return;
470
+ }
471
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
472
+ requestId: `ws-${event.socketId}`,
473
+ kind: "websocket",
474
+ url: event.url,
475
+ socketId: event.socketId,
476
+ status: "connecting",
477
+ startedAt: event.timestamp,
478
+ messages: []
479
+ }));
480
+ const message = {
481
+ id: nextMessageId(record.requestId),
482
+ direction: "sent",
483
+ data: event.data,
484
+ messageType: event.messageType,
485
+ timestamp: event.timestamp
486
+ };
487
+ record.messages = [...record.messages, message].slice(
488
+ -32
489
+ );
490
+ },
491
+ onWebSocketMessageReceived(event) {
492
+ if (!state.isRecording) {
493
+ return;
494
+ }
495
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
496
+ requestId: `ws-${event.socketId}`,
497
+ kind: "websocket",
498
+ url: event.url,
499
+ socketId: event.socketId,
500
+ status: "connecting",
501
+ startedAt: event.timestamp,
502
+ messages: []
503
+ }));
504
+ const message = {
505
+ id: nextMessageId(record.requestId),
506
+ direction: "received",
507
+ data: event.data,
508
+ messageType: event.messageType,
509
+ timestamp: event.timestamp
510
+ };
511
+ record.messages = [...record.messages, message].slice(
512
+ -32
513
+ );
514
+ },
515
+ onWebSocketError(event) {
516
+ if (!state.isRecording) {
517
+ return;
518
+ }
519
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
520
+ requestId: `ws-${event.socketId}`,
521
+ kind: "websocket",
522
+ url: event.url,
523
+ socketId: event.socketId,
524
+ status: "connecting",
525
+ startedAt: event.timestamp,
526
+ messages: []
527
+ }));
528
+ record.status = "error";
529
+ record.error = event.error;
530
+ },
531
+ onWebSocketConnectionStatusChanged(event) {
532
+ if (!state.isRecording) {
533
+ return;
534
+ }
535
+ const record = ensureRealtimeRecord(`ws-${event.socketId}`, () => ({
536
+ requestId: `ws-${event.socketId}`,
537
+ kind: "websocket",
538
+ url: event.url,
539
+ socketId: event.socketId,
540
+ status: "connecting",
541
+ startedAt: event.timestamp,
542
+ messages: []
543
+ }));
544
+ record.status = event.status;
545
+ },
546
+ onSSEOpen(event) {
547
+ if (!state.isRecording) {
548
+ return;
549
+ }
550
+ const httpRecord = state.httpRecords.get(event.requestId);
551
+ ensureRealtimeRecord(event.requestId, () => ({
552
+ requestId: event.requestId,
553
+ kind: "sse",
554
+ status: "open",
555
+ startedAt: httpRecord?.startTimeMs ?? event.timestamp,
556
+ request: httpRecord?.request,
557
+ response: event.response,
558
+ initiator: httpRecord?.initiator,
559
+ resourceType: httpRecord?.resourceType,
560
+ messages: []
561
+ }));
562
+ },
563
+ onSSEMessage(event) {
564
+ if (!state.isRecording) {
565
+ return;
566
+ }
567
+ const record = ensureRealtimeRecord(event.requestId, () => ({
568
+ requestId: event.requestId,
569
+ kind: "sse",
570
+ status: "connecting",
571
+ startedAt: event.timestamp,
572
+ messages: []
573
+ }));
574
+ record.messages = [
575
+ ...record.messages,
576
+ {
577
+ id: nextMessageId(record.requestId),
578
+ type: event.payload.type,
579
+ data: event.payload.data,
580
+ timestamp: event.timestamp
581
+ }
582
+ ].slice(-32);
583
+ },
584
+ onSSEError(event) {
585
+ if (!state.isRecording) {
586
+ return;
587
+ }
588
+ const record = ensureRealtimeRecord(event.requestId, () => ({
589
+ requestId: event.requestId,
590
+ kind: "sse",
591
+ status: "connecting",
592
+ startedAt: event.timestamp,
593
+ messages: []
594
+ }));
595
+ record.status = "error";
596
+ record.error = event.error.message;
597
+ },
598
+ onSSEClose(event) {
599
+ if (!state.isRecording) {
600
+ return;
601
+ }
602
+ const record = ensureRealtimeRecord(event.requestId, () => ({
603
+ requestId: event.requestId,
604
+ kind: "sse",
605
+ status: "connecting",
606
+ startedAt: event.timestamp,
607
+ messages: []
608
+ }));
609
+ record.status = "closed";
610
+ record.endedAt = event.timestamp;
611
+ record.durationMs = event.timestamp - record.startedAt;
612
+ }
613
+ };
614
+ };
615
+ const getNetworkActivityAgentState = /* @__PURE__ */ (() => {
616
+ let instance = null;
617
+ return () => {
618
+ if (!instance) {
619
+ instance = createNetworkActivityAgentState();
620
+ }
621
+ return instance;
622
+ };
623
+ })();
624
+ const startRecordingTool = {
625
+ name: "startRecording",
626
+ description: "Start recording network activity in the fallback network activity plugin.",
627
+ inputSchema: {
628
+ type: "object",
629
+ properties: {}
630
+ }
631
+ };
632
+ const stopRecordingTool = {
633
+ name: "stopRecording",
634
+ description: "Stop recording network activity without clearing the captured plugin buffer.",
635
+ inputSchema: {
636
+ type: "object",
637
+ properties: {}
638
+ }
639
+ };
640
+ const getRecordingStatusTool = {
641
+ name: "getRecordingStatus",
642
+ description: "Return network activity plugin recording state and buffer metadata.",
643
+ inputSchema: {
644
+ type: "object",
645
+ properties: {}
646
+ }
647
+ };
648
+ const listRequestsTool = {
649
+ name: "listRequests",
650
+ description: "List captured HTTP request summaries with cursor pagination from the fallback plugin.",
651
+ inputSchema: {
652
+ type: "object",
653
+ properties: {
654
+ limit: {
655
+ type: "number",
656
+ description: "Maximum number of requests to return. Defaults to 20."
657
+ },
658
+ cursor: {
659
+ type: "string",
660
+ description: "Opaque pagination cursor from a previous listRequests call."
661
+ }
662
+ }
663
+ }
664
+ };
665
+ const getRequestDetailsTool = {
666
+ name: "getRequestDetails",
667
+ description: "Return detailed metadata for a captured HTTP request without fetching response body.",
668
+ inputSchema: {
669
+ type: "object",
670
+ properties: {
671
+ requestId: {
672
+ type: "string",
673
+ description: "Captured plugin request ID to inspect."
674
+ }
675
+ },
676
+ required: ["requestId"]
677
+ }
678
+ };
679
+ const getRequestBodyTool = {
680
+ name: "getRequestBody",
681
+ description: "Return the captured request body for a plugin-recorded HTTP request when available.",
682
+ inputSchema: {
683
+ type: "object",
684
+ properties: {
685
+ requestId: {
686
+ type: "string",
687
+ description: "Captured plugin request ID to inspect."
688
+ }
689
+ },
690
+ required: ["requestId"]
691
+ }
692
+ };
693
+ const getResponseBodyTool = {
694
+ name: "getResponseBody",
695
+ description: "Return the captured response body for a plugin-recorded HTTP request when available.",
696
+ inputSchema: {
697
+ type: "object",
698
+ properties: {
699
+ requestId: {
700
+ type: "string",
701
+ description: "Captured plugin request ID to inspect."
702
+ }
703
+ },
704
+ required: ["requestId"]
705
+ }
706
+ };
707
+ const listRealtimeConnectionsTool = {
708
+ name: "listRealtimeConnections",
709
+ description: "List captured WebSocket and SSE connections with cursor pagination.",
710
+ inputSchema: {
711
+ type: "object",
712
+ properties: {
713
+ limit: {
714
+ type: "number",
715
+ description: "Maximum number of realtime connections to return. Defaults to 20."
716
+ },
717
+ cursor: {
718
+ type: "string",
719
+ description: "Opaque pagination cursor from a previous listRealtimeConnections call."
720
+ }
721
+ }
722
+ }
723
+ };
724
+ const getRealtimeConnectionDetailsTool = {
725
+ name: "getRealtimeConnectionDetails",
726
+ description: "Return details for a captured WebSocket or SSE connection, including recent messages.",
727
+ inputSchema: {
728
+ type: "object",
729
+ properties: {
730
+ requestId: {
731
+ type: "string",
732
+ description: "Captured realtime request ID to inspect."
733
+ }
734
+ },
735
+ required: ["requestId"]
736
+ }
737
+ };
738
+ const pluginId = "@rozenite/network-activity-plugin";
739
+ const useNetworkActivityAgentTools = ({
740
+ client,
741
+ networkInspector,
742
+ enabledInspectors
743
+ }) => {
744
+ const state = getNetworkActivityAgentState();
745
+ useEffect(() => {
746
+ const unsubscribe = [
747
+ networkInspector.http.on(
748
+ "request-sent",
749
+ (event) => state.onRequestSent(event)
750
+ ),
751
+ networkInspector.http.on(
752
+ "request-progress",
753
+ (event) => state.onRequestProgress(event)
754
+ ),
755
+ networkInspector.http.on(
756
+ "response-received",
757
+ (event) => state.onResponseReceived(event)
758
+ ),
759
+ networkInspector.http.on(
760
+ "request-completed",
761
+ (event) => state.onRequestCompleted(event)
762
+ ),
763
+ networkInspector.http.on(
764
+ "request-failed",
765
+ (event) => state.onRequestFailed(event)
766
+ ),
767
+ networkInspector.websocket.on(
768
+ "websocket-connect",
769
+ (event) => state.onWebSocketConnect(event)
770
+ ),
771
+ networkInspector.websocket.on(
772
+ "websocket-open",
773
+ (event) => state.onWebSocketOpen(event)
774
+ ),
775
+ networkInspector.websocket.on(
776
+ "websocket-close",
777
+ (event) => state.onWebSocketClose(event)
778
+ ),
779
+ networkInspector.websocket.on(
780
+ "websocket-message-sent",
781
+ (event) => state.onWebSocketMessageSent(event)
782
+ ),
783
+ networkInspector.websocket.on(
784
+ "websocket-message-received",
785
+ (event) => state.onWebSocketMessageReceived(event)
786
+ ),
787
+ networkInspector.websocket.on(
788
+ "websocket-error",
789
+ (event) => state.onWebSocketError(event)
790
+ ),
791
+ networkInspector.websocket.on(
792
+ "websocket-connection-status-changed",
793
+ (event) => state.onWebSocketConnectionStatusChanged(event)
794
+ ),
795
+ networkInspector.sse.on("sse-open", (event) => state.onSSEOpen(event)),
796
+ networkInspector.sse.on("sse-message", (event) => state.onSSEMessage(event)),
797
+ networkInspector.sse.on("sse-error", (event) => state.onSSEError(event)),
798
+ networkInspector.sse.on("sse-close", (event) => state.onSSEClose(event))
799
+ ];
800
+ return () => {
801
+ unsubscribe.forEach((remove) => remove());
802
+ };
803
+ }, [networkInspector, state]);
804
+ useEffect(() => {
805
+ if (!client) {
806
+ return;
807
+ }
808
+ const subscriptions = [
809
+ client.onMessage("network-enable", () => {
810
+ state.startRecording({ enabledInspectors });
811
+ }),
812
+ client.onMessage("network-disable", () => {
813
+ if (state.getStatus().recording.isRecording) {
814
+ state.stopRecording();
815
+ }
816
+ })
817
+ ];
818
+ return () => {
819
+ subscriptions.forEach((subscription) => subscription.remove());
820
+ };
821
+ }, [client, enabledInspectors, state]);
822
+ useRozenitePluginAgentTool({
823
+ pluginId,
824
+ tool: startRecordingTool,
825
+ handler: () => {
826
+ networkInspector.http.getNetworkRequestsRegistry().clear();
827
+ const result = state.startRecording({ enabledInspectors });
828
+ networkInspector.enable(enabledInspectors);
829
+ return {
830
+ started: true,
831
+ ...result
832
+ };
833
+ }
834
+ });
835
+ useRozenitePluginAgentTool({
836
+ pluginId,
837
+ tool: stopRecordingTool,
838
+ handler: () => {
839
+ const result = state.stopRecording();
840
+ networkInspector.disable();
841
+ return {
842
+ stopped: true,
843
+ ...result
844
+ };
845
+ }
846
+ });
847
+ useRozenitePluginAgentTool({
848
+ pluginId,
849
+ tool: getRecordingStatusTool,
850
+ handler: () => state.getStatus()
851
+ });
852
+ useRozenitePluginAgentTool({
853
+ pluginId,
854
+ tool: listRequestsTool,
855
+ handler: (input = {}) => state.listRequests(input)
856
+ });
857
+ useRozenitePluginAgentTool({
858
+ pluginId,
859
+ tool: getRequestDetailsTool,
860
+ handler: ({ requestId }) => state.getRequestDetails(requestId)
861
+ });
862
+ useRozenitePluginAgentTool({
863
+ pluginId,
864
+ tool: getRequestBodyTool,
865
+ handler: ({ requestId }) => state.getRequestBody(requestId)
866
+ });
867
+ useRozenitePluginAgentTool({
868
+ pluginId,
869
+ tool: getResponseBodyTool,
870
+ handler: async ({ requestId }) => {
871
+ const record = state.getHttpRecord(requestId);
872
+ if (!record) {
873
+ throw new Error(`Unknown request "${requestId}"`);
874
+ }
875
+ if (record.status === "failed") {
876
+ return {
877
+ requestId,
878
+ available: false,
879
+ reason: "Response body is unavailable because the request failed."
880
+ };
881
+ }
882
+ if (record.status !== "finished") {
883
+ return {
884
+ requestId,
885
+ available: false,
886
+ reason: "Response body is unavailable until the request finishes loading."
887
+ };
888
+ }
889
+ const request = networkInspector.http.getNetworkRequestsRegistry().getEntry(requestId);
890
+ if (!request) {
891
+ return {
892
+ requestId,
893
+ available: false,
894
+ reason: "Response body is unavailable because the request object is no longer in the plugin registry."
895
+ };
896
+ }
897
+ const body = await getResponseBody(request);
898
+ if (body === null) {
899
+ return {
900
+ requestId,
901
+ available: false,
902
+ reason: "The plugin could not extract a text response body for this request."
903
+ };
904
+ }
905
+ return {
906
+ requestId,
907
+ available: true,
908
+ body,
909
+ base64Encoded: false,
910
+ decoded: false,
911
+ mimeType: record.response?.contentType
912
+ };
913
+ }
914
+ });
915
+ useRozenitePluginAgentTool({
916
+ pluginId,
917
+ tool: listRealtimeConnectionsTool,
918
+ handler: (input = {}) => state.listRealtimeConnections(input)
919
+ });
920
+ useRozenitePluginAgentTool({
921
+ pluginId,
922
+ tool: getRealtimeConnectionDetailsTool,
923
+ handler: ({ requestId }) => state.getRealtimeConnectionDetails(requestId)
924
+ });
925
+ };
4
926
  const overridesRegistry = getOverridesRegistry();
5
927
  const useHttpInspector = (client, httpInspector, isEnabled, isRecordingEnabled) => {
6
928
  useEffect(() => {
@@ -94,6 +1016,15 @@ const useNetworkActivityDevTools = (config = DEFAULT_CONFIG) => {
94
1016
  const isSSEInspectorEnabled = config.inspectors?.sse ?? true;
95
1017
  const showUrlAsName = config.clientUISettings?.showUrlAsName;
96
1018
  const { eventsListener, networkInspector } = inspectorsConfig;
1019
+ useNetworkActivityAgentTools({
1020
+ client,
1021
+ networkInspector,
1022
+ enabledInspectors: {
1023
+ http: isHttpInspectorEnabled,
1024
+ websocket: isWebSocketInspectorEnabled,
1025
+ sse: isSSEInspectorEnabled
1026
+ }
1027
+ });
97
1028
  useEffect(() => {
98
1029
  if (!client) {
99
1030
  return;