@tonyclaw/agent-inspector 3.0.46 → 3.0.48

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.
Files changed (40) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +6791 -6006
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-D18yUq36.mjs → router-Cj10mv-Z.mjs} +334 -5
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-DAUuIJ6G.js → CompareDrawer-uRiXFOCp.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-BRBVjOWI.js → InspectorPet-j-r6xk0V.js} +1 -1
  8. package/.output/ui/assets/{ProxyViewerContainer-D7Sq0ctc.js → ProxyViewerContainer-DnUrkMJF.js} +23 -23
  9. package/.output/ui/assets/{ReplayDialog-nDLsjOhs.js → ReplayDialog-B2URyHMv.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-zd4BDgL0.js → RequestAnatomy-CK0M0CJO.js} +1 -1
  11. package/.output/ui/assets/{ResponseView-AdIXGcSn.js → ResponseView-CYSVA4lT.js} +1 -1
  12. package/.output/ui/assets/{StreamingChunkSequence-CesTz8He.js → StreamingChunkSequence-CHf54ZUV.js} +1 -1
  13. package/.output/ui/assets/{_sessionId-QnBomgPD.js → _sessionId-BXULpTNP.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-BAfzrhSU.js → _sessionId-E4lbj5-I.js} +1 -1
  15. package/.output/ui/assets/{index-D0rtCN9V.js → index-B4ixI3kG.js} +1 -1
  16. package/.output/ui/assets/{index-BcsdxBAd.js → index-BJ-71WLq.js} +1 -1
  17. package/.output/ui/assets/{index-DHpwr08Z.js → index-CW0ab9r3.js} +2 -2
  18. package/.output/ui/assets/{index-dNuk2dsU.js → index-Ce-lO51c.js} +1 -1
  19. package/.output/ui/assets/{json-viewer-BvFHglMb.js → json-viewer-C6E8aLaV.js} +1 -1
  20. package/.output/ui/assets/{jszip.min-B5Z7gNZB.js → jszip.min-DjWdTBbp.js} +1 -1
  21. package/.output/ui/index.html +1 -1
  22. package/README.md +53 -0
  23. package/package.json +17 -3
  24. package/src/backend/routes/api/-instances.ts +7 -109
  25. package/src/cli/instance.ts +21 -0
  26. package/src/cli/instanceApi.ts +213 -0
  27. package/src/cli/instanceArgs.ts +16 -0
  28. package/src/cli/instanceControl.ts +10 -0
  29. package/src/cli/instanceModel.ts +9 -0
  30. package/src/cli/rustBackendPackage.ts +458 -0
  31. package/src/cli.ts +171 -14
  32. package/src/components/ProxyViewer.tsx +4 -13
  33. package/src/components/proxy-viewer/proxyViewerLogic.ts +9 -0
  34. package/src/lib/backendImplementation.ts +16 -0
  35. package/src/lib/instanceContract.ts +3 -0
  36. package/src/lib/managedInstance.ts +9 -1
  37. package/src/mcp/instanceHandlers.ts +1 -0
  38. package/src/proxy/identityProxy.ts +148 -0
  39. package/src/proxy/sessionArchive.ts +0 -8
  40. package/src/proxy/sqliteLogIndex.ts +0 -40
package/src/cli.ts CHANGED
@@ -7,6 +7,13 @@ import { dirname, join, resolve as resolvePath } from "node:path";
7
7
  import { existsSync } from "node:fs";
8
8
  import type { Readable, Writable } from "node:stream";
9
9
  import packageJson from "../package.json";
