@tonyclaw/agent-inspector 2.0.14 → 2.0.16

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 (41) hide show
  1. package/.output/cli.js +107 -11
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BR_kCD7Q.js → CompareDrawer-DPs4UHwd.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-vc50msJj.js → ProxyViewerContainer-B1KylMPs.js} +32 -32
  5. package/.output/public/assets/{ReplayDialog-DsAYQBHZ.js → ReplayDialog-8ISC28XF.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-B4uqyUt8.js → RequestAnatomy-BhbsgfBO.js} +1 -1
  7. package/.output/public/assets/{ResponseView-C1vQL-fM.js → ResponseView-CyM7JXeA.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-BECMV-xf.js → StreamingChunkSequence-BaX0x38n.js} +1 -1
  9. package/.output/public/assets/_sessionId-CShO0OrR.js +1 -0
  10. package/.output/public/assets/index-BkdYiTpV.js +1 -0
  11. package/.output/public/assets/{main-D6wegveF.js → main-B2t-3SYj.js} +2 -2
  12. package/.output/server/{_sessionId-CeHY-3xa.mjs → _sessionId-CEbZTs68.mjs} +2 -2
  13. package/.output/server/_ssr/{CompareDrawer-blWGaGiT.mjs → CompareDrawer-CEo48Wsu.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-Dt_9BuM9.mjs → ProxyViewerContainer-5ekIK_jx.mjs} +64 -22
  15. package/.output/server/_ssr/{ReplayDialog-mzsq7AjW.mjs → ReplayDialog-C0nEvbYo.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-CDAcXMBK.mjs → RequestAnatomy-CT55prmM.mjs} +2 -2
  17. package/.output/server/_ssr/{ResponseView-B__xXCsZ.mjs → ResponseView-DBZSBCOD.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-BnJkTdXF.mjs → StreamingChunkSequence-BuVT7Oke.mjs} +2 -2
  19. package/.output/server/_ssr/{index-B2-uF9XT.mjs → index-Cw_zJVQv.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-BpEBhA7S.mjs → router-DxjqHipT.mjs} +89 -46
  22. package/.output/server/{_tanstack-start-manifest_v-CHekFSzj.mjs → _tanstack-start-manifest_v-xeOo5gW5.mjs} +1 -1
  23. package/.output/server/index.mjs +59 -59
  24. package/README.md +31 -1
  25. package/package.json +1 -1
  26. package/src/cli/templates/codex-skill-onboard.ts +8 -4
  27. package/src/cli/templates/skill-onboard.ts +15 -7
  28. package/src/cli.ts +91 -0
  29. package/src/components/OnboardingBanner.tsx +32 -9
  30. package/src/components/ProxyViewer.tsx +32 -24
  31. package/src/components/ProxyViewerContainer.tsx +2 -1
  32. package/src/components/providers/SettingsDialog.tsx +10 -0
  33. package/src/lib/runtimeConfig.ts +4 -0
  34. package/src/lib/useOnboarding.ts +9 -1
  35. package/src/lib/useStripConfig.ts +10 -2
  36. package/src/proxy/config.ts +33 -3
  37. package/src/proxy/handler.ts +30 -13
  38. package/src/proxy/logFinalizer.ts +4 -2
  39. package/src/routes/api/providers.$providerId.test.log.ts +17 -7
  40. package/.output/public/assets/_sessionId-3bjQPmHl.js +0 -1
  41. package/.output/public/assets/index-_7blCOK0.js +0 -1
@@ -3,7 +3,7 @@ import { ArrowDownRight, ArrowLeft, ArrowUpRight, Check, Copy, Download, Plus }
3
3
 
4
4
  import type { CapturedLog } from "../proxy/schemas";
5
5
  import { exportLogsAsZip } from "../lib/export-logs";
6
- import type { TimeDisplayFormat } from "../lib/runtimeConfig";
6
+ import type { CaptureMode, TimeDisplayFormat } from "../lib/runtimeConfig";
7
7
  import { formatTimestampRange } from "../lib/timeDisplay";
