@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.
- package/.output/cli.js +107 -11
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BR_kCD7Q.js → CompareDrawer-DPs4UHwd.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-vc50msJj.js → ProxyViewerContainer-B1KylMPs.js} +32 -32
- package/.output/public/assets/{ReplayDialog-DsAYQBHZ.js → ReplayDialog-8ISC28XF.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-B4uqyUt8.js → RequestAnatomy-BhbsgfBO.js} +1 -1
- package/.output/public/assets/{ResponseView-C1vQL-fM.js → ResponseView-CyM7JXeA.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-BECMV-xf.js → StreamingChunkSequence-BaX0x38n.js} +1 -1
- package/.output/public/assets/_sessionId-CShO0OrR.js +1 -0
- package/.output/public/assets/index-BkdYiTpV.js +1 -0
- package/.output/public/assets/{main-D6wegveF.js → main-B2t-3SYj.js} +2 -2
- package/.output/server/{_sessionId-CeHY-3xa.mjs → _sessionId-CEbZTs68.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-blWGaGiT.mjs → CompareDrawer-CEo48Wsu.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-Dt_9BuM9.mjs → ProxyViewerContainer-5ekIK_jx.mjs} +64 -22
- package/.output/server/_ssr/{ReplayDialog-mzsq7AjW.mjs → ReplayDialog-C0nEvbYo.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-CDAcXMBK.mjs → RequestAnatomy-CT55prmM.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-B__xXCsZ.mjs → ResponseView-DBZSBCOD.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-BnJkTdXF.mjs → StreamingChunkSequence-BuVT7Oke.mjs} +2 -2
- package/.output/server/_ssr/{index-B2-uF9XT.mjs → index-Cw_zJVQv.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-BpEBhA7S.mjs → router-DxjqHipT.mjs} +89 -46
- package/.output/server/{_tanstack-start-manifest_v-CHekFSzj.mjs → _tanstack-start-manifest_v-xeOo5gW5.mjs} +1 -1
- package/.output/server/index.mjs +59 -59
- package/README.md +31 -1
- package/package.json +1 -1
- package/src/cli/templates/codex-skill-onboard.ts +8 -4
- package/src/cli/templates/skill-onboard.ts +15 -7
- package/src/cli.ts +91 -0
- package/src/components/OnboardingBanner.tsx +32 -9
- package/src/components/ProxyViewer.tsx +32 -24
- package/src/components/ProxyViewerContainer.tsx +2 -1
- package/src/components/providers/SettingsDialog.tsx +10 -0
- package/src/lib/runtimeConfig.ts +4 -0
- package/src/lib/useOnboarding.ts +9 -1
- package/src/lib/useStripConfig.ts +10 -2
- package/src/proxy/config.ts +33 -3
- package/src/proxy/handler.ts +30 -13
- package/src/proxy/logFinalizer.ts +4 -2
- package/src/routes/api/providers.$providerId.test.log.ts +17 -7
- package/.output/public/assets/_sessionId-3bjQPmHl.js +0 -1
- 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
|
-
|
|
825
|
-
<
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
</
|
|
836
|
-
|
|
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">
|
package/src/lib/runtimeConfig.ts
CHANGED
|
@@ -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>;
|
package/src/lib/useOnboarding.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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:
|
|
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,
|
package/src/proxy/config.ts
CHANGED
|
@@ -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<
|
|
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
|
+
}
|
package/src/proxy/handler.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
47
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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};
|