@tonyclaw/agent-inspector 2.1.13 → 2.1.15
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/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-BcEd6V-V.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +106 -0
- package/.output/public/assets/ReplayDialog-BNpC0548.js +1 -0
- package/.output/public/assets/{RequestAnatomy-BjlBT-Cy.js → RequestAnatomy-Ds1uRLVB.js} +1 -1
- package/.output/public/assets/ResponseView-7KPVqKl5.js +3 -0
- package/.output/public/assets/{StreamingChunkSequence-D2_SMhlE.js → StreamingChunkSequence-BHQT261s.js} +1 -1
- package/.output/public/assets/_sessionId-DWePGjnS.js +1 -0
- package/.output/public/assets/index-CI1-G8ua.js +1 -0
- package/.output/public/assets/index-DdhFqPsI.css +1 -0
- package/.output/public/assets/index-DjKt8XKe.js +14 -0
- package/.output/public/assets/json-viewer-CkCu-rka.js +1 -0
- package/.output/public/assets/{main-Dtspb4Ui.js → main-DpD1N0S8.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +194 -200
- package/.output/server/_libs/react-markdown.mjs +90 -2
- package/.output/server/{_sessionId-CXDcLuvi.mjs → _sessionId-DF9Sy8cP.mjs} +6 -57
- package/.output/server/_ssr/{CompareDrawer-CMoCAoeq.mjs → CompareDrawer-BoxztaO7.mjs} +50 -74
- package/.output/server/_ssr/{ProxyViewerContainer-BW2vVCBN.mjs → ProxyViewerContainer-CRBkqFlJ.mjs} +1221 -799
- package/.output/server/_ssr/{ReplayDialog-ChXL1t8H.mjs → ReplayDialog-Cc1dyDuK.mjs} +18 -62
- package/.output/server/_ssr/{RequestAnatomy-DtKzIlfU.mjs → RequestAnatomy-CMGSsz5Z.mjs} +6 -57
- package/.output/server/_ssr/{ResponseView-B5I8drzc.mjs → ResponseView-Cp10DM1D.mjs} +139 -61
- package/.output/server/_ssr/{StreamingChunkSequence-DvwjQNcO.mjs → StreamingChunkSequence-B1VGxy3A.mjs} +13 -58
- package/.output/server/_ssr/{index-D_ZHtRfl.mjs → index-47XVPghS.mjs} +6 -57
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-zDE2rrmJ.mjs +478 -0
- package/.output/server/_ssr/{router-DhL9Wp3N.mjs → router-DVeuZFqI.mjs} +680 -174
- package/.output/server/{_tanstack-start-manifest_v-BHgoAmxZ.mjs → _tanstack-start-manifest_v-Bp8JxtPW.mjs} +1 -1
- package/.output/server/index.mjs +72 -58
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +28 -2
- package/src/components/ProxyViewerContainer.tsx +25 -17
- package/src/components/clients/ClientLogo.tsx +132 -0
- package/src/components/groups/GroupsDialog.tsx +18 -13
- 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/AgentTraceSummary.tsx +56 -23
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +8 -3
- package/src/components/proxy-viewer/CompareDrawer.tsx +58 -14
- package/src/components/proxy-viewer/ConversationGroup.tsx +11 -15
- package/src/components/proxy-viewer/ConversationHeader.tsx +33 -41
- package/src/components/proxy-viewer/LogEntry.tsx +41 -12
- package/src/components/proxy-viewer/LogEntryHeader.tsx +122 -51
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +60 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +8 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +19 -13
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +9 -3
- package/src/components/proxy-viewer/ThreadConnector.tsx +18 -7
- package/src/components/proxy-viewer/TurnGroup.tsx +67 -32
- 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 +77 -5
- 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/sessionInfoContract.ts +1 -0
- package/src/lib/stopReason.ts +59 -15
- 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/providerImporters.ts +45 -5
- package/src/proxy/providers.ts +31 -11
- package/src/proxy/schemas.ts +6 -0
- package/src/proxy/sessionInfo.ts +5 -1
- 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-Cz_1vIpR.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-7QSiluMf.js +0 -117
- package/.output/public/assets/ReplayDialog-sBA1KAYD.js +0 -1
- package/.output/public/assets/ResponseView-PtEKzml9.js +0 -1
- package/.output/public/assets/_sessionId-DZfB4ruK.js +0 -1
- package/.output/public/assets/index-B-QQLbpz.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
|
}
|
|
@@ -146,11 +146,29 @@ function firstToolLog(logs: CapturedLog[]): CapturedLog | null {
|
|
|
146
146
|
return null;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
function buildDisplayNumberByLogId(logs: readonly CapturedLog[]): Map<number, number> {
|
|
150
|
+
const result = new Map<number, number>();
|
|
151
|
+
for (let index = 0; index < logs.length; index += 1) {
|
|
152
|
+
const log = logs[index];
|
|
153
|
+
if (log === undefined) continue;
|
|
154
|
+
result.set(log.id, index + 1);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function displayLogNumber(
|
|
160
|
+
logId: number,
|
|
161
|
+
displayNumberByLogId: ReadonlyMap<number, number>,
|
|
162
|
+
): string {
|
|
163
|
+
return String(displayNumberByLogId.get(logId) ?? logId);
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
function buildTraceInsights(input: {
|
|
150
167
|
logs: CapturedLog[];
|
|
151
168
|
scopeId: string;
|
|
152
169
|
summary: TraceSummary;
|
|
153
170
|
candidates: KnowledgeCandidate[];
|
|
171
|
+
displayNumberByLogId: ReadonlyMap<number, number>;
|
|
154
172
|
}): TraceInsight[] {
|
|
155
173
|
const insights: TraceInsight[] = [
|
|
156
174
|
{
|
|
@@ -170,7 +188,7 @@ function buildTraceInsights(input: {
|
|
|
170
188
|
title: `${String(input.summary.toolCallCount)} tool call${
|
|
171
189
|
input.summary.toolCallCount === 1 ? "" : "s"
|
|
172
190
|
}`,
|
|
173
|
-
detail: `First tool evidence at #${
|
|
191
|
+
detail: `First tool evidence at #${displayLogNumber(toolLog.id, input.displayNumberByLogId)}`,
|
|
174
192
|
logId: toolLog.id,
|
|
175
193
|
});
|
|
176
194
|
}
|
|
@@ -180,7 +198,7 @@ function buildTraceInsights(input: {
|
|
|
180
198
|
insights.push({
|
|
181
199
|
kind: "failure",
|
|
182
200
|
title: `Failure #${String(failure.responseStatus)}`,
|
|
183
|
-
detail: `First failed request is #${
|
|
201
|
+
detail: `First failed request is #${displayLogNumber(failure.id, input.displayNumberByLogId)}`,
|
|
184
202
|
logId: failure.id,
|
|
185
203
|
});
|
|
186
204
|
}
|
|
@@ -190,7 +208,7 @@ function buildTraceInsights(input: {
|
|
|
190
208
|
insights.push({
|
|
191
209
|
kind: "slow",
|
|
192
210
|
title: `Slowest ${formatElapsed(input.summary.maxElapsedMs)}`,
|
|
193
|
-
detail: `Max latency observed at #${
|
|
211
|
+
detail: `Max latency observed at #${displayLogNumber(slowest.id, input.displayNumberByLogId)}`,
|
|
194
212
|
logId: slowest.id,
|
|
195
213
|
});
|
|
196
214
|
}
|
|
@@ -291,12 +309,16 @@ function previewText(value: string, maxLength: number): string {
|
|
|
291
309
|
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
292
310
|
}
|
|
293
311
|
|
|
294
|
-
function logRangeLabel(
|
|
312
|
+
function logRangeLabel(
|
|
313
|
+
logIds: number[],
|
|
314
|
+
displayNumberByLogId: ReadonlyMap<number, number>,
|
|
315
|
+
): string {
|
|
295
316
|
const first = logIds[0];
|
|
296
317
|
const last = logIds[logIds.length - 1];
|
|
297
318
|
if (first === undefined) return "No evidence logs";
|
|
298
|
-
|
|
299
|
-
|
|
319
|
+
const firstLabel = displayLogNumber(first, displayNumberByLogId);
|
|
320
|
+
if (last === undefined || first === last) return `#${firstLabel}`;
|
|
321
|
+
return `#${firstLabel}-#${displayLogNumber(last, displayNumberByLogId)}`;
|
|
300
322
|
}
|
|
301
323
|
|
|
302
324
|
function tagsToText(tags: string[]): string {
|
|
@@ -316,12 +338,14 @@ function CandidateItem({
|
|
|
316
338
|
isUpdating,
|
|
317
339
|
onPromoteCandidate,
|
|
318
340
|
onUpdateCandidate,
|
|
341
|
+
displayNumberByLogId,
|
|
319
342
|
}: {
|
|
320
343
|
candidate: KnowledgeCandidate;
|
|
321
344
|
isPromoting: boolean;
|
|
322
345
|
isUpdating: boolean;
|
|
323
346
|
onPromoteCandidate: (candidateId: string) => void;
|
|
324
347
|
onUpdateCandidate: (candidateId: string, update: CandidateDraftUpdate) => Promise<boolean>;
|
|
348
|
+
displayNumberByLogId: ReadonlyMap<number, number>;
|
|
325
349
|
}): JSX.Element {
|
|
326
350
|
const [editing, setEditing] = useState(false);
|
|
327
351
|
const [draftType, setDraftType] = useState<KnowledgeCandidate["type"]>(candidate.type);
|
|
@@ -484,7 +508,7 @@ function CandidateItem({
|
|
|
484
508
|
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[10px] text-muted-foreground">
|
|
485
509
|
<span className="inline-flex items-center gap-1">
|
|
486
510
|
<FileSearch className="size-3" />
|
|
487
|
-
{logRangeLabel(candidate.logIds)}
|
|
511
|
+
{logRangeLabel(candidate.logIds, displayNumberByLogId)}
|
|
488
512
|
</span>
|
|
489
513
|
<span className="inline-flex items-center gap-1">
|
|
490
514
|
<ShieldCheck className="size-3" />
|
|
@@ -504,20 +528,24 @@ function CandidateItem({
|
|
|
504
528
|
)}
|
|
505
529
|
</div>
|
|
506
530
|
<div className="mt-1 flex flex-wrap items-center gap-1.5">
|
|
507
|
-
{candidate.logIds.map((logId) =>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
531
|
+
{candidate.logIds.map((logId) => {
|
|
532
|
+
const displayNumber = displayLogNumber(logId, displayNumberByLogId);
|
|
533
|
+
return (
|
|
534
|
+
<a
|
|
535
|
+
key={logId}
|
|
536
|
+
href={`#${getLogAnchor(logId)}`}
|
|
537
|
+
onClick={(event) => {
|
|
538
|
+
event.preventDefault();
|
|
539
|
+
jumpToLog(logId);
|
|
540
|
+
}}
|
|
541
|
+
className="rounded border border-blue-400/25 px-1.5 py-0.5 font-mono text-[10px] text-blue-400 underline-offset-2 transition-colors hover:bg-blue-400/10 hover:text-blue-300 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
542
|
+
aria-label={`Jump to evidence log ${displayNumber}`}
|
|
543
|
+
title={`Log ID ${String(logId)}`}
|
|
544
|
+
>
|
|
545
|
+
#{displayNumber}
|
|
546
|
+
</a>
|
|
547
|
+
);
|
|
548
|
+
})}
|
|
521
549
|
<span className="flex-1" />
|
|
522
550
|
{canEdit && (
|
|
523
551
|
<Button
|
|
@@ -560,12 +588,14 @@ function CandidateList({
|
|
|
560
588
|
updatingCandidateIds,
|
|
561
589
|
onPromoteCandidate,
|
|
562
590
|
onUpdateCandidate,
|
|
591
|
+
displayNumberByLogId,
|
|
563
592
|
}: {
|
|
564
593
|
candidates: KnowledgeCandidate[];
|
|
565
594
|
promotingCandidateIds: ReadonlySet<string>;
|
|
566
595
|
updatingCandidateIds: ReadonlySet<string>;
|
|
567
596
|
onPromoteCandidate: (candidateId: string) => void;
|
|
568
597
|
onUpdateCandidate: (candidateId: string, update: CandidateDraftUpdate) => Promise<boolean>;
|
|
598
|
+
displayNumberByLogId: ReadonlyMap<number, number>;
|
|
569
599
|
}): JSX.Element | null {
|
|
570
600
|
if (candidates.length === 0) return null;
|
|
571
601
|
return (
|
|
@@ -578,6 +608,7 @@ function CandidateList({
|
|
|
578
608
|
isUpdating={updatingCandidateIds.has(candidate.id)}
|
|
579
609
|
onPromoteCandidate={onPromoteCandidate}
|
|
580
610
|
onUpdateCandidate={onUpdateCandidate}
|
|
611
|
+
displayNumberByLogId={displayNumberByLogId}
|
|
581
612
|
/>
|
|
582
613
|
))}
|
|
583
614
|
</div>
|
|
@@ -608,9 +639,10 @@ export function AgentTraceSummary({
|
|
|
608
639
|
() => buildTraceSummary(logs, slowResponseThresholdSeconds, candidates.length),
|
|
609
640
|
[candidates.length, logs, slowResponseThresholdSeconds],
|
|
610
641
|
);
|
|
642
|
+
const displayNumberByLogId = useMemo(() => buildDisplayNumberByLogId(logs), [logs]);
|
|
611
643
|
const traceInsights = useMemo(
|
|
612
|
-
() => buildTraceInsights({ logs, scopeId, summary, candidates }),
|
|
613
|
-
[candidates, logs, scopeId, summary],
|
|
644
|
+
() => buildTraceInsights({ logs, scopeId, summary, candidates, displayNumberByLogId }),
|
|
645
|
+
[candidates, displayNumberByLogId, logs, scopeId, summary],
|
|
614
646
|
);
|
|
615
647
|
const showElapsedSummary = showRollupMetrics || summary.maxElapsedMs !== null;
|
|
616
648
|
const timeRange = useMemo(
|
|
@@ -870,6 +902,7 @@ export function AgentTraceSummary({
|
|
|
870
902
|
updatingCandidateIds={updatingCandidateIds}
|
|
871
903
|
onPromoteCandidate={promoteCandidate}
|
|
872
904
|
onUpdateCandidate={updateCandidate}
|
|
905
|
+
displayNumberByLogId={displayNumberByLogId}
|
|
873
906
|
/>
|
|
874
907
|
)}
|
|
875
908
|
</section>
|
|
@@ -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
|
}
|