8
8
  import { cn, formatContextWindowTokens, formatTokens } from "../lib/utils";
9
9
  import { useProviders } from "../lib/useProviders";
@@ -599,6 +599,7 @@ export type ProxyViewerProps = {
599
599
  onClearGroup: (ids: number[]) => void;
600
600
  viewMode: "simple" | "full";
601
601
  onViewModeChange: (mode: "simple" | "full") => void;
602
+ captureMode: CaptureMode;
602
603
  /** Live strip-Claude-Code-billing-header flag, sourced once at the container. */
603
604
  strip: boolean;
604
605
  /** Slow-response threshold in seconds. `0` disables the warning indicator. */
@@ -625,6 +626,7 @@ export function ProxyViewer({
625
626
  onClearGroup,
626
627
  viewMode,
627
628
  onViewModeChange,
629
+ captureMode,
628
630
  strip,
629
631
  slowResponseThresholdSeconds,
630
632
  timeDisplayFormat,
@@ -821,30 +823,36 @@ export function ProxyViewer({
821
823
  ))}
822
824
  </SelectContent>
823
825
  </Select>
824
- <div className="flex items-center border border-border rounded-md overflow-hidden">
825
- <button
826
- type="button"
827
- onClick={() => onViewModeChange("simple")}
828
- className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
829
- viewMode === "simple"
830
- ? "bg-muted text-foreground"
831
- : "text-muted-foreground hover:bg-muted/50"
832
- }`}
833
- >
826
+ {captureMode === "full" ? (
827
+ <div className="flex items-center border border-border rounded-md overflow-hidden">
828
+ <button
829
+ type="button"
830
+ onClick={() => onViewModeChange("simple")}
831
+ className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
832
+ viewMode === "simple"
833
+ ? "bg-muted text-foreground"
834
+ : "text-muted-foreground hover:bg-muted/50"
835
+ }`}
836
+ >
837
+ Simple
838
+ </button>
839
+ <button
840
+ type="button"
841
+ onClick={() => onViewModeChange("full")}
842
+ className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
843
+ viewMode === "full"
844
+ ? "bg-muted text-foreground"
845
+ : "text-muted-foreground hover:bg-muted/50"
846
+ }`}
847
+ >
848
+ Full
849
+ </button>
850
+ </div>
851
+ ) : (
852
+ <div className="flex h-8 items-center rounded-md border border-border bg-muted px-3 text-xs text-foreground">
834
853
  Simple
835
- </button>
836
- <button
837
- type="button"
838
- onClick={() => onViewModeChange("full")}
839
- className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
840
- viewMode === "full"
841
- ? "bg-muted text-foreground"
842
- : "text-muted-foreground hover:bg-muted/50"
843
- }`}
844
- >
845
- Full
846
- </button>
847
- </div>
854
+ </div>
855
+ )}
848
856
  <div className="flex-1" />
