react-native-ai-devtools 1.1.8 → 1.2.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 +44 -6
- package/build/__tests__/integration/execute-in-app.test.js +2 -1
- package/build/__tests__/integration/execute-in-app.test.js.map +1 -1
- package/build/__tests__/unit/networkInterceptor.test.d.ts +2 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.js +104 -0
- package/build/__tests__/unit/networkInterceptor.test.js.map +1 -0
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +43 -5
- package/build/core/connection.js.map +1 -1
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +21 -2
- package/build/core/guides.js.map +1 -1
- package/build/core/networkInterceptor.d.ts +30 -0
- package/build/core/networkInterceptor.d.ts.map +1 -0
- package/build/core/networkInterceptor.js +207 -0
- package/build/core/networkInterceptor.js.map +1 -0
- package/build/core/sdkBridge.d.ts +42 -0
- package/build/core/sdkBridge.d.ts.map +1 -0
- package/build/core/sdkBridge.js +68 -0
- package/build/core/sdkBridge.js.map +1 -0
- package/build/core/telemetry.d.ts +1 -1
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +3 -1
- package/build/core/telemetry.js.map +1 -1
- package/build/core/types.d.ts +1 -0
- package/build/core/types.d.ts.map +1 -1
- package/build/index.js +141 -9
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createServer as createHttpServer } from "node:http";
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { getGuideOverview, getGuideByTopic, getAvailableTopics } from "./core/guides.js";
|
|
8
8
|
import { initLicense, getLicenseStatus, getDashboardUrl } from "./core/license.js";
|
|
9
|
+
import { isSDKInstalled, readSDKNetworkRequests, readSDKNetworkRequest, readSDKNetworkStats, clearSDKNetwork } from "./core/sdkBridge.js";
|
|
9
10
|
import { tap } from "./pro/tap.js";
|
|
10
11
|
import { logBuffer, networkBuffer, bundleErrorBuffer, connectedApps, getActiveSimulatorUdid, scanMetroPorts, fetchDevices, selectMainDevice, connectToDevice, getConnectedApps, executeInApp, listDebugGlobals, inspectGlobal, reloadApp,
|
|
11
12
|
// React Component Inspection
|
|
@@ -116,7 +117,7 @@ function estimateImageTokens(base64Data) {
|
|
|
116
117
|
// Registry for dev meta-tool — stores handlers and configs for dynamic dispatch
|
|
117
118
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
118
119
|
const toolRegistry = new Map();
|
|
119
|
-
function registerToolWithTelemetry(toolName, config, handler) {
|
|
120
|
+
function registerToolWithTelemetry(toolName, config, handler, emptyResultDetector) {
|
|
120
121
|
toolRegistry.set(toolName, { config, handler });
|
|
121
122
|
server.registerTool(toolName, config, async (args) => {
|
|
122
123
|
const startTime = Date.now();
|
|
@@ -125,6 +126,7 @@ function registerToolWithTelemetry(toolName, config, handler) {
|
|
|
125
126
|
let errorContext;
|
|
126
127
|
let inputTokens;
|
|
127
128
|
let outputTokens;
|
|
129
|
+
let emptyResult;
|
|
128
130
|
try {
|
|
129
131
|
inputTokens = Math.ceil(JSON.stringify(args).length / 4);
|
|
130
132
|
}
|
|
@@ -140,6 +142,15 @@ function registerToolWithTelemetry(toolName, config, handler) {
|
|
|
140
142
|
// Extract error context if provided (e.g., the expression that caused a syntax error)
|
|
141
143
|
errorContext = result._errorContext;
|
|
142
144
|
}
|
|
145
|
+
// Check for empty result (only on success, only if detector provided)
|
|
146
|
+
if (success && emptyResultDetector) {
|
|
147
|
+
try {
|
|
148
|
+
emptyResult = emptyResultDetector(result);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Detector failure should never affect tool execution
|
|
152
|
+
}
|
|
153
|
+
}
|
|
143
154
|
if (Array.isArray(result?.content)) {
|
|
144
155
|
let totalTokens = 0;
|
|
145
156
|
for (const item of result.content) {
|
|
@@ -162,7 +173,7 @@ function registerToolWithTelemetry(toolName, config, handler) {
|
|
|
162
173
|
}
|
|
163
174
|
finally {
|
|
164
175
|
const duration = Date.now() - startTime;
|
|
165
|
-
trackToolInvocation(toolName, success, duration, errorMessage, errorContext, inputTokens, outputTokens, getTargetPlatform());
|
|
176
|
+
trackToolInvocation(toolName, success, duration, errorMessage, errorContext, inputTokens, outputTokens, getTargetPlatform(), emptyResult);
|
|
166
177
|
}
|
|
167
178
|
});
|
|
168
179
|
}
|
|
@@ -556,7 +567,9 @@ registerToolWithTelemetry("get_logs", {
|
|
|
556
567
|
}
|
|
557
568
|
]
|
|
558
569
|
};
|
|
559
|
-
}
|
|
570
|
+
},
|
|
571
|
+
// Empty result detector: buffer has no entries at all
|
|
572
|
+
() => logBuffer.size === 0);
|
|
560
573
|
// Tool: Search logs
|
|
561
574
|
registerToolWithTelemetry("search_logs", {
|
|
562
575
|
description: "Search console logs for text (case-insensitive)",
|
|
@@ -801,6 +814,13 @@ registerToolWithTelemetry("execute_in_app", {
|
|
|
801
814
|
}
|
|
802
815
|
]
|
|
803
816
|
};
|
|
817
|
+
},
|
|
818
|
+
// Empty result detector: successful execution but no meaningful output
|
|
819
|
+
(result) => {
|
|
820
|
+
if (result?.isError)
|
|
821
|
+
return false;
|
|
822
|
+
const text = result?.content?.[0]?.text;
|
|
823
|
+
return text === undefined || text === "" || text === "undefined" || text === "null";
|
|
804
824
|
});
|
|
805
825
|
// Tool: List debug globals available in the app
|
|
806
826
|
registerToolWithTelemetry("list_debug_globals", {
|
|
@@ -1369,7 +1389,7 @@ registerToolWithTelemetry("inspect_at_point", {
|
|
|
1369
1389
|
});
|
|
1370
1390
|
// Tool: Get network requests
|
|
1371
1391
|
registerToolWithTelemetry("get_network_requests", {
|
|
1372
|
-
description: "Retrieve captured network requests from connected React Native app. Shows URL, method, status, and timing.
|
|
1392
|
+
description: "Retrieve captured network requests from connected React Native app. Shows URL, method, status, and timing. Note: On Bridgeless targets (Expo SDK 52+) without the SDK, capture may miss early startup requests. Install react-native-ai-devtools-sdk for full capture with headers and response bodies. Tip: Use summary=true first for stats overview.",
|
|
1373
1393
|
inputSchema: {
|
|
1374
1394
|
maxRequests: z
|
|
1375
1395
|
.number()
|
|
@@ -1391,6 +1411,53 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1391
1411
|
.describe("Return statistics only (count, methods, domains, status codes). Use for quick overview.")
|
|
1392
1412
|
}
|
|
1393
1413
|
}, async ({ maxRequests, method, urlPattern, status, format, summary }) => {
|
|
1414
|
+
// Check if SDK is installed — prefer SDK data over CDP/interceptor buffer
|
|
1415
|
+
const sdkAvailable = await isSDKInstalled();
|
|
1416
|
+
if (sdkAvailable) {
|
|
1417
|
+
if (summary) {
|
|
1418
|
+
const sdkStats = await readSDKNetworkStats();
|
|
1419
|
+
if (sdkStats.success) {
|
|
1420
|
+
const s = sdkStats.data;
|
|
1421
|
+
const lines = [];
|
|
1422
|
+
lines.push(`Total requests: ${s.total}`);
|
|
1423
|
+
lines.push(`Completed: ${s.completed}`);
|
|
1424
|
+
lines.push(`Errors: ${s.errors}`);
|
|
1425
|
+
if (s.avgDuration != null)
|
|
1426
|
+
lines.push(`Avg duration: ${s.avgDuration}ms`);
|
|
1427
|
+
if (s.byMethod && Object.keys(s.byMethod).length > 0) {
|
|
1428
|
+
lines.push("\nBy Method:");
|
|
1429
|
+
for (const [m, c] of Object.entries(s.byMethod))
|
|
1430
|
+
lines.push(` ${m}: ${c}`);
|
|
1431
|
+
}
|
|
1432
|
+
if (s.byStatus && Object.keys(s.byStatus).length > 0) {
|
|
1433
|
+
lines.push("\nBy Status:");
|
|
1434
|
+
for (const [st, c] of Object.entries(s.byStatus))
|
|
1435
|
+
lines.push(` ${st}: ${c}`);
|
|
1436
|
+
}
|
|
1437
|
+
if (s.byDomain && Object.keys(s.byDomain).length > 0) {
|
|
1438
|
+
lines.push("\nBy Domain:");
|
|
1439
|
+
for (const [d, c] of Object.entries(s.byDomain).sort((a, b) => b[1] - a[1]).slice(0, 10))
|
|
1440
|
+
lines.push(` ${d}: ${c}`);
|
|
1441
|
+
}
|
|
1442
|
+
return { content: [{ type: "text", text: `Network Summary (SDK):\n\n${lines.join("\n")}` }] };
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
const sdkResult = await readSDKNetworkRequests({ count: maxRequests, method, urlPattern, status });
|
|
1446
|
+
if (sdkResult.success && sdkResult.data) {
|
|
1447
|
+
const entries = sdkResult.data;
|
|
1448
|
+
if (entries.length === 0) {
|
|
1449
|
+
return { content: [{ type: "text", text: "No network requests captured yet." }] };
|
|
1450
|
+
}
|
|
1451
|
+
const lines = entries.map((r) => {
|
|
1452
|
+
const time = new Date(r.timestamp).toLocaleTimeString();
|
|
1453
|
+
const st = r.status ?? "pending";
|
|
1454
|
+
const dur = r.duration != null ? `${r.duration}ms` : "-";
|
|
1455
|
+
return `[${r.id}] ${time} ${r.method} ${st} ${dur} ${r.url}`;
|
|
1456
|
+
});
|
|
1457
|
+
return { content: [{ type: "text", text: `Network Requests (${entries.length} entries, SDK):\n\n${lines.join("\n")}` }] };
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
// Fallback: read from in-process buffer (CDP/interceptor)
|
|
1394
1461
|
// Return summary if requested
|
|
1395
1462
|
if (summary) {
|
|
1396
1463
|
const stats = getNetworkStats(networkBuffer);
|
|
@@ -1398,6 +1465,9 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1398
1465
|
if (networkBuffer.size === 0) {
|
|
1399
1466
|
const connStatus = await checkAndEnsureConnection();
|
|
1400
1467
|
connectionWarning = connStatus.message ? `\n\n${connStatus.message}` : "";
|
|
1468
|
+
if (!sdkAvailable) {
|
|
1469
|
+
connectionWarning += "\n\n[TIP] For full network capture including startup requests and response bodies, install the SDK: npm install react-native-ai-devtools-sdk";
|
|
1470
|
+
}
|
|
1401
1471
|
}
|
|
1402
1472
|
return {
|
|
1403
1473
|
content: [
|
|
@@ -1419,6 +1489,9 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1419
1489
|
if (count === 0) {
|
|
1420
1490
|
const connStatus = await checkAndEnsureConnection();
|
|
1421
1491
|
connectionWarning = connStatus.message ? `\n\n${connStatus.message}` : "";
|
|
1492
|
+
if (!sdkAvailable) {
|
|
1493
|
+
connectionWarning += "\n\n[TIP] For full network capture including startup requests and response bodies, install the SDK: npm install react-native-ai-devtools-sdk";
|
|
1494
|
+
}
|
|
1422
1495
|
}
|
|
1423
1496
|
else {
|
|
1424
1497
|
const passive = getPassiveConnectionStatus();
|
|
@@ -1461,7 +1534,9 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1461
1534
|
}
|
|
1462
1535
|
]
|
|
1463
1536
|
};
|
|
1464
|
-
}
|
|
1537
|
+
},
|
|
1538
|
+
// Empty result detector: buffer has no entries at all
|
|
1539
|
+
() => networkBuffer.size === 0);
|
|
1465
1540
|
// Tool: Search network requests
|
|
1466
1541
|
registerToolWithTelemetry("search_network", {
|
|
1467
1542
|
description: "Search network requests by URL pattern (case-insensitive)",
|
|
@@ -1511,7 +1586,7 @@ registerToolWithTelemetry("search_network", {
|
|
|
1511
1586
|
});
|
|
1512
1587
|
// Tool: Get request details
|
|
1513
1588
|
registerToolWithTelemetry("get_request_details", {
|
|
1514
|
-
description: "Get full details of a specific network request including headers, body, and timing. Use get_network_requests first to find the request ID.",
|
|
1589
|
+
description: "Get full details of a specific network request including headers, body, and timing. With the SDK installed, includes full request/response bodies. Without SDK, bodies are not available on most targets. Use get_network_requests first to find the request ID.",
|
|
1515
1590
|
inputSchema: {
|
|
1516
1591
|
requestId: z.string().describe("The request ID to get details for"),
|
|
1517
1592
|
maxBodyLength: z.coerce
|
|
@@ -1526,6 +1601,53 @@ registerToolWithTelemetry("get_request_details", {
|
|
|
1526
1601
|
.describe("Disable body truncation. Tip: Use when you need to inspect full JSON payloads.")
|
|
1527
1602
|
}
|
|
1528
1603
|
}, async ({ requestId, maxBodyLength, verbose }) => {
|
|
1604
|
+
// Check SDK first — it has full headers and body
|
|
1605
|
+
const sdkAvailable = await isSDKInstalled();
|
|
1606
|
+
if (sdkAvailable) {
|
|
1607
|
+
const sdkResult = await readSDKNetworkRequest(requestId);
|
|
1608
|
+
if (sdkResult.success && sdkResult.data) {
|
|
1609
|
+
const r = sdkResult.data;
|
|
1610
|
+
const lines = [];
|
|
1611
|
+
lines.push(`=== ${r.method} ${r.url} ===`);
|
|
1612
|
+
lines.push(`Request ID: ${r.id}`);
|
|
1613
|
+
lines.push(`Time: ${new Date(r.timestamp).toISOString()}`);
|
|
1614
|
+
lines.push(`Status: ${r.status ?? "pending"} ${r.statusText ?? ""}`);
|
|
1615
|
+
if (r.duration != null)
|
|
1616
|
+
lines.push(`Duration: ${r.duration}ms`);
|
|
1617
|
+
if (r.mimeType)
|
|
1618
|
+
lines.push(`Content-Type: ${r.mimeType}`);
|
|
1619
|
+
if (r.error)
|
|
1620
|
+
lines.push(`Error: ${r.error}`);
|
|
1621
|
+
if (r.requestHeaders && Object.keys(r.requestHeaders).length > 0) {
|
|
1622
|
+
lines.push("\n--- Request Headers ---");
|
|
1623
|
+
for (const [k, v] of Object.entries(r.requestHeaders))
|
|
1624
|
+
lines.push(`${k}: ${v}`);
|
|
1625
|
+
}
|
|
1626
|
+
if (r.requestBody) {
|
|
1627
|
+
lines.push("\n--- Request Body ---");
|
|
1628
|
+
let body = r.requestBody;
|
|
1629
|
+
if (!verbose && maxBodyLength > 0 && body.length > maxBodyLength) {
|
|
1630
|
+
body = body.slice(0, maxBodyLength) + `... [truncated: ${r.requestBody.length} chars]`;
|
|
1631
|
+
}
|
|
1632
|
+
lines.push(body);
|
|
1633
|
+
}
|
|
1634
|
+
if (r.responseHeaders && Object.keys(r.responseHeaders).length > 0) {
|
|
1635
|
+
lines.push("\n--- Response Headers ---");
|
|
1636
|
+
for (const [k, v] of Object.entries(r.responseHeaders))
|
|
1637
|
+
lines.push(`${k}: ${v}`);
|
|
1638
|
+
}
|
|
1639
|
+
if (r.responseBody) {
|
|
1640
|
+
lines.push("\n--- Response Body ---");
|
|
1641
|
+
let body = r.responseBody;
|
|
1642
|
+
if (!verbose && maxBodyLength > 0 && body.length > maxBodyLength) {
|
|
1643
|
+
body = body.slice(0, maxBodyLength) + `... [truncated: ${r.responseBody.length} chars]`;
|
|
1644
|
+
}
|
|
1645
|
+
lines.push(body);
|
|
1646
|
+
}
|
|
1647
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
// Fallback: read from in-process buffer
|
|
1529
1651
|
const request = networkBuffer.get(requestId);
|
|
1530
1652
|
if (!request) {
|
|
1531
1653
|
const status = await checkAndEnsureConnection();
|
|
@@ -1575,18 +1697,28 @@ registerToolWithTelemetry("get_network_stats", {
|
|
|
1575
1697
|
}
|
|
1576
1698
|
]
|
|
1577
1699
|
};
|
|
1578
|
-
}
|
|
1700
|
+
},
|
|
1701
|
+
// Empty result detector: buffer has no entries at all
|
|
1702
|
+
() => networkBuffer.size === 0);
|
|
1579
1703
|
// Tool: Clear network requests
|
|
1580
1704
|
registerToolWithTelemetry("clear_network", {
|
|
1581
1705
|
description: "Clear the network request buffer",
|
|
1582
1706
|
inputSchema: {}
|
|
1583
1707
|
}, async () => {
|
|
1584
|
-
|
|
1708
|
+
let totalCleared = networkBuffer.clear();
|
|
1709
|
+
// Also clear SDK buffer if available
|
|
1710
|
+
const sdkAvailable = await isSDKInstalled();
|
|
1711
|
+
if (sdkAvailable) {
|
|
1712
|
+
const sdkResult = await clearSDKNetwork();
|
|
1713
|
+
if (sdkResult.success && sdkResult.count) {
|
|
1714
|
+
totalCleared += sdkResult.count;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1585
1717
|
return {
|
|
1586
1718
|
content: [
|
|
1587
1719
|
{
|
|
1588
1720
|
type: "text",
|
|
1589
|
-
text: `Cleared ${
|
|
1721
|
+
text: `Cleared ${totalCleared} network requests from buffer.`
|
|
1590
1722
|
}
|
|
1591
1723
|
]
|
|
1592
1724
|
};
|