mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 12:14:40 +00:00
feat(providers): add DashScope and Bailian Coding providers with reasoning_content support
- Add DashScope (Qwen) native provider with tools+streaming fallback - Add Bailian Coding provider with hardcoded model list (no /v1/models API) - Parse reasoning_content in OpenAI-compat streaming/non-streaming responses - Emit ChatEventThinking events in agent loop for thinking models - Add vision support for DashScope (qwen3-vl) - Fix provider form dialog not updating API base URL when switching types - Update README provider count from 11+ to 13+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2041b7037b
commit
d913f29c69
15 files changed
+157
-13
No files matched your search
@@ -6,7 +6,7 @@
|
||||
|
||||
[](https://go.dev/) [](https://www.postgresql.org/) [](https://www.docker.com/) [](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) [](https://opentelemetry.io/) [](https://www.anthropic.com/) [](https://openai.com/) [](LICENSE)
|
||||
|
||||
**GoClaw** is a multi-agent AI gateway that connects LLMs to your tools, channels, and data — deployed as a single Go binary with zero runtime dependencies. It orchestrates agent teams, inter-agent delegation, and quality-gated workflows across 11+ LLM providers with full multi-tenant isolation.
|
||||
**GoClaw** is a multi-agent AI gateway that connects LLMs to your tools, channels, and data — deployed as a single Go binary with zero runtime dependencies. It orchestrates agent teams, inter-agent delegation, and quality-gated workflows across 13+ LLM providers with full multi-tenant isolation.
|
||||
|
||||
A Go port of [OpenClaw](https://github.com/openclaw/openclaw) with enhanced security, multi-tenant PostgreSQL, and production-grade observability.
|
||||
|
||||
@@ -16,7 +16,7 @@ A Go port of [OpenClaw](https://github.com/openclaw/openclaw) with enhanced secu
|
||||
- **Multi-Tenant PostgreSQL** — Per-user workspaces, per-user context files, encrypted API keys (AES-256-GCM), isolated sessions — the only Claw project with DB-native multi-tenancy
|
||||
- **Single Binary** — ~25 MB static Go binary, no Node.js runtime, <1s startup, runs on a $5 VPS
|
||||
- **Production Security** — 5-layer defense: rate limiting, prompt injection detection, SSRF protection, shell deny patterns, AES-256-GCM encryption
|
||||
- **11+ LLM Providers** — Anthropic (native HTTP+SSE with prompt caching), OpenAI, OpenRouter, Groq, DeepSeek, Gemini, Mistral, xAI, MiniMax, Cohere, Perplexity
|
||||
- **13+ LLM Providers** — Anthropic (native HTTP+SSE with prompt caching), OpenAI, OpenRouter, Groq, DeepSeek, Gemini, Mistral, xAI, MiniMax, Cohere, Perplexity, DashScope (Qwen), Bailian Coding
|
||||
- **5 Messaging Channels** — Telegram, Discord, Zalo, Feishu/Lark, WhatsApp with `/stop` and `/stopall` commands
|
||||
|
||||
## Claw Ecosystem
|
||||
@@ -51,7 +51,7 @@ A Go port of [OpenClaw](https://github.com/openclaw/openclaw) with enhanced secu
|
||||
| Messaging channels | 37+ | 15+ | 10+ | 5+ |
|
||||
| Companion apps | macOS, iOS, Android | Python SDK | — | Web dashboard |
|
||||
| Live Canvas / Voice | ✅ (A2UI + TTS/STT) | — | Voice transcription | TTS (4 providers) |
|
||||
| LLM providers | 10+ | 8 native + 29 compat | 13+ | **11+** |
|
||||
| LLM providers | 10+ | 8 native + 29 compat | 13+ | **13+** |
|
||||
| Per-user workspaces | ✅ (file-based) | — | — | ✅ (managed mode only) |
|
||||
| Encrypted secrets | — (env vars only) | ✅ ChaCha20-Poly1305 | — (plaintext JSON) | ✅ AES-256-GCM in DB |
|
||||
|
||||
@@ -78,7 +78,7 @@ graph TB
|
||||
SCHED --> ROUTER["Agent Router"]
|
||||
ROUTER --> LOOP["Agent Loop<br/>think → act → observe"]
|
||||
LOOP --> TOOLS["Tool Registry<br/>fs · exec · web · memory · delegate · team · mcp · custom"]
|
||||
LOOP --> LLM["LLM Providers<br/>Anthropic (native + prompt caching) · OpenAI-compat (10+)"]
|
||||
LOOP --> LLM["LLM Providers<br/>Anthropic (native + prompt caching) · OpenAI-compat (12+)"]
|
||||
end
|
||||
|
||||
subgraph Storage
|
||||
@@ -294,7 +294,7 @@ Quality gates validate agent output before it reaches users. Configured in agent
|
||||
## Features
|
||||
|
||||
### LLM Providers
|
||||
- **11+ providers** — OpenRouter, Anthropic, OpenAI, Groq, DeepSeek, Gemini, Mistral, xAI, MiniMax, Cohere, Perplexity, and any OpenAI-compatible endpoint
|
||||
- **13+ providers** — OpenRouter, Anthropic, OpenAI, Groq, DeepSeek, Gemini, Mistral, xAI, MiniMax, Cohere, Perplexity, DashScope (Qwen), Bailian Coding, and any OpenAI-compatible endpoint
|
||||
- **Anthropic native** — Direct HTTP+SSE integration with prompt caching (`cache_control`) for ~90% cost reduction on repeated prefixes
|
||||
- **OpenAI-compatible** — Automatic prompt caching for OpenAI, MiniMax, OpenRouter (cache metrics tracked in traces)
|
||||
|
||||
@@ -557,6 +557,8 @@ When `GOCLAW_*_API_KEY` environment variables are set, the gateway automatically
|
||||
| `GOCLAW_MINIMAX_API_KEY` | MiniMax |
|
||||
| `GOCLAW_COHERE_API_KEY` | Cohere |
|
||||
| `GOCLAW_PERPLEXITY_API_KEY` | Perplexity |
|
||||
| `GOCLAW_DASHSCOPE_API_KEY` | DashScope (Qwen) |
|
||||
| `GOCLAW_BAILIAN_API_KEY` | Bailian Coding |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -66,6 +66,20 @@ func registerProviders(registry *providers.Registry, cfg *config.Config) {
|
||||
registry.Register(providers.NewOpenAIProvider("perplexity", cfg.Providers.Perplexity.APIKey, "https://api.perplexity.ai", "sonar-pro"))
|
||||
slog.Info("registered provider", "name", "perplexity")
|
||||
}
|
||||
|
||||
if cfg.Providers.DashScope.APIKey != "" {
|
||||
registry.Register(providers.NewDashScopeProvider(cfg.Providers.DashScope.APIKey, cfg.Providers.DashScope.APIBase, "qwen3-max"))
|
||||
slog.Info("registered provider", "name", "dashscope")
|
||||
}
|
||||
|
||||
if cfg.Providers.Bailian.APIKey != "" {
|
||||
base := cfg.Providers.Bailian.APIBase
|
||||
if base == "" {
|
||||
base = "https://coding-intl.dashscope.aliyuncs.com/v1"
|
||||
}
|
||||
registry.Register(providers.NewOpenAIProvider("bailian", cfg.Providers.Bailian.APIKey, base, "qwen3.5-plus"))
|
||||
slog.Info("registered provider", "name", "bailian")
|
||||
}
|
||||
}
|
||||
|
||||
// registerProvidersFromDB loads providers from Postgres and registers them.
|
||||
@@ -84,6 +98,14 @@ func registerProvidersFromDB(registry *providers.Registry, provStore store.Provi
|
||||
if p.ProviderType == store.ProviderAnthropicNative {
|
||||
registry.Register(providers.NewAnthropicProvider(p.APIKey,
|
||||
providers.WithAnthropicBaseURL(p.APIBase)))
|
||||
} else if p.ProviderType == store.ProviderDashScope {
|
||||
registry.Register(providers.NewDashScopeProvider(p.APIKey, p.APIBase, ""))
|
||||
} else if p.ProviderType == store.ProviderBailian {
|
||||
base := p.APIBase
|
||||
if base == "" {
|
||||
base = "https://coding-intl.dashscope.aliyuncs.com/v1"
|
||||
}
|
||||
registry.Register(providers.NewOpenAIProvider(p.Name, p.APIKey, base, "qwen3.5-plus"))
|
||||
} else {
|
||||
prov := providers.NewOpenAIProvider(p.Name, p.APIKey, p.APIBase, "")
|
||||
if p.ProviderType == store.ProviderMiniMax {
|
||||
|
||||
@@ -553,6 +553,14 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
|
||||
if req.Stream {
|
||||
resp, err = l.provider.ChatStream(ctx, chatReq, func(chunk providers.StreamChunk) {
|
||||
if chunk.Thinking != "" {
|
||||
l.emit(AgentEvent{
|
||||
Type: protocol.ChatEventThinking,
|
||||
AgentID: l.id,
|
||||
RunID: req.RunID,
|
||||
Payload: map[string]string{"content": chunk.Thinking},
|
||||
})
|
||||
}
|
||||
if chunk.Content != "" {
|
||||
l.emit(AgentEvent{
|
||||
Type: protocol.ChatEventChunk,
|
||||
|
||||
@@ -110,6 +110,8 @@ type ProvidersConfig struct {
|
||||
MiniMax ProviderConfig `json:"minimax"`
|
||||
Cohere ProviderConfig `json:"cohere"`
|
||||
Perplexity ProviderConfig `json:"perplexity"`
|
||||
DashScope ProviderConfig `json:"dashscope"`
|
||||
Bailian ProviderConfig `json:"bailian"`
|
||||
}
|
||||
|
||||
type ProviderConfig struct {
|
||||
@@ -130,7 +132,9 @@ func (c *Config) HasAnyProvider() bool {
|
||||
p.XAI.APIKey != "" ||
|
||||
p.MiniMax.APIKey != "" ||
|
||||
p.Cohere.APIKey != "" ||
|
||||
p.Perplexity.APIKey != ""
|
||||
p.Perplexity.APIKey != "" ||
|
||||
p.DashScope.APIKey != "" ||
|
||||
p.Bailian.APIKey != ""
|
||||
}
|
||||
|
||||
// GatewayConfig controls the gateway server.
|
||||
|
||||
@@ -105,6 +105,8 @@ func (c *Config) applyEnvOverrides() {
|
||||
envStr("GOCLAW_MINIMAX_API_KEY", &c.Providers.MiniMax.APIKey)
|
||||
envStr("GOCLAW_COHERE_API_KEY", &c.Providers.Cohere.APIKey)
|
||||
envStr("GOCLAW_PERPLEXITY_API_KEY", &c.Providers.Perplexity.APIKey)
|
||||
envStr("GOCLAW_DASHSCOPE_API_KEY", &c.Providers.DashScope.APIKey)
|
||||
envStr("GOCLAW_BAILIAN_API_KEY", &c.Providers.Bailian.APIKey)
|
||||
envStr("GOCLAW_GATEWAY_TOKEN", &c.Gateway.Token)
|
||||
envStr("GOCLAW_TELEGRAM_TOKEN", &c.Channels.Telegram.Token)
|
||||
envStr("GOCLAW_DISCORD_TOKEN", &c.Channels.Discord.Token)
|
||||
|
||||
@@ -32,6 +32,8 @@ func (c *Config) MaskedCopy() *Config {
|
||||
maskNonEmpty(&cp.Providers.MiniMax.APIKey)
|
||||
maskNonEmpty(&cp.Providers.Cohere.APIKey)
|
||||
maskNonEmpty(&cp.Providers.Perplexity.APIKey)
|
||||
maskNonEmpty(&cp.Providers.DashScope.APIKey)
|
||||
maskNonEmpty(&cp.Providers.Bailian.APIKey)
|
||||
|
||||
// Mask gateway token
|
||||
maskNonEmpty(&cp.Gateway.Token)
|
||||
@@ -77,6 +79,8 @@ func (c *Config) StripSecrets() {
|
||||
c.Providers.MiniMax.APIKey = ""
|
||||
c.Providers.Cohere.APIKey = ""
|
||||
c.Providers.Perplexity.APIKey = ""
|
||||
c.Providers.DashScope.APIKey = ""
|
||||
c.Providers.Bailian.APIKey = ""
|
||||
|
||||
// Gateway token
|
||||
c.Gateway.Token = ""
|
||||
@@ -127,6 +131,8 @@ func (c *Config) StripMaskedSecrets() {
|
||||
stripIfMasked(&c.Providers.MiniMax.APIKey)
|
||||
stripIfMasked(&c.Providers.Cohere.APIKey)
|
||||
stripIfMasked(&c.Providers.Perplexity.APIKey)
|
||||
stripIfMasked(&c.Providers.DashScope.APIKey)
|
||||
stripIfMasked(&c.Providers.Bailian.APIKey)
|
||||
|
||||
// Gateway token
|
||||
stripIfMasked(&c.Gateway.Token)
|
||||
|
||||
@@ -51,6 +51,8 @@ func (h *ProvidersHandler) handleListProviderModels(w http.ResponseWriter, r *ht
|
||||
models, err = fetchAnthropicModels(ctx, p.APIKey)
|
||||
case "gemini_native":
|
||||
models, err = fetchGeminiModels(ctx, p.APIKey)
|
||||
case "bailian":
|
||||
models = bailianModels()
|
||||
default:
|
||||
// All other types use OpenAI-compatible /models endpoint
|
||||
apiBase := strings.TrimRight(p.APIBase, "/")
|
||||
@@ -145,6 +147,22 @@ func fetchGeminiModels(ctx context.Context, apiKey string) ([]ModelInfo, error)
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// bailianModels returns a hardcoded list of models available on the
|
||||
// Bailian Coding platform (coding-intl.dashscope.aliyuncs.com).
|
||||
// The platform does not expose a /v1/models endpoint.
|
||||
func bailianModels() []ModelInfo {
|
||||
return []ModelInfo{
|
||||
{ID: "qwen3.5-plus", Name: "Qwen 3.5 Plus"},
|
||||
{ID: "kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "GLM-5", Name: "GLM-5"},
|
||||
{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "qwen3-max-2026-01-23", Name: "Qwen 3 Max (2026-01-23)"},
|
||||
{ID: "qwen3-coder-next", Name: "Qwen 3 Coder Next"},
|
||||
{ID: "qwen3-coder-plus", Name: "Qwen 3 Coder Plus"},
|
||||
{ID: "glm-4.7", Name: "GLM 4.7"},
|
||||
}
|
||||
}
|
||||
|
||||
// fetchOpenAIModels calls an OpenAI-compatible /models endpoint.
|
||||
func fetchOpenAIModels(ctx context.Context, apiBase, apiKey string) ([]ModelInfo, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", apiBase+"/models", nil)
|
||||
@@ -180,3 +198,4 @@ func fetchOpenAIModels(ctx context.Context, apiBase, apiKey string) ([]ModelInfo
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
|
||||
@@ -67,6 +67,14 @@ func (h *ProvidersHandler) registerInMemory(p *store.LLMProviderData) {
|
||||
if p.ProviderType == store.ProviderAnthropicNative {
|
||||
h.providerReg.Register(providers.NewAnthropicProvider(p.APIKey,
|
||||
providers.WithAnthropicBaseURL(p.APIBase)))
|
||||
} else if p.ProviderType == store.ProviderDashScope {
|
||||
h.providerReg.Register(providers.NewDashScopeProvider(p.APIKey, p.APIBase, ""))
|
||||
} else if p.ProviderType == store.ProviderBailian {
|
||||
base := p.APIBase
|
||||
if base == "" {
|
||||
base = "https://coding-intl.dashscope.aliyuncs.com/v1"
|
||||
}
|
||||
h.providerReg.Register(providers.NewOpenAIProvider(p.Name, p.APIKey, base, "qwen3.5-plus"))
|
||||
} else {
|
||||
prov := providers.NewOpenAIProvider(p.Name, p.APIKey, p.APIBase, "")
|
||||
if p.ProviderType == store.ProviderMiniMax {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
const (
|
||||
dashscopeDefaultBase = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
||||
dashscopeDefaultModel = "qwen3-max"
|
||||
)
|
||||
|
||||
// DashScopeProvider wraps OpenAIProvider to handle DashScope-specific behaviors.
|
||||
// Critical: DashScope does NOT support tools + streaming simultaneously.
|
||||
// When tools are present, ChatStream falls back to non-streaming Chat().
|
||||
type DashScopeProvider struct {
|
||||
*OpenAIProvider
|
||||
}
|
||||
|
||||
func NewDashScopeProvider(apiKey, apiBase, defaultModel string) *DashScopeProvider {
|
||||
if apiBase == "" {
|
||||
apiBase = dashscopeDefaultBase
|
||||
}
|
||||
if defaultModel == "" {
|
||||
defaultModel = dashscopeDefaultModel
|
||||
}
|
||||
return &DashScopeProvider{
|
||||
OpenAIProvider: NewOpenAIProvider("dashscope", apiKey, apiBase, defaultModel),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DashScopeProvider) Name() string { return "dashscope" }
|
||||
|
||||
// ChatStream handles DashScope's limitation: tools + streaming cannot coexist.
|
||||
// When tools are present, falls back to non-streaming Chat() and synthesizes
|
||||
// chunk callbacks for the caller.
|
||||
func (p *DashScopeProvider) ChatStream(ctx context.Context, req ChatRequest, onChunk func(StreamChunk)) (*ChatResponse, error) {
|
||||
if len(req.Tools) > 0 {
|
||||
slog.Debug("dashscope: tools present, falling back to non-streaming Chat")
|
||||
resp, err := p.Chat(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if onChunk != nil {
|
||||
if resp.Thinking != "" {
|
||||
onChunk(StreamChunk{Thinking: resp.Thinking})
|
||||
}
|
||||
if resp.Content != "" {
|
||||
onChunk(StreamChunk{Content: resp.Content})
|
||||
}
|
||||
onChunk(StreamChunk{Done: true})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
return p.OpenAIProvider.ChatStream(ctx, req, onChunk)
|
||||
}
|
||||
@@ -122,6 +122,12 @@ func (p *OpenAIProvider) ChatStream(ctx context.Context, req ChatRequest, onChun
|
||||
}
|
||||
|
||||
delta := chunk.Choices[0].Delta
|
||||
if delta.ReasoningContent != "" {
|
||||
result.Thinking += delta.ReasoningContent
|
||||
if onChunk != nil {
|
||||
onChunk(StreamChunk{Thinking: delta.ReasoningContent})
|
||||
}
|
||||
}
|
||||
if delta.Content != "" {
|
||||
result.Content += delta.Content
|
||||
if onChunk != nil {
|
||||
@@ -326,6 +332,7 @@ func (p *OpenAIProvider) parseResponse(resp *openAIResponse) *ChatResponse {
|
||||
if len(resp.Choices) > 0 {
|
||||
msg := resp.Choices[0].Message
|
||||
result.Content = msg.Content
|
||||
result.Thinking = msg.ReasoningContent
|
||||
result.FinishReason = resp.Choices[0].FinishReason
|
||||
|
||||
for _, tc := range msg.ToolCalls {
|
||||
|
||||
@@ -13,9 +13,10 @@ type openAIChoice struct {
|
||||
}
|
||||
|
||||
type openAIMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ToolCalls []openAIToolCall `json:"tool_calls,omitempty"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
ToolCalls []openAIToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
type openAIToolCall struct {
|
||||
@@ -54,8 +55,9 @@ type openAIStreamChoice struct {
|
||||
}
|
||||
|
||||
type openAIStreamDelta struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
ToolCalls []openAIStreamToolCall `json:"tool_calls,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
ToolCalls []openAIStreamToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
type openAIStreamToolCall struct {
|
||||
|
||||
@@ -30,6 +30,7 @@ type ChatRequest struct {
|
||||
// ChatResponse is the result from an LLM call.
|
||||
type ChatResponse struct {
|
||||
Content string `json:"content"`
|
||||
Thinking string `json:"thinking,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
FinishReason string `json:"finish_reason"` // "stop", "tool_calls", "length"
|
||||
Usage *Usage `json:"usage,omitempty"`
|
||||
|
||||
@@ -19,6 +19,8 @@ const (
|
||||
ProviderMiniMax = "minimax_native"
|
||||
ProviderCohere = "cohere"
|
||||
ProviderPerplexity = "perplexity"
|
||||
ProviderDashScope = "dashscope"
|
||||
ProviderBailian = "bailian"
|
||||
)
|
||||
|
||||
// ValidProviderTypes lists all accepted provider_type values.
|
||||
@@ -34,6 +36,8 @@ var ValidProviderTypes = map[string]bool{
|
||||
ProviderMiniMax: true,
|
||||
ProviderCohere: true,
|
||||
ProviderPerplexity: true,
|
||||
ProviderDashScope: true,
|
||||
ProviderBailian: true,
|
||||
}
|
||||
|
||||
// LLMProviderData represents an LLM provider configuration.
|
||||
|
||||
@@ -27,12 +27,13 @@ func MediaImagesFromCtx(ctx context.Context) []providers.ImageContent {
|
||||
// --- ReadImageTool ---
|
||||
|
||||
// visionProviderPriority is the order in which providers are tried for vision.
|
||||
var visionProviderPriority = []string{"openrouter", "gemini", "anthropic"}
|
||||
var visionProviderPriority = []string{"openrouter", "gemini", "anthropic", "dashscope"}
|
||||
|
||||
// visionModelOverrides maps provider names to preferred vision models.
|
||||
// Providers not listed here use their default model.
|
||||
var visionModelOverrides = map[string]string{
|
||||
"openrouter": "google/gemini-2.5-flash-image",
|
||||
"dashscope": "qwen3-vl",
|
||||
}
|
||||
|
||||
// ReadImageTool uses a vision-capable provider to describe images attached to the current message.
|
||||
|
||||
@@ -40,6 +40,8 @@ const PROVIDER_TYPES = [
|
||||
{ value: "minimax_native", label: "MiniMax (Native)", apiBase: "https://api.minimax.io/v1", placeholder: "" },
|
||||
{ value: "cohere", label: "Cohere", apiBase: "https://api.cohere.ai/compatibility/v1", placeholder: "" },
|
||||
{ value: "perplexity", label: "Perplexity", apiBase: "https://api.perplexity.ai", placeholder: "" },
|
||||
{ value: "dashscope", label: "DashScope (Qwen)", apiBase: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", placeholder: "" },
|
||||
{ value: "bailian", label: "Bailian Coding", apiBase: "https://coding-intl.dashscope.aliyuncs.com/v1", placeholder: "" },
|
||||
];
|
||||
|
||||
export function ProviderFormDialog({ open, onOpenChange, provider, onSubmit }: ProviderFormDialogProps) {
|
||||
@@ -139,7 +141,7 @@ export function ProviderFormDialog({ open, onOpenChange, provider, onSubmit }: P
|
||||
setProviderType(v);
|
||||
if (!isEdit) {
|
||||
const preset = PROVIDER_TYPES.find((t) => t.value === v);
|
||||
if (preset?.apiBase) setApiBase(preset.apiBase);
|
||||
setApiBase(preset?.apiBase || "");
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user