@tonyclaw/agent-inspector 4.0.1 → 4.0.2
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/backend/nitro.json +1 -1
- package/.output/cli.js +192 -109
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-CTHqmroT.mjs → router-BBY_zWRK.mjs} +91 -3
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-rtNElN-B.js} +1 -1
- package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-QaAiuF3d.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-6twNZHwl.js} +4 -4
- package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-B7KlziO2.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-2XVFBrW4.js} +1 -1
- package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BULgzLVB.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-DSkvkbns.js} +1 -1
- package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-D2dmJi3G.js} +1 -1
- package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-DcS0s1Mr.js} +1 -1
- package/.output/ui/assets/{index-Du4qNOOt.js → index-B0TN1KKO.js} +2 -2
- package/.output/ui/assets/{index-gvFj_9K6.js → index-Bva1zHEs.js} +1 -1
- package/.output/ui/assets/{index-Ea-c8hPg.js → index-BzBExOxu.js} +1 -1
- package/.output/ui/assets/{index-zVVS53Hd.js → index-k6sRQz55.js} +1 -1
- package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-CWM4tY-j.js} +1 -1
- package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkWvRFlo.js} +1 -1
- package/.output/ui/index.html +1 -1
- package/package.json +5 -10
- package/src/assets/IDE/claudecode-color.svg +0 -1
- package/src/assets/IDE/codex-color.svg +0 -1
- package/src/assets/IDE/opencode.svg +0 -1
- package/src/assets/IDE/xiaomimimo.svg +0 -1
- package/src/assets/favicon.svg +0 -21
- package/src/assets/logos/alibaba.svg +0 -1
- package/src/assets/logos/anthropic.svg +0 -1
- package/src/assets/logos/claude-code.svg +0 -4
- package/src/assets/logos/deepseek.svg +0 -1
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +0 -1
- package/src/assets/logos/opencode.svg +0 -4
- package/src/assets/logos/qwen.webp +0 -0
- package/src/assets/logos/zhipuai.svg +0 -219
- package/src/backend/router.tsx +0 -36
- package/src/backend/routes/$.ts +0 -26
- package/src/backend/routes/__root.ts +0 -4
- package/src/backend/routes/api/-instances.ts +0 -7
- package/src/backend/routes/api/alerts.summary.ts +0 -28
- package/src/backend/routes/api/alerts.ts +0 -70
- package/src/backend/routes/api/config.paths.ts +0 -14
- package/src/backend/routes/api/config.ts +0 -73
- package/src/backend/routes/api/ecosystem.packages.$packageId.help.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.install.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.runner-presets.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.upgrade.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.ts +0 -21
- package/src/backend/routes/api/ecosystem.recipes.$recipeId.run.ts +0 -11
- package/src/backend/routes/api/ecosystem.tasks.$taskId.ts +0 -16
- package/src/backend/routes/api/ecosystem.tasks.ts +0 -12
- package/src/backend/routes/api/groups.$groupId.evidence.ts +0 -55
- package/src/backend/routes/api/groups.$groupId.sessions.ts +0 -50
- package/src/backend/routes/api/groups.$groupId.ts +0 -93
- package/src/backend/routes/api/groups.ts +0 -79
- package/src/backend/routes/api/health.ts +0 -22
- package/src/backend/routes/api/instances.$name.connection.ts +0 -12
- package/src/backend/routes/api/instances.$name.restart.ts +0 -23
- package/src/backend/routes/api/instances.$name.start.ts +0 -18
- package/src/backend/routes/api/instances.$name.stop.ts +0 -23
- package/src/backend/routes/api/instances.$name.ts +0 -12
- package/src/backend/routes/api/instances.ts +0 -16
- package/src/backend/routes/api/knowledge.candidates.$candidateId.promote.ts +0 -32
- package/src/backend/routes/api/knowledge.candidates.$candidateId.ts +0 -50
- package/src/backend/routes/api/knowledge.candidates.ts +0 -10
- package/src/backend/routes/api/knowledge.project-context.ts +0 -29
- package/src/backend/routes/api/knowledge.search.ts +0 -62
- package/src/backend/routes/api/knowledge.sessions.$sessionId.candidates.ts +0 -26
- package/src/backend/routes/api/logs.$id.body.ts +0 -58
- package/src/backend/routes/api/logs.$id.chunks.ts +0 -37
- package/src/backend/routes/api/logs.$id.replay.ts +0 -297
- package/src/backend/routes/api/logs.$id.ts +0 -23
- package/src/backend/routes/api/logs.import.ts +0 -39
- package/src/backend/routes/api/logs.stream.ts +0 -174
- package/src/backend/routes/api/logs.ts +0 -214
- package/src/backend/routes/api/mcp.ts +0 -25
- package/src/backend/routes/api/models.ts +0 -10
- package/src/backend/routes/api/pi-agent.ts +0 -29
- package/src/backend/routes/api/providers.$providerId.model-metadata.ts +0 -173
- package/src/backend/routes/api/providers.$providerId.test.log.ts +0 -342
- package/src/backend/routes/api/providers.$providerId.ts +0 -111
- package/src/backend/routes/api/providers.export.ts +0 -44
- package/src/backend/routes/api/providers.import.ts +0 -81
- package/src/backend/routes/api/providers.scan.ts +0 -28
- package/src/backend/routes/api/providers.ts +0 -102
- package/src/backend/routes/api/runs.$runId.evidence.ts +0 -55
- package/src/backend/routes/api/runs.$runId.ts +0 -50
- package/src/backend/routes/api/runs.ts +0 -62
- package/src/backend/routes/api/runtime.ts +0 -46
- package/src/backend/routes/api/sessions.ts +0 -51
- package/src/backend/routes/api/storage.ts +0 -56
- package/src/backend/routes/livez.ts +0 -13
- package/src/backend/routes/metrics.ts +0 -14
- package/src/backend/routes/proxy/$.ts +0 -15
- package/src/backend/routes/readyz.ts +0 -18
- package/src/backend/start.ts +0 -10
- package/src/cli/alias.ts +0 -431
- package/src/cli/detect-tools.ts +0 -123
- package/src/cli/doctor.ts +0 -836
- package/src/cli/instance.ts +0 -1071
- package/src/cli/instanceApi.ts +0 -213
- package/src/cli/instanceArgs.ts +0 -301
- package/src/cli/instanceControl.ts +0 -191
- package/src/cli/instanceModel.ts +0 -276
- package/src/cli/instanceRegistry.ts +0 -731
- package/src/cli/networkHints.ts +0 -170
- package/src/cli/onboard.ts +0 -1668
- package/src/cli/onboardTarget.ts +0 -228
- package/src/cli/rustBackendPackage.ts +0 -458
- package/src/cli/startupOutput.ts +0 -16
- package/src/cli/templates/codex-skill-onboard.ts +0 -320
- package/src/cli/templates/command-onboard.ts +0 -25
- package/src/cli/templates/skill-onboard.ts +0 -625
- package/src/cli.ts +0 -1433
- package/src/components/OnboardingBanner.tsx +0 -115
- package/src/components/ProxyViewer.tsx +0 -2259
- package/src/components/ProxyViewerContainer.tsx +0 -1076
- package/src/components/alerts/AlertsDialog.tsx +0 -615
- package/src/components/clients/ClientLogo.tsx +0 -175
- package/src/components/ecosystem/AgentLabDialog.tsx +0 -947
- package/src/components/errors/SafeErrorBoundary.tsx +0 -201
- package/src/components/groups/GroupsDialog.tsx +0 -1304
- package/src/components/inspector-pet/InspectorPet.tsx +0 -398
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +0 -455
- package/src/components/inspector-pet/inspectorPetLogic.ts +0 -272
- package/src/components/pi-agent/PiAgentPanel.tsx +0 -446
- package/src/components/pi-agent/piAgentChatLogic.ts +0 -273
- package/src/components/providers/ImportWizardDialog.tsx +0 -281
- package/src/components/providers/ProviderCard.tsx +0 -347
- package/src/components/providers/ProviderForm.tsx +0 -1183
- package/src/components/providers/ProviderLogo.tsx +0 -122
- package/src/components/providers/ProviderTestResultsView.tsx +0 -734
- package/src/components/providers/ProvidersPanel.tsx +0 -714
- package/src/components/providers/SettingsDialog.tsx +0 -1042
- package/src/components/providers/providerFormCapabilities.ts +0 -36
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +0 -902
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +0 -51
- package/src/components/proxy-viewer/ApplicationBar.tsx +0 -123
- package/src/components/proxy-viewer/CompareDrawer.tsx +0 -984
- package/src/components/proxy-viewer/ConversationGroup.tsx +0 -208
- package/src/components/proxy-viewer/ConversationGroupList.tsx +0 -652
- package/src/components/proxy-viewer/ConversationHeader.tsx +0 -429
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +0 -186
- package/src/components/proxy-viewer/LogEntry.tsx +0 -1419
- package/src/components/proxy-viewer/LogEntryHeader.tsx +0 -596
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -57
- package/src/components/proxy-viewer/RawExportWarning.tsx +0 -77
- package/src/components/proxy-viewer/ReplayDialog.tsx +0 -636
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +0 -299
- package/src/components/proxy-viewer/ResponseView.tsx +0 -202
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +0 -61
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +0 -265
- package/src/components/proxy-viewer/ThreadConnector.tsx +0 -165
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +0 -64
- package/src/components/proxy-viewer/TurnGroup.tsx +0 -383
- package/src/components/proxy-viewer/TurnGroupList.tsx +0 -156
- package/src/components/proxy-viewer/accessibility.ts +0 -8
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +0 -887
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +0 -222
- package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +0 -737
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +0 -268
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +0 -53
- package/src/components/proxy-viewer/anatomy/types.ts +0 -41
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +0 -114
- package/src/components/proxy-viewer/bodyHydration.ts +0 -123
- package/src/components/proxy-viewer/cacheTrend.ts +0 -50
- package/src/components/proxy-viewer/diff/DiffView.tsx +0 -321
- package/src/components/proxy-viewer/diff/computeDiff.ts +0 -178
- package/src/components/proxy-viewer/diff/index.ts +0 -3
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +0 -194
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +0 -81
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +0 -21
- package/src/components/proxy-viewer/formats/index.tsx +0 -47
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +0 -442
- package/src/components/proxy-viewer/index.ts +0 -9
- package/src/components/proxy-viewer/lazy.ts +0 -41
- package/src/components/proxy-viewer/liveConnectionState.ts +0 -158
- package/src/components/proxy-viewer/log-formats/anthropic.ts +0 -191
- package/src/components/proxy-viewer/log-formats/index.ts +0 -23
- package/src/components/proxy-viewer/log-formats/openai.ts +0 -256
- package/src/components/proxy-viewer/log-formats/types.ts +0 -40
- package/src/components/proxy-viewer/log-formats/unknown.ts +0 -18
- package/src/components/proxy-viewer/logEntryVisibility.ts +0 -73
- package/src/components/proxy-viewer/logFocus.ts +0 -54
- package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +0 -124
- package/src/components/proxy-viewer/proxyViewerLogic.ts +0 -488
- package/src/components/proxy-viewer/replayComparison.ts +0 -131
- package/src/components/proxy-viewer/requestDiff.ts +0 -277
- package/src/components/proxy-viewer/requestTools.ts +0 -220
- package/src/components/proxy-viewer/useCopyFeedback.ts +0 -38
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +0 -232
- package/src/components/proxy-viewer/viewerState.ts +0 -176
- package/src/components/ui/badge.tsx +0 -47
- package/src/components/ui/button.tsx +0 -47
- package/src/components/ui/collapsible.tsx +0 -21
- package/src/components/ui/confirm-dialog.tsx +0 -51
- package/src/components/ui/crab-logo.tsx +0 -45
- package/src/components/ui/crab-variants.tsx +0 -467
- package/src/components/ui/dialog.tsx +0 -130
- package/src/components/ui/icon-trigger.ts +0 -2
- package/src/components/ui/json-viewer-bulk.ts +0 -132
- package/src/components/ui/json-viewer.tsx +0 -508
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/select.tsx +0 -178
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -88
- package/src/components/ui/tooltip.tsx +0 -51
- package/src/components/ui/transient-toast.tsx +0 -105
- package/src/contracts/anthropic.ts +0 -230
- package/src/contracts/index.ts +0 -59
- package/src/contracts/json.ts +0 -37
- package/src/contracts/log.ts +0 -139
- package/src/contracts/openai.ts +0 -267
- package/src/index.css +0 -11
- package/src/knowledge/candidateStore.ts +0 -94
- package/src/knowledge/distiller.ts +0 -98
- package/src/knowledge/openclawClient.ts +0 -161
- package/src/knowledge/openclawGatewayClient.ts +0 -249
- package/src/knowledge/openclawMarkdown.ts +0 -146
- package/src/knowledge/redactor.ts +0 -80
- package/src/knowledge/types.ts +0 -83
- package/src/lib/alertContract.ts +0 -76
- package/src/lib/apiClient.ts +0 -166
- package/src/lib/apiFormat.ts +0 -29
- package/src/lib/backendImplementation.ts +0 -16
- package/src/lib/basePath.ts +0 -107
- package/src/lib/browserRuntimeContract.ts +0 -124
- package/src/lib/browserRuntimeResolver.ts +0 -333
- package/src/lib/chunkLoadRecovery.ts +0 -83
- package/src/lib/claudeCodeStrip.ts +0 -98
- package/src/lib/clipboard.ts +0 -67
- package/src/lib/codingAgentEndpoints.ts +0 -54
- package/src/lib/ecosystemContract.ts +0 -130
- package/src/lib/export-logs.ts +0 -287
- package/src/lib/groupContract.ts +0 -166
- package/src/lib/instanceContract.ts +0 -239
- package/src/lib/jsonFile.ts +0 -46
- package/src/lib/logImportContract.ts +0 -12
- package/src/lib/managedInstance.ts +0 -112
- package/src/lib/mask.ts +0 -4
- package/src/lib/objectUtils.ts +0 -22
- package/src/lib/piAgentContract.ts +0 -102
- package/src/lib/providerContract.ts +0 -125
- package/src/lib/providerImportContract.ts +0 -27
- package/src/lib/providerModelMetadata.ts +0 -458
- package/src/lib/providerTestContract.ts +0 -170
- package/src/lib/providerTestPrompt.ts +0 -100
- package/src/lib/publicBasePathContext.ts +0 -137
- package/src/lib/resourceLimits.ts +0 -345
- package/src/lib/runContract.ts +0 -164
- package/src/lib/runtimeConfig.ts +0 -43
- package/src/lib/safeDiagnostic.ts +0 -38
- package/src/lib/safeFetch.ts +0 -508
- package/src/lib/serverPort.ts +0 -47
- package/src/lib/sessionInfoContract.ts +0 -78
- package/src/lib/sessionUrl.ts +0 -46
- package/src/lib/ssrfGuard.ts +0 -133
- package/src/lib/stopReason.ts +0 -130
- package/src/lib/themeMode.ts +0 -41
- package/src/lib/timeDisplay.ts +0 -22
- package/src/lib/toolTrace.ts +0 -290
- package/src/lib/uiScale.ts +0 -117
- package/src/lib/unknown.ts +0 -27
- package/src/lib/upstreamUrl.ts +0 -150
- package/src/lib/useAlerts.ts +0 -82
- package/src/lib/useGroupEvidence.ts +0 -37
- package/src/lib/useGroups.ts +0 -32
- package/src/lib/useOnboarding.ts +0 -93
- package/src/lib/useProviders.ts +0 -30
- package/src/lib/useStripConfig.ts +0 -154
- package/src/lib/useThemeMode.ts +0 -40
- package/src/lib/useUiScale.ts +0 -52
- package/src/lib/utils.ts +0 -78
- package/src/mcp/currentContext.ts +0 -65
- package/src/mcp/instanceHandlers.ts +0 -151
- package/src/mcp/loopback.ts +0 -88
- package/src/mcp/mode.ts +0 -75
- package/src/mcp/previewExtractor.ts +0 -233
- package/src/mcp/server.ts +0 -2274
- package/src/mcp/toolHandlers.ts +0 -1533
- package/src/proxy/alerts.ts +0 -664
- package/src/proxy/chunkStorage.ts +0 -123
- package/src/proxy/config.ts +0 -218
- package/src/proxy/constants.ts +0 -66
- package/src/proxy/dataDir.ts +0 -147
- package/src/proxy/ecosystemExecutionConfirmation.ts +0 -114
- package/src/proxy/ecosystemExecutionRoute.ts +0 -116
- package/src/proxy/ecosystemRegistry.ts +0 -296
- package/src/proxy/ecosystemTasks.ts +0 -415
- package/src/proxy/evidenceAnalysis.ts +0 -526
- package/src/proxy/evidenceExporter.ts +0 -235
- package/src/proxy/formats/anthropic/anthropicProvider.ts +0 -75
- package/src/proxy/formats/anthropic/handler.ts +0 -75
- package/src/proxy/formats/anthropic/index.ts +0 -13
- package/src/proxy/formats/anthropic/register.ts +0 -4
- package/src/proxy/formats/anthropic/schemas.ts +0 -6
- package/src/proxy/formats/anthropic/stream.ts +0 -205
- package/src/proxy/formats/handler.ts +0 -46
- package/src/proxy/formats/index.ts +0 -13
- package/src/proxy/formats/jsonSchema.ts +0 -2
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -34
- package/src/proxy/formats/openai/codexMetadata.ts +0 -61
- package/src/proxy/formats/openai/handler.ts +0 -133
- package/src/proxy/formats/openai/index.ts +0 -33
- package/src/proxy/formats/openai/provider.ts +0 -50
- package/src/proxy/formats/openai/register.ts +0 -4
- package/src/proxy/formats/openai/schemas.ts +0 -25
- package/src/proxy/formats/openai/stream.ts +0 -600
- package/src/proxy/formats/openai/zhipuProvider.ts +0 -31
- package/src/proxy/formats/providerAdapter.ts +0 -50
- package/src/proxy/formats/providerRegistry.ts +0 -51
- package/src/proxy/formats/providers/index.ts +0 -3
- package/src/proxy/formats/registry.ts +0 -65
- package/src/proxy/groupEvidenceExporter.ts +0 -371
- package/src/proxy/groupStore.ts +0 -336
- package/src/proxy/handler.ts +0 -729
- package/src/proxy/identityProxy.ts +0 -1842
- package/src/proxy/jsonlRecovery.ts +0 -554
- package/src/proxy/logBodyChunks.ts +0 -91
- package/src/proxy/logFinalizer.ts +0 -777
- package/src/proxy/logFinalizer.worker.ts +0 -24
- package/src/proxy/logImportUpload.ts +0 -128
- package/src/proxy/logImporter.ts +0 -813
- package/src/proxy/logIndex.ts +0 -663
- package/src/proxy/logSearch.ts +0 -122
- package/src/proxy/logger.ts +0 -537
- package/src/proxy/openaiOrphanToolStrip.ts +0 -142
- package/src/proxy/platformCommands.ts +0 -258
- package/src/proxy/privateDataPath.ts +0 -419
- package/src/proxy/providerImporters.ts +0 -578
- package/src/proxy/providerScanStore.ts +0 -87
- package/src/proxy/providerSecretStore.ts +0 -216
- package/src/proxy/providers.ts +0 -998
- package/src/proxy/rawStreamCapture.ts +0 -342
- package/src/proxy/runFailures.ts +0 -94
- package/src/proxy/runStore.ts +0 -214
- package/src/proxy/runtimeAdmission.ts +0 -55
- package/src/proxy/runtimeHealth.ts +0 -221
- package/src/proxy/runtimeMetrics.ts +0 -149
- package/src/proxy/runtimeShutdown.ts +0 -157
- package/src/proxy/schemas.ts +0 -112
- package/src/proxy/sessionArchive.ts +0 -479
- package/src/proxy/sessionInfo.ts +0 -266
- package/src/proxy/sessionProcess.ts +0 -194
- package/src/proxy/sessionRuntime.ts +0 -121
- package/src/proxy/sessionSupervisor.ts +0 -305
- package/src/proxy/sessionWorkerEntry.ts +0 -26
- package/src/proxy/shutdownCoordinator.ts +0 -90
- package/src/proxy/socketTracker.ts +0 -639
- package/src/proxy/sqliteLogIndex.ts +0 -733
- package/src/proxy/storageLifecycle.ts +0 -432
- package/src/proxy/store.ts +0 -1773
- package/src/proxy/streamTiming.ts +0 -31
- package/src/proxy/toolSchemaWarnings.ts +0 -529
- package/src/proxy/uiAssetServer.ts +0 -106
- package/src/proxy/upstream.ts +0 -127
- package/src/services/alerts.ts +0 -12
- package/src/services/groups.ts +0 -119
- package/src/services/piAgent.ts +0 -421
- package/src/services/piAgentRetrieval.ts +0 -1180
- package/src/services/result.ts +0 -17
- package/src/services/runs.ts +0 -76
- package/src/ui/bootstrap.ts +0 -167
- package/src/ui/index.html +0 -38
- package/src/ui/logsStreamClient.ts +0 -20
- package/src/ui/main.tsx +0 -13
- package/src/ui/public/runtime-config.js +0 -2
- package/src/ui/router.tsx +0 -13
- package/src/ui/routes/__root.tsx +0 -69
- package/src/ui/routes/index.tsx +0 -35
- package/src/ui/routes/session/$sessionId.tsx +0 -50
- package/src/ui/runtimeDiscovery.ts +0 -50
|
@@ -1,1183 +0,0 @@
|
|
|
1
|
-
import { type JSX, useState, useEffect, useRef } from "react";
|
|
2
|
-
import { Button } from "../ui/button";
|
|
3
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
|
4
|
-
import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
|
|
5
|
-
import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
|
|
6
|
-
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
7
|
-
import {
|
|
8
|
-
PATH_V1_CHAT_COMPLETIONS,
|
|
9
|
-
PATH_V1_MESSAGES,
|
|
10
|
-
PATH_V1_RESPONSES,
|
|
11
|
-
previewUpstreamUrl,
|
|
12
|
-
type UpstreamUrlPreview,
|
|
13
|
-
} from "../../lib/upstreamUrl";
|
|
14
|
-
import { formatContextWindowInput, parseContextWindowTokensInput } from "../../lib/utils";
|
|
15
|
-
import { deriveProviderCapabilities, endpointForCapability } from "./providerFormCapabilities";
|
|
16
|
-
|
|
17
|
-
const ZHIPU_PROVIDER_KEYWORDS = ["zhipu", "zhipuai", "bigmodel", "z.ai", "zai", "glm"];
|
|
18
|
-
const ZHIPU_CODING_PROVIDER_KEYWORDS = [
|
|
19
|
-
"zhipu coding",
|
|
20
|
-
"glm coding",
|
|
21
|
-
"bigmodel coding",
|
|
22
|
-
"z.ai coding",
|
|
23
|
-
"zai coding",
|
|
24
|
-
];
|
|
25
|
-
const ANTHROPIC_MESSAGES_ENDPOINT = PATH_V1_MESSAGES;
|
|
26
|
-
const OPENAI_CHAT_COMPLETIONS_ENDPOINT = PATH_V1_CHAT_COMPLETIONS;
|
|
27
|
-
const OPENAI_RESPONSES_ENDPOINT = PATH_V1_RESPONSES;
|
|
28
|
-
|
|
29
|
-
// Known provider presets - maps provider name keywords to their API URLs
|
|
30
|
-
const KNOWN_PROVIDER_PRESETS: Record<
|
|
31
|
-
string,
|
|
32
|
-
{
|
|
33
|
-
anthropicBaseUrl?: string;
|
|
34
|
-
openaiBaseUrl?: string;
|
|
35
|
-
openaiResponsesBaseUrl?: string;
|
|
36
|
-
apiDocsUrl?: string;
|
|
37
|
-
}
|
|
38
|
-
> = {
|
|
39
|
-
deepseek: {
|
|
40
|
-
openaiBaseUrl: "https://api.deepseek.com",
|
|
41
|
-
},
|
|
42
|
-
minimax: {
|
|
43
|
-
anthropicBaseUrl: "https://api.minimaxi.com/anthropic",
|
|
44
|
-
openaiBaseUrl: "https://api.minimaxi.com",
|
|
45
|
-
openaiResponsesBaseUrl: "https://api.minimaxi.com",
|
|
46
|
-
apiDocsUrl: "https://platform.minimaxi.com/docs/api-reference/api-overview",
|
|
47
|
-
},
|
|
48
|
-
"zhipu coding": {
|
|
49
|
-
anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
|
|
50
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
51
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
|
|
52
|
-
},
|
|
53
|
-
"glm coding": {
|
|
54
|
-
anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
|
|
55
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
56
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
|
|
57
|
-
},
|
|
58
|
-
"bigmodel coding": {
|
|
59
|
-
anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
|
|
60
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
61
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
|
|
62
|
-
},
|
|
63
|
-
"z.ai coding": {
|
|
64
|
-
anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
|
|
65
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
66
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
|
|
67
|
-
},
|
|
68
|
-
"zai coding": {
|
|
69
|
-
anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
|
|
70
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
71
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/coding-plan/quick-start",
|
|
72
|
-
},
|
|
73
|
-
zhipu: {
|
|
74
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
75
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
|
|
76
|
-
},
|
|
77
|
-
zhipuai: {
|
|
78
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
79
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
|
|
80
|
-
},
|
|
81
|
-
bigmodel: {
|
|
82
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
83
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
|
|
84
|
-
},
|
|
85
|
-
"z.ai": {
|
|
86
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
87
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
|
|
88
|
-
},
|
|
89
|
-
zai: {
|
|
90
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
91
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
|
|
92
|
-
},
|
|
93
|
-
glm: {
|
|
94
|
-
openaiBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
95
|
-
apiDocsUrl: "https://docs.bigmodel.cn/cn/guide/develop/openai/introduction",
|
|
96
|
-
},
|
|
97
|
-
alibaba: {
|
|
98
|
-
openaiBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode",
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// MiniMax model options
|
|
103
|
-
const MINIMAX_MODELS = [
|
|
104
|
-
"MiniMax M3",
|
|
105
|
-
"MiniMax M2.7",
|
|
106
|
-
"MiniMax M2.7-highspeed",
|
|
107
|
-
"MiniMax M2.5",
|
|
108
|
-
"MiniMax M2.5-highspeed",
|
|
109
|
-
"MiniMax M2.1",
|
|
110
|
-
"MiniMax M2.1-highspeed",
|
|
111
|
-
"MiniMax M2",
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
// Alibaba model options
|
|
115
|
-
const ALIBABA_MODELS = ["qwen3.6-plus", "qwen3.7-max"];
|
|
116
|
-
|
|
117
|
-
// ZhipuAI / Z.AI model options
|
|
118
|
-
const ZHIPU_MODELS = [
|
|
119
|
-
"glm-5.2",
|
|
120
|
-
"glm-5.1",
|
|
121
|
-
"glm-5",
|
|
122
|
-
"glm-5-turbo",
|
|
123
|
-
"glm-4.7",
|
|
124
|
-
"glm-4.7-flashx",
|
|
125
|
-
"glm-4.6",
|
|
126
|
-
"glm-4.5",
|
|
127
|
-
"glm-4.5-air",
|
|
128
|
-
"glm-4.5-x",
|
|
129
|
-
"glm-4.5-airx",
|
|
130
|
-
"glm-4.5-flash",
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
type ProviderFormProps = {
|
|
134
|
-
provider?: ProviderConfig;
|
|
135
|
-
onSubmit: (data: {
|
|
136
|
-
name: string;
|
|
137
|
-
apiKey: string;
|
|
138
|
-
models: string[];
|
|
139
|
-
anthropicBaseUrl?: string;
|
|
140
|
-
openaiBaseUrl?: string;
|
|
141
|
-
openaiResponsesBaseUrl?: string;
|
|
142
|
-
apiDocsUrl?: string;
|
|
143
|
-
modelMetadataUrl?: string;
|
|
144
|
-
modelMetadata?: ProviderModelMetadata[];
|
|
145
|
-
source?: "company" | "personal";
|
|
146
|
-
}) => void;
|
|
147
|
-
onCancel: () => void;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
type ModelMetadataDraft = {
|
|
151
|
-
contextWindow: string;
|
|
152
|
-
contextWindowMode: ContextWindowMode;
|
|
153
|
-
outputLimit: string;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
type ContextWindowMode = "unset" | "preset" | "custom";
|
|
157
|
-
|
|
158
|
-
type ContextWindowUnit = "K" | "M" | "tokens";
|
|
159
|
-
|
|
160
|
-
const CONTEXT_WINDOW_UNSET = "__unset__";
|
|
161
|
-
const CONTEXT_WINDOW_CUSTOM = "__custom__";
|
|
162
|
-
|
|
163
|
-
const CONTEXT_WINDOW_PRESETS = [
|
|
164
|
-
{ value: "32K", label: "Small" },
|
|
165
|
-
{ value: "64K", label: "Standard" },
|
|
166
|
-
{ value: "128K", label: "Medium" },
|
|
167
|
-
{ value: "200K", label: "Long" },
|
|
168
|
-
{ value: "256K", label: "Extended" },
|
|
169
|
-
{ value: "512K", label: "Large" },
|
|
170
|
-
{ value: "1M", label: "Max" },
|
|
171
|
-
];
|
|
172
|
-
|
|
173
|
-
function emptyMetadataDraft(): ModelMetadataDraft {
|
|
174
|
-
return { contextWindow: "", contextWindowMode: "unset", outputLimit: "" };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function normalizeModelName(value: string): string {
|
|
178
|
-
return value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function findModelMetadata(
|
|
182
|
-
provider: ProviderConfig | undefined,
|
|
183
|
-
model: string,
|
|
184
|
-
): ProviderModelMetadata | null {
|
|
185
|
-
const normalized = normalizeModelName(model);
|
|
186
|
-
return (
|
|
187
|
-
provider?.modelMetadata?.find(
|
|
188
|
-
(metadata) => normalizeModelName(metadata.model) === normalized,
|
|
189
|
-
) ?? null
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function findContextWindowPreset(value: string): string | null {
|
|
194
|
-
const tokens = parseContextWindowTokensInput(value);
|
|
195
|
-
if (tokens === null) return null;
|
|
196
|
-
for (const preset of CONTEXT_WINDOW_PRESETS) {
|
|
197
|
-
if (parseContextWindowTokensInput(preset.value) === tokens) return preset.value;
|
|
198
|
-
}
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function contextWindowModeForValue(value: string): ContextWindowMode {
|
|
203
|
-
if (value.trim() === "") return "unset";
|
|
204
|
-
return findContextWindowPreset(value) !== null ? "preset" : "custom";
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function selectedContextWindowPresetValue(draft: ModelMetadataDraft): string {
|
|
208
|
-
switch (draft.contextWindowMode) {
|
|
209
|
-
case "unset":
|
|
210
|
-
return CONTEXT_WINDOW_UNSET;
|
|
211
|
-
case "custom":
|
|
212
|
-
return CONTEXT_WINDOW_CUSTOM;
|
|
213
|
-
case "preset":
|
|
214
|
-
return findContextWindowPreset(draft.contextWindow) ?? CONTEXT_WINDOW_CUSTOM;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function splitContextWindowEditorValue(value: string): { amount: string; unit: ContextWindowUnit } {
|
|
219
|
-
const trimmed = value.trim().replaceAll(",", "");
|
|
220
|
-
if (trimmed === "") return { amount: "", unit: "K" };
|
|
221
|
-
const match = /^(\d+(?:\.\d+)?)\s*([kKmM])?$/.exec(trimmed);
|
|
222
|
-
if (match === null) return { amount: trimmed, unit: "K" };
|
|
223
|
-
const amount = match[1];
|
|
224
|
-
if (amount === undefined) return { amount: trimmed, unit: "K" };
|
|
225
|
-
const unit = match[2];
|
|
226
|
-
if (unit === undefined || unit === "") return { amount, unit: "tokens" };
|
|
227
|
-
switch (unit.toLowerCase()) {
|
|
228
|
-
case "k":
|
|
229
|
-
return { amount, unit: "K" };
|
|
230
|
-
case "m":
|
|
231
|
-
return { amount, unit: "M" };
|
|
232
|
-
default:
|
|
233
|
-
return { amount: trimmed, unit: "K" };
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function joinContextWindowEditorValue(amount: string, unit: ContextWindowUnit): string {
|
|
238
|
-
const trimmed = amount.trim();
|
|
239
|
-
if (trimmed === "") return "";
|
|
240
|
-
switch (unit) {
|
|
241
|
-
case "K":
|
|
242
|
-
return `${trimmed}K`;
|
|
243
|
-
case "M":
|
|
244
|
-
return `${trimmed}M`;
|
|
245
|
-
case "tokens":
|
|
246
|
-
return trimmed;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function createMetadataDrafts(
|
|
251
|
-
provider: ProviderConfig | undefined,
|
|
252
|
-
models: readonly string[],
|
|
253
|
-
): ModelMetadataDraft[] {
|
|
254
|
-
return models.map((model) => {
|
|
255
|
-
const metadata = findModelMetadata(provider, model);
|
|
256
|
-
const contextWindow =
|
|
257
|
-
metadata?.contextWindow !== undefined ? formatContextWindowInput(metadata.contextWindow) : "";
|
|
258
|
-
return {
|
|
259
|
-
contextWindow,
|
|
260
|
-
contextWindowMode: contextWindowModeForValue(contextWindow),
|
|
261
|
-
outputLimit: metadata?.outputLimit?.toString() ?? "",
|
|
262
|
-
};
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function readPositiveIntegerInput(value: string): number | null {
|
|
267
|
-
const trimmed = value.trim();
|
|
268
|
-
if (trimmed === "") return null;
|
|
269
|
-
const parsed = Number(trimmed);
|
|
270
|
-
if (!Number.isInteger(parsed) || parsed <= 0) return null;
|
|
271
|
-
return parsed;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function stripEndpointSuffix(value: string | undefined, endpoint: string): string {
|
|
275
|
-
if (value === undefined) return "";
|
|
276
|
-
const trimmed = value.trim();
|
|
277
|
-
const normalized = trimmed.replace(/\/+$/, "");
|
|
278
|
-
return normalized.endsWith(endpoint)
|
|
279
|
-
? normalized.slice(0, -endpoint.length).replace(/\/+$/, "")
|
|
280
|
-
: trimmed;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function previewEndpointUrl(
|
|
284
|
-
baseUrl: string,
|
|
285
|
-
endpoint: string,
|
|
286
|
-
providerName: string,
|
|
287
|
-
): UpstreamUrlPreview | null {
|
|
288
|
-
const trimmed = baseUrl.trim();
|
|
289
|
-
return trimmed === "" ? null : previewUpstreamUrl(trimmed, endpoint, { providerName });
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function EndpointUrlPreview({
|
|
293
|
-
baseUrl,
|
|
294
|
-
endpoint,
|
|
295
|
-
providerName,
|
|
296
|
-
}: {
|
|
297
|
-
baseUrl: string;
|
|
298
|
-
endpoint: string;
|
|
299
|
-
providerName: string;
|
|
300
|
-
}): JSX.Element | null {
|
|
301
|
-
const preview = previewEndpointUrl(baseUrl, endpoint, providerName);
|
|
302
|
-
if (preview === null) return null;
|
|
303
|
-
|
|
304
|
-
return (
|
|
305
|
-
<div className="border border-border bg-muted/40 rounded-md px-3 py-2 text-xs">
|
|
306
|
-
<div className="text-muted-foreground">Final URL</div>
|
|
307
|
-
<div className="break-all font-mono text-foreground">{preview.finalUrl}</div>
|
|
308
|
-
{preview.diagnostics.length > 0 && (
|
|
309
|
-
<div className="mt-2 space-y-1">
|
|
310
|
-
{preview.diagnostics.map((diagnostic) => (
|
|
311
|
-
<div
|
|
312
|
-
key={`${diagnostic.severity}:${diagnostic.message}`}
|
|
313
|
-
className={
|
|
314
|
-
diagnostic.severity === "warning" ? "text-status-watch" : "text-muted-foreground"
|
|
315
|
-
}
|
|
316
|
-
>
|
|
317
|
-
{diagnostic.message}
|
|
318
|
-
</div>
|
|
319
|
-
))}
|
|
320
|
-
</div>
|
|
321
|
-
)}
|
|
322
|
-
</div>
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps): JSX.Element {
|
|
327
|
-
const initialCapabilities = deriveProviderCapabilities(provider);
|
|
328
|
-
const [name, setName] = useState(provider?.name ?? "");
|
|
329
|
-
const [apiKey, setApiKey] = useState("");
|
|
330
|
-
const [showApiKey, setShowApiKey] = useState(false);
|
|
331
|
-
const [copied, setCopied] = useState(false);
|
|
332
|
-
const initialModels = provider?.models;
|
|
333
|
-
const [models, setModels] = useState<string[]>(
|
|
334
|
-
initialModels !== undefined && initialModels.length > 0 ? initialModels : [""],
|
|
335
|
-
);
|
|
336
|
-
const [modelMetadataDrafts, setModelMetadataDrafts] = useState<ModelMetadataDraft[]>(() =>
|
|
337
|
-
createMetadataDrafts(
|
|
338
|
-
provider,
|
|
339
|
-
initialModels !== undefined && initialModels.length > 0 ? initialModels : [""],
|
|
340
|
-
),
|
|
341
|
-
);
|
|
342
|
-
const [supportsAnthropic, setSupportsAnthropic] = useState(initialCapabilities.anthropic);
|
|
343
|
-
const [supportsOpenai, setSupportsOpenai] = useState(initialCapabilities.openai);
|
|
344
|
-
const [supportsOpenaiChat, setSupportsOpenaiChat] = useState(initialCapabilities.openaiChat);
|
|
345
|
-
const [supportsOpenaiResponses, setSupportsOpenaiResponses] = useState(
|
|
346
|
-
initialCapabilities.openaiResponses,
|
|
347
|
-
);
|
|
348
|
-
const [anthropicBaseUrl, setAnthropicBaseUrl] = useState(
|
|
349
|
-
stripEndpointSuffix(provider?.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT),
|
|
350
|
-
);
|
|
351
|
-
const [openaiBaseUrl, setOpenaiBaseUrl] = useState(
|
|
352
|
-
stripEndpointSuffix(provider?.openaiBaseUrl, OPENAI_CHAT_COMPLETIONS_ENDPOINT),
|
|
353
|
-
);
|
|
354
|
-
const [openaiResponsesBaseUrl, setOpenaiResponsesBaseUrl] = useState(
|
|
355
|
-
stripEndpointSuffix(provider?.openaiResponsesBaseUrl, OPENAI_RESPONSES_ENDPOINT),
|
|
356
|
-
);
|
|
357
|
-
const [apiDocsUrl, setApiDocsUrl] = useState(provider?.apiDocsUrl ?? "");
|
|
358
|
-
const [modelMetadataUrl, setModelMetadataUrl] = useState(provider?.modelMetadataUrl ?? "");
|
|
359
|
-
const [source, setSource] = useState<"company" | "personal" | undefined>(provider?.source);
|
|
360
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
361
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
362
|
-
const [openModelDropdown, setOpenModelDropdown] = useState<number | null>(null);
|
|
363
|
-
const modelRowRefs = useRef<(HTMLDivElement | null)[]>([]);
|
|
364
|
-
|
|
365
|
-
// Close model dropdown when clicking outside
|
|
366
|
-
useEffect(() => {
|
|
367
|
-
if (openModelDropdown === null) return;
|
|
368
|
-
const index = openModelDropdown;
|
|
369
|
-
function handleClick(e: MouseEvent) {
|
|
370
|
-
if (!(e.target instanceof Node)) return;
|
|
371
|
-
const ref = modelRowRefs.current[index];
|
|
372
|
-
if (ref !== null && ref !== undefined && !ref.contains(e.target)) {
|
|
373
|
-
setOpenModelDropdown(null);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
document.addEventListener("mousedown", handleClick);
|
|
377
|
-
return () => document.removeEventListener("mousedown", handleClick);
|
|
378
|
-
}, [openModelDropdown]);
|
|
379
|
-
|
|
380
|
-
// Track if URL fields have been manually edited (to avoid overriding user edits)
|
|
381
|
-
const [manualAnthropicUrlOverride, setManualAnthropicUrlOverride] = useState(false);
|
|
382
|
-
const [manualOpenaiUrlOverride, setManualOpenaiUrlOverride] = useState(false);
|
|
383
|
-
const [manualOpenaiResponsesUrlOverride, setManualOpenaiResponsesUrlOverride] = useState(false);
|
|
384
|
-
|
|
385
|
-
// Check if MiniMax is detected
|
|
386
|
-
const isMiniMax = name.toLowerCase().includes("minimax");
|
|
387
|
-
// Check if Alibaba is detected
|
|
388
|
-
const isAlibaba = name.toLowerCase().includes("alibaba");
|
|
389
|
-
const isZhipu = [...ZHIPU_PROVIDER_KEYWORDS, ...ZHIPU_CODING_PROVIDER_KEYWORDS].some((keyword) =>
|
|
390
|
-
name.toLowerCase().includes(keyword),
|
|
391
|
-
);
|
|
392
|
-
const suggestedModels = isMiniMax
|
|
393
|
-
? MINIMAX_MODELS
|
|
394
|
-
: isZhipu
|
|
395
|
-
? ZHIPU_MODELS
|
|
396
|
-
: isAlibaba
|
|
397
|
-
? ALIBABA_MODELS
|
|
398
|
-
: [];
|
|
399
|
-
const hasContextMetadata =
|
|
400
|
-
provider?.modelMetadata?.some((metadata) => metadata.contextWindow !== undefined) ?? false;
|
|
401
|
-
|
|
402
|
-
function handleCopy() {
|
|
403
|
-
void copyTextToClipboard(apiKey).then((success) => {
|
|
404
|
-
if (!success) return;
|
|
405
|
-
setCopied(true);
|
|
406
|
-
setTimeout(() => setCopied(false), 2000);
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
useEffect(() => {
|
|
411
|
-
if (provider) {
|
|
412
|
-
setName(provider.name);
|
|
413
|
-
setApiKey("");
|
|
414
|
-
setShowApiKey(false);
|
|
415
|
-
const nextModels = (provider.models?.length ?? 0) > 0 ? provider.models : [""];
|
|
416
|
-
setModels(nextModels);
|
|
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);
|
|
423
|
-
setAnthropicBaseUrl(
|
|
424
|
-
stripEndpointSuffix(provider.anthropicBaseUrl, ANTHROPIC_MESSAGES_ENDPOINT),
|
|
425
|
-
);
|
|
426
|
-
setOpenaiBaseUrl(
|
|
427
|
-
stripEndpointSuffix(provider.openaiBaseUrl, OPENAI_CHAT_COMPLETIONS_ENDPOINT),
|
|
428
|
-
);
|
|
429
|
-
setOpenaiResponsesBaseUrl(
|
|
430
|
-
stripEndpointSuffix(provider.openaiResponsesBaseUrl, OPENAI_RESPONSES_ENDPOINT),
|
|
431
|
-
);
|
|
432
|
-
setApiDocsUrl(provider.apiDocsUrl ?? "");
|
|
433
|
-
setModelMetadataUrl(provider.modelMetadataUrl ?? "");
|
|
434
|
-
setSource(provider.source);
|
|
435
|
-
setManualAnthropicUrlOverride(false);
|
|
436
|
-
setManualOpenaiUrlOverride(false);
|
|
437
|
-
setManualOpenaiResponsesUrlOverride(false);
|
|
438
|
-
}
|
|
439
|
-
}, [provider]);
|
|
440
|
-
|
|
441
|
-
// Detect known provider presets and auto-fill URLs
|
|
442
|
-
useEffect(() => {
|
|
443
|
-
const lowerName = name.toLowerCase();
|
|
444
|
-
for (const [keyword, preset] of Object.entries(KNOWN_PROVIDER_PRESETS)) {
|
|
445
|
-
if (lowerName.includes(keyword)) {
|
|
446
|
-
if (preset.anthropicBaseUrl !== undefined && !manualAnthropicUrlOverride) {
|
|
447
|
-
setAnthropicBaseUrl(preset.anthropicBaseUrl);
|
|
448
|
-
}
|
|
449
|
-
if (preset.openaiBaseUrl !== undefined && !manualOpenaiUrlOverride) {
|
|
450
|
-
setOpenaiBaseUrl(preset.openaiBaseUrl);
|
|
451
|
-
}
|
|
452
|
-
if (preset.openaiResponsesBaseUrl !== undefined && !manualOpenaiResponsesUrlOverride) {
|
|
453
|
-
setOpenaiResponsesBaseUrl(preset.openaiResponsesBaseUrl);
|
|
454
|
-
}
|
|
455
|
-
if (preset.apiDocsUrl !== undefined && !apiDocsUrl) {
|
|
456
|
-
setApiDocsUrl(preset.apiDocsUrl);
|
|
457
|
-
}
|
|
458
|
-
// For MiniMax, auto-select the first model if not already set
|
|
459
|
-
if (keyword === "minimax" && models.length === 1 && models[0] === "") {
|
|
460
|
-
setModels([MINIMAX_MODELS[0] ?? ""]);
|
|
461
|
-
}
|
|
462
|
-
// For Alibaba, auto-select the first model if not already set
|
|
463
|
-
if (keyword === "alibaba" && models.length === 1 && models[0] === "") {
|
|
464
|
-
setModels([ALIBABA_MODELS[0] ?? ""]);
|
|
465
|
-
}
|
|
466
|
-
// For ZhipuAI, auto-select the first model if not already set
|
|
467
|
-
if (
|
|
468
|
-
(ZHIPU_PROVIDER_KEYWORDS.includes(keyword) ||
|
|
469
|
-
ZHIPU_CODING_PROVIDER_KEYWORDS.includes(keyword)) &&
|
|
470
|
-
models.length === 1 &&
|
|
471
|
-
models[0] === ""
|
|
472
|
-
) {
|
|
473
|
-
setModels([ZHIPU_MODELS[0] ?? ""]);
|
|
474
|
-
}
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}, [name]);
|
|
479
|
-
|
|
480
|
-
function validate(): boolean {
|
|
481
|
-
const newErrors: Record<string, string> = {};
|
|
482
|
-
if (!name.trim()) {
|
|
483
|
-
newErrors.name = "Name is required";
|
|
484
|
-
}
|
|
485
|
-
if (provider === undefined && !apiKey.trim()) {
|
|
486
|
-
newErrors.apiKey = "API key is required";
|
|
487
|
-
}
|
|
488
|
-
if (models.length === 0 || models.every((m) => !m.trim())) {
|
|
489
|
-
newErrors.models = "At least one model is required";
|
|
490
|
-
}
|
|
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
|
-
}
|
|
503
|
-
}
|
|
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
|
-
}
|
|
510
|
-
}
|
|
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
|
-
}
|
|
517
|
-
}
|
|
518
|
-
if (modelMetadataUrl.trim() && !isValidUrl(modelMetadataUrl.trim())) {
|
|
519
|
-
newErrors.modelMetadataUrl = "Invalid URL format";
|
|
520
|
-
}
|
|
521
|
-
modelMetadataDrafts.forEach((draft, index) => {
|
|
522
|
-
const contextWindow = parseContextWindowTokensInput(draft.contextWindow);
|
|
523
|
-
const outputLimit = readPositiveIntegerInput(draft.outputLimit);
|
|
524
|
-
if (draft.contextWindow.trim() !== "" && contextWindow === null) {
|
|
525
|
-
newErrors[`modelMetadata.${index}.contextWindow`] =
|
|
526
|
-
"Context window must be a positive number, optionally using K or M";
|
|
527
|
-
}
|
|
528
|
-
if (draft.outputLimit.trim() !== "" && outputLimit === null) {
|
|
529
|
-
newErrors[`modelMetadata.${index}.outputLimit`] = "Output limit must be a positive integer";
|
|
530
|
-
}
|
|
531
|
-
if (contextWindow !== null && outputLimit !== null && outputLimit > contextWindow) {
|
|
532
|
-
newErrors[`modelMetadata.${index}.outputLimit`] =
|
|
533
|
-
"Output limit should not exceed context window";
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
setErrors(newErrors);
|
|
537
|
-
return Object.keys(newErrors).length === 0;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
function isValidUrl(str: string): boolean {
|
|
541
|
-
try {
|
|
542
|
-
new URL(str);
|
|
543
|
-
return true;
|
|
544
|
-
} catch {
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function updateModel(index: number, value: string): void {
|
|
550
|
-
setModels((prev) => {
|
|
551
|
-
const next = [...prev];
|
|
552
|
-
next[index] = value;
|
|
553
|
-
return next;
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function updateMetadataDraft(index: number, patch: Partial<ModelMetadataDraft>): void {
|
|
558
|
-
setModelMetadataDrafts((prev) => {
|
|
559
|
-
const next = [...prev];
|
|
560
|
-
next[index] = { ...(next[index] ?? emptyMetadataDraft()), ...patch };
|
|
561
|
-
return next;
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function updateContextWindowPreset(index: number, value: string): void {
|
|
566
|
-
switch (value) {
|
|
567
|
-
case CONTEXT_WINDOW_UNSET:
|
|
568
|
-
updateMetadataDraft(index, { contextWindow: "", contextWindowMode: "unset" });
|
|
569
|
-
return;
|
|
570
|
-
case CONTEXT_WINDOW_CUSTOM: {
|
|
571
|
-
const current = modelMetadataDrafts[index] ?? emptyMetadataDraft();
|
|
572
|
-
updateMetadataDraft(index, {
|
|
573
|
-
contextWindow: current.contextWindow,
|
|
574
|
-
contextWindowMode: "custom",
|
|
575
|
-
});
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
default:
|
|
579
|
-
updateMetadataDraft(index, { contextWindow: value, contextWindowMode: "preset" });
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function updateContextWindowAmount(index: number, amount: string): void {
|
|
585
|
-
const current = modelMetadataDrafts[index] ?? emptyMetadataDraft();
|
|
586
|
-
const editorValue = splitContextWindowEditorValue(current.contextWindow);
|
|
587
|
-
updateMetadataDraft(index, {
|
|
588
|
-
contextWindow: joinContextWindowEditorValue(amount, editorValue.unit),
|
|
589
|
-
contextWindowMode: "custom",
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function updateContextWindowUnit(index: number, unit: string): void {
|
|
594
|
-
const current = modelMetadataDrafts[index] ?? emptyMetadataDraft();
|
|
595
|
-
const editorValue = splitContextWindowEditorValue(current.contextWindow);
|
|
596
|
-
switch (unit) {
|
|
597
|
-
case "K":
|
|
598
|
-
case "M":
|
|
599
|
-
case "tokens":
|
|
600
|
-
updateMetadataDraft(index, {
|
|
601
|
-
contextWindow: joinContextWindowEditorValue(editorValue.amount, unit),
|
|
602
|
-
contextWindowMode: "custom",
|
|
603
|
-
});
|
|
604
|
-
return;
|
|
605
|
-
default:
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function removeModel(index: number): void {
|
|
611
|
-
setModels((prev) => prev.filter((_, idx) => idx !== index));
|
|
612
|
-
setModelMetadataDrafts((prev) => prev.filter((_, idx) => idx !== index));
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function addModel(): void {
|
|
616
|
-
setModels((prev) => [...prev, ""]);
|
|
617
|
-
setModelMetadataDrafts((prev) => [...prev, emptyMetadataDraft()]);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function buildModelMetadata(): ProviderModelMetadata[] | undefined {
|
|
621
|
-
const now = new Date().toISOString();
|
|
622
|
-
const metadata: ProviderModelMetadata[] = [];
|
|
623
|
-
|
|
624
|
-
models.forEach((model, index) => {
|
|
625
|
-
const trimmedModel = model.trim();
|
|
626
|
-
if (trimmedModel === "") return;
|
|
627
|
-
const draft = modelMetadataDrafts[index] ?? emptyMetadataDraft();
|
|
628
|
-
const contextWindow = parseContextWindowTokensInput(draft.contextWindow);
|
|
629
|
-
const outputLimit = readPositiveIntegerInput(draft.outputLimit);
|
|
630
|
-
if (contextWindow === null && outputLimit === null) return;
|
|
631
|
-
|
|
632
|
-
const existing = findModelMetadata(provider, trimmedModel);
|
|
633
|
-
if (
|
|
634
|
-
existing !== null &&
|
|
635
|
-
existing.contextWindow === (contextWindow ?? undefined) &&
|
|
636
|
-
existing.outputLimit === (outputLimit ?? undefined)
|
|
637
|
-
) {
|
|
638
|
-
metadata.push({ ...existing, model: trimmedModel });
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const entry: ProviderModelMetadata = {
|
|
643
|
-
model: trimmedModel,
|
|
644
|
-
source: "manual",
|
|
645
|
-
updatedAt: now,
|
|
646
|
-
};
|
|
647
|
-
if (contextWindow !== null) entry.contextWindow = contextWindow;
|
|
648
|
-
if (outputLimit !== null) entry.outputLimit = outputLimit;
|
|
649
|
-
metadata.push(entry);
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
if (metadata.length > 0) return metadata;
|
|
653
|
-
return provider?.modelMetadata !== undefined ? [] : undefined;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function handleSubmit(e: React.FormEvent): void {
|
|
657
|
-
e.preventDefault();
|
|
658
|
-
if (!validate()) return;
|
|
659
|
-
setIsSubmitting(true);
|
|
660
|
-
const clearDisabledEndpoints = provider !== undefined;
|
|
661
|
-
try {
|
|
662
|
-
onSubmit({
|
|
663
|
-
name: name.trim(),
|
|
664
|
-
apiKey: apiKey.trim(),
|
|
665
|
-
models: models.map((m) => m.trim()).filter((m) => m !== ""),
|
|
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
|
-
),
|
|
681
|
-
apiDocsUrl: apiDocsUrl.trim() || undefined,
|
|
682
|
-
modelMetadataUrl: modelMetadataUrl.trim() || undefined,
|
|
683
|
-
modelMetadata: buildModelMetadata(),
|
|
684
|
-
source,
|
|
685
|
-
});
|
|
686
|
-
} finally {
|
|
687
|
-
setIsSubmitting(false);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
return (
|
|
692
|
-
<form onSubmit={handleSubmit} className="space-y-4">
|
|
693
|
-
<div className="space-y-2">
|
|
694
|
-
<label htmlFor="provider-name" className="text-sm font-medium">
|
|
695
|
-
Name <span className="text-destructive">*</span>
|
|
696
|
-
</label>
|
|
697
|
-
<input
|
|
698
|
-
id="provider-name"
|
|
699
|
-
type="text"
|
|
700
|
-
value={name}
|
|
701
|
-
onChange={(e) => setName(e.target.value)}
|
|
702
|
-
placeholder="Provider Name"
|
|
703
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
704
|
-
/>
|
|
705
|
-
{errors.name !== undefined && <p className="text-xs text-destructive">{errors.name}</p>}
|
|
706
|
-
</div>
|
|
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-border bg-muted/35 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-border bg-muted/35 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-border bg-muted/35 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
|
-
|
|
893
|
-
<div className="space-y-2">
|
|
894
|
-
<label htmlFor="provider-source" className="text-sm font-medium">
|
|
895
|
-
Token Source
|
|
896
|
-
</label>
|
|
897
|
-
<select
|
|
898
|
-
id="provider-source"
|
|
899
|
-
value={source ?? ""}
|
|
900
|
-
onChange={(e) =>
|
|
901
|
-
setSource(
|
|
902
|
-
e.target.value === "company" || e.target.value === "personal"
|
|
903
|
-
? e.target.value
|
|
904
|
-
: undefined,
|
|
905
|
-
)
|
|
906
|
-
}
|
|
907
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
908
|
-
>
|
|
909
|
-
<option value="">Unspecified</option>
|
|
910
|
-
<option value="personal">个人 (Personal)</option>
|
|
911
|
-
<option value="company">公司 (Company)</option>
|
|
912
|
-
</select>
|
|
913
|
-
<p className="text-xs text-muted-foreground">
|
|
914
|
-
Label whether this API key is company-provided or personal.
|
|
915
|
-
</p>
|
|
916
|
-
</div>
|
|
917
|
-
|
|
918
|
-
<div className="space-y-2">
|
|
919
|
-
<label htmlFor="provider-model-metadata-url" className="text-sm font-medium">
|
|
920
|
-
Model Metadata URL
|
|
921
|
-
</label>
|
|
922
|
-
<input
|
|
923
|
-
id="provider-model-metadata-url"
|
|
924
|
-
type="text"
|
|
925
|
-
value={modelMetadataUrl}
|
|
926
|
-
onChange={(e) => setModelMetadataUrl(e.target.value)}
|
|
927
|
-
placeholder="https://example.com/agent-inspector-models.json"
|
|
928
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
929
|
-
/>
|
|
930
|
-
{errors.modelMetadataUrl !== undefined && (
|
|
931
|
-
<p className="text-xs text-destructive">{errors.modelMetadataUrl}</p>
|
|
932
|
-
)}
|
|
933
|
-
{modelMetadataUrl.trim() === "" && (
|
|
934
|
-
<div className="border border-border bg-muted/40 rounded-md px-3 py-2 text-xs text-muted-foreground">
|
|
935
|
-
Built-in limits cover DeepSeek v4 pro/flash, MiniMax M3/M2.7/M2.5/M2.1, and GLM coding
|
|
936
|
-
models. Use a registry URL for private or custom model limits.
|
|
937
|
-
</div>
|
|
938
|
-
)}
|
|
939
|
-
{provider !== undefined && !hasContextMetadata && modelMetadataUrl.trim() === "" && (
|
|
940
|
-
<p className="text-xs text-status-watch">
|
|
941
|
-
This provider has no matching context metadata yet.
|
|
942
|
-
</p>
|
|
943
|
-
)}
|
|
944
|
-
{modelMetadataUrl.trim() !== "" && (
|
|
945
|
-
<p className="text-xs text-muted-foreground">
|
|
946
|
-
Agent Inspector will refresh model limits from this JSON registry after save.
|
|
947
|
-
</p>
|
|
948
|
-
)}
|
|
949
|
-
</div>
|
|
950
|
-
|
|
951
|
-
<div className="space-y-2">
|
|
952
|
-
<label htmlFor="provider-apikey" className="text-sm font-medium">
|
|
953
|
-
API Key {provider === undefined && <span className="text-destructive">*</span>}
|
|
954
|
-
</label>
|
|
955
|
-
<div className="flex items-center gap-2">
|
|
956
|
-
<input
|
|
957
|
-
id="provider-apikey"
|
|
958
|
-
type={showApiKey ? "text" : "password"}
|
|
959
|
-
value={apiKey}
|
|
960
|
-
onChange={(e) => setApiKey(e.target.value)}
|
|
961
|
-
placeholder={provider === undefined ? "sk-ant-..." : "Leave blank to keep current key"}
|
|
962
|
-
className="flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
963
|
-
/>
|
|
964
|
-
<button
|
|
965
|
-
type="button"
|
|
966
|
-
onClick={() => setShowApiKey((s) => !s)}
|
|
967
|
-
className="text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0"
|
|
968
|
-
aria-label={showApiKey ? "Hide API key" : "Show API key"}
|
|
969
|
-
>
|
|
970
|
-
{showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
|
971
|
-
</button>
|
|
972
|
-
<button
|
|
973
|
-
type="button"
|
|
974
|
-
onClick={handleCopy}
|
|
975
|
-
className="text-muted-foreground hover:text-foreground transition-colors p-1 shrink-0"
|
|
976
|
-
aria-label="Copy API key"
|
|
977
|
-
>
|
|
978
|
-
{copied ? (
|
|
979
|
-
<Check className="size-4 text-status-success" />
|
|
980
|
-
) : (
|
|
981
|
-
<Copy className="size-4" />
|
|
982
|
-
)}
|
|
983
|
-
</button>
|
|
984
|
-
</div>
|
|
985
|
-
{provider !== undefined && apiKey.trim() === "" && (
|
|
986
|
-
<p className="text-xs text-muted-foreground">
|
|
987
|
-
A key is configured. Enter a new value only to rotate it.
|
|
988
|
-
</p>
|
|
989
|
-
)}
|
|
990
|
-
{errors.apiKey !== undefined && <p className="text-xs text-destructive">{errors.apiKey}</p>}
|
|
991
|
-
</div>
|
|
992
|
-
|
|
993
|
-
<div className="space-y-2">
|
|
994
|
-
<label className="text-sm font-medium">
|
|
995
|
-
Models <span className="text-destructive">*</span>
|
|
996
|
-
</label>
|
|
997
|
-
{models.map((m, i) => {
|
|
998
|
-
const draft = modelMetadataDrafts[i] ?? emptyMetadataDraft();
|
|
999
|
-
const contextWindowError = errors[`modelMetadata.${i}.contextWindow`];
|
|
1000
|
-
const outputLimitError = errors[`modelMetadata.${i}.outputLimit`];
|
|
1001
|
-
const contextWindowPresetValue = selectedContextWindowPresetValue(draft);
|
|
1002
|
-
const contextWindowEditorValue = splitContextWindowEditorValue(draft.contextWindow);
|
|
1003
|
-
return (
|
|
1004
|
-
<div
|
|
1005
|
-
key={i}
|
|
1006
|
-
ref={(el) => {
|
|
1007
|
-
modelRowRefs.current[i] = el;
|
|
1008
|
-
}}
|
|
1009
|
-
className="border border-border bg-card shadow-sm space-y-2 rounded-[8px] p-2"
|
|
1010
|
-
>
|
|
1011
|
-
<div className="flex items-center gap-2">
|
|
1012
|
-
<div className="relative flex-1">
|
|
1013
|
-
<input
|
|
1014
|
-
type="text"
|
|
1015
|
-
value={m}
|
|
1016
|
-
onChange={(e) => updateModel(i, e.target.value)}
|
|
1017
|
-
placeholder={
|
|
1018
|
-
suggestedModels.length > 0 ? "Type or select a model..." : "Model name"
|
|
1019
|
-
}
|
|
1020
|
-
className="w-full rounded-md border border-input bg-background px-4 py-3 pr-8 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
1021
|
-
/>
|
|
1022
|
-
{suggestedModels.length > 0 && (
|
|
1023
|
-
<>
|
|
1024
|
-
<button
|
|
1025
|
-
type="button"
|
|
1026
|
-
onClick={() => setOpenModelDropdown(openModelDropdown === i ? null : i)}
|
|
1027
|
-
className="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors p-1 z-10"
|
|
1028
|
-
aria-label="Show model suggestions"
|
|
1029
|
-
>
|
|
1030
|
-
<ChevronDown className="size-4" />
|
|
1031
|
-
</button>
|
|
1032
|
-
{openModelDropdown === i && (
|
|
1033
|
-
<div className="bg-popover text-popover-foreground absolute left-0 right-0 top-full z-50 mt-1 max-h-48 overflow-y-auto rounded-md border shadow-md">
|
|
1034
|
-
{suggestedModels.map((opt) => (
|
|
1035
|
-
<button
|
|
1036
|
-
key={opt}
|
|
1037
|
-
type="button"
|
|
1038
|
-
onClick={() => {
|
|
1039
|
-
updateModel(i, opt);
|
|
1040
|
-
setOpenModelDropdown(null);
|
|
1041
|
-
}}
|
|
1042
|
-
className="w-full px-3 py-2 text-left text-sm transition-colors hover:bg-muted/60"
|
|
1043
|
-
>
|
|
1044
|
-
{opt}
|
|
1045
|
-
</button>
|
|
1046
|
-
))}
|
|
1047
|
-
</div>
|
|
1048
|
-
)}
|
|
1049
|
-
</>
|
|
1050
|
-
)}
|
|
1051
|
-
</div>
|
|
1052
|
-
{models.length > 1 && (
|
|
1053
|
-
<button
|
|
1054
|
-
type="button"
|
|
1055
|
-
onClick={() => removeModel(i)}
|
|
1056
|
-
className="text-muted-foreground hover:text-destructive transition-colors p-1 shrink-0"
|
|
1057
|
-
aria-label="Remove model"
|
|
1058
|
-
>
|
|
1059
|
-
<svg
|
|
1060
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
1061
|
-
width="16"
|
|
1062
|
-
height="16"
|
|
1063
|
-
viewBox="0 0 24 24"
|
|
1064
|
-
fill="none"
|
|
1065
|
-
stroke="currentColor"
|
|
1066
|
-
strokeWidth="2"
|
|
1067
|
-
strokeLinecap="round"
|
|
1068
|
-
strokeLinejoin="round"
|
|
1069
|
-
>
|
|
1070
|
-
<path d="M3 6h18" />
|
|
1071
|
-
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
|
1072
|
-
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
|
1073
|
-
</svg>
|
|
1074
|
-
</button>
|
|
1075
|
-
)}
|
|
1076
|
-
</div>
|
|
1077
|
-
|
|
1078
|
-
<div className="grid gap-2 sm:grid-cols-2">
|
|
1079
|
-
<div className="space-y-1 text-xs text-muted-foreground">
|
|
1080
|
-
<span>Context Window</span>
|
|
1081
|
-
<Select
|
|
1082
|
-
value={contextWindowPresetValue}
|
|
1083
|
-
onValueChange={(value) => updateContextWindowPreset(i, value)}
|
|
1084
|
-
>
|
|
1085
|
-
<SelectTrigger size="sm" className="w-full bg-background font-mono text-xs">
|
|
1086
|
-
<SelectValue />
|
|
1087
|
-
</SelectTrigger>
|
|
1088
|
-
<SelectContent className="max-h-64">
|
|
1089
|
-
<SelectItem value={CONTEXT_WINDOW_UNSET}>Not set</SelectItem>
|
|
1090
|
-
{CONTEXT_WINDOW_PRESETS.map((preset) => (
|
|
1091
|
-
<SelectItem key={preset.value} value={preset.value}>
|
|
1092
|
-
{preset.label} · {preset.value}
|
|
1093
|
-
</SelectItem>
|
|
1094
|
-
))}
|
|
1095
|
-
<SelectItem value={CONTEXT_WINDOW_CUSTOM}>Custom</SelectItem>
|
|
1096
|
-
</SelectContent>
|
|
1097
|
-
</Select>
|
|
1098
|
-
{draft.contextWindowMode === "custom" && (
|
|
1099
|
-
<div className="grid grid-cols-[minmax(0,1fr)_5.75rem] gap-1.5">
|
|
1100
|
-
<input
|
|
1101
|
-
type="text"
|
|
1102
|
-
inputMode="decimal"
|
|
1103
|
-
value={contextWindowEditorValue.amount}
|
|
1104
|
-
onChange={(e) => updateContextWindowAmount(i, e.target.value)}
|
|
1105
|
-
placeholder="128"
|
|
1106
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs text-foreground ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px]"
|
|
1107
|
-
/>
|
|
1108
|
-
<Select
|
|
1109
|
-
value={contextWindowEditorValue.unit}
|
|
1110
|
-
onValueChange={(value) => updateContextWindowUnit(i, value)}
|
|
1111
|
-
>
|
|
1112
|
-
<SelectTrigger size="sm" className="w-full bg-background font-mono text-xs">
|
|
1113
|
-
<SelectValue />
|
|
1114
|
-
</SelectTrigger>
|
|
1115
|
-
<SelectContent>
|
|
1116
|
-
<SelectItem value="K">K</SelectItem>
|
|
1117
|
-
<SelectItem value="M">M</SelectItem>
|
|
1118
|
-
<SelectItem value="tokens">tokens</SelectItem>
|
|
1119
|
-
</SelectContent>
|
|
1120
|
-
</Select>
|
|
1121
|
-
</div>
|
|
1122
|
-
)}
|
|
1123
|
-
{contextWindowError !== undefined && (
|
|
1124
|
-
<span className="block text-destructive">{contextWindowError}</span>
|
|
1125
|
-
)}
|
|
1126
|
-
</div>
|
|
1127
|
-
<label className="space-y-1 text-xs text-muted-foreground">
|
|
1128
|
-
<span>Output Limit</span>
|
|
1129
|
-
<input
|
|
1130
|
-
type="number"
|
|
1131
|
-
min={1}
|
|
1132
|
-
step={1000}
|
|
1133
|
-
value={draft.outputLimit}
|
|
1134
|
-
onChange={(e) => updateMetadataDraft(i, { outputLimit: e.target.value })}
|
|
1135
|
-
placeholder="e.g. 8192"
|
|
1136
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs text-foreground ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px]"
|
|
1137
|
-
/>
|
|
1138
|
-
{outputLimitError !== undefined && (
|
|
1139
|
-
<span className="block text-destructive">{outputLimitError}</span>
|
|
1140
|
-
)}
|
|
1141
|
-
</label>
|
|
1142
|
-
</div>
|
|
1143
|
-
</div>
|
|
1144
|
-
);
|
|
1145
|
-
})}
|
|
1146
|
-
<button
|
|
1147
|
-
type="button"
|
|
1148
|
-
onClick={addModel}
|
|
1149
|
-
className="text-xs text-primary hover:underline flex items-center gap-1"
|
|
1150
|
-
>
|
|
1151
|
-
+ Add Model
|
|
1152
|
-
</button>
|
|
1153
|
-
{errors.models !== undefined && <p className="text-xs text-destructive">{errors.models}</p>}
|
|
1154
|
-
</div>
|
|
1155
|
-
|
|
1156
|
-
<div className="space-y-2">
|
|
1157
|
-
<label htmlFor="provider-api-docs-url" className="text-sm font-medium">
|
|
1158
|
-
API Docs URL
|
|
1159
|
-
</label>
|
|
1160
|
-
<input
|
|
1161
|
-
id="provider-api-docs-url"
|
|
1162
|
-
type="text"
|
|
1163
|
-
value={apiDocsUrl}
|
|
1164
|
-
onChange={(e) => setApiDocsUrl(e.target.value)}
|
|
1165
|
-
placeholder="https://api.example.com/docs"
|
|
1166
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
1167
|
-
/>
|
|
1168
|
-
<p className="text-xs text-muted-foreground">
|
|
1169
|
-
Optional API documentation URL. If not set, uses known provider docs.
|
|
1170
|
-
</p>
|
|
1171
|
-
</div>
|
|
1172
|
-
|
|
1173
|
-
<div className="border border-border bg-background/95 shadow-sm sticky bottom-0 flex justify-end gap-2 rounded-[8px] px-3 py-2">
|
|
1174
|
-
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
|
1175
|
-
Cancel
|
|
1176
|
-
</Button>
|
|
1177
|
-
<Button type="submit" disabled={isSubmitting}>
|
|
1178
|
-
{isSubmitting ? "Saving..." : provider ? "Update Provider" : "Add Provider"}
|
|
1179
|
-
</Button>
|
|
1180
|
-
</div>
|
|
1181
|
-
</form>
|
|
1182
|
-
);
|
|
1183
|
-
}
|