Files
Thibault Jaigu 32eb2328f3 feat(provider): add Requesty OpenAI-compatible provider (#1579)
Register Requesty (https://router.requesty.ai/v1) next to OpenRouter on the
existing OpenAI-compatible transport: config and env vars
(GOCLAW_REQUESTY_API_KEY, GOCLAW_REQUESTY_BASE_URL), secret masking, DB and
in-memory registration, CLI setup, doctor, placeholder provider, OpenAPI enum,
Web/Desktop provider lists and docs.

The models list merges Requesty managed policies (GET /models/managed) with
the key's catalog from GET /models.
2026-09-26 03:33:33 +07:00

302 lines
11 KiB
Go

package store
import (
"context"
"encoding/json"
"github.com/google/uuid"
)
// Provider type constants.
const (
ProviderAnthropicNative = "anthropic_native"
ProviderOpenAICompat = "openai_compat"
ProviderGeminiNative = "gemini_native"
ProviderOpenRouter = "openrouter"
ProviderAIMLAPI = "aimlapi"
ProviderGroq = "groq"
ProviderDeepSeek = "deepseek"
ProviderMistral = "mistral"
ProviderXAI = "xai"
ProviderMiniMax = "minimax_native"
ProviderCohere = "cohere"
ProviderPerplexity = "perplexity"
ProviderDashScope = "dashscope"
ProviderBailian = "bailian"
ProviderChatGPTOAuth = "chatgpt_oauth"
ProviderClaudeCLI = "claude_cli"
ProviderYesScale = "yescale"
ProviderZai = "zai"
ProviderZaiCoding = "zai_coding"
ProviderOllama = "ollama" // local or self-hosted Ollama (no API key)
ProviderOllamaCloud = "ollama_cloud" // Ollama Cloud (Bearer token required)
ProviderACP = "acp" // ACP (Agent Client Protocol) agent subprocess
ProviderNovita = "novita" // Novita AI (OpenAI-compatible endpoint)
ProviderBytePlus = "byteplus" // BytePlus ModelArk (Seed 2.0 models)
ProviderBytePlusCoding = "byteplus_coding" // BytePlus ModelArk Coding Plan
ProviderVertex = "vertex" // Google Cloud Vertex AI (OAuth2 service account + ADC)
ProviderKimiCoding = "kimi_coding" // Moonshot Kimi Coding (OpenAI-compat, requires fixed User-Agent)
ProviderAtlasCloud = "atlascloud" // Atlas Cloud (OpenAI-compatible endpoint)
ProviderAPIRoute = "api_route" // API Route (OpenAI-compatible endpoint)
ProviderRequesty = "requesty" // Requesty (OpenAI-compatible router)
// MiniMax defaults.
MiniMaxDefaultAPIBase = "https://api.minimax.io/v1"
MiniMaxDefaultModel = "MiniMax-M3"
// Z.AI defaults.
ZaiDefaultAPIBase = "https://api.z.ai/api/paas/v4"
ZaiCodingDefaultAPIBase = "https://api.z.ai/api/coding/paas/v4"
ZaiDefaultModel = "glm-5.2"
// Novita AI defaults.
NovitaDefaultAPIBase = "https://api.novita.ai/openai"
NovitaDefaultModel = "moonshotai/kimi-k2.5"
// BytePlus ModelArk defaults.
BytePlusDefaultAPIBase = "https://ark.ap-southeast.bytepluses.com/api/v3"
BytePlusCodingDefaultAPIBase = "https://ark.ap-southeast.bytepluses.com/api/coding/v3"
BytePlusDefaultModel = "seed-2-0-lite-260228"
// Kimi Coding defaults. The upstream requires a fixed User-Agent on every
// request — handled by the runtime in cmd/gateway_providers.go via
// OpenAIProvider.WithExtraHeaders.
KimiCodingDefaultAPIBase = "https://api.kimi.com/coding/v1"
KimiCodingDefaultModel = "kimi-k2-turbo-preview"
KimiCodingRequiredUserAgent = "claude-code/0.1.0"
// Atlas Cloud defaults.
AtlasCloudDefaultAPIBase = "https://api.atlascloud.ai/v1"
AtlasCloudDefaultModel = "qwen/qwen3.5-flash"
// API Route defaults.
APIRouteDefaultAPIBase = "https://global.api-route.com/v1"
APIRouteDefaultModel = "gpt-5.4-mini"
// Requesty defaults. Regional endpoints (e.g. https://router.eu.requesty.ai/v1)
// can be set through api_base.
RequestyDefaultAPIBase = "https://router.requesty.ai/v1"
RequestyDefaultModel = "openai/gpt-4o-mini"
)
// Vertex AI constants live in internal/providers/vertex.go to avoid a store→providers import cycle
// (store is imported by providers). DB-layer concerns (ProviderVertex type + settings parsing)
// remain in this package.
// ValidProviderTypes lists all accepted provider_type values.
var ValidProviderTypes = map[string]bool{
ProviderAnthropicNative: true,
ProviderOpenAICompat: true,
ProviderGeminiNative: true,
ProviderOpenRouter: true,
ProviderAIMLAPI: true,
ProviderGroq: true,
ProviderDeepSeek: true,
ProviderMistral: true,
ProviderXAI: true,
ProviderMiniMax: true,
ProviderCohere: true,
ProviderPerplexity: true,
ProviderDashScope: true,
ProviderBailian: true,
ProviderChatGPTOAuth: true,
ProviderClaudeCLI: true,
ProviderYesScale: true,
ProviderZai: true,
ProviderZaiCoding: true,
ProviderOllama: true,
ProviderOllamaCloud: true,
ProviderACP: true,
ProviderNovita: true,
ProviderBytePlus: true,
ProviderBytePlusCoding: true,
ProviderVertex: true,
ProviderKimiCoding: true,
ProviderAtlasCloud: true,
ProviderAPIRoute: true,
ProviderRequesty: true,
}
// VertexProviderSettings holds Vertex-specific config stored in llm_providers.settings JSONB.
type VertexProviderSettings struct {
ProjectID string `json:"project_id"`
Region string `json:"region"`
Model string `json:"model,omitempty"` // optional default model override (e.g. "google/gemini-2.5-pro-001")
}
// ParseVertexProviderSettings extracts Vertex config from settings JSONB.
// Returns nil if project_id or region is missing (both required).
func ParseVertexProviderSettings(settings json.RawMessage) *VertexProviderSettings {
if len(settings) == 0 {
return nil
}
var s VertexProviderSettings
if json.Unmarshal(settings, &s) != nil {
return nil
}
if s.ProjectID == "" || s.Region == "" {
return nil
}
return &s
}
// LLMProviderData represents an LLM provider configuration.
type LLMProviderData struct {
BaseModel
TenantID uuid.UUID `json:"tenant_id,omitempty" db:"tenant_id"`
Name string `json:"name" db:"name"`
DisplayName string `json:"display_name,omitempty" db:"display_name"`
ProviderType string `json:"provider_type" db:"provider_type"`
APIBase string `json:"api_base,omitempty" db:"api_base"`
APIKey string `json:"api_key,omitempty" db:"api_key"`
Enabled bool `json:"enabled" db:"enabled"`
Settings json.RawMessage `json:"settings,omitempty" db:"settings"`
}
// RequiredMemoryEmbeddingDimensions is the fixed vector size used by the pgvector memory schema.
// All memory embeddings must match this dimensionality until the schema supports variable sizes.
const RequiredMemoryEmbeddingDimensions = 1536
// EmbeddingSettings holds embedding-specific configuration stored in provider settings JSONB.
type EmbeddingSettings struct {
Enabled bool `json:"enabled" db:"-"`
Model string `json:"model,omitempty" db:"-"` // e.g. "text-embedding-3-small"
APIBase string `json:"api_base,omitempty" db:"-"` // override if embedding endpoint differs from chat
Dimensions int `json:"dimensions,omitempty" db:"-"` // truncate output to N dims (e.g. 1536); 0 = model default
}
// ProviderReasoningConfig holds provider-owned default reasoning settings.
// These defaults are inherited by agents unless they save a custom override.
type ProviderReasoningConfig struct {
Effort string `json:"effort,omitempty" db:"-"`
Fallback string `json:"fallback,omitempty" db:"-"`
}
// OllamaSettings holds Ollama-specific configuration stored in the provider settings JSONB.
type OllamaSettings struct {
// NumCtx overrides the context window size sent in options.num_ctx on every request.
// When nil, the gateway queries the Ollama API (/api/show) for the model's native
// context length, falling back to 131072 if the API is unreachable.
NumCtx *int `json:"num_ctx,omitempty" db:"-"`
}
// ParseOllamaSettings extracts Ollama-specific config from a provider's settings JSONB.
// Returns nil when no relevant settings are present.
func ParseOllamaSettings(settings json.RawMessage) *OllamaSettings {
if len(settings) == 0 {
return nil
}
var s OllamaSettings
if json.Unmarshal(settings, &s) != nil || s.NumCtx == nil {
return nil
}
return &s
}
// ChatGPTOAuthProviderSettings holds provider-level defaults for Codex account pooling.
type ChatGPTOAuthProviderSettings struct {
CodexPool *ChatGPTOAuthRoutingConfig `json:"codex_pool,omitempty" db:"-"`
}
// ParseEmbeddingSettings extracts embedding config from a provider's settings JSONB.
// Returns nil if not configured.
func ParseEmbeddingSettings(settings json.RawMessage) *EmbeddingSettings {
if len(settings) == 0 {
return nil
}
var s struct {
Embedding *EmbeddingSettings `json:"embedding"`
}
if json.Unmarshal(settings, &s) != nil || s.Embedding == nil {
return nil
}
return s.Embedding
}
// ParseThinkingEnabled extracts the provider-level override for whether the
// provider should be asked to emit visible reasoning/thinking tokens (e.g.
// Ollama native "think" field, OpenAI-compat "think" for Ollama endpoints).
// Returns nil when unset in settings JSONB, meaning "use provider default"
// (currently off for Ollama). Explicit true/false overrides that default.
func ParseThinkingEnabled(settings json.RawMessage) *bool {
if len(settings) == 0 {
return nil
}
var s struct {
ThinkingEnabled *bool `json:"thinking_enabled"`
}
if json.Unmarshal(settings, &s) != nil {
return nil
}
return s.ThinkingEnabled
}
// ParseChatGPTOAuthProviderSettings extracts provider-level Codex pool defaults from settings JSONB.
func ParseChatGPTOAuthProviderSettings(settings json.RawMessage) *ChatGPTOAuthProviderSettings {
if len(settings) == 0 {
return nil
}
var s ChatGPTOAuthProviderSettings
if json.Unmarshal(settings, &s) != nil {
return nil
}
s.CodexPool = normalizeChatGPTOAuthRoutingConfig(s.CodexPool)
if s.CodexPool == nil {
return nil
}
s.CodexPool.OverrideMode = ""
return &s
}
// ParseProviderReasoningConfig extracts provider-owned reasoning defaults from settings JSONB.
// Returns nil when no non-default provider reasoning is configured.
func ParseProviderReasoningConfig(settings json.RawMessage) *ProviderReasoningConfig {
if len(settings) == 0 {
return nil
}
var raw struct {
ReasoningDefaults *ProviderReasoningConfig `json:"reasoning_defaults"`
}
if json.Unmarshal(settings, &raw) != nil {
return nil
}
return normalizeProviderReasoningConfig(raw.ReasoningDefaults)
}
func normalizeProviderReasoningConfig(raw *ProviderReasoningConfig) *ProviderReasoningConfig {
if raw == nil {
return nil
}
cfg := &ProviderReasoningConfig{
Effort: normalizeReasoningEffort(raw.Effort),
Fallback: normalizeReasoningFallback(raw.Fallback),
}
if cfg.Effort == "" {
cfg.Effort = "off"
}
if cfg.Effort == "off" && cfg.Fallback == ReasoningFallbackDowngrade {
return nil
}
return cfg
}
// NoEmbeddingTypes lists provider types that cannot serve embeddings.
var NoEmbeddingTypes = map[string]bool{
ProviderAnthropicNative: true, // uses x-api-key auth, not Bearer; no embedding models
ProviderACP: true,
ProviderClaudeCLI: true,
ProviderChatGPTOAuth: true,
ProviderVertex: true, // Vertex embeddings live on a different native endpoint, not on /endpoints/openapi
}
// ProviderStore manages LLM providers.
type ProviderStore interface {
CreateProvider(ctx context.Context, p *LLMProviderData) error
GetProvider(ctx context.Context, id uuid.UUID) (*LLMProviderData, error)
GetProviderByName(ctx context.Context, name string) (*LLMProviderData, error)
ListProviders(ctx context.Context) ([]LLMProviderData, error)
ListAllProviders(ctx context.Context) ([]LLMProviderData, error)
UpdateProvider(ctx context.Context, id uuid.UUID, updates map[string]any) error
DeleteProvider(ctx context.Context, id uuid.UUID) error
}