Files
cursor-byok/internal/runtime/local_runtime.go
T
2026-06-30 10:38:52 +08:00

507 lines
20 KiB
Go

package runtime
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"cursor/internal/modelchannel"
)
var (
// ErrInvalidSystemSetting 表示当前模块中的 ErrInvalidSystemSetting 状态值。
ErrInvalidSystemSetting = errors.New("invalid system setting")
// ErrChannelNotAvailable 表示当前没有可用模型渠道。
ErrChannelNotAvailable = errors.New("model channel not available")
// ErrChannelRateLimited 表示当前模型渠道被限流。
ErrChannelRateLimited = errors.New("model channel rate limited")
)
const (
// configurableChannelTimeoutMS 表示当前声明中的 configurableChannelTimeoutMS。
configurableChannelTimeoutMS = int((2 * time.Hour) / time.Millisecond)
// configurableChannelContextWindowTokens 表示当前声明中的默认上下文窗口大小。
configurableChannelContextWindowTokens = 200_000
// configurableChannelMaxTokens 表示当前声明中的 configurableChannelMaxTokens。
configurableChannelMaxTokens = 65_536
// configurableChannelThinkingBudgetTokens 表示当前声明中的 configurableChannelThinkingBudgetTokens。
configurableChannelThinkingBudgetTokens = 4_096
// configurableChannelAnthropicThinkingEffort 表示 Anthropic adaptive thinking 默认强度。
configurableChannelAnthropicThinkingEffort = "xhigh"
)
// ModelAdapterConfig 定义了当前模块中的 ModelAdapterConfig 类型。
type ModelAdapterConfig struct {
ID string `json:"id,omitempty"`
// DisplayName 表示当前声明中的 DisplayName。
DisplayName string `json:"displayName"`
// Type 表示当前声明中的 Type。
Type string `json:"type"`
// BaseURL 表示当前声明中的 BaseURL。
BaseURL string `json:"baseURL"`
// APIKey 表示当前声明中的 APIKey。
APIKey string `json:"apiKey"`
// TooltipData 表示当前声明中的 TooltipData。
TooltipData string `json:"tooltipData"`
// ModelID 表示当前声明中的 ModelID。
ModelID string `json:"modelID"`
// ReasoningEffort 表示当前声明中的 ReasoningEffort。
ReasoningEffort string `json:"reasoningEffort"`
// OpenAIEndpoint 表示 OpenAI 兼容适配器使用的 API 端点。
OpenAIEndpoint string `json:"openAIEndpoint"`
// OpenAIExtraParamsEnabled 表示是否启用 OpenAI 额外请求参数。
OpenAIExtraParamsEnabled bool `json:"openAIExtraParamsEnabled"`
// OpenAIExtraParamsJSON 表示 OpenAI 额外请求参数 JSON 对象。
OpenAIExtraParamsJSON string `json:"openAIExtraParamsJSON"`
// CustomHeadersEnabled 表示是否启用自定义请求头。
CustomHeadersEnabled bool `json:"customHeadersEnabled"`
// CustomHeadersJSON 表示自定义请求头 JSON 对象。
CustomHeadersJSON string `json:"customHeadersJSON"`
// AnthropicExtraParamsEnabled 表示是否启用 Anthropic 额外请求参数。
AnthropicExtraParamsEnabled bool `json:"anthropicExtraParamsEnabled"`
// AnthropicExtraParamsJSON 表示 Anthropic 额外请求参数 JSON 对象。
AnthropicExtraParamsJSON string `json:"anthropicExtraParamsJSON"`
// ContextWindowTokens 表示当前声明中的 ContextWindowTokens。
ContextWindowTokens int `json:"contextWindowTokens"`
// MaxCompletionTokens 表示当前声明中的 MaxCompletionTokens。
MaxCompletionTokens int `json:"maxCompletionTokens"`
// AnthropicMaxTokens 表示当前声明中的 AnthropicMaxTokens。
AnthropicMaxTokens int `json:"anthropicMaxTokens"`
// AnthropicThinkingEffort 表示 Anthropic adaptive thinking 的 output_config.effort。
AnthropicThinkingEffort string `json:"anthropicThinkingEffort,omitempty"`
// ThinkingBudgetTokens 表示当前声明中的 ThinkingBudgetTokens。
ThinkingBudgetTokens int `json:"thinkingBudgetTokens"`
}
// RuntimeConfigSnapshot 定义了当前模块中的 RuntimeConfigSnapshot 类型。
type RuntimeConfigSnapshot struct {
// ObservabilityLogEnabled 表示当前声明中的 ObservabilityLogEnabled。
ObservabilityLogEnabled bool
// ProviderStreamIdleTimeout 表示 provider 流式响应无有效内容时的空闲超时,单位秒。
ProviderStreamIdleTimeout int
// ModelAdapters 表示当前声明中的 ModelAdapters。
ModelAdapters []ModelAdapterConfig
}
// RuntimeConfigProvider 定义了当前模块中的 RuntimeConfigProvider 类型。
type RuntimeConfigProvider func(context.Context) (RuntimeConfigSnapshot, error)
// NormalizeModelAdapterConfigs 用于处理与 NormalizeModelAdapterConfigs 相关的逻辑。
func NormalizeModelAdapterConfigs(input []ModelAdapterConfig) ([]ModelAdapterConfig, error) {
if len(input) == 0 {
return []ModelAdapterConfig{}, nil
}
normalized := make([]ModelAdapterConfig, 0, len(input))
seenChannelIDs := make(map[string]struct{}, len(input))
for _, item := range input {
baseURL, err := modelchannel.NormalizeBaseURL(item.BaseURL)
if err != nil {
return nil, err
}
next := ModelAdapterConfig{
DisplayName: strings.TrimSpace(item.DisplayName),
Type: normalizeModelAdapterType(item.Type),
BaseURL: baseURL,
APIKey: strings.TrimSpace(item.APIKey),
TooltipData: strings.TrimSpace(item.TooltipData),
ModelID: strings.TrimSpace(item.ModelID),
ReasoningEffort: normalizeReasoningEffort(item.ReasoningEffort),
OpenAIEndpoint: modelchannel.NormalizeOpenAIEndpoint(item.Type, item.OpenAIEndpoint),
ContextWindowTokens: normalizeMaxCompletionTokens(item.ContextWindowTokens),
MaxCompletionTokens: normalizeMaxCompletionTokens(item.MaxCompletionTokens),
AnthropicMaxTokens: normalizeMaxCompletionTokens(item.AnthropicMaxTokens),
ThinkingBudgetTokens: normalizeMaxCompletionTokens(item.ThinkingBudgetTokens),
}
if next.Type == "openai" {
next.OpenAIExtraParamsEnabled = item.OpenAIExtraParamsEnabled
next.OpenAIExtraParamsJSON = strings.TrimSpace(item.OpenAIExtraParamsJSON)
} else if next.Type == "anthropic" {
next.AnthropicThinkingEffort = normalizeAnthropicThinkingEffort(item.AnthropicThinkingEffort)
next.AnthropicExtraParamsEnabled = item.AnthropicExtraParamsEnabled
next.AnthropicExtraParamsJSON = strings.TrimSpace(item.AnthropicExtraParamsJSON)
}
next.CustomHeadersEnabled = item.CustomHeadersEnabled
next.CustomHeadersJSON = strings.TrimSpace(item.CustomHeadersJSON)
switch {
case next.DisplayName == "":
return nil, errors.New("模型适配器 displayName 不能为空")
case next.Type == "":
return nil, errors.New("模型适配器 type 仅支持 openai 或 anthropic")
case next.APIKey == "":
return nil, errors.New("模型适配器 apiKey 不能为空")
case next.TooltipData == "":
return nil, errors.New("模型适配器 tooltipData 不能为空")
case next.ModelID == "":
return nil, errors.New("模型适配器 modelID 不能为空")
case next.Type == "openai" && next.ReasoningEffort == "":
return nil, errors.New("模型适配器 reasoningEffort 仅支持 low、medium、high、xhigh")
case next.Type == "openai" && next.OpenAIEndpoint == "":
return nil, errors.New("模型适配器 openAIEndpoint 仅支持 /v1/responses 或 /v1/chat/completions")
case next.Type == "openai" && next.OpenAIExtraParamsEnabled:
if err := validateJSONMap(next.OpenAIExtraParamsJSON, "openAIExtraParamsJSON"); err != nil {
return nil, err
}
case next.CustomHeadersEnabled:
if err := validateHeadersJSON(next.CustomHeadersJSON); err != nil {
return nil, err
}
case next.Type == "anthropic" && next.AnthropicExtraParamsEnabled:
if err := validateJSONMap(next.AnthropicExtraParamsJSON, "anthropicExtraParamsJSON"); err != nil {
return nil, err
}
case next.Type == "anthropic" && next.AnthropicThinkingEffort == "":
return nil, errors.New("模型适配器 anthropicThinkingEffort 仅支持 low、medium、high、xhigh、max")
}
next.ID = modelchannel.BuildChannelID(next.BaseURL, next.ModelID, next.APIKey, next.DisplayName, next.OpenAIEndpoint)
if _, exists := seenChannelIDs[next.ID]; exists {
return nil, errors.New("模型适配器渠道不能重复,请检查 url、modelID、apiKey、displayName、endpoint 组合")
}
seenChannelIDs[next.ID] = struct{}{}
normalized = append(normalized, next)
}
return normalized, nil
}
func validateJSONMap(value string, fieldName string) error {
text := strings.TrimSpace(value)
if text == "" {
return fmt.Errorf("模型适配器 %s 不能为空", fieldName)
}
var parsed map[string]any
if err := json.Unmarshal([]byte(text), &parsed); err != nil {
return fmt.Errorf("模型适配器 %s 必须是合法 JSON 对象", fieldName)
}
if parsed == nil {
return fmt.Errorf("模型适配器 %s 必须是 JSON 对象", fieldName)
}
return nil
}
func validateHeadersJSON(value string) error {
text := strings.TrimSpace(value)
if err := validateJSONMap(text, "customHeadersJSON"); err != nil {
return err
}
var parsed map[string]string
if err := json.Unmarshal([]byte(text), &parsed); err != nil {
return errors.New("模型适配器 customHeadersJSON 的值必须是字符串")
}
for key := range parsed {
if strings.TrimSpace(key) == "" {
return errors.New("模型适配器 customHeadersJSON 的请求头名称不能为空")
}
}
return nil
}
func normalizeReasoningEffort(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "", "medium":
return "medium"
case "low", "high", "xhigh":
return strings.ToLower(strings.TrimSpace(value))
default:
return ""
}
}
func normalizeAnthropicThinkingEffort(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "", "xhigh":
return "xhigh"
case "low", "medium", "high", "max":
return strings.ToLower(strings.TrimSpace(value))
default:
return ""
}
}
func normalizeMaxCompletionTokens(value int) int {
if value <= 0 {
return 0
}
return value
}
// normalizeModelAdapterType 用于处理与 normalizeModelAdapterType 相关的逻辑。
func normalizeModelAdapterType(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "openai":
return "openai"
case "anthropic":
return "anthropic"
default:
return ""
}
}
// ResolvedChannel 表示当前选中的模型渠道。
type ResolvedChannel struct {
// ID 表示当前声明中的 ID。
ID string
// Name 表示当前声明中的 Name。
Name string
// GroupName 表示当前声明中的 GroupName。
GroupName string
// Code 表示当前声明中的 Code。
Code string
// Provider 表示当前声明中的 Provider。
Provider string
// BaseURL 表示当前声明中的 BaseURL。
BaseURL string
// APIKey 表示当前声明中的 APIKey。
APIKey string
// Model 表示当前声明中的 Model。
Model string
// TimeoutMS 表示当前声明中的 TimeoutMS。
TimeoutMS int
// ContextWindowTokens 表示当前声明中的 ContextWindowTokens。
ContextWindowTokens int
// MaxTokens 表示当前声明中的 MaxTokens。
MaxTokens int
// ReasoningEffort 表示当前声明中的 ReasoningEffort。
ReasoningEffort string
// OpenAIEndpoint 表示 OpenAI 兼容适配器使用的 API 端点。
OpenAIEndpoint string
// OpenAIExtraParamsEnabled 表示是否启用 OpenAI 额外请求参数。
OpenAIExtraParamsEnabled bool
// OpenAIExtraParamsJSON 表示 OpenAI 额外请求参数 JSON 对象。
OpenAIExtraParamsJSON string
// CustomHeadersEnabled 表示是否启用自定义请求头。
CustomHeadersEnabled bool
// CustomHeadersJSON 表示自定义请求头 JSON 对象。
CustomHeadersJSON string
// AnthropicExtraParamsEnabled 表示是否启用 Anthropic 额外请求参数。
AnthropicExtraParamsEnabled bool
// AnthropicExtraParamsJSON 表示 Anthropic 额外请求参数 JSON 对象。
AnthropicExtraParamsJSON string
// AnthropicMaxTokens 表示当前声明中的 AnthropicMaxTokens。
AnthropicMaxTokens int
// AnthropicThinkingEffort 表示 Anthropic adaptive thinking 的 output_config.effort。
AnthropicThinkingEffort string
// ThinkingEnabled 表示当前声明中的 ThinkingEnabled。
ThinkingEnabled bool
// ThinkingBudgetTokens 表示当前声明中的 ThinkingBudgetTokens。
ThinkingBudgetTokens int
}
// ChannelUsageRecordCreatePayload 定义了一次渠道使用记录的最小载荷。
type ChannelUsageRecordCreatePayload struct {
// RequestID 表示当前声明中的 RequestID。
RequestID string
// ConversationID 表示当前声明中的 ConversationID。
ConversationID string
// RuntimeModelID 表示当前声明中的 RuntimeModelID。
RuntimeModelID string
}
// ChannelCallRecordCreatePayload 定义了一次渠道调用记录的最小载荷。
type ChannelCallRecordCreatePayload struct {
// RequestID 表示当前声明中的 RequestID。
RequestID string
// ConversationID 表示当前声明中的 ConversationID。
ConversationID string
// ChannelID 表示当前声明中的 ChannelID。
ChannelID string
// ChannelName 表示当前声明中的 ChannelName。
ChannelName string
// GroupName 表示当前声明中的 GroupName。
GroupName string
// Provider 表示当前声明中的 Provider。
Provider string
// RuntimeModelID 表示当前声明中的 RuntimeModelID。
RuntimeModelID string
// ProviderModelID 表示当前声明中的 ProviderModelID。
ProviderModelID string
// StatusCode 表示当前声明中的 StatusCode。
StatusCode int
// Success 表示当前声明中的 Success。
Success bool
// DurationMS 表示当前声明中的 DurationMS。
DurationMS int64
// ErrorCode 表示当前声明中的 ErrorCode。
ErrorCode string
// ErrorMessage 表示当前声明中的 ErrorMessage。
ErrorMessage string
}
// FixedChannelService 定义了当前模块中的 FixedChannelService 类型。
type FixedChannelService struct {
// channel 表示当前声明中的 channel。
channel ResolvedChannel
// configProvider 表示当前声明中的 configProvider。
configProvider RuntimeConfigProvider
}
// NewFixedChannelService 用于处理与 NewFixedChannelService 相关的逻辑。
func NewFixedChannelService(channel ResolvedChannel, logsRoot string) *FixedChannelService {
_ = logsRoot
return &FixedChannelService{
channel: channel,
}
}
// NewConfigurableChannelService 用于处理与 NewConfigurableChannelService 相关的逻辑。
func NewConfigurableChannelService(provider RuntimeConfigProvider, logsRoot string) *FixedChannelService {
_ = logsRoot
return &FixedChannelService{
configProvider: provider,
}
}
// SelectChannelForRequestBody 用于处理与 SelectChannelForRequestBody 相关的逻辑。
func (s *FixedChannelService) SelectChannelForRequestBody(_ context.Context, _ []byte) (*ResolvedChannel, error) {
return s.SelectChannelForModel(context.Background(), "")
}
// SelectChannelForModel 用于处理与 SelectChannelForModel 相关的逻辑。
func (s *FixedChannelService) SelectChannelForModel(ctx context.Context, modelID string) (*ResolvedChannel, error) {
if s == nil {
return nil, ErrChannelNotAvailable
}
if s.configProvider != nil {
cfg, err := s.configProvider(ctx)
if err != nil {
return nil, err
}
adapters, err := NormalizeModelAdapterConfigs(cfg.ModelAdapters)
if err != nil {
return nil, err
}
matchIndex, ok := modelchannel.ResolveAdapterIndex(
adapters,
modelID,
func(adapter ModelAdapterConfig) string { return adapter.ID },
func(adapter ModelAdapterConfig) string { return adapter.ModelID },
func(adapter ModelAdapterConfig) string {
return modelchannel.BuildLegacyChannelID(adapter.BaseURL, adapter.ModelID, adapter.APIKey, adapter.DisplayName)
},
)
if !ok {
return nil, ErrChannelNotAvailable
}
adapter := adapters[matchIndex]
resolved := ResolvedChannel{
ID: strings.TrimSpace(adapter.ID),
Name: strings.TrimSpace(adapter.DisplayName),
GroupName: "local",
Code: strings.TrimSpace(adapter.ID),
Provider: strings.TrimSpace(adapter.Type),
BaseURL: strings.TrimSpace(adapter.BaseURL),
APIKey: strings.TrimSpace(adapter.APIKey),
Model: strings.TrimSpace(adapter.ModelID),
TimeoutMS: configurableChannelTimeoutMS,
ContextWindowTokens: configurableChannelContextWindowTokens,
MaxTokens: configurableChannelMaxTokens,
ReasoningEffort: strings.TrimSpace(adapter.ReasoningEffort),
OpenAIEndpoint: strings.TrimSpace(adapter.OpenAIEndpoint),
OpenAIExtraParamsEnabled: adapter.OpenAIExtraParamsEnabled,
OpenAIExtraParamsJSON: strings.TrimSpace(adapter.OpenAIExtraParamsJSON),
CustomHeadersEnabled: adapter.CustomHeadersEnabled,
CustomHeadersJSON: strings.TrimSpace(adapter.CustomHeadersJSON),
AnthropicExtraParamsEnabled: adapter.AnthropicExtraParamsEnabled,
AnthropicExtraParamsJSON: strings.TrimSpace(adapter.AnthropicExtraParamsJSON),
AnthropicMaxTokens: configurableChannelMaxTokens,
AnthropicThinkingEffort: configurableChannelAnthropicThinkingEffort,
ThinkingEnabled: true,
ThinkingBudgetTokens: configurableChannelThinkingBudgetTokens,
}
if adapter.ContextWindowTokens > 0 {
resolved.ContextWindowTokens = adapter.ContextWindowTokens
}
if adapter.MaxCompletionTokens > 0 {
resolved.MaxTokens = adapter.MaxCompletionTokens
}
if adapter.ThinkingBudgetTokens > 0 {
resolved.ThinkingBudgetTokens = adapter.ThinkingBudgetTokens
}
if adapter.AnthropicMaxTokens > 0 {
resolved.AnthropicMaxTokens = adapter.AnthropicMaxTokens
}
if strings.TrimSpace(adapter.AnthropicThinkingEffort) != "" {
resolved.AnthropicThinkingEffort = strings.TrimSpace(adapter.AnthropicThinkingEffort)
}
return &resolved, nil
}
if strings.TrimSpace(s.channel.BaseURL) == "" || strings.TrimSpace(s.channel.APIKey) == "" {
return nil, ErrChannelNotAvailable
}
resolved := s.channel
return &resolved, nil
}
// RecordRunRequestUsage 用于处理与 RecordRunRequestUsage 相关的逻辑。
func (s *FixedChannelService) RecordRunRequestUsage(_ context.Context, payload ChannelUsageRecordCreatePayload) error {
_ = s
_ = payload
return nil
}
// RecordChannelCall 用于处理与 RecordChannelCall 相关的逻辑。
func (s *FixedChannelService) RecordChannelCall(_ context.Context, payload ChannelCallRecordCreatePayload) error {
_ = s
_ = payload
return nil
}
// LocalSystemSettingService 定义了当前模块中的 LocalSystemSettingService 类型。
type LocalSystemSettingService struct {
// provider 表示当前声明中的 provider。
provider RuntimeConfigProvider
}
// NewLocalSystemSettingService 用于处理与 NewLocalSystemSettingService 相关的逻辑。
func NewLocalSystemSettingService(provider RuntimeConfigProvider) *LocalSystemSettingService {
return &LocalSystemSettingService{provider: provider}
}
// ResolveFrontendBaseURL 用于处理与 ResolveFrontendBaseURL 相关的逻辑。
func (s *LocalSystemSettingService) ResolveFrontendBaseURL(context.Context) (string, error) {
return "http://127.0.0.1", nil
}
// IsObservabilityLogEnabled 用于处理与 IsObservabilityLogEnabled 相关的逻辑。
func (s *LocalSystemSettingService) IsObservabilityLogEnabled(ctx context.Context) bool {
cfg, err := s.load(ctx)
if err != nil {
return true
}
return cfg.ObservabilityLogEnabled
}
// IsAgentRuntimeModelEnabled 用于处理与 IsAgentRuntimeModelEnabled 相关的逻辑。
func (s *LocalSystemSettingService) IsAgentRuntimeModelEnabled(context.Context) bool {
return true
}
// ResolveCursorServerBridge 用于处理与 ResolveCursorServerBridge 相关的逻辑。
func (s *LocalSystemSettingService) ResolveCursorServerBridge(context.Context) (string, bool) {
return "", false
}
// LoadRuntimeConfigSnapshot 用于处理与 LoadRuntimeConfigSnapshot 相关的逻辑。
func (s *LocalSystemSettingService) LoadRuntimeConfigSnapshot(ctx context.Context) (RuntimeConfigSnapshot, error) {
return s.load(ctx)
}
// ResolveModelAdapters 用于处理与 ResolveModelAdapters 相关的逻辑。
func (s *LocalSystemSettingService) ResolveModelAdapters(ctx context.Context) ([]ModelAdapterConfig, error) {
cfg, err := s.load(ctx)
if err != nil {
return nil, err
}
return NormalizeModelAdapterConfigs(cfg.ModelAdapters)
}
// load 用于处理与 load 相关的逻辑。
func (s *LocalSystemSettingService) load(ctx context.Context) (RuntimeConfigSnapshot, error) {
if s == nil || s.provider == nil {
return RuntimeConfigSnapshot{}, nil
}
return s.provider(ctx)
}