10
+ import {
11
+ AGENT_INSPECTOR_BACKEND_ENV,
12
+ AGENT_INSPECTOR_BACKEND_HEADER,
13
+ BackendImplementationSchema,
14
+ parseBackendImplementation,
15
+ type BackendImplementation,
16
+ } from "./lib/backendImplementation.js";
10
17
  import {
11
18
  AGENT_INSPECTOR_BASE_PATH_ENV,
12
19
  AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV,
@@ -33,6 +40,11 @@ import {
33
40
  shouldSuppressServerOutputLine,
34
41
  type ServerOutputFilterContext,
35
42
  } from "./cli/startupOutput.js";
43
+ import {
44
+ resolvePreferredBackendImplementation,
45
+ resolveRustBackendPackage,
46
+ } from "./cli/rustBackendPackage.js";
47
+ import { handleNamedInstanceApi } from "./cli/instanceApi.js";
36
48
  import {
37
49
  closeIdentityProxyGracefully,
38
50
  forceCloseIdentityProxy,
@@ -87,6 +99,7 @@ Global options:
87
99
  -v, --version Print the package version
88
100
 
89
101
  Start options:
102
+ --backend <typescript|rust> Select the backend implementation (default: rust when native package is available)
90
103
  --backend-only, --headless Start the API, proxy, and MCP runtime without serving the UI
91
104
  --with-ui Serve the independently built UI through the protected ingress
92
105
  --port, -p <port> Public ingress port (default: 9527)
@@ -194,6 +207,33 @@ async function getRunningCaptureMode(
194
207
  }
195
208
  }
196
209
 
210
+ async function getRunningBackend(
211
+ port: number,
212
+ host?: string,
213
+ basePath = "",
214
+ ): Promise<BackendImplementation | null> {
215
+ const controller = new AbortController();
216
+ const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
217
+ try {
218
+ const response = await fetch(
219
+ appendBasePathToOrigin(urlForHost(port, probeHostForBindHost(host)), "/api/health", basePath),
220
+ {
221
+ cache: "no-store",
222
+ signal: controller.signal,
223
+ },
224
+ );
225
+ if (!response.ok) return null;
226
+ const header = response.headers.get(AGENT_INSPECTOR_BACKEND_HEADER);
227
+ if (header === null) return "typescript";
228
+ const parsed = BackendImplementationSchema.safeParse(header.trim().toLowerCase());
229
+ return parsed.success ? parsed.data : null;
230
+ } catch {
231
+ return null;
232
+ } finally {
233
+ clearTimeout(timeout);
234
+ }
235
+ }
236
+
197
237
  function isPortAcceptingConnections(port: number, host?: string): Promise<boolean> {
198
238
  return new Promise((resolve) => {
199
239
  const socket = createConnection({ host: probeHostForBindHost(host), port });
@@ -293,11 +333,35 @@ type ServerCommand = {
293
333
  args: string[];
294
334
  };
295
335
 
296
- function resolveServerCommand(outputDir: string, serverPath: string): ServerCommand {
336
+ type ServerCommandResolution =
337
+ | { kind: "success"; command: ServerCommand }
338
+ | { kind: "error"; message: string };
339
+
340
+ function resolveServerCommand(
341
+ outputDir: string,
342
+ serverPath: string,
343
+ backend: BackendImplementation,
344
+ ): ServerCommandResolution {
345
+ if (backend === "rust") {
346
+ const resolved = resolveRustBackendPackage({ rootVersion: packageJson.version });
347
+ return resolved.kind === "error"
348
+ ? {
349
+ kind: "error",
350
+ message: `Cannot start the Rust backend (${resolved.error.code}): ${resolved.error.message}`,
351
+ }
352
+ : {
353
+ kind: "success",
354
+ command: { command: resolved.value.command, args: [...resolved.value.args] },
355
+ };
356
+ }
297
357
  const brandedRuntime = join(outputDir, BRANDED_WINDOWS_RUNTIME_EXE);
298
- return process.platform === "win32" && existsSync(brandedRuntime)
299
- ? { command: brandedRuntime, args: [serverPath] }
300
- : { command: process.execPath, args: [serverPath] };
358
+ return {
359
+ kind: "success",
360
+ command:
361
+ process.platform === "win32" && existsSync(brandedRuntime)
362
+ ? { command: brandedRuntime, args: [serverPath] }
363
+ : { command: process.execPath, args: [serverPath] },
364
+ };
301
365
  }
302
366
 
303
367
  function parseCaptureMode(value: string | undefined): CaptureMode | null {
@@ -385,6 +449,7 @@ function normalizeExplicitPublicOrigin(value: string | undefined): string | null
385
449
  }
386
450
 
387
451
  type BackgroundSupervisorOptions = {
452
+ backend: BackendImplementation;
388
453
  port: number;
389
454
  host: string | undefined;
390
455
  configDir: string | undefined;
@@ -396,7 +461,15 @@ type BackgroundSupervisorOptions = {
396
461
  };
397
462
 
398
463
  function buildBackgroundSupervisorArgs(options: BackgroundSupervisorOptions): string[] {
399
- const args = ["--no-open", "--port", String(options.port), "--mode", options.captureMode];
464
+ const args = [
465
+ "--no-open",
466
+ "--backend",
467
+ options.backend,
468
+ "--port",
469
+ String(options.port),
470
+ "--mode",
471
+ options.captureMode,
472
+ ];
400
473
  if (options.host !== undefined) {
401
474
  args.push("--host", options.host);
402
475
  }
@@ -449,6 +522,7 @@ async function tryStartIdentityProxy(
449
522
  allowedUiOrigins: readonly string[],
450
523
  uiDirectory: string | undefined,
451
524
  publicOrigin: string | undefined,
525
+ backend: BackendImplementation,
452
526
  ): Promise<import("node:http").Server | null> {
453
527
  try {
454
528
  const server = await startIdentityProxy({
@@ -456,6 +530,7 @@ async function tryStartIdentityProxy(
456
530
  listenHost,
457
531
  upstreamHost,
458
532
  upstreamPort,
533
+ backendImplementation: backend,
459
534
  restrictRemoteControl: !isLoopbackHost(listenHost),
460
535
  allowRemoteControl: process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1",
461
536
  controlToken: process.env["AGENT_INSPECTOR_CONTROL_TOKEN"],
@@ -472,6 +547,7 @@ async function tryStartIdentityProxy(
472
547
  maxRequestBytes: readPositiveIntegerEnv("AGENT_INSPECTOR_MAX_REQUEST_BYTES"),
473
548
  requestTimeoutMs: readPositiveIntegerEnv("AGENT_INSPECTOR_REQUEST_TIMEOUT_MS"),
474
549
  managedInstance,
550
+ instanceApiHandler: backend === "rust" ? handleNamedInstanceApi : undefined,
475
551
  });
476
552
  return server;
477
553
  } catch (err) {
@@ -495,6 +571,16 @@ async function runStart(args: string[]): Promise<void> {
495
571
  const envHost = process.env["NITRO_HOST"] ?? process.env["HOST"];
496
572
  const envMode =
497
573
  process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
574
+ const rawEnvBackend = process.env[AGENT_INSPECTOR_BACKEND_ENV];
575
+ const envBackend =
576
+ rawEnvBackend === undefined ? undefined : parseBackendImplementation(rawEnvBackend);
577
+ if (envBackend === null) {
578
+ console.error(
579
+ `Invalid ${AGENT_INSPECTOR_BACKEND_ENV}. Use typescript or rust; no backend was started.`,
580
+ );
581
+ process.exitCode = 1;
582
+ return;
583
+ }
498
584
 
499
585
  let port = portDefault;
500
586
  let host =
@@ -510,6 +596,8 @@ async function runStart(args: string[]): Promise<void> {
510
596
  let enableIdentityProxy = true;
511
597
  let legacyAliasEnabled = false;
512
598
  let serveUi = process.env["AGENT_INSPECTOR_BACKEND_ONLY"] !== "1";
599
+ let backend: BackendImplementation = envBackend ?? "typescript";
600
+ let backendWasSpecified = envBackend !== undefined;
513
601
 
514
602
  if (envMode !== undefined && envMode !== "") {
515
603
  const parsedMode = parseCaptureMode(envMode);
@@ -556,6 +644,22 @@ async function runStart(args: string[]): Promise<void> {
556
644
  captureModeWasSpecified = true;
557
645
  continue;
558
646
  }
647
+ if (arg.startsWith("--backend=")) {
648
+ const parsedBackend = BackendImplementationSchema.safeParse(
649
+ arg
650
+ .slice(arg.indexOf("=") + 1)
651
+ .trim()
652
+ .toLowerCase(),
653
+ );
654
+ if (!parsedBackend.success) {
655
+ console.error("Invalid backend implementation. Use typescript or rust.");
656
+ process.exitCode = 1;
657
+ return;
658
+ }
659
+ backend = parsedBackend.data;
660
+ backendWasSpecified = true;
661
+ continue;
662
+ }
559
663
  switch (arg) {
560
664
  case "--port":
561
665
  case "-p":
@@ -574,6 +678,20 @@ async function runStart(args: string[]): Promise<void> {
574
678
  i++;
575
679
  break;
576
680
  }
681
+ case "--backend": {
682
+ const parsedBackend = BackendImplementationSchema.safeParse(
683
+ args[i + 1]?.trim().toLowerCase(),
684
+ );
685
+ if (!parsedBackend.success) {
686
+ console.error("Invalid backend implementation. Use typescript or rust.");
687
+ process.exitCode = 1;
688
+ return;
689
+ }
690
+ backend = parsedBackend.data;
691
+ backendWasSpecified = true;
692
+ i++;
693
+ break;
694
+ }
577
695
  case "--no-open":
578
696
  open = false;
579
697
  openWasSpecified = true;
@@ -662,6 +780,20 @@ async function runStart(args: string[]): Promise<void> {
662
780
  return;
663
781
  }
664
782
 
783
+ if (!backendWasSpecified) {
784
+ backend =
785
+ managedLaunch?.backend ??
786
+ resolvePreferredBackendImplementation({ rootVersion: packageJson.version });
787
+ }
788
+
789
+ if (managedLaunch !== null && managedLaunch.backend !== backend) {
790
+ console.error(
791
+ `Managed instance backend mismatch: launch identity requires ${managedLaunch.backend}, but ${backend} was selected.`,
792
+ );
793
+ process.exitCode = 1;
794
+ return;
795
+ }
796
+
665
797
  if (!enableIdentityProxy && process.env["AGENT_INSPECTOR_ALLOW_UNPROTECTED_INGRESS"] !== "1") {
666
798
  console.error(
667
799
  "Refusing --no-identity-proxy because it also disables Host, CSRF, credential-scope, and first-hop budget enforcement. Keep the protected ingress enabled or set AGENT_INSPECTOR_ALLOW_UNPROTECTED_INGRESS=1 explicitly for temporary diagnostics.",
@@ -704,6 +836,16 @@ async function runStart(args: string[]): Promise<void> {
704
836
  return;
705
837
  }
706
838
 
839
+ const outputDir = __dirname;
840
+ const serverPath = join(outputDir, "../.output/server/index.mjs");
841
+ const serverCommandResolution = resolveServerCommand(outputDir, serverPath, backend);
842
+ if (serverCommandResolution.kind === "error") {
843
+ console.error(serverCommandResolution.message);
844
+ process.exitCode = 1;
845
+ return;
846
+ }
847
+ const serverCommand = serverCommandResolution.command;
848
+
707
849
  /**
708
850
  * Check if a port is in use and kill the process using it
709
851
  */
@@ -770,6 +912,16 @@ async function runStart(args: string[]): Promise<void> {
770
912
  : appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/api/mcp", basePath);
771
913
 
772
914
  if (!forceRestart && (await isInspectorHealthy(port, host, basePath))) {
915
+ const runningBackend = await getRunningBackend(port, host, basePath);
916
+ if (runningBackend !== backend) {
917
+ console.error(
918
+ runningBackend === null
919
+ ? `A healthy Agent Inspector is already running at ${surfaceBaseUrl}, but its backend implementation cannot be verified. Use --force-restart to replace it safely.`
920
+ : `Agent Inspector at ${surfaceBaseUrl} uses ${runningBackend}, but ${backend} was requested. Use --force-restart to switch implementations.`,
921
+ );
922
+ process.exitCode = 1;
923
+ return;
924
+ }
773
925
  console.log(
774
926
  `agent-inspector ${serveUi ? "is already running" : "backend is already running"} at ${surfaceBaseUrl}`,
775
927
  );
@@ -816,6 +968,7 @@ async function runStart(args: string[]): Promise<void> {
816
968
  }
817
969
 
818
970
  const supervisorArgs = buildBackgroundSupervisorArgs({
971
+ backend,
819
972
  port,
820
973
  host,
821
974
  configDir,
@@ -861,11 +1014,6 @@ async function runStart(args: string[]): Promise<void> {
861
1014
  return;
862
1015
  }
863
1016
 
864
- // Compute server path
865
- const outputDir = __dirname;
866
- const serverPath = join(outputDir, "../.output/server/index.mjs");
867
- const serverCommand = resolveServerCommand(outputDir, serverPath);
868
-
869
1017
  // Start the server with the branded Windows runtime when postinstall created it.
870
1018
  const serverEnv = { ...process.env };
871
1019
  const currentCliEntry = process.env[AGENT_INSPECTOR_CLI_ENTRY_ENV] ?? process.argv[1];
@@ -902,6 +1050,7 @@ async function runStart(args: string[]): Promise<void> {
902
1050
  serverEnv["AGENT_INSPECTOR_PUBLIC_PORT"] = String(port);
903
1051
  serverEnv["PROXY_PORT"] = String(port);
904
1052
  serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
1053
+ serverEnv[AGENT_INSPECTOR_BACKEND_ENV] = backend;
905
1054
  serverEnv["AGENT_INSPECTOR_BACKEND_ONLY"] = serveUi ? "0" : "1";
906
1055
  const workerDir = join(__dirname, "workers");
907
1056
  if (existsSync(workerDir)) {
@@ -913,9 +1062,13 @@ async function runStart(args: string[]): Promise<void> {
913
1062
  ? background
914
1063
  ? ["ignore", "ignore", "ignore"]
915
1064
  : ["ignore", "pipe", "pipe"]
916
- : background
917
- ? ["ignore", "ignore", "ignore", "ipc"]
918
- : ["ignore", "pipe", "pipe", "ipc"],
1065
+ : backend === "typescript"
1066
+ ? background
1067
+ ? ["ignore", "ignore", "ignore", "ipc"]
1068
+ : ["ignore", "pipe", "pipe", "ipc"]
1069
+ : background
1070
+ ? ["ignore", "ignore", "ignore"]
1071
+ : ["ignore", "pipe", "pipe"],
919
1072
  detached: background,
920
1073
  env: serverEnv,
921
1074
  windowsHide: background,
@@ -947,7 +1100,7 @@ async function runStart(args: string[]): Promise<void> {
947
1100
  const requestSupervisorShutdown = (signal: "SIGINT" | "SIGTERM"): void => {
948
1101
  if (supervisorShutdownPromise !== null) return;
949
1102
  for (const proxy of identityProxies) stopIdentityProxyAdmission(proxy.server);
950
- if (managedLaunch !== null && serverProcess.connected) {
1103
+ if (managedLaunch !== null && backend === "typescript" && serverProcess.connected) {
951
1104
  serverProcess.send(managedInstanceShutdownMessage(managedLaunch.controlToken), (error) => {
952
1105
  if (error !== null && serverProcess.exitCode === null) serverProcess.kill(signal);
953
1106
  });
@@ -994,6 +1147,7 @@ async function runStart(args: string[]): Promise<void> {
994
1147
  controlToken: managedLaunch.controlToken,
995
1148
  supervisorPid: process.pid,
996
1149
  startedAt: managedLaunch.startedAt,
1150
+ backend,
997
1151
  publicPort: port,
998
1152
  upstreamPort,
999
1153
  requestShutdown: () => requestSupervisorShutdown("SIGTERM"),
@@ -1008,6 +1162,7 @@ async function runStart(args: string[]): Promise<void> {
1008
1162
  uiOriginAllowlist.origins,
1009
1163
  uiDirectory,
1010
1164
  publicOrigin,
1165
+ backend,
1011
1166
  );
1012
1167
  if (primaryProxy === null) {
1013
1168
  console.error(
@@ -1032,6 +1187,7 @@ async function runStart(args: string[]): Promise<void> {
1032
1187
  uiOriginAllowlist.origins,
1033
1188
  uiDirectory,
1034
1189
  publicOrigin,
1190
+ backend,
1035
1191
  );
1036
1192
  if (legacyProxy !== null) {
1037
1193
  identityProxies.push({ label: "legacy", port: legacyAliasPort, server: legacyProxy });
@@ -1104,6 +1260,7 @@ async function runStart(args: string[]): Promise<void> {
1104
1260
  `${serveUi ? "UI and backend" : "Backend control and agent runtime"} running at ${surfaceBaseUrl}`,
1105
1261
  );
1106
1262
  console.log(` Capture mode: ${captureMode}`);
1263
+ console.log(` Backend: ${backend}`);
1107
1264
  console.log(` Proxy: ${proxyUrl}`);
1108
1265
  printAccessHints(port, host, basePath, serveUi, publicOrigin);
1109
1266
  if (legacyUrl !== null && legacyProxyUrl !== null && legacyMcpUrl !== null) {
@@ -61,6 +61,7 @@ import { useProviders } from "../lib/useProviders";
61
61
  import { groupLogsByConversation, type ConversationGroupData } from "./proxy-viewer";
62
62
  import {
63
63
  buildSessionSlateStats,
64
+ buildHomeConnectionCommands,
64
65
  COPYABLE_COMMAND_CONTAINER_CLASS_NAME,
65
66
  COPYABLE_COMMAND_TEXT_CLASS_NAME,
66
67
  exportRequiresConfirmation,
@@ -2148,19 +2149,9 @@ export function ProxyViewer({
2148
2149
  {liveEmptyStateCopy.showConnectionCommands && connectionEndpoints !== null && (
2149
2150
  <>
2150
2151
  <div className="flex flex-col items-center gap-2">
2151
- <CopyableCommand
2152
- command={`ANTHROPIC_BASE_URL=${connectionEndpoints.proxy} <your-tool>`}
2153
- />
2154
- <CopyableCommand
2155
- command={`LLM_BASE_URL=${connectionEndpoints.proxy} opencode`}
2156
- />
2157
- <CopyableCommand
2158
- command={`OPENAI_BASE_URL=${connectionEndpoints.openAiV1} <your-tool>`}
2159
- />
2160
- <CopyableCommand
2161
- command={`base_url = "${connectionEndpoints.openAiV1}"`}
2162
- />
2163
- <CopyableCommand command={`MCP_URL=${connectionEndpoints.mcp}`} />
2152
+ {buildHomeConnectionCommands(connectionEndpoints).map((command) => (
2153
+ <CopyableCommand key={command} command={command} />
2154
+ ))}
2164
2155
  </div>
2165
2156
  <p className="mx-auto max-w-xl text-xs leading-relaxed text-muted-foreground">
2166
2157
  Container note: if your AI tool runs in a different container or host than
@@ -24,11 +24,20 @@ export type ConversationLogs = {
24
24
  logs: CapturedLog[];
25
25
  };
26
26
 
27
+ export type HomeConnectionEndpoints = {
28
+ proxy: string;
29
+ mcp: string;
30
+ };
31
+
27
32
  export const COPYABLE_COMMAND_CONTAINER_CLASS_NAME =
28
33
  "flex w-full min-w-0 items-center gap-2 rounded-md border border-border/70 bg-muted/35 px-3 py-2 sm:w-auto sm:max-w-full";
29
34
  export const COPYABLE_COMMAND_TEXT_CLASS_NAME =
30
35
  "m-0 min-w-0 flex-1 overflow-x-auto font-mono text-xs text-foreground sm:text-sm";
31
36
 
37
+ export function buildHomeConnectionCommands(endpoints: HomeConnectionEndpoints): readonly string[] {
38
+ return [`PROXY_BASE_URL=${endpoints.proxy}`, `MCP_URL=${endpoints.mcp}`];
39
+ }
40
+
32
41
  export type SessionSlateStats = {
33
42
  status: SessionSlateStatus;
34
43
  statusLabel: string;
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+
3
+ export const AGENT_INSPECTOR_BACKEND_ENV = "AGENT_INSPECTOR_BACKEND";
4
+ export const AGENT_INSPECTOR_BACKEND_HEADER = "x-agent-inspector-backend";
5
+
6
+ export const BackendImplementationSchema = z.enum(["typescript", "rust"]);
7
+
8
+ export type BackendImplementation = z.infer<typeof BackendImplementationSchema>;
9
+
10
+ export function parseBackendImplementation(
11
+ value: string | undefined,
12
+ ): BackendImplementation | null {
13
+ if (value === undefined) return "typescript";
14
+ const parsed = BackendImplementationSchema.safeParse(value.trim().toLowerCase());
15
+ return parsed.success ? parsed.data : null;
16
+ }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { BackendImplementationSchema } from "./backendImplementation";
2
3
  import { normalizePublicBasePath } from "./basePath";
3
4
 
4
5
  export const INSTANCE_API_SCHEMA_VERSION = 1;
@@ -43,6 +44,7 @@ export const InstanceStatusOutputSchema = z
43
44
  upstreamPort: z.number().int().min(1).max(65_535),
44
45
  basePath: z.string().refine(isCanonicalPublicBasePath, "Invalid public Base Path"),
45
46
  captureMode: z.enum(["simple", "full"]),
47
+ backend: BackendImplementationSchema.default("typescript"),
46
48
  uiEnabled: z.boolean(),
47
49
  dataDir: z.string().min(1),
48
50
  supervisorPid: z.number().int().positive().nullable(),
@@ -124,6 +126,7 @@ export const InstanceStartRequestSchema = z
124
126
  )
125
127
  .optional(),
126
128
  captureMode: z.enum(["simple", "full"]).optional(),
129
+ backend: BackendImplementationSchema.optional(),
127
130
  uiEnabled: z.boolean().optional(),
128
131
  dataDir: z.string().trim().min(1).max(32_767).optional(),
129
132
  })
@@ -5,6 +5,7 @@ export const MANAGED_INSTANCE_SHUTDOWN_MESSAGE_TYPE = "agent-inspector:managed-s
5
5
  export const AGENT_INSPECTOR_CLI_ENTRY_ENV = "AGENT_INSPECTOR_CLI_ENTRY";
6
6
 
7
7
  export const MANAGED_INSTANCE_ENV = {
8
+ backend: "AGENT_INSPECTOR_INSTANCE_BACKEND",
8
9
  name: "AGENT_INSPECTOR_INSTANCE_NAME",
9
10
  instanceId: "AGENT_INSPECTOR_INSTANCE_ID",
10
11
  launchId: "AGENT_INSPECTOR_INSTANCE_LAUNCH_ID",
@@ -16,6 +17,7 @@ export const MANAGED_INSTANCE_ENV = {
16
17
  type ManagedInstanceEnv = Readonly<Record<string, string | undefined>>;
17
18
 
18
19
  export type ManagedInstanceLaunch = {
20
+ backend: BackendImplementation;
19
21
  name: string;
20
22
  instanceId: string;
21
23
  launchId: string;
@@ -47,6 +49,7 @@ export function readManagedInstanceLaunch(
47
49
  env: ManagedInstanceEnv = process.env,
48
50
  ): ManagedInstanceLaunchResult {
49
51
  const values = [
52
+ env[MANAGED_INSTANCE_ENV.backend],
50
53
  env[MANAGED_INSTANCE_ENV.name],
51
54
  env[MANAGED_INSTANCE_ENV.instanceId],
52
55
  env[MANAGED_INSTANCE_ENV.launchId],
@@ -59,6 +62,9 @@ export function readManagedInstanceLaunch(
59
62
  }
60
63
 
61
64
  const name = nonEmpty(env[MANAGED_INSTANCE_ENV.name]);
65
+ const backend = parseBackendImplementation(
66
+ nonEmpty(env[MANAGED_INSTANCE_ENV.backend]) ?? undefined,
67
+ );
62
68
  const instanceId = nonEmpty(env[MANAGED_INSTANCE_ENV.instanceId]);
63
69
  const launchId = nonEmpty(env[MANAGED_INSTANCE_ENV.launchId]);
64
70
  const controlToken = nonEmpty(env[MANAGED_INSTANCE_ENV.controlToken]);
@@ -66,6 +72,7 @@ export function readManagedInstanceLaunch(
66
72
  const upstreamPort = Number(env[MANAGED_INSTANCE_ENV.upstreamPort]);
67
73
  if (
68
74
  name === null ||
75
+ backend === null ||
69
76
  instanceId === null ||
70
77
  launchId === null ||
71
78
  controlToken === null ||
@@ -83,7 +90,7 @@ export function readManagedInstanceLaunch(
83
90
  }
84
91
  return {
85
92
  kind: "managed",
86
- launch: { name, instanceId, launchId, controlToken, startedAt, upstreamPort },
93
+ launch: { backend, name, instanceId, launchId, controlToken, startedAt, upstreamPort },
87
94
  };
88
95
  }
89
96
 
@@ -102,3 +109,4 @@ export function parseManagedInstanceShutdownMessage(
102
109
  ? { type: MANAGED_INSTANCE_SHUTDOWN_MESSAGE_TYPE, controlToken: value["controlToken"] }
103
110
  : null;
104
111
  }
112
+ import { parseBackendImplementation, type BackendImplementation } from "./backendImplementation";
@@ -90,6 +90,7 @@ function lifecycleRequest(input: InstanceLifecycleToolInput): InstanceStartReque
90
90
  basePath: input.basePath,
91
91
  host: input.host,
92
92
  captureMode: input.captureMode,
93
+ backend: input.backend,
93
94
  uiEnabled: input.uiEnabled,
94
95
  dataDir: input.dataDir,
95
96
  };