849
857
  {!hasSessionContext && (
850
858
  <span className="text-muted-foreground text-xs font-mono">
@@ -330,7 +330,7 @@ export function ProxyViewerContainer({
330
330
 
331
331
  // Read the strip config once at the container so the virtualized list does
332
332
  // not need N independent SWR subscriptions per row.
333
- const { strip, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
333
+ const { strip, captureMode, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
334
334
 
335
335
  return (
336
336
  <>
@@ -352,6 +352,7 @@ export function ProxyViewerContainer({
352
352
  onClearGroup={handleClearGroup}
353
353
  viewMode={viewMode}
354
354
  onViewModeChange={setViewMode}
355
+ captureMode={captureMode}
355
356
  strip={strip}
356
357
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
357
358
  timeDisplayFormat={timeDisplayFormat}
@@ -196,6 +196,7 @@ function OnboardingSettingsTab(): JSX.Element {
196
196
  function ProxySettingsTab(): JSX.Element {
197
197
  const {
198
198
  strip,
199
+ captureMode,
199
200
  slowResponseThresholdSeconds,
200
201
  timeDisplayFormat,
201
202
  isLoading,
@@ -205,6 +206,7 @@ function ProxySettingsTab(): JSX.Element {
205
206
  } = useStripConfig();
206
207
  const [error, setError] = useState<string | null>(null);
207
208
  const [pending, setPending] = useState(false);
209
+ const alternateCaptureMode = captureMode === "full" ? "simple" : "full";
208
210
 
209
211
  const handleToggle = useCallback(
210
212
  async (next: boolean) => {
@@ -253,6 +255,14 @@ function ProxySettingsTab(): JSX.Element {
253
255
 
254
256
  return (
255
257
  <div className="space-y-4">
258
+ <div className="space-y-1">
259
+ <h3 className="text-sm font-semibold">Capture mode</h3>
260
+ <p className="text-xs text-muted-foreground">
261
+ This process is running in <code>{captureMode}</code> mode. Restart with{" "}
262
+ <code>agent-inspector --mode {alternateCaptureMode}</code> to change it.
263
+ </p>
264
+ </div>
265
+
256
266
  <div className="space-y-1">
257
267
  <h3 className="text-sm font-semibold">Claude Code billing header</h3>
258
268
  <p className="text-xs text-muted-foreground">
@@ -3,10 +3,13 @@ import { z } from "zod";
3
3
  export const DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS = 10;
4
4
  export const MAX_SLOW_RESPONSE_THRESHOLD_SECONDS = 600;
5
5
  export const DEFAULT_TIME_DISPLAY_FORMAT = "time";
6
+ export const DEFAULT_CAPTURE_MODE = "simple";
6
7
 
7
8
  export const TimeDisplayFormatSchema = z.enum(["time", "full"]);
9
+ export const CaptureModeSchema = z.enum(["simple", "full"]);
8
10
 
9
11
  export type TimeDisplayFormat = z.infer<typeof TimeDisplayFormatSchema>;
12
+ export type CaptureMode = z.infer<typeof CaptureModeSchema>;
10
13
 
11
14
  /**
12
15
  * Schema for the runtime proxy config. Shared between server
@@ -26,6 +29,7 @@ export const RuntimeConfigSchema = z.object({
26
29
  .max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS)
27
30
  .default(DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS),
28
31
  timeDisplayFormat: TimeDisplayFormatSchema.default(DEFAULT_TIME_DISPLAY_FORMAT),
32
+ captureMode: CaptureModeSchema.default(DEFAULT_CAPTURE_MODE),
29
33
  });
30
34
 
31
35
  export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
@@ -1,14 +1,18 @@
1
1
  import useSWR, { type SWRResponse, useSWRConfig } from "swr";
2
2
  import { fetchJson } from "./apiClient";
3
3
  import {
4
+ DEFAULT_CAPTURE_MODE,
4
5
  DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
5
6
  DEFAULT_TIME_DISPLAY_FORMAT,
6
7
  RuntimeConfigSchema,
8
+ type CaptureMode,
7
9
  type RuntimeConfig,
8
10
  } from "./runtimeConfig";
9
11
 
10
12
  export const ONBOARDING_SWR_KEY = "/api/config";
11
13
 
14
+ type RuntimeConfigPatch = Partial<Omit<RuntimeConfig, "captureMode">>;
15
+
12
16
  async function fetcher(url: string): Promise<RuntimeConfig> {
13
17
  return fetchJson(
14
18
  url,
@@ -18,7 +22,7 @@ async function fetcher(url: string): Promise<RuntimeConfig> {
18
22
  );
19
23
  }
20
24
 
21
- async function patchRuntimeConfig(patch: Partial<RuntimeConfig>): Promise<RuntimeConfig> {
25
+ async function patchRuntimeConfig(patch: RuntimeConfigPatch): Promise<RuntimeConfig> {
22
26
  return fetchJson(
23
27
  ONBOARDING_SWR_KEY,
24
28
  RuntimeConfigSchema,
@@ -33,6 +37,7 @@ async function patchRuntimeConfig(patch: Partial<RuntimeConfig>): Promise<Runtim
33
37
 
34
38
  export type UseOnboarding = {
35
39
  hasSeenOnboarding: boolean;
40
+ captureMode: CaptureMode;
36
41
  isLoading: boolean;
37
42
  markSeen: () => Promise<void>;
38
43
  };
@@ -59,6 +64,7 @@ export function useOnboarding(): UseOnboarding {
59
64
  const { mutate: globalMutate } = useSWRConfig();
60
65
 
61
66
  const hasSeenOnboarding = response.data?.hasSeenOnboarding ?? false;
67
+ const captureMode = response.data?.captureMode ?? DEFAULT_CAPTURE_MODE;
62
68
 
63
69
  const markSeen = async (): Promise<void> => {
64
70
  await globalMutate(ONBOARDING_SWR_KEY, patchRuntimeConfig({ hasSeenOnboarding: true }), {
@@ -68,6 +74,7 @@ export function useOnboarding(): UseOnboarding {
68
74
  slowResponseThresholdSeconds:
69
75
  response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
70
76
  timeDisplayFormat: response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT,
77
+ captureMode,
71
78
  },
72
79
  rollbackOnError: true,
73
80
  revalidate: false,
@@ -76,6 +83,7 @@ export function useOnboarding(): UseOnboarding {
76
83
 
77
84
  return {
78
85
  hasSeenOnboarding,
86
+ captureMode,
79
87
  isLoading: response.isLoading,
80
88
  markSeen,
81
89
  };
@@ -1,8 +1,10 @@
1
1
  import useSWR, { type SWRResponse, useSWRConfig } from "swr";
2
2
  import { fetchJson } from "./apiClient";
3
3
  import {
4
+ DEFAULT_CAPTURE_MODE,
4
5
  DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
5
6
  DEFAULT_TIME_DISPLAY_FORMAT,
7
+ type CaptureMode,
6
8
  RuntimeConfigSchema,
7
9
  type TimeDisplayFormat,
8
10
  type RuntimeConfig,
@@ -22,7 +24,9 @@ async function fetcher(url: string): Promise<RuntimeConfig> {
22
24
  /**
23
25
  * PATCH the runtime config. Exported separately for testability.
24
26
  */
25
- export async function setRuntimeConfig(patch: Partial<RuntimeConfig>): Promise<RuntimeConfig> {
27
+ type RuntimeConfigPatch = Partial<Omit<RuntimeConfig, "captureMode">>;
28
+
29
+ export async function setRuntimeConfig(patch: RuntimeConfigPatch): Promise<RuntimeConfig> {
26
30
  return fetchJson(
27
31
  STRIP_CONFIG_SWR_KEY,
28
32
  RuntimeConfigSchema,
@@ -37,6 +41,7 @@ export async function setRuntimeConfig(patch: Partial<RuntimeConfig>): Promise<R
37
41
 
38
42
  export type UseStripConfig = {
39
43
  strip: boolean;
44
+ captureMode: CaptureMode;
40
45
  slowResponseThresholdSeconds: number;
41
46
  timeDisplayFormat: TimeDisplayFormat;
42
47
  isLoading: boolean;
@@ -66,16 +71,18 @@ export function useStripConfig(): UseStripConfig {
66
71
  const { mutate: globalMutate } = useSWRConfig();
67
72
 
68
73
  const strip = response.data?.stripClaudeCodeBillingHeader ?? false;
74
+ const captureMode = response.data?.captureMode ?? DEFAULT_CAPTURE_MODE;
69
75
  const slowResponseThresholdSeconds =
70
76
  response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS;
71
77
  const timeDisplayFormat = response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT;
72
78
 
73
- const optimisticConfig = (patch: Partial<RuntimeConfig>): RuntimeConfig => ({
79
+ const optimisticConfig = (patch: RuntimeConfigPatch): RuntimeConfig => ({
74
80
  stripClaudeCodeBillingHeader: response.data?.stripClaudeCodeBillingHeader ?? false,
75
81
  hasSeenOnboarding: response.data?.hasSeenOnboarding ?? false,
76
82
  slowResponseThresholdSeconds:
77
83
  response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
78
84
  timeDisplayFormat: response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT,
85
+ captureMode,
79
86
  ...patch,
80
87
  });
81
88
 
@@ -113,6 +120,7 @@ export function useStripConfig(): UseStripConfig {
113
120
 
114
121
  return {
115
122
  strip,
123
+ captureMode,
116
124
  slowResponseThresholdSeconds,
117
125
  timeDisplayFormat,
118
126
  isLoading: response.isLoading,
@@ -11,9 +11,12 @@ import { join } from "node:path";
11
11
  import { logger } from "./logger";
12
12
  import { getDataDir } from "./dataDir";
13
13
  import {
14
+ CaptureModeSchema,
15
+ DEFAULT_CAPTURE_MODE,
14
16
  DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
15
17
  DEFAULT_TIME_DISPLAY_FORMAT,
16
18
  RuntimeConfigSchema,
19
+ type CaptureMode,
17
20
  type RuntimeConfig,
18
21
  } from "../lib/runtimeConfig";
19
22
 
@@ -52,7 +55,22 @@ function getConfigTempPath(): string {
52
55
  // Internal: writeable singleton. Read via getConfig(), mutate via setConfig().
53
56
  let currentConfig: RuntimeConfig = resolveInitialConfig();
54
57
 
58
+ type MutableRuntimeConfig = Omit<RuntimeConfig, "captureMode">;
59
+
60
+ function resolveInitialCaptureMode(): CaptureMode {
61
+ const value = process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
62
+ if (value === undefined || value === "") return DEFAULT_CAPTURE_MODE;
63
+
64
+ const result = CaptureModeSchema.safeParse(value);
65
+ if (result.success) return result.data;
66
+
67
+ logger.warn(`[config] Invalid capture mode ${value}; falling back to ${DEFAULT_CAPTURE_MODE}`);
68
+ return DEFAULT_CAPTURE_MODE;
69
+ }
70
+
55
71
  function resolveInitialConfig(): RuntimeConfig {
72
+ const captureMode = resolveInitialCaptureMode();
73
+
56
74
  // 1. Persisted file
57
75
  const filePath = getConfigFilePath();
58
76
  if (existsSync(filePath)) {
@@ -62,7 +80,7 @@ function resolveInitialConfig(): RuntimeConfig {
62
80
  const result = RuntimeConfigSchema.safeParse(parsed);
63
81
  if (result.success) {
64
82
  logger.info(`[config] Loaded persisted config from ${filePath}`);
65
- return result.data;
83
+ return { ...result.data, captureMode };
66
84
  }
67
85
  logger.warn(
68
86
  `[config] Persisted config at ${filePath} is invalid; falling back to env/default`,
@@ -81,6 +99,7 @@ function resolveInitialConfig(): RuntimeConfig {
81
99
  hasSeenOnboarding: false,
82
100
  slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
83
101
  timeDisplayFormat: DEFAULT_TIME_DISPLAY_FORMAT,
102
+ captureMode,
84
103
  };
85
104
  }
86
105
 
@@ -90,6 +109,7 @@ function resolveInitialConfig(): RuntimeConfig {
90
109
  hasSeenOnboarding: false,
91
110
  slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
92
111
  timeDisplayFormat: DEFAULT_TIME_DISPLAY_FORMAT,
112
+ captureMode,
93
113
  };
94
114
  }
95
115
 
@@ -106,10 +126,11 @@ export function getConfig(): RuntimeConfig {
106
126
  * do not throw — the in-memory value is always updated so the caller
107
127
  * sees a consistent response.
108
128
  */
109
- export function setConfig(patch: Partial<RuntimeConfig>): RuntimeConfig {
129
+ export function setConfig(patch: Partial<MutableRuntimeConfig>): RuntimeConfig {
110
130
  const next: RuntimeConfig = {
111
131
  ...currentConfig,
112
132
  ...patch,
133
+ captureMode: currentConfig.captureMode,
113
134
  };
114
135
  currentConfig = next;
115
136
  persistConfig(next);
@@ -152,7 +173,7 @@ function persistConfig(value: RuntimeConfig): boolean {
152
173
  }
153
174
 
154
175
  try {
155
- writeFileSync(tempPath, JSON.stringify(value), "utf-8");
176
+ writeFileSync(tempPath, JSON.stringify(toPersistedConfig(value)), "utf-8");
156
177
  } catch (err) {
157
178
  logger.error(`[config] Failed to write config temp file: ${String(err)}`);
158
179
  return false;
@@ -173,3 +194,12 @@ function persistConfig(value: RuntimeConfig): boolean {
173
194
  }
174
195
  }
175
196
  }
197
+
198
+ function toPersistedConfig(value: RuntimeConfig): Record<string, unknown> {
199
+ return {
200
+ stripClaudeCodeBillingHeader: value.stripClaudeCodeBillingHeader,
201
+ hasSeenOnboarding: value.hasSeenOnboarding,
202
+ slowResponseThresholdSeconds: value.slowResponseThresholdSeconds,
203
+ timeDisplayFormat: value.timeDisplayFormat,
204
+ };
205
+ }
@@ -36,16 +36,21 @@ import {
36
36
  * Strips all custom/non-standard headers from the request and replaces with
37
37
  * unified proxy identity. Only preserves standard HTTP headers needed for API calls.
38
38
  */
39
- function buildProxyHeaders(originalHeaders: Headers): {
39
+ function buildProxyHeaders(
40
+ originalHeaders: Headers,
41
+ captureRawHeaders: boolean,
42
+ ): {
40
43
  headers: Headers;
41
- rawHeaders: Record<string, string>;
44
+ rawHeaders: Record<string, string> | undefined;
42
45
  } {
43
- const rawHeaders: Record<string, string> = {};
46
+ const rawHeaders: Record<string, string> | undefined = captureRawHeaders ? {} : undefined;
44
47
  const headers = new Headers();
45
48
 
46
- originalHeaders.forEach((value, key) => {
47
- rawHeaders[key.toLowerCase()] = value;
48
- });
49
+ if (rawHeaders !== undefined) {
50
+ originalHeaders.forEach((value, key) => {
51
+ rawHeaders[key.toLowerCase()] = value;
52
+ });
53
+ }
49
54
 
50
55
  headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
51
56
  headers.set(HEADER_X_PROXY_IDENTITY, PROXY_IDENTITY);
@@ -128,6 +133,7 @@ function handleStreamingResponse(
128
133
  startTime: number,
129
134
  upstreamUrl: string,
130
135
  log: CapturedLog,
136
+ collectStreamingChunks: boolean,
131
137
  ): Response {
132
138
  log.streaming = true;
133
139
  log.responseStatus = upstreamRes.status;
@@ -151,6 +157,7 @@ function handleStreamingResponse(
151
157
  elapsedMs,
152
158
  responseStatus: upstreamRes.status,
153
159
  rawStream: full,
160
+ collectStreamingChunks,
154
161
  });
155
162
  },
156
163
  });
@@ -175,6 +182,7 @@ function handleStreamingResponse(
175
182
  elapsedMs,
176
183
  rawStream: full,
177
184
  hasChunks,
185
+ collectStreamingChunks,
178
186
  });
179
187
  }
180
188
  });
@@ -208,8 +216,9 @@ export async function handleProxy(req: Request): Promise<Response> {
208
216
  // Important: the original `requestBody` must be preserved so the log stores
209
217
  // the unmodified request (the "Raw Request" tab needs the true bytes the
210
218
  // client sent). The rewritten body is only used for the upstream fetch.
219
+ const config = getConfig();
211
220
  let bodyToForward = requestBody;
212
- if (getConfig().stripClaudeCodeBillingHeader && requestBody !== null && parsed.isMessages) {
221
+ if (config.stripClaudeCodeBillingHeader && requestBody !== null && parsed.isMessages) {
213
222
  const stripped = stripClaudeCodeBillingHeader(requestBody);
214
223
  if (stripped.removed > 0) {
215
224
  logger.info(
@@ -248,7 +257,11 @@ export async function handleProxy(req: Request): Promise<Response> {
248
257
  const upstreamUrl = buildUpstreamUrl(upstreamBase, parsed.normalizedPath);
249
258
  const startTime = Date.now();
250
259
 
251
- const { headers: upstreamHeaders, rawHeaders } = buildProxyHeaders(req.headers);
260
+ const captureFullDetails = config.captureMode === "full";
261
+ const { headers: upstreamHeaders, rawHeaders } = buildProxyHeaders(
262
+ req.headers,
263
+ captureFullDetails,
264
+ );
252
265
  setUpstreamHost(upstreamHeaders, upstreamBase);
253
266
  injectProviderAuth(upstreamHeaders, matchedProviderConfig);
254
267
 
@@ -277,10 +290,14 @@ export async function handleProxy(req: Request): Promise<Response> {
277
290
  // full PowerShell round-trip on top of everything else before
278
291
  // `fetch` is even issued.
279
292
  const [clientInfo, preAcquiredId] = await Promise.all([getClientInfo(req), getNextLogId()]);
280
- const upstreamHeadersObj: Record<string, string> = {};
281
- upstreamHeaders.forEach((value, key) => {
282
- upstreamHeadersObj[key.toLowerCase()] = value;
283
- });
293
+ const upstreamHeadersObj: Record<string, string> | undefined = captureFullDetails
294
+ ? {}
295
+ : undefined;
296
+ if (upstreamHeadersObj !== undefined) {
297
+ upstreamHeaders.forEach((value, key) => {
298
+ upstreamHeadersObj[key.toLowerCase()] = value;
299
+ });
300
+ }
284
301
  const log = await createLog(
285
302
  req.method,
286
303
  parsed.apiPath,
@@ -330,5 +347,5 @@ export async function handleProxy(req: Request): Promise<Response> {
330
347
  return handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstreamUrl, log);
331
348
  }
332
349
 
333
- return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log);
350
+ return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, captureFullDetails);
334
351
  }
@@ -22,12 +22,14 @@ export type FinalizeStreamingLogJob = BaseFinalizeLogJob & {
22
22
  type: "streaming";
23
23
  responseStatus: number;
24
24
  rawStream: string;
25
+ collectStreamingChunks: boolean;
25
26
  };
26
27
 
27
28
  export type FinalizeStreamAbortLogJob = BaseFinalizeLogJob & {
28
29
  type: "stream-abort";
29
30
  rawStream: string;
30
31
  hasChunks: boolean;
32
+ collectStreamingChunks: boolean;
31
33
  };
32
34
 
33
35
  export type FinalizeLogJob =
@@ -145,7 +147,7 @@ function finalizeStreaming(job: FinalizeStreamingLogJob, log: CapturedLog): Fina
145
147
  job.rawStream,
146
148
  log,
147
149
  log.model ?? undefined,
148
- true,
150
+ job.collectStreamingChunks,
149
151
  );
150
152
  persistStreamingChunks(log);
151
153
 
@@ -168,7 +170,7 @@ function finalizeStreamAbort(job: FinalizeStreamAbortLogJob, log: CapturedLog):
168
170
  job.rawStream,
169
171
  log,
170
172
  log.model ?? undefined,
171
- true,
173
+ job.collectStreamingChunks,
172
174
  );
173
175
  persistStreamingChunks(log);
174
176
  } else {
@@ -1,6 +1,7 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { getProvider, getModelUsageName } from "../../proxy/providers";
3
3
  import { addTestLogEntry } from "../../proxy/store";
4
+ import { getConfig } from "../../proxy/config";
4
5
  import { buildProviderTestRequestBody } from "../../lib/providerTestPrompt";
5
6
  import {
6
7
  ProviderTestResultsSchema,
@@ -21,6 +22,7 @@ async function logModelResults(
21
22
  anthropic: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
22
23
  openai: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
23
24
  },
25
+ captureFullDetails: boolean,
24
26
  ): Promise<void> {
25
27
  const usageModel = getModelUsageName(displayName, providerName);
26
28
 
@@ -53,7 +55,7 @@ async function logModelResults(
53
55
  apiFormat: "anthropic",
54
56
  isTest: true,
55
57
  providerName,
56
- headers: nonStreamingResult.requestHeaders ?? {},
58
+ headers: captureFullDetails ? (nonStreamingResult.requestHeaders ?? {}) : undefined,
57
59
  });
58
60
 
59
61
  const streamingRequestBody = JSON.stringify(
@@ -75,13 +77,13 @@ async function logModelResults(
75
77
  cacheReadInputTokens: streamingResult.cacheReadInputTokens ?? null,
76
78
  elapsedMs: streamingResult.latencyMs ?? 0,
77
79
  streaming: true,
78
- streamingChunks: streamingResult.streamingChunks,
80
+ streamingChunks: captureFullDetails ? streamingResult.streamingChunks : undefined,
79
81
  userAgent: "provider-test",
80
82
  origin: null,
81
83
  apiFormat: "anthropic",
82
84
  isTest: true,
83
85
  providerName,
84
- headers: streamingResult.requestHeaders ?? {},
86
+ headers: captureFullDetails ? (streamingResult.requestHeaders ?? {}) : undefined,
85
87
  });
86
88
  }
87
89
  }
@@ -115,7 +117,7 @@ async function logModelResults(
115
117
  apiFormat: "openai",
116
118
  isTest: true,
117
119
  providerName,
118
- headers: nonStreamingResult.requestHeaders ?? {},
120
+ headers: captureFullDetails ? (nonStreamingResult.requestHeaders ?? {}) : undefined,
119
121
  });
120
122
 
121
123
  const streamingRequestBody = JSON.stringify(
@@ -137,13 +139,13 @@ async function logModelResults(
137
139
  cacheReadInputTokens: null,
138
140
  elapsedMs: streamingResult.latencyMs ?? 0,
139
141
  streaming: true,
140
- streamingChunks: streamingResult.streamingChunks,
142
+ streamingChunks: captureFullDetails ? streamingResult.streamingChunks : undefined,
141
143
  userAgent: "provider-test",
142
144
  origin: null,
143
145
  apiFormat: "openai",
144
146
  isTest: true,
145
147
  providerName,
146
- headers: streamingResult.requestHeaders ?? {},
148
+ headers: captureFullDetails ? (streamingResult.requestHeaders ?? {}) : undefined,
147
149
  });
148
150
  }
149
151
  }
@@ -174,6 +176,7 @@ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
174
176
  }
175
177
 
176
178
  const results = parsed.data;
179
+ const captureFullDetails = getConfig().captureMode === "full";
177
180
  const anthropicUrl =
178
181
  provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl.length > 0
179
182
  ? provider.anthropicBaseUrl
@@ -187,7 +190,14 @@ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
187
190
  if (results.models !== undefined) {
188
191
  for (const [modelName, modelResult] of Object.entries(results.models)) {
189
192
  entries.push(
190
- logModelResults(modelName, provider.name, anthropicUrl, openaiUrl, modelResult),
193
+ logModelResults(
194
+ modelName,
195
+ provider.name,
196
+ anthropicUrl,
197
+ openaiUrl,
198
+ modelResult,
199
+ captureFullDetails,
200
+ ),
191
201
  );
192
202
  }
193
203
  }
@@ -1 +0,0 @@
1
- import{R as s,j as e}from"./main-D6wegveF.js";import{P as i}from"./ProxyViewerContainer-vc50msJj.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
@@ -1 +0,0 @@
1
- import{P as o}from"./ProxyViewerContainer-vc50msJj.js";import"./main-D6wegveF.js";const r=o;export{r as component};