@tonyclaw/agent-inspector 2.1.12 → 2.1.14
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 +0 -8
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DjgjIFx7.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +106 -0
- package/.output/public/assets/{ReplayDialog-Cd1VsfRe.js → ReplayDialog-CU0Tbb2c.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-QxSpbmsj.js → RequestAnatomy-BNahe83D.js} +1 -1
- package/.output/public/assets/ResponseView-DSOnGqi6.js +3 -0
- package/.output/public/assets/{StreamingChunkSequence-CwNGqJ5n.js → StreamingChunkSequence-BEKTDklB.js} +1 -1
- package/.output/public/assets/_sessionId-Cif8JZdn.js +1 -0
- package/.output/public/assets/index-DWOkqdCa.js +14 -0
- package/.output/public/assets/index-D_WfwzUi.js +1 -0
- package/.output/public/assets/index-DtLuQrs0.css +1 -0
- package/.output/public/assets/json-viewer-C2JpgcW0.js +1 -0
- package/.output/public/assets/{main-B_EcGyf-.js → main-CSONBwwn.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +20 -20
- package/.output/server/_libs/react-markdown.mjs +90 -2
- package/.output/server/{_sessionId-CmO9read.mjs → _sessionId-CPAa37n5.mjs} +6 -57
- package/.output/server/_ssr/{CompareDrawer-D9wf870o.mjs → CompareDrawer-ceW5VxMo.mjs} +17 -62
- package/.output/server/_ssr/{ProxyViewerContainer-BDLJSawP.mjs → ProxyViewerContainer-CDfEE_w-.mjs} +872 -688
- package/.output/server/_ssr/{ReplayDialog-B2dS4kmP.mjs → ReplayDialog-V0s_eEbR.mjs} +8 -59
- package/.output/server/_ssr/{RequestAnatomy-k49Peshi.mjs → RequestAnatomy-f1ccwR9d.mjs} +6 -57
- package/.output/server/_ssr/{ResponseView-Bk3B1suu.mjs → ResponseView-BIRrqG4H.mjs} +138 -60
- package/.output/server/_ssr/{StreamingChunkSequence-D6MDGCgh.mjs → StreamingChunkSequence-V3JFjCgX.mjs} +13 -58
- package/.output/server/_ssr/{index-CC8KdlTb.mjs → index-DsykulzS.mjs} +6 -57
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-Dcnm0Ivf.mjs +478 -0
- package/.output/server/_ssr/{router-BEmMnVa7.mjs → router-4bdm6Mt2.mjs} +674 -173
- package/.output/server/{_tanstack-start-manifest_v-Bf_4hG4w.mjs → _tanstack-start-manifest_v-Xp4CO64V.mjs} +1 -1
- package/.output/server/index.mjs +70 -56
- package/package.json +1 -1
- package/src/components/ProxyViewerContainer.tsx +25 -17
- package/src/components/providers/ImportWizardDialog.tsx +7 -1
- package/src/components/providers/ProviderCard.tsx +10 -1
- package/src/components/providers/ProviderForm.tsx +157 -41
- package/src/components/providers/ProvidersPanel.tsx +5 -1
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +8 -3
- package/src/components/proxy-viewer/CompareDrawer.tsx +20 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +11 -15
- package/src/components/proxy-viewer/LogEntry.tsx +31 -11
- package/src/components/proxy-viewer/LogEntryHeader.tsx +111 -40
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +19 -13
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +9 -3
- package/src/components/proxy-viewer/TurnGroup.tsx +53 -7
- package/src/components/proxy-viewer/TurnGroupList.tsx +153 -0
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +74 -2
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +13 -7
- package/src/components/proxy-viewer/formats/index.tsx +10 -3
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +185 -17
- package/src/components/proxy-viewer/log-formats/openai.ts +107 -15
- package/src/components/proxy-viewer/logFocus.ts +15 -2
- package/src/components/proxy-viewer/requestTools.ts +7 -3
- package/src/components/proxy-viewer/viewerState.ts +72 -3
- package/src/components/ui/json-viewer-bulk.ts +41 -6
- package/src/components/ui/json-viewer.tsx +9 -8
- package/src/contracts/index.ts +15 -1
- package/src/contracts/openai.ts +94 -0
- package/src/lib/providerContract.ts +1 -0
- package/src/lib/providerModelMetadata.ts +7 -1
- package/src/lib/stopReason.ts +12 -0
- package/src/lib/upstreamUrl.ts +46 -0
- package/src/mcp/previewExtractor.ts +73 -3
- package/src/mcp/server.ts +2 -0
- package/src/mcp/toolHandlers.ts +2 -0
- package/src/proxy/constants.ts +2 -0
- package/src/proxy/formats/openai/handler.ts +40 -9
- package/src/proxy/formats/openai/index.ts +7 -0
- package/src/proxy/formats/openai/schemas.ts +15 -1
- package/src/proxy/formats/openai/stream.ts +529 -135
- package/src/proxy/formats/registry.ts +9 -1
- package/src/proxy/identityProxy.ts +0 -7
- package/src/proxy/providerImporters.ts +45 -5
- package/src/proxy/providers.ts +31 -11
- package/src/proxy/schemas.ts +6 -0
- package/src/proxy/toolSchemaWarnings.ts +25 -3
- package/src/proxy/upstream.ts +18 -37
- package/src/routes/api/providers.$providerId.ts +1 -0
- package/src/routes/api/providers.ts +2 -0
- package/.output/public/assets/CompareDrawer-XTdSOto9.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-Cc944N8i.js +0 -117
- package/.output/public/assets/ResponseView-8GivXCzp.js +0 -1
- package/.output/public/assets/_sessionId-D89uDTQs.js +0 -1
- package/.output/public/assets/index-BsFdgXac.js +0 -1
- package/.output/public/assets/index-CmtfjQPv.css +0 -1
|
@@ -6,6 +6,7 @@ import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
|
|
|
6
6
|
import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
|
|
7
7
|
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
8
8
|
import { maskApiKey } from "../../lib/mask";
|
|
9
|
+
import { buildUpstreamUrl } from "../../lib/upstreamUrl";
|
|
9
10
|
import { formatContextWindowInput, parseContextWindowTokensInput } from "../../lib/utils";
|
|
10
11
|
|
|
11
12
|
const ZHIPU_PROVIDER_KEYWORDS = ["zhipu", "zhipuai", "bigmodel", "z.ai", "zai", "glm"];
|
|
@@ -16,17 +17,27 @@ const ZHIPU_CODING_PROVIDER_KEYWORDS = [
|
|
|
16
17
|
"z.ai coding",
|
|
17
18
|
"zai coding",
|
|
18
19
|
];
|
|
20
|
+
const ANTHROPIC_MESSAGES_ENDPOINT = "/v1/messages";
|
|
21
|
+
const OPENAI_CHAT_COMPLETIONS_ENDPOINT = "/v1/chat/completions";
|
|
22
|
+
const OPENAI_RESPONSES_ENDPOINT = "/v1/responses";
|
|
19
23
|
|
|
20
24
|
// Known provider presets - maps provider name keywords to their API URLs
|
|
21
25
|
const KNOWN_PROVIDER_PRESETS: Record<
|
|
22
26
|
string,
|
|
23
|
-
{
|
|
27
|
+
{
|
|
28
|
+
anthropicBaseUrl?: string;
|
|
29
|
+
openaiBaseUrl?: string;
|
|
30
|
+
openaiResponsesBaseUrl?: string;
|
|
31
|
+
apiDocsUrl?: string;
|
|
32
|
+
}
|
|
24
33
|
> = {
|
|
25
34
|
deepseek: {
|
|
26
35
|
openaiBaseUrl: "https://api.deepseek.com",
|
|
27
36
|
},
|
|
28
37
|
minimax: {
|
|
29
38
|
anthropicBaseUrl: "https://api.minimaxi.com/anthropic",
|
|
39
|
+
openaiBaseUrl: "https://api.minimaxi.com",
|
|
40
|
+
openaiResponsesBaseUrl: "https://api.minimaxi.com",
|
|
30
41
|
apiDocsUrl: "https://platform.minimaxi.com/docs/api-reference/api-overview",
|
|
31
42
|
},
|
|
32
43
|
"zhipu coding": {
|
|
@@ -122,6 +133,7 @@ type ProviderFormProps = {
|
|
|
122
133
|
models: string[];
|
|
123
134
|
anthropicBaseUrl?: string;
|
|
124
135
|
openaiBaseUrl?: string;
|
|
136
|
+
openaiResponsesBaseUrl?: string;
|
|
125
137
|
apiDocsUrl?: string;
|
|
126
138
|
modelMetadataUrl?: string;
|
|
127
139
|
modelMetadata?: ProviderModelMetadata[];
|
|
@@ -254,6 +266,38 @@ function readPositiveIntegerInput(value: string): number | null {
|
|
|
254
266
|
return parsed;
|
|
255
267
|
}
|
|
256
268
|
|
|
269
|
+
function stripEndpointSuffix(value: string | undefined, endpoint: string): string {
|
|
270
|
+
if (value === undefined) return "";
|
|
271
|
+
const trimmed = value.trim();
|
|
272
|
+
const normalized = trimmed.replace(/\/+$/, "");
|
|
273
|
+
return normalized.endsWith(endpoint)
|
|
274
|
+
? normalized.slice(0, -endpoint.length).replace(/\/+$/, "")
|
|
275
|
+
: trimmed;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function previewEndpointUrl(baseUrl: string, endpoint: string): string | null {
|
|
279
|
+
const trimmed = baseUrl.trim();
|
|
280
|
+
return trimmed === "" ? null : buildUpstreamUrl(trimmed, endpoint);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function EndpointUrlPreview({
|
|
284
|
+
baseUrl,
|
|
285
|
+
endpoint,
|
|
286
|
+
}: {
|
|
287
|
+
baseUrl: string;
|
|
288
|
+
endpoint: string;
|
|
289
|
+
}): JSX.Element | null {
|
|
290
|
+
const finalUrl = previewEndpointUrl(baseUrl, endpoint);
|
|
291
|
+
if (finalUrl === null) return null;
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div className="rounded-md border border-border/70 bg-muted/35 px-3 py-2 text-xs">
|
|
295
|
+
<div className="text-muted-foreground">Final URL</div>
|
|
296
|
+
<div className="break-all font-mono text-foreground">{finalUrl}</div>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
257
301
|
export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps): JSX.Element {
|
|
258
302
|
const [name, setName] = useState(provider?.name ?? "");
|
|
259
303
|
const [apiKey, setApiKey] = useState(provider?.apiKey ?? "");
|
|
@@ -270,8 +314,15 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
270
314
|
),
|
|
271
315
|
);
|
|
272
316
|
const [activeTab, setActiveTab] = useState<"anthropic" | "openai">("anthropic");
|
|
273
|
-
const [anthropicBaseUrl, setAnthropicBaseUrl] = useState(
|
|
274
|
-
|
|
317
|
+
const [anthropicBaseUrl, setAnthropicBaseUrl] = useState(
|
|
318
|
+
stripEndpointSuffix(provider?.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT),
|
|
319
|
+
);
|
|
320
|
+
const [openaiBaseUrl, setOpenaiBaseUrl] = useState(
|
|
321
|
+
stripEndpointSuffix(provider?.openaiBaseUrl, OPENAI_CHAT_COMPLETIONS_ENDPOINT),
|
|
322
|
+
);
|
|
323
|
+
const [openaiResponsesBaseUrl, setOpenaiResponsesBaseUrl] = useState(
|
|
324
|
+
stripEndpointSuffix(provider?.openaiResponsesBaseUrl, OPENAI_RESPONSES_ENDPOINT),
|
|
325
|
+
);
|
|
275
326
|
const [apiDocsUrl, setApiDocsUrl] = useState(provider?.apiDocsUrl ?? "");
|
|
276
327
|
const [modelMetadataUrl, setModelMetadataUrl] = useState(provider?.modelMetadataUrl ?? "");
|
|
277
328
|
const [source, setSource] = useState<"company" | "personal" | undefined>(provider?.source);
|
|
@@ -298,6 +349,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
298
349
|
// Track if URL fields have been manually edited (to avoid overriding user edits)
|
|
299
350
|
const [manualAnthropicUrlOverride, setManualAnthropicUrlOverride] = useState(false);
|
|
300
351
|
const [manualOpenaiUrlOverride, setManualOpenaiUrlOverride] = useState(false);
|
|
352
|
+
const [manualOpenaiResponsesUrlOverride, setManualOpenaiResponsesUrlOverride] = useState(false);
|
|
301
353
|
|
|
302
354
|
// Check if MiniMax is detected
|
|
303
355
|
const isMiniMax = name.toLowerCase().includes("minimax");
|
|
@@ -331,13 +383,21 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
331
383
|
const nextModels = (provider.models?.length ?? 0) > 0 ? provider.models : [""];
|
|
332
384
|
setModels(nextModels);
|
|
333
385
|
setModelMetadataDrafts(createMetadataDrafts(provider, nextModels));
|
|
334
|
-
setAnthropicBaseUrl(
|
|
335
|
-
|
|
386
|
+
setAnthropicBaseUrl(
|
|
387
|
+
stripEndpointSuffix(provider.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT),
|
|
388
|
+
);
|
|
389
|
+
setOpenaiBaseUrl(
|
|
390
|
+
stripEndpointSuffix(provider.openaiBaseUrl, OPENAI_CHAT_COMPLETIONS_ENDPOINT),
|
|
391
|
+
);
|
|
392
|
+
setOpenaiResponsesBaseUrl(
|
|
393
|
+
stripEndpointSuffix(provider.openaiResponsesBaseUrl, OPENAI_RESPONSES_ENDPOINT),
|
|
394
|
+
);
|
|
336
395
|
setApiDocsUrl(provider.apiDocsUrl ?? "");
|
|
337
396
|
setModelMetadataUrl(provider.modelMetadataUrl ?? "");
|
|
338
397
|
setSource(provider.source);
|
|
339
398
|
setManualAnthropicUrlOverride(false);
|
|
340
399
|
setManualOpenaiUrlOverride(false);
|
|
400
|
+
setManualOpenaiResponsesUrlOverride(false);
|
|
341
401
|
}
|
|
342
402
|
}, [provider]);
|
|
343
403
|
|
|
@@ -352,6 +412,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
352
412
|
if (preset.openaiBaseUrl !== undefined && !manualOpenaiUrlOverride) {
|
|
353
413
|
setOpenaiBaseUrl(preset.openaiBaseUrl);
|
|
354
414
|
}
|
|
415
|
+
if (preset.openaiResponsesBaseUrl !== undefined && !manualOpenaiResponsesUrlOverride) {
|
|
416
|
+
setOpenaiResponsesBaseUrl(preset.openaiResponsesBaseUrl);
|
|
417
|
+
}
|
|
355
418
|
if (preset.apiDocsUrl !== undefined && !apiDocsUrl) {
|
|
356
419
|
setApiDocsUrl(preset.apiDocsUrl);
|
|
357
420
|
}
|
|
@@ -394,6 +457,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
394
457
|
if (openaiBaseUrl.trim() && !isValidUrl(openaiBaseUrl.trim())) {
|
|
395
458
|
newErrors.openaiBaseUrl = "Invalid URL format";
|
|
396
459
|
}
|
|
460
|
+
if (openaiResponsesBaseUrl.trim() && !isValidUrl(openaiResponsesBaseUrl.trim())) {
|
|
461
|
+
newErrors.openaiResponsesBaseUrl = "Invalid URL format";
|
|
462
|
+
}
|
|
397
463
|
if (modelMetadataUrl.trim() && !isValidUrl(modelMetadataUrl.trim())) {
|
|
398
464
|
newErrors.modelMetadataUrl = "Invalid URL format";
|
|
399
465
|
}
|
|
@@ -412,7 +478,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
412
478
|
"Output limit should not exceed context window";
|
|
413
479
|
}
|
|
414
480
|
});
|
|
415
|
-
if (!anthropicBaseUrl.trim() && !openaiBaseUrl.trim()) {
|
|
481
|
+
if (!anthropicBaseUrl.trim() && !openaiBaseUrl.trim() && !openaiResponsesBaseUrl.trim()) {
|
|
416
482
|
newErrors.format = "At least one format URL (Anthropic or OpenAI) is required";
|
|
417
483
|
}
|
|
418
484
|
setErrors(newErrors);
|
|
@@ -546,6 +612,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
546
612
|
models: models.map((m) => m.trim()).filter((m) => m !== ""),
|
|
547
613
|
anthropicBaseUrl: anthropicBaseUrl.trim() || undefined,
|
|
548
614
|
openaiBaseUrl: openaiBaseUrl.trim() || undefined,
|
|
615
|
+
openaiResponsesBaseUrl: openaiResponsesBaseUrl.trim() || undefined,
|
|
549
616
|
apiDocsUrl: apiDocsUrl.trim() || undefined,
|
|
550
617
|
modelMetadataUrl: modelMetadataUrl.trim() || undefined,
|
|
551
618
|
modelMetadata: buildModelMetadata(),
|
|
@@ -878,50 +945,99 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
878
945
|
<label htmlFor="provider-anthropic-base-url" className="text-sm font-medium">
|
|
879
946
|
Anthropic Base URL
|
|
880
947
|
</label>
|
|
881
|
-
<input
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
948
|
+
<div className="flex min-w-0 overflow-hidden rounded-md border border-input bg-background ring-offset-background focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
949
|
+
<input
|
|
950
|
+
id="provider-anthropic-base-url"
|
|
951
|
+
type="text"
|
|
952
|
+
value={anthropicBaseUrl}
|
|
953
|
+
onChange={(e) => {
|
|
954
|
+
setManualAnthropicUrlOverride(true);
|
|
955
|
+
setAnthropicBaseUrl(e.target.value);
|
|
956
|
+
}}
|
|
957
|
+
placeholder="https://api.anthropic.com"
|
|
958
|
+
className="min-w-0 flex-1 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
959
|
+
/>
|
|
960
|
+
<span className="shrink-0 select-none border-l bg-muted px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
961
|
+
{ANTHROPIC_MESSAGES_ENDPOINT}
|
|
962
|
+
</span>
|
|
963
|
+
</div>
|
|
892
964
|
{errors.anthropicBaseUrl !== undefined && (
|
|
893
965
|
<p className="text-xs text-destructive">{errors.anthropicBaseUrl}</p>
|
|
894
966
|
)}
|
|
967
|
+
<EndpointUrlPreview baseUrl={anthropicBaseUrl} endpoint={ANTHROPIC_MESSAGES_ENDPOINT} />
|
|
895
968
|
<p className="text-xs text-muted-foreground">
|
|
896
|
-
Anthropic-compatible
|
|
897
|
-
|
|
969
|
+
Anthropic-compatible base URL. Leave empty if this provider does not support Anthropic
|
|
970
|
+
format.
|
|
898
971
|
</p>
|
|
899
972
|
</div>
|
|
900
973
|
)}
|
|
901
974
|
|
|
902
975
|
{activeTab === "openai" && (
|
|
903
|
-
<div className="space-y-
|
|
904
|
-
<
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
976
|
+
<div className="space-y-4">
|
|
977
|
+
<div className="space-y-2">
|
|
978
|
+
<label htmlFor="provider-openai-base-url" className="text-sm font-medium">
|
|
979
|
+
OpenAI Chat Base URL
|
|
980
|
+
</label>
|
|
981
|
+
<div className="flex min-w-0 overflow-hidden rounded-md border border-input bg-background ring-offset-background focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
982
|
+
<input
|
|
983
|
+
id="provider-openai-base-url"
|
|
984
|
+
type="text"
|
|
985
|
+
value={openaiBaseUrl}
|
|
986
|
+
onChange={(e) => {
|
|
987
|
+
setManualOpenaiUrlOverride(true);
|
|
988
|
+
setOpenaiBaseUrl(e.target.value);
|
|
989
|
+
}}
|
|
990
|
+
placeholder="https://api.openai.com"
|
|
991
|
+
className="min-w-0 flex-1 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
992
|
+
/>
|
|
993
|
+
<span className="shrink-0 select-none border-l bg-muted px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
994
|
+
{OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
995
|
+
</span>
|
|
996
|
+
</div>
|
|
997
|
+
{errors.openaiBaseUrl !== undefined && (
|
|
998
|
+
<p className="text-xs text-destructive">{errors.openaiBaseUrl}</p>
|
|
999
|
+
)}
|
|
1000
|
+
<EndpointUrlPreview
|
|
1001
|
+
baseUrl={openaiBaseUrl}
|
|
1002
|
+
endpoint={OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
1003
|
+
/>
|
|
1004
|
+
<p className="text-xs text-muted-foreground">
|
|
1005
|
+
OpenAI Chat Completions-compatible base URL. Leave empty if this provider only
|
|
1006
|
+
supports the Responses API.
|
|
1007
|
+
</p>
|
|
1008
|
+
</div>
|
|
1009
|
+
<div className="space-y-2">
|
|
1010
|
+
<label htmlFor="provider-openai-responses-base-url" className="text-sm font-medium">
|
|
1011
|
+
OpenAI Responses Base URL
|
|
1012
|
+
</label>
|
|
1013
|
+
<div className="flex min-w-0 overflow-hidden rounded-md border border-input bg-background ring-offset-background focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
1014
|
+
<input
|
|
1015
|
+
id="provider-openai-responses-base-url"
|
|
1016
|
+
type="text"
|
|
1017
|
+
value={openaiResponsesBaseUrl}
|
|
1018
|
+
onChange={(e) => {
|
|
1019
|
+
setManualOpenaiResponsesUrlOverride(true);
|
|
1020
|
+
setOpenaiResponsesBaseUrl(e.target.value);
|
|
1021
|
+
}}
|
|
1022
|
+
placeholder="https://api.openai.com"
|
|
1023
|
+
className="min-w-0 flex-1 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
1024
|
+
/>
|
|
1025
|
+
<span className="shrink-0 select-none border-l bg-muted px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
1026
|
+
{OPENAI_RESPONSES_ENDPOINT}
|
|
1027
|
+
</span>
|
|
1028
|
+
</div>
|
|
1029
|
+
{errors.openaiResponsesBaseUrl !== undefined && (
|
|
1030
|
+
<p className="text-xs text-destructive">{errors.openaiResponsesBaseUrl}</p>
|
|
1031
|
+
)}
|
|
1032
|
+
<EndpointUrlPreview
|
|
1033
|
+
baseUrl={openaiResponsesBaseUrl}
|
|
1034
|
+
endpoint={OPENAI_RESPONSES_ENDPOINT}
|
|
1035
|
+
/>
|
|
1036
|
+
<p className="text-xs text-muted-foreground">
|
|
1037
|
+
Optional Responses-compatible base URL. Set only when the provider supports the
|
|
1038
|
+
Responses API.
|
|
1039
|
+
</p>
|
|
1040
|
+
</div>
|
|
925
1041
|
</div>
|
|
926
1042
|
)}
|
|
927
1043
|
|
|
@@ -51,6 +51,7 @@ type ProviderFormData = {
|
|
|
51
51
|
models: string[];
|
|
52
52
|
anthropicBaseUrl?: string;
|
|
53
53
|
openaiBaseUrl?: string;
|
|
54
|
+
openaiResponsesBaseUrl?: string;
|
|
54
55
|
apiDocsUrl?: string;
|
|
55
56
|
modelMetadataUrl?: string;
|
|
56
57
|
modelMetadata?: ProviderModelMetadata[];
|
|
@@ -64,6 +65,8 @@ function createProviderPayload(data: ProviderFormData) {
|
|
|
64
65
|
models: data.models,
|
|
65
66
|
anthropicBaseUrl: (data.anthropicBaseUrl?.length ?? 0) > 0 ? data.anthropicBaseUrl : undefined,
|
|
66
67
|
openaiBaseUrl: (data.openaiBaseUrl?.length ?? 0) > 0 ? data.openaiBaseUrl : undefined,
|
|
68
|
+
openaiResponsesBaseUrl:
|
|
69
|
+
(data.openaiResponsesBaseUrl?.length ?? 0) > 0 ? data.openaiResponsesBaseUrl : undefined,
|
|
67
70
|
apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : undefined,
|
|
68
71
|
modelMetadataUrl: (data.modelMetadataUrl?.length ?? 0) > 0 ? data.modelMetadataUrl : undefined,
|
|
69
72
|
modelMetadata: data.modelMetadata,
|
|
@@ -407,7 +410,8 @@ export function ProvidersPanel({
|
|
|
407
410
|
(data.apiKey ?? "") !== (editingProvider.apiKey ?? "") ||
|
|
408
411
|
JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) ||
|
|
409
412
|
(data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") ||
|
|
410
|
-
(data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "")
|
|
413
|
+
(data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "") ||
|
|
414
|
+
(data.openaiResponsesBaseUrl ?? "") !== (editingProvider.openaiResponsesBaseUrl ?? "");
|
|
411
415
|
if (criticalFieldsChanged) {
|
|
412
416
|
await runTest(updated.id);
|
|
413
417
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
1
|
+
import { lazy, Suspense, type JSX } from "react";
|
|
2
|
+
|
|
3
|
+
const LazyReactMarkdown = lazy(() => import("react-markdown"));
|
|
3
4
|
|
|
4
5
|
const ANSWER_MARKDOWN_CLASS =
|
|
5
6
|
"prose prose-sm dark:prose-invert max-w-none text-[13px] leading-[1.65] " +
|
|
@@ -10,7 +11,11 @@ const ANSWER_MARKDOWN_CLASS =
|
|
|
10
11
|
export function AnswerMarkdown({ text }: { text: string }): JSX.Element {
|
|
11
12
|
return (
|
|
12
13
|
<div className={ANSWER_MARKDOWN_CLASS}>
|
|
13
|
-
<
|
|
14
|
+
<Suspense
|
|
15
|
+
fallback={<span className="text-xs text-muted-foreground">Loading Markdown...</span>}
|
|
16
|
+
>
|
|
17
|
+
<LazyReactMarkdown>{text}</LazyReactMarkdown>
|
|
18
|
+
</Suspense>
|
|
14
19
|
</div>
|
|
15
20
|
);
|
|
16
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { JSX } from "react";
|
|
3
3
|
import {
|
|
4
4
|
Check,
|
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
previewNode,
|
|
24
24
|
} from "./requestDiff";
|
|
25
25
|
import { getConversationId } from "./ConversationHeader";
|
|
26
|
-
import { JsonViewerFromString } from "../ui/json-viewer";
|
|
27
26
|
import { Badge } from "../ui/badge";
|
|
28
27
|
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
|
|
28
|
+
import { LazyJsonViewerFromString } from "./lazy";
|
|
29
29
|
|
|
30
30
|
export type CompareDrawerProps = {
|
|
31
31
|
/** Log selected first (shown on the left). */
|
|
@@ -65,6 +65,20 @@ function nodeToJsonValue(node: JsonNode): unknown {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function LazyJsonStringView({
|
|
69
|
+
text,
|
|
70
|
+
defaultExpandDepth,
|
|
71
|
+
}: {
|
|
72
|
+
text: string;
|
|
73
|
+
defaultExpandDepth: number;
|
|
74
|
+
}): JSX.Element {
|
|
75
|
+
return (
|
|
76
|
+
<Suspense fallback={<div className="text-xs text-muted-foreground">Loading JSON...</div>}>
|
|
77
|
+
<LazyJsonViewerFromString text={text} defaultExpandDepth={defaultExpandDepth} />
|
|
78
|
+
</Suspense>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
68
82
|
/** The parent path of a JSON path string. E.g. `messages[3].content` →
|
|
69
83
|
* `messages[3]`, `messages[3]` → `messages`, `messages` → `""`. */
|
|
70
84
|
function parentPath(path: string): string {
|
|
@@ -222,7 +236,7 @@ function EqualRunRow({
|
|
|
222
236
|
{ops.map((op) => (
|
|
223
237
|
<div key={op.path} className="border border-border/50 rounded p-2 bg-muted/20">
|
|
224
238
|
<div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
|
|
225
|
-
<
|
|
239
|
+
<LazyJsonStringView text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
|
|
226
240
|
</div>
|
|
227
241
|
))}
|
|
228
242
|
</div>
|
|
@@ -393,7 +407,7 @@ function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.E
|
|
|
393
407
|
if (op.kind === "added" || op.kind === "removed") {
|
|
394
408
|
return (
|
|
395
409
|
<div className="pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20">
|
|
396
|
-
<
|
|
410
|
+
<LazyJsonStringView text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
|
|
397
411
|
</div>
|
|
398
412
|
);
|
|
399
413
|
}
|
|
@@ -411,11 +425,11 @@ function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.E
|
|
|
411
425
|
<div className="pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
412
426
|
<div className="border border-rose-500/30 rounded p-2 bg-rose-500/5">
|
|
413
427
|
<div className="text-[10px] uppercase tracking-wider text-rose-500 mb-1">Old</div>
|
|
414
|
-
<
|
|
428
|
+
<LazyJsonStringView text={nodeToJsonString(op.left)} defaultExpandDepth={0} />
|
|
415
429
|
</div>
|
|
416
430
|
<div className="border border-emerald-500/30 rounded p-2 bg-emerald-500/5">
|
|
417
431
|
<div className="text-[10px] uppercase tracking-wider text-emerald-500 mb-1">New</div>
|
|
418
|
-
<
|
|
432
|
+
<LazyJsonStringView text={nodeToJsonString(op.right)} defaultExpandDepth={0} />
|
|
419
433
|
</div>
|
|
420
434
|
</div>
|
|
421
435
|
);
|
|
@@ -4,7 +4,7 @@ import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
|
4
4
|
import type { CapturedLog } from "../../contracts";
|
|
5
5
|
import { ConversationHeader, type ConversationGroupData } from "./ConversationHeader";
|
|
6
6
|
import { detectProvider } from "../providers/ProviderLogo";
|
|
7
|
-
import {
|
|
7
|
+
import { TurnGroupList } from "./TurnGroupList";
|
|
8
8
|
import { AgentTraceSummary } from "./AgentTraceSummary";
|
|
9
9
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
10
10
|
import { LOG_FOCUS_REQUEST_EVENT, readLogFocusRequest } from "./logFocus";
|
|
@@ -180,20 +180,16 @@ export const ConversationGroup = memo(function ({
|
|
|
180
180
|
showRollupMetrics={showTraceRollupMetrics}
|
|
181
181
|
timeDisplayFormat={timeDisplayFormat}
|
|
182
182
|
/>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
comparisonPredecessors={comparisonPredecessors}
|
|
194
|
-
turnIndex={tg.turnIndex}
|
|
195
|
-
/>
|
|
196
|
-
))}
|
|
183
|
+
<TurnGroupList
|
|
184
|
+
turnGroups={turnGroups}
|
|
185
|
+
viewMode={viewMode}
|
|
186
|
+
strip={strip}
|
|
187
|
+
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
188
|
+
timeDisplayFormat={timeDisplayFormat}
|
|
189
|
+
cacheTrends={cacheTrends}
|
|
190
|
+
onCompareWithPrevious={onCompareWithPrevious}
|
|
191
|
+
comparisonPredecessors={comparisonPredecessors}
|
|
192
|
+
/>
|
|
197
193
|
</div>
|
|
198
194
|
)}
|
|
199
195
|
</div>
|
|
@@ -75,7 +75,7 @@ const BODY_CHUNK_BYTES = 256 * 1024;
|
|
|
75
75
|
const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
|
|
76
76
|
|
|
77
77
|
const TAB_TRIGGER_CLASS =
|
|
78
|
-
"h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none hover:bg-cyan-400/[0.
|
|
78
|
+
"h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:bg-cyan-400/[0.05] hover:text-foreground data-[state=active]:border-cyan-300 data-[state=active]:bg-cyan-300/[0.04] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
|
|
79
79
|
|
|
80
80
|
function createEmptyBodyPreviewState(totalBytes: number | null = null): BodyPreviewState {
|
|
81
81
|
return {
|
|
@@ -140,7 +140,7 @@ function ExpandedPanel({
|
|
|
140
140
|
return (
|
|
141
141
|
<div
|
|
142
142
|
className={cn(
|
|
143
|
-
"inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-md border border-
|
|
143
|
+
"inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-md border border-cyan-300/10 bg-[#05070a] p-3 shadow-[0_16px_45px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.05)]",
|
|
144
144
|
className,
|
|
145
145
|
)}
|
|
146
146
|
>
|
|
@@ -384,6 +384,7 @@ export const LogEntry = memo(function ({
|
|
|
384
384
|
);
|
|
385
385
|
const requestJsonRef = useRef<HTMLDivElement | null>(null);
|
|
386
386
|
const logIdRef = useRef(log.id);
|
|
387
|
+
const bodyHydrationInFlightRef = useRef(false);
|
|
387
388
|
const displayLog = hydratedLog ?? log;
|
|
388
389
|
const useChunkedBody = hydratedLog === null && shouldUseChunkedBodyLoading(log);
|
|
389
390
|
const requestPreviewBody = bodyPreviewTextForView(useChunkedBody, requestPreview);
|
|
@@ -421,10 +422,10 @@ export const LogEntry = memo(function ({
|
|
|
421
422
|
requestPreviewShouldParse ? displayedRequestBody : null,
|
|
422
423
|
);
|
|
423
424
|
const rawRequestExpansion = useJsonBulkExpansion(
|
|
424
|
-
requestPreviewShouldParse ? rawRequestBodyForView : null,
|
|
425
|
+
activeTab === "raw-request" && requestPreviewShouldParse ? rawRequestBodyForView : null,
|
|
425
426
|
);
|
|
426
427
|
const responseExpansion = useJsonBulkExpansion(
|
|
427
|
-
responsePreviewShouldParse ? responseTextForView : null,
|
|
428
|
+
activeTab === "raw" && responsePreviewShouldParse ? responseTextForView : null,
|
|
428
429
|
);
|
|
429
430
|
|
|
430
431
|
// Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
|
|
@@ -634,6 +635,7 @@ export const LogEntry = memo(function ({
|
|
|
634
635
|
|
|
635
636
|
useEffect(() => {
|
|
636
637
|
logIdRef.current = log.id;
|
|
638
|
+
bodyHydrationInFlightRef.current = false;
|
|
637
639
|
setHydratedLog(null);
|
|
638
640
|
setBodyHydrationState("idle");
|
|
639
641
|
setRequestPreview(createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null));
|
|
@@ -643,9 +645,10 @@ export const LogEntry = memo(function ({
|
|
|
643
645
|
useEffect(() => {
|
|
644
646
|
if (!expanded) return;
|
|
645
647
|
if (!shouldHydrateLogBody(log)) return;
|
|
646
|
-
if (hydratedLog !== null ||
|
|
648
|
+
if (hydratedLog !== null || bodyHydrationInFlightRef.current) return;
|
|
647
649
|
|
|
648
650
|
let cancelled = false;
|
|
651
|
+
bodyHydrationInFlightRef.current = true;
|
|
649
652
|
setBodyHydrationState("loading");
|
|
650
653
|
void fetchJsonWithTimeout(
|
|
651
654
|
`/api/logs/${String(log.id)}`,
|
|
@@ -654,19 +657,21 @@ export const LogEntry = memo(function ({
|
|
|
654
657
|
)
|
|
655
658
|
.then((fullLog) => {
|
|
656
659
|
if (cancelled) return;
|
|
660
|
+
bodyHydrationInFlightRef.current = false;
|
|
657
661
|
setHydratedLog(fullLog);
|
|
658
662
|
setBodyHydrationState("idle");
|
|
659
663
|
})
|
|
660
664
|
.catch(() => {
|
|
661
665
|
if (cancelled) return;
|
|
666
|
+
bodyHydrationInFlightRef.current = false;
|
|
662
667
|
setBodyHydrationState("failed");
|
|
663
668
|
});
|
|
664
669
|
|
|
665
670
|
return () => {
|
|
666
671
|
cancelled = true;
|
|
672
|
+
bodyHydrationInFlightRef.current = false;
|
|
667
673
|
};
|
|
668
674
|
}, [
|
|
669
|
-
bodyHydrationState,
|
|
670
675
|
expanded,
|
|
671
676
|
hydratedLog,
|
|
672
677
|
log.bodyContentMode,
|
|
@@ -710,12 +715,23 @@ export const LogEntry = memo(function ({
|
|
|
710
715
|
<TooltipProvider>
|
|
711
716
|
<div
|
|
712
717
|
className={cn(
|
|
713
|
-
"mb-2 overflow-hidden rounded-
|
|
718
|
+
"relative mb-2 overflow-hidden rounded-lg border bg-[#07090d] transition-[background-color,border-color,box-shadow]",
|
|
714
719
|
expanded
|
|
715
|
-
? "border-cyan-300/
|
|
716
|
-
: "border-
|
|
720
|
+
? "border-cyan-300/25 shadow-[0_24px_80px_rgba(0,0,0,0.34),0_0_0_1px_rgba(103,232,249,0.04),inset_0_1px_0_rgba(255,255,255,0.06)]"
|
|
721
|
+
: "border-white/10 shadow-[0_6px_24px_rgba(0,0,0,0.16)] hover:border-cyan-300/15",
|
|
717
722
|
)}
|
|
718
723
|
>
|
|
724
|
+
<div
|
|
725
|
+
className={cn(
|
|
726
|
+
"pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
|
|
727
|
+
expanded ? "bg-cyan-300/45 shadow-[0_0_20px_rgba(103,232,249,0.28)]" : "bg-white/10",
|
|
728
|
+
)}
|
|
729
|
+
aria-hidden="true"
|
|
730
|
+
/>
|
|
731
|
+
<div
|
|
732
|
+
className="pointer-events-none absolute inset-x-3 top-0 h-px bg-gradient-to-r from-transparent via-white/15 to-transparent"
|
|
733
|
+
aria-hidden="true"
|
|
734
|
+
/>
|
|
719
735
|
<LogEntryHeader
|
|
720
736
|
log={displayLog}
|
|
721
737
|
messageCount={requestAnalysis.messageCount}
|
|
@@ -738,14 +754,18 @@ export const LogEntry = memo(function ({
|
|
|
738
754
|
|
|
739
755
|
{expanded && (
|
|
740
756
|
<div
|
|
741
|
-
className="border-t border-
|
|
757
|
+
className="relative border-t border-cyan-300/10 bg-[linear-gradient(180deg,#06090d_0%,#040506_100%)] pt-2"
|
|
742
758
|
onClick={(e) => e.stopPropagation()}
|
|
743
759
|
onKeyDown={(e) => e.stopPropagation()}
|
|
744
760
|
>
|
|
761
|
+
<div
|
|
762
|
+
className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-cyan-300/[0.035] to-transparent"
|
|
763
|
+
aria-hidden="true"
|
|
764
|
+
/>
|
|
745
765
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
|
|
746
766
|
<TabsList
|
|
747
767
|
variant="line"
|
|
748
|
-
className="inspector-scrollbar mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-none border-b border-
|
|
768
|
+
className="inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-none border-b border-cyan-300/10 bg-black/10 p-0"
|
|
749
769
|
>
|
|
750
770
|
{viewMode === "full" && (
|
|
751
771
|
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
|