@tonyclaw/agent-inspector 3.0.25 → 3.0.26
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-BnvOPwpe.js → CompareDrawer-SZRUDQc9.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-BJvwOrSi.js +4234 -0
- package/.output/public/assets/{ReplayDialog-BJgoQWdW.js → ReplayDialog-DgfmqiM3.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-CpBZEHnM.js → RequestAnatomy-CS6c7Qfn.js} +1 -1
- package/.output/public/assets/{ResponseView-CWmyRb2M.js → ResponseView-C0Ri_5gm.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BWhC8bwx.js → StreamingChunkSequence-7dYcHWyZ.js} +1 -1
- package/.output/public/assets/{_sessionId-Cq-2OqUU.js → _sessionId-D-V84cgw.js} +1 -1
- package/.output/public/assets/{index-BKi9IRtV.js → index-CSIL7-MK.js} +3 -3
- package/.output/public/assets/index-CmeF5XXS.css +1 -0
- package/.output/public/assets/{index-Cf8O0Oos.js → index-DgFWCxOs.js} +1 -1
- package/.output/public/assets/{index-DdgTJ4QJ.js → index-DrVJsks2.js} +1 -1
- package/.output/public/assets/{json-viewer-Bqu_4hmm.js → json-viewer-C9Ji3gYN.js} +1 -1
- package/.output/server/_libs/three.mjs +26016 -0
- package/.output/server/{_sessionId-Ba7H3Gjw.mjs → _sessionId-BQ6YQOO1.mjs} +7 -6
- package/.output/server/_ssr/{CompareDrawer-aH59Ab6x.mjs → CompareDrawer-CqNzIeMR.mjs} +7 -6
- package/.output/server/_ssr/{ProxyViewerContainer-By-I0tJv.mjs → ProxyViewerContainer-CMC7Vsbw.mjs} +785 -311
- package/.output/server/_ssr/{ReplayDialog-BO4Vy0et.mjs → ReplayDialog-D9p_LaTr.mjs} +8 -7
- package/.output/server/_ssr/{RequestAnatomy-FtOr7vSS.mjs → RequestAnatomy-B4ge3qNZ.mjs} +7 -6
- package/.output/server/_ssr/{ResponseView-cvw6ajue.mjs → ResponseView-BCqMKApI.mjs} +7 -6
- package/.output/server/_ssr/{StreamingChunkSequence-Oc8S2kD6.mjs → StreamingChunkSequence-BA4zTLE4.mjs} +7 -6
- package/.output/server/_ssr/{index-BDRlQheR.mjs → index-CzxhDjlq.mjs} +7 -6
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-Cd6trhQr.mjs → json-viewer-B3qV6iEJ.mjs} +7 -6
- package/.output/server/_ssr/{router-bofP-Ekl.mjs → router-CZe_R31M.mjs} +4 -4
- package/.output/server/{_tanstack-start-manifest_v-BPuf86qB.mjs → _tanstack-start-manifest_v-B50VTLrH.mjs} +1 -1
- package/.output/server/index.mjs +68 -68
- package/package.json +3 -1
- package/src/components/inspector-pet/InspectorPet.tsx +66 -51
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +455 -0
- package/src/components/inspector-pet/inspectorPetLogic.ts +28 -43
- package/src/components/providers/ProviderForm.tsx +238 -163
- package/src/components/providers/ProvidersPanel.tsx +16 -6
- package/src/components/providers/providerFormCapabilities.ts +36 -0
- package/styles/globals.css +0 -61
- package/.output/public/assets/ProxyViewerContainer-DcqFqo1m.js +0 -127
- package/.output/public/assets/index-BMYuDL5X.css +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type JSX, useState, useEffect, useRef } from "react";
|
|
2
2
|
import { Button } from "../ui/button";
|
|
3
3
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
|
4
|
-
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
5
4
|
import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
|
|
6
5
|
import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
|
|
7
6
|
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
type UpstreamUrlPreview,
|
|
15
14
|
} from "../../lib/upstreamUrl";
|
|
16
15
|
import { formatContextWindowInput, parseContextWindowTokensInput } from "../../lib/utils";
|
|
16
|
+
import { deriveProviderCapabilities, endpointForCapability } from "./providerFormCapabilities";
|
|
17
17
|
|
|
18
18
|
const ZHIPU_PROVIDER_KEYWORDS = ["zhipu", "zhipuai", "bigmodel", "z.ai", "zai", "glm"];
|
|
19
19
|
const ZHIPU_CODING_PROVIDER_KEYWORDS = [
|
|
@@ -325,6 +325,7 @@ function EndpointUrlPreview({
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps): JSX.Element {
|
|
328
|
+
const initialCapabilities = deriveProviderCapabilities(provider);
|
|
328
329
|
const [name, setName] = useState(provider?.name ?? "");
|
|
329
330
|
const [apiKey, setApiKey] = useState(provider?.apiKey ?? "");
|
|
330
331
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
@@ -339,7 +340,12 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
339
340
|
initialModels !== undefined && initialModels.length > 0 ? initialModels : [""],
|
|
340
341
|
),
|
|
341
342
|
);
|
|
342
|
-
const [
|
|
343
|
+
const [supportsAnthropic, setSupportsAnthropic] = useState(initialCapabilities.anthropic);
|
|
344
|
+
const [supportsOpenai, setSupportsOpenai] = useState(initialCapabilities.openai);
|
|
345
|
+
const [supportsOpenaiChat, setSupportsOpenaiChat] = useState(initialCapabilities.openaiChat);
|
|
346
|
+
const [supportsOpenaiResponses, setSupportsOpenaiResponses] = useState(
|
|
347
|
+
initialCapabilities.openaiResponses,
|
|
348
|
+
);
|
|
343
349
|
const [anthropicBaseUrl, setAnthropicBaseUrl] = useState(
|
|
344
350
|
stripEndpointSuffix(provider?.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT),
|
|
345
351
|
);
|
|
@@ -409,6 +415,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
409
415
|
const nextModels = (provider.models?.length ?? 0) > 0 ? provider.models : [""];
|
|
410
416
|
setModels(nextModels);
|
|
411
417
|
setModelMetadataDrafts(createMetadataDrafts(provider, nextModels));
|
|
418
|
+
const nextCapabilities = deriveProviderCapabilities(provider);
|
|
419
|
+
setSupportsAnthropic(nextCapabilities.anthropic);
|
|
420
|
+
setSupportsOpenai(nextCapabilities.openai);
|
|
421
|
+
setSupportsOpenaiChat(nextCapabilities.openaiChat);
|
|
422
|
+
setSupportsOpenaiResponses(nextCapabilities.openaiResponses);
|
|
412
423
|
setAnthropicBaseUrl(
|
|
413
424
|
stripEndpointSuffix(provider.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT),
|
|
414
425
|
);
|
|
@@ -477,14 +488,32 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
477
488
|
if (models.length === 0 || models.every((m) => !m.trim())) {
|
|
478
489
|
newErrors.models = "At least one model is required";
|
|
479
490
|
}
|
|
480
|
-
if (
|
|
481
|
-
newErrors.
|
|
491
|
+
if (!supportsAnthropic && !supportsOpenai) {
|
|
492
|
+
newErrors.capabilities = "Select at least one API capability";
|
|
493
|
+
}
|
|
494
|
+
if (supportsOpenai && !supportsOpenaiChat && !supportsOpenaiResponses) {
|
|
495
|
+
newErrors.openaiCapabilities = "Select Chat Completions, Responses, or both";
|
|
496
|
+
}
|
|
497
|
+
if (supportsAnthropic) {
|
|
498
|
+
if (anthropicBaseUrl.trim() === "") {
|
|
499
|
+
newErrors.anthropicBaseUrl = "Anthropic base URL is required";
|
|
500
|
+
} else if (!isValidUrl(anthropicBaseUrl.trim())) {
|
|
501
|
+
newErrors.anthropicBaseUrl = "Invalid URL format";
|
|
502
|
+
}
|
|
482
503
|
}
|
|
483
|
-
if (
|
|
484
|
-
|
|
504
|
+
if (supportsOpenai && supportsOpenaiChat) {
|
|
505
|
+
if (openaiBaseUrl.trim() === "") {
|
|
506
|
+
newErrors.openaiBaseUrl = "OpenAI Chat base URL is required";
|
|
507
|
+
} else if (!isValidUrl(openaiBaseUrl.trim())) {
|
|
508
|
+
newErrors.openaiBaseUrl = "Invalid URL format";
|
|
509
|
+
}
|
|
485
510
|
}
|
|
486
|
-
if (
|
|
487
|
-
|
|
511
|
+
if (supportsOpenai && supportsOpenaiResponses) {
|
|
512
|
+
if (openaiResponsesBaseUrl.trim() === "") {
|
|
513
|
+
newErrors.openaiResponsesBaseUrl = "OpenAI Responses base URL is required";
|
|
514
|
+
} else if (!isValidUrl(openaiResponsesBaseUrl.trim())) {
|
|
515
|
+
newErrors.openaiResponsesBaseUrl = "Invalid URL format";
|
|
516
|
+
}
|
|
488
517
|
}
|
|
489
518
|
if (modelMetadataUrl.trim() && !isValidUrl(modelMetadataUrl.trim())) {
|
|
490
519
|
newErrors.modelMetadataUrl = "Invalid URL format";
|
|
@@ -504,9 +533,6 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
504
533
|
"Output limit should not exceed context window";
|
|
505
534
|
}
|
|
506
535
|
});
|
|
507
|
-
if (!anthropicBaseUrl.trim() && !openaiBaseUrl.trim() && !openaiResponsesBaseUrl.trim()) {
|
|
508
|
-
newErrors.format = "At least one format URL (Anthropic or OpenAI) is required";
|
|
509
|
-
}
|
|
510
536
|
setErrors(newErrors);
|
|
511
537
|
return Object.keys(newErrors).length === 0;
|
|
512
538
|
}
|
|
@@ -631,14 +657,27 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
631
657
|
e.preventDefault();
|
|
632
658
|
if (!validate()) return;
|
|
633
659
|
setIsSubmitting(true);
|
|
660
|
+
const clearDisabledEndpoints = provider !== undefined;
|
|
634
661
|
try {
|
|
635
662
|
onSubmit({
|
|
636
663
|
name: name.trim(),
|
|
637
664
|
apiKey: apiKey.trim(),
|
|
638
665
|
models: models.map((m) => m.trim()).filter((m) => m !== ""),
|
|
639
|
-
anthropicBaseUrl:
|
|
640
|
-
|
|
641
|
-
|
|
666
|
+
anthropicBaseUrl: endpointForCapability(
|
|
667
|
+
supportsAnthropic,
|
|
668
|
+
anthropicBaseUrl,
|
|
669
|
+
clearDisabledEndpoints,
|
|
670
|
+
),
|
|
671
|
+
openaiBaseUrl: endpointForCapability(
|
|
672
|
+
supportsOpenai && supportsOpenaiChat,
|
|
673
|
+
openaiBaseUrl,
|
|
674
|
+
clearDisabledEndpoints,
|
|
675
|
+
),
|
|
676
|
+
openaiResponsesBaseUrl: endpointForCapability(
|
|
677
|
+
supportsOpenai && supportsOpenaiResponses,
|
|
678
|
+
openaiResponsesBaseUrl,
|
|
679
|
+
clearDisabledEndpoints,
|
|
680
|
+
),
|
|
642
681
|
apiDocsUrl: apiDocsUrl.trim() || undefined,
|
|
643
682
|
modelMetadataUrl: modelMetadataUrl.trim() || undefined,
|
|
644
683
|
modelMetadata: buildModelMetadata(),
|
|
@@ -666,6 +705,191 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
666
705
|
{errors.name !== undefined && <p className="text-xs text-destructive">{errors.name}</p>}
|
|
667
706
|
</div>
|
|
668
707
|
|
|
708
|
+
<fieldset className="space-y-3">
|
|
709
|
+
<legend className="text-sm font-medium">
|
|
710
|
+
Capabilities <span className="text-destructive">*</span>
|
|
711
|
+
</legend>
|
|
712
|
+
<div className="divide-y divide-border overflow-hidden rounded-md border border-border">
|
|
713
|
+
<div className={supportsOpenai ? "bg-primary/[0.04] px-3 py-3" : "px-3 py-3"}>
|
|
714
|
+
<label
|
|
715
|
+
htmlFor="provider-capability-openai"
|
|
716
|
+
className="flex cursor-pointer items-start gap-3"
|
|
717
|
+
>
|
|
718
|
+
<input
|
|
719
|
+
id="provider-capability-openai"
|
|
720
|
+
type="checkbox"
|
|
721
|
+
checked={supportsOpenai}
|
|
722
|
+
onChange={(event) => setSupportsOpenai(event.target.checked)}
|
|
723
|
+
className="mt-0.5 size-4 shrink-0 accent-primary"
|
|
724
|
+
/>
|
|
725
|
+
<span className="min-w-0">
|
|
726
|
+
<span className="block text-sm font-medium">OpenAI</span>
|
|
727
|
+
<span className="block text-xs text-muted-foreground">
|
|
728
|
+
Chat Completions and Responses
|
|
729
|
+
</span>
|
|
730
|
+
</span>
|
|
731
|
+
</label>
|
|
732
|
+
{supportsOpenai && (
|
|
733
|
+
<div className="ml-7 mt-3 flex flex-wrap gap-x-5 gap-y-2 border-t border-border/70 pt-3">
|
|
734
|
+
<label
|
|
735
|
+
htmlFor="provider-capability-openai-chat"
|
|
736
|
+
className="flex cursor-pointer items-center gap-2 text-sm"
|
|
737
|
+
>
|
|
738
|
+
<input
|
|
739
|
+
id="provider-capability-openai-chat"
|
|
740
|
+
type="checkbox"
|
|
741
|
+
checked={supportsOpenaiChat}
|
|
742
|
+
onChange={(event) => setSupportsOpenaiChat(event.target.checked)}
|
|
743
|
+
className="size-4 shrink-0 accent-primary"
|
|
744
|
+
/>
|
|
745
|
+
Chat Completions
|
|
746
|
+
</label>
|
|
747
|
+
<label
|
|
748
|
+
htmlFor="provider-capability-openai-responses"
|
|
749
|
+
className="flex cursor-pointer items-center gap-2 text-sm"
|
|
750
|
+
>
|
|
751
|
+
<input
|
|
752
|
+
id="provider-capability-openai-responses"
|
|
753
|
+
type="checkbox"
|
|
754
|
+
checked={supportsOpenaiResponses}
|
|
755
|
+
onChange={(event) => setSupportsOpenaiResponses(event.target.checked)}
|
|
756
|
+
className="size-4 shrink-0 accent-primary"
|
|
757
|
+
/>
|
|
758
|
+
Responses
|
|
759
|
+
</label>
|
|
760
|
+
</div>
|
|
761
|
+
)}
|
|
762
|
+
{errors.openaiCapabilities !== undefined && supportsOpenai && (
|
|
763
|
+
<p className="ml-7 mt-2 text-xs text-destructive">{errors.openaiCapabilities}</p>
|
|
764
|
+
)}
|
|
765
|
+
</div>
|
|
766
|
+
<label
|
|
767
|
+
htmlFor="provider-capability-anthropic"
|
|
768
|
+
className={`flex cursor-pointer items-start gap-3 px-3 py-3 ${
|
|
769
|
+
supportsAnthropic ? "bg-primary/[0.04]" : ""
|
|
770
|
+
}`}
|
|
771
|
+
>
|
|
772
|
+
<input
|
|
773
|
+
id="provider-capability-anthropic"
|
|
774
|
+
type="checkbox"
|
|
775
|
+
checked={supportsAnthropic}
|
|
776
|
+
onChange={(event) => setSupportsAnthropic(event.target.checked)}
|
|
777
|
+
className="mt-0.5 size-4 shrink-0 accent-primary"
|
|
778
|
+
/>
|
|
779
|
+
<span className="min-w-0">
|
|
780
|
+
<span className="block text-sm font-medium">Anthropic</span>
|
|
781
|
+
<span className="block text-xs text-muted-foreground">Messages</span>
|
|
782
|
+
</span>
|
|
783
|
+
</label>
|
|
784
|
+
</div>
|
|
785
|
+
{errors.capabilities !== undefined && (
|
|
786
|
+
<p className="text-xs text-destructive">{errors.capabilities}</p>
|
|
787
|
+
)}
|
|
788
|
+
</fieldset>
|
|
789
|
+
|
|
790
|
+
{(supportsAnthropic ||
|
|
791
|
+
(supportsOpenai && (supportsOpenaiChat || supportsOpenaiResponses))) && (
|
|
792
|
+
<section className="space-y-5 border-l border-border pl-4" aria-label="API endpoints">
|
|
793
|
+
<h3 className="text-sm font-medium">Endpoint Configuration</h3>
|
|
794
|
+
|
|
795
|
+
{supportsAnthropic && (
|
|
796
|
+
<div className="space-y-2">
|
|
797
|
+
<label htmlFor="provider-anthropic-base-url" className="text-sm font-medium">
|
|
798
|
+
Anthropic Messages Base URL <span className="text-destructive">*</span>
|
|
799
|
+
</label>
|
|
800
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
801
|
+
<input
|
|
802
|
+
id="provider-anthropic-base-url"
|
|
803
|
+
type="text"
|
|
804
|
+
value={anthropicBaseUrl}
|
|
805
|
+
onChange={(event) => {
|
|
806
|
+
setManualAnthropicUrlOverride(true);
|
|
807
|
+
setAnthropicBaseUrl(event.target.value);
|
|
808
|
+
}}
|
|
809
|
+
placeholder="https://api.anthropic.com"
|
|
810
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
811
|
+
/>
|
|
812
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
813
|
+
{ANTHROPIC_MESSAGES_ENDPOINT}
|
|
814
|
+
</span>
|
|
815
|
+
</div>
|
|
816
|
+
{errors.anthropicBaseUrl !== undefined && (
|
|
817
|
+
<p className="text-xs text-destructive">{errors.anthropicBaseUrl}</p>
|
|
818
|
+
)}
|
|
819
|
+
<EndpointUrlPreview
|
|
820
|
+
baseUrl={anthropicBaseUrl}
|
|
821
|
+
endpoint={ANTHROPIC_MESSAGES_ENDPOINT}
|
|
822
|
+
providerName={name}
|
|
823
|
+
/>
|
|
824
|
+
</div>
|
|
825
|
+
)}
|
|
826
|
+
|
|
827
|
+
{supportsOpenai && supportsOpenaiChat && (
|
|
828
|
+
<div className="space-y-2">
|
|
829
|
+
<label htmlFor="provider-openai-base-url" className="text-sm font-medium">
|
|
830
|
+
OpenAI Chat Base URL <span className="text-destructive">*</span>
|
|
831
|
+
</label>
|
|
832
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
833
|
+
<input
|
|
834
|
+
id="provider-openai-base-url"
|
|
835
|
+
type="text"
|
|
836
|
+
value={openaiBaseUrl}
|
|
837
|
+
onChange={(event) => {
|
|
838
|
+
setManualOpenaiUrlOverride(true);
|
|
839
|
+
setOpenaiBaseUrl(event.target.value);
|
|
840
|
+
}}
|
|
841
|
+
placeholder="https://api.openai.com"
|
|
842
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
843
|
+
/>
|
|
844
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
845
|
+
{OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
846
|
+
</span>
|
|
847
|
+
</div>
|
|
848
|
+
{errors.openaiBaseUrl !== undefined && (
|
|
849
|
+
<p className="text-xs text-destructive">{errors.openaiBaseUrl}</p>
|
|
850
|
+
)}
|
|
851
|
+
<EndpointUrlPreview
|
|
852
|
+
baseUrl={openaiBaseUrl}
|
|
853
|
+
endpoint={OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
854
|
+
providerName={name}
|
|
855
|
+
/>
|
|
856
|
+
</div>
|
|
857
|
+
)}
|
|
858
|
+
|
|
859
|
+
{supportsOpenai && supportsOpenaiResponses && (
|
|
860
|
+
<div className="space-y-2">
|
|
861
|
+
<label htmlFor="provider-openai-responses-base-url" className="text-sm font-medium">
|
|
862
|
+
OpenAI Responses Base URL <span className="text-destructive">*</span>
|
|
863
|
+
</label>
|
|
864
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
865
|
+
<input
|
|
866
|
+
id="provider-openai-responses-base-url"
|
|
867
|
+
type="text"
|
|
868
|
+
value={openaiResponsesBaseUrl}
|
|
869
|
+
onChange={(event) => {
|
|
870
|
+
setManualOpenaiResponsesUrlOverride(true);
|
|
871
|
+
setOpenaiResponsesBaseUrl(event.target.value);
|
|
872
|
+
}}
|
|
873
|
+
placeholder="https://api.openai.com"
|
|
874
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
875
|
+
/>
|
|
876
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
877
|
+
{OPENAI_RESPONSES_ENDPOINT}
|
|
878
|
+
</span>
|
|
879
|
+
</div>
|
|
880
|
+
{errors.openaiResponsesBaseUrl !== undefined && (
|
|
881
|
+
<p className="text-xs text-destructive">{errors.openaiResponsesBaseUrl}</p>
|
|
882
|
+
)}
|
|
883
|
+
<EndpointUrlPreview
|
|
884
|
+
baseUrl={openaiResponsesBaseUrl}
|
|
885
|
+
endpoint={OPENAI_RESPONSES_ENDPOINT}
|
|
886
|
+
providerName={name}
|
|
887
|
+
/>
|
|
888
|
+
</div>
|
|
889
|
+
)}
|
|
890
|
+
</section>
|
|
891
|
+
)}
|
|
892
|
+
|
|
669
893
|
<div className="space-y-2">
|
|
670
894
|
<label htmlFor="provider-source" className="text-sm font-medium">
|
|
671
895
|
Token Source
|
|
@@ -924,155 +1148,6 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
924
1148
|
{errors.models !== undefined && <p className="text-xs text-destructive">{errors.models}</p>}
|
|
925
1149
|
</div>
|
|
926
1150
|
|
|
927
|
-
<div className="space-y-2">
|
|
928
|
-
<div className="border border-border bg-muted/40 flex gap-1 rounded-[8px] p-1">
|
|
929
|
-
<TooltipProvider>
|
|
930
|
-
<Tooltip>
|
|
931
|
-
<TooltipTrigger asChild>
|
|
932
|
-
<button
|
|
933
|
-
type="button"
|
|
934
|
-
onClick={() => setActiveTab("anthropic")}
|
|
935
|
-
className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
|
|
936
|
-
activeTab === "anthropic"
|
|
937
|
-
? "border-primary text-primary"
|
|
938
|
-
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
939
|
-
}`}
|
|
940
|
-
>
|
|
941
|
-
Anthropic Format
|
|
942
|
-
</button>
|
|
943
|
-
</TooltipTrigger>
|
|
944
|
-
<TooltipContent>Anthropic Messages API format</TooltipContent>
|
|
945
|
-
</Tooltip>
|
|
946
|
-
</TooltipProvider>
|
|
947
|
-
<TooltipProvider>
|
|
948
|
-
<Tooltip>
|
|
949
|
-
<TooltipTrigger asChild>
|
|
950
|
-
<button
|
|
951
|
-
type="button"
|
|
952
|
-
onClick={() => setActiveTab("openai")}
|
|
953
|
-
className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
|
|
954
|
-
activeTab === "openai"
|
|
955
|
-
? "border-primary text-primary"
|
|
956
|
-
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
957
|
-
}`}
|
|
958
|
-
>
|
|
959
|
-
OpenAI Format
|
|
960
|
-
</button>
|
|
961
|
-
</TooltipTrigger>
|
|
962
|
-
<TooltipContent>OpenAI Chat Completions API format</TooltipContent>
|
|
963
|
-
</Tooltip>
|
|
964
|
-
</TooltipProvider>
|
|
965
|
-
</div>
|
|
966
|
-
{errors.format !== undefined && <p className="text-xs text-destructive">{errors.format}</p>}
|
|
967
|
-
</div>
|
|
968
|
-
|
|
969
|
-
{activeTab === "anthropic" && (
|
|
970
|
-
<div className="space-y-2">
|
|
971
|
-
<label htmlFor="provider-anthropic-base-url" className="text-sm font-medium">
|
|
972
|
-
Anthropic Base URL
|
|
973
|
-
</label>
|
|
974
|
-
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
975
|
-
<input
|
|
976
|
-
id="provider-anthropic-base-url"
|
|
977
|
-
type="text"
|
|
978
|
-
value={anthropicBaseUrl}
|
|
979
|
-
onChange={(e) => {
|
|
980
|
-
setManualAnthropicUrlOverride(true);
|
|
981
|
-
setAnthropicBaseUrl(e.target.value);
|
|
982
|
-
}}
|
|
983
|
-
placeholder="https://api.anthropic.com"
|
|
984
|
-
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
985
|
-
/>
|
|
986
|
-
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
987
|
-
{ANTHROPIC_MESSAGES_ENDPOINT}
|
|
988
|
-
</span>
|
|
989
|
-
</div>
|
|
990
|
-
{errors.anthropicBaseUrl !== undefined && (
|
|
991
|
-
<p className="text-xs text-destructive">{errors.anthropicBaseUrl}</p>
|
|
992
|
-
)}
|
|
993
|
-
<EndpointUrlPreview
|
|
994
|
-
baseUrl={anthropicBaseUrl}
|
|
995
|
-
endpoint={ANTHROPIC_MESSAGES_ENDPOINT}
|
|
996
|
-
providerName={name}
|
|
997
|
-
/>
|
|
998
|
-
<p className="text-xs text-muted-foreground">
|
|
999
|
-
Anthropic-compatible base URL. Leave empty if this provider does not support Anthropic
|
|
1000
|
-
format.
|
|
1001
|
-
</p>
|
|
1002
|
-
</div>
|
|
1003
|
-
)}
|
|
1004
|
-
|
|
1005
|
-
{activeTab === "openai" && (
|
|
1006
|
-
<div className="space-y-4">
|
|
1007
|
-
<div className="space-y-2">
|
|
1008
|
-
<label htmlFor="provider-openai-base-url" className="text-sm font-medium">
|
|
1009
|
-
OpenAI Chat Base URL
|
|
1010
|
-
</label>
|
|
1011
|
-
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
1012
|
-
<input
|
|
1013
|
-
id="provider-openai-base-url"
|
|
1014
|
-
type="text"
|
|
1015
|
-
value={openaiBaseUrl}
|
|
1016
|
-
onChange={(e) => {
|
|
1017
|
-
setManualOpenaiUrlOverride(true);
|
|
1018
|
-
setOpenaiBaseUrl(e.target.value);
|
|
1019
|
-
}}
|
|
1020
|
-
placeholder="https://api.openai.com"
|
|
1021
|
-
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
1022
|
-
/>
|
|
1023
|
-
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
1024
|
-
{OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
1025
|
-
</span>
|
|
1026
|
-
</div>
|
|
1027
|
-
{errors.openaiBaseUrl !== undefined && (
|
|
1028
|
-
<p className="text-xs text-destructive">{errors.openaiBaseUrl}</p>
|
|
1029
|
-
)}
|
|
1030
|
-
<EndpointUrlPreview
|
|
1031
|
-
baseUrl={openaiBaseUrl}
|
|
1032
|
-
endpoint={OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
1033
|
-
providerName={name}
|
|
1034
|
-
/>
|
|
1035
|
-
<p className="text-xs text-muted-foreground">
|
|
1036
|
-
OpenAI Chat Completions-compatible base URL. Leave empty if this provider only
|
|
1037
|
-
supports the Responses API.
|
|
1038
|
-
</p>
|
|
1039
|
-
</div>
|
|
1040
|
-
<div className="space-y-2">
|
|
1041
|
-
<label htmlFor="provider-openai-responses-base-url" className="text-sm font-medium">
|
|
1042
|
-
OpenAI Responses Base URL
|
|
1043
|
-
</label>
|
|
1044
|
-
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
1045
|
-
<input
|
|
1046
|
-
id="provider-openai-responses-base-url"
|
|
1047
|
-
type="text"
|
|
1048
|
-
value={openaiResponsesBaseUrl}
|
|
1049
|
-
onChange={(e) => {
|
|
1050
|
-
setManualOpenaiResponsesUrlOverride(true);
|
|
1051
|
-
setOpenaiResponsesBaseUrl(e.target.value);
|
|
1052
|
-
}}
|
|
1053
|
-
placeholder="https://api.openai.com"
|
|
1054
|
-
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
1055
|
-
/>
|
|
1056
|
-
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
1057
|
-
{OPENAI_RESPONSES_ENDPOINT}
|
|
1058
|
-
</span>
|
|
1059
|
-
</div>
|
|
1060
|
-
{errors.openaiResponsesBaseUrl !== undefined && (
|
|
1061
|
-
<p className="text-xs text-destructive">{errors.openaiResponsesBaseUrl}</p>
|
|
1062
|
-
)}
|
|
1063
|
-
<EndpointUrlPreview
|
|
1064
|
-
baseUrl={openaiResponsesBaseUrl}
|
|
1065
|
-
endpoint={OPENAI_RESPONSES_ENDPOINT}
|
|
1066
|
-
providerName={name}
|
|
1067
|
-
/>
|
|
1068
|
-
<p className="text-xs text-muted-foreground">
|
|
1069
|
-
Optional Responses-compatible base URL. Set only when the provider supports the
|
|
1070
|
-
Responses API.
|
|
1071
|
-
</p>
|
|
1072
|
-
</div>
|
|
1073
|
-
</div>
|
|
1074
|
-
)}
|
|
1075
|
-
|
|
1076
1151
|
<div className="space-y-2">
|
|
1077
1152
|
<label htmlFor="provider-api-docs-url" className="text-sm font-medium">
|
|
1078
1153
|
API Docs URL
|
|
@@ -70,15 +70,25 @@ type ProviderFormData = {
|
|
|
70
70
|
source?: "company" | "personal";
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
function
|
|
73
|
+
function endpointPayloadValue(
|
|
74
|
+
value: string | undefined,
|
|
75
|
+
preserveEmptyEndpoints: boolean,
|
|
76
|
+
): string | undefined {
|
|
77
|
+
if ((value?.length ?? 0) > 0) return value;
|
|
78
|
+
return preserveEmptyEndpoints && value !== undefined ? "" : undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createProviderPayload(data: ProviderFormData, preserveEmptyEndpoints = false) {
|
|
74
82
|
return {
|
|
75
83
|
name: data.name,
|
|
76
84
|
apiKey: data.apiKey,
|
|
77
85
|
models: data.models,
|
|
78
|
-
anthropicBaseUrl: (data.anthropicBaseUrl
|
|
79
|
-
openaiBaseUrl: (data.openaiBaseUrl
|
|
80
|
-
openaiResponsesBaseUrl:
|
|
81
|
-
|
|
86
|
+
anthropicBaseUrl: endpointPayloadValue(data.anthropicBaseUrl, preserveEmptyEndpoints),
|
|
87
|
+
openaiBaseUrl: endpointPayloadValue(data.openaiBaseUrl, preserveEmptyEndpoints),
|
|
88
|
+
openaiResponsesBaseUrl: endpointPayloadValue(
|
|
89
|
+
data.openaiResponsesBaseUrl,
|
|
90
|
+
preserveEmptyEndpoints,
|
|
91
|
+
),
|
|
82
92
|
apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : undefined,
|
|
83
93
|
modelMetadataUrl: (data.modelMetadataUrl?.length ?? 0) > 0 ? data.modelMetadataUrl : undefined,
|
|
84
94
|
modelMetadata: data.modelMetadata,
|
|
@@ -408,7 +418,7 @@ export function ProvidersPanel({
|
|
|
408
418
|
{
|
|
409
419
|
method: "PUT",
|
|
410
420
|
headers: { "Content-Type": "application/json" },
|
|
411
|
-
body: JSON.stringify(createProviderPayload(data)),
|
|
421
|
+
body: JSON.stringify(createProviderPayload(data, true)),
|
|
412
422
|
},
|
|
413
423
|
() => "Failed to update provider",
|
|
414
424
|
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ProviderConfig } from "../../lib/providerContract";
|
|
2
|
+
|
|
3
|
+
export type ProviderCapabilitySelection = {
|
|
4
|
+
anthropic: boolean;
|
|
5
|
+
openai: boolean;
|
|
6
|
+
openaiChat: boolean;
|
|
7
|
+
openaiResponses: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function hasEndpoint(value: string | undefined): boolean {
|
|
11
|
+
return value !== undefined && value.trim() !== "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function deriveProviderCapabilities(
|
|
15
|
+
provider: ProviderConfig | undefined,
|
|
16
|
+
): ProviderCapabilitySelection {
|
|
17
|
+
const anthropic = hasEndpoint(provider?.anthropicBaseUrl);
|
|
18
|
+
const openaiChat = hasEndpoint(provider?.openaiBaseUrl);
|
|
19
|
+
const openaiResponses = hasEndpoint(provider?.openaiResponsesBaseUrl);
|
|
20
|
+
return {
|
|
21
|
+
anthropic,
|
|
22
|
+
openai: openaiChat || openaiResponses,
|
|
23
|
+
openaiChat,
|
|
24
|
+
openaiResponses,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function endpointForCapability(
|
|
29
|
+
enabled: boolean,
|
|
30
|
+
value: string,
|
|
31
|
+
clearWhenDisabled: boolean,
|
|
32
|
+
): string | undefined {
|
|
33
|
+
if (!enabled) return clearWhenDisabled ? "" : undefined;
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
return trimmed === "" ? undefined : trimmed;
|
|
36
|
+
}
|
package/styles/globals.css
CHANGED
|
@@ -45,8 +45,6 @@
|
|
|
45
45
|
--animate-crab-appear: crab-appear 300ms ease-out;
|
|
46
46
|
--animate-crab-settle: crab-settle 400ms ease-in-out;
|
|
47
47
|
--animate-crab-piano-pop: crab-piano-pop 400ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
|
|
48
|
-
--animate-crab-short-leg-body: crab-short-leg-body 720ms ease-in-out infinite;
|
|
49
|
-
--animate-crab-short-leg-step: crab-short-leg-step 360ms ease-in-out infinite;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
:root {
|
|
@@ -222,34 +220,6 @@
|
|
|
222
220
|
}
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
@keyframes crab-short-leg-body {
|
|
226
|
-
0%,
|
|
227
|
-
100% {
|
|
228
|
-
transform: translateY(0) rotate(-1deg);
|
|
229
|
-
}
|
|
230
|
-
25% {
|
|
231
|
-
transform: translateY(1px) rotate(1.5deg);
|
|
232
|
-
}
|
|
233
|
-
50% {
|
|
234
|
-
transform: translateY(-1px) rotate(-1.5deg);
|
|
235
|
-
}
|
|
236
|
-
75% {
|
|
237
|
-
transform: translateY(1px) rotate(1deg);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
@keyframes crab-short-leg-step {
|
|
242
|
-
0%,
|
|
243
|
-
100% {
|
|
244
|
-
transform: translateY(0) scaleY(0.72) rotate(var(--crab-leg-rest, 0deg));
|
|
245
|
-
opacity: 0.42;
|
|
246
|
-
}
|
|
247
|
-
50% {
|
|
248
|
-
transform: translateY(3px) scaleY(1.15) rotate(var(--crab-leg-step, 0deg));
|
|
249
|
-
opacity: 0.9;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
223
|
@keyframes inspector-pet-watch {
|
|
254
224
|
0%,
|
|
255
225
|
100% {
|
|
@@ -317,8 +287,6 @@
|
|
|
317
287
|
.animate-crab-piano-pop {
|
|
318
288
|
animation: none !important;
|
|
319
289
|
}
|
|
320
|
-
.floating-crab-body,
|
|
321
|
-
.floating-crab-leg,
|
|
322
290
|
.inspector-pet-active,
|
|
323
291
|
.inspector-pet-attention,
|
|
324
292
|
.inspector-pet-sleeping,
|
|
@@ -379,26 +347,6 @@
|
|
|
379
347
|
font-size: var(--inspector-font-13);
|
|
380
348
|
}
|
|
381
349
|
|
|
382
|
-
.floating-crab-body {
|
|
383
|
-
animation: var(--animate-crab-short-leg-body);
|
|
384
|
-
transform-origin: 50% 74%;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
.floating-crab-leg {
|
|
388
|
-
animation: var(--animate-crab-short-leg-step);
|
|
389
|
-
transform-origin: 50% 0%;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.floating-crab-leg-left {
|
|
393
|
-
--crab-leg-rest: -10deg;
|
|
394
|
-
--crab-leg-step: 14deg;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
.floating-crab-leg-right {
|
|
398
|
-
--crab-leg-rest: 10deg;
|
|
399
|
-
--crab-leg-step: -14deg;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
350
|
.inspector-pet-active {
|
|
403
351
|
animation: inspector-pet-watch 1.25s ease-in-out infinite;
|
|
404
352
|
}
|
|
@@ -419,15 +367,6 @@
|
|
|
419
367
|
animation: inspector-pet-heart 720ms ease-out forwards;
|
|
420
368
|
}
|
|
421
369
|
|
|
422
|
-
[data-pet-mood="active"] .floating-crab-leg {
|
|
423
|
-
animation-duration: 260ms;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
[data-pet-mood="sleeping"] .floating-crab-leg {
|
|
427
|
-
animation-duration: 1.8s;
|
|
428
|
-
opacity: 0.3;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
370
|
.inspector-scrollbar {
|
|
432
371
|
scrollbar-color: color-mix(in srgb, var(--muted-foreground) 32%, transparent) transparent;
|
|
433
372
|
scrollbar-width: thin;
|