Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -690,27 +690,6 @@ func (cfg CacheConfig) Validate() error {
return nil
}

func valueOrDefault(value string, fallback string) string {
if value == "" {
return fallback
}
return value
}

func intOrDefault(value int, fallback int) int {
if value == 0 {
return fallback
}
return value
}

func boolOrDefault(value *bool, fallback bool) bool {
if value == nil {
return fallback
}
return *value
}

func (cfg Config) validateExtensions() error {
for _, spec := range cfg.extensionSpecs {
if spec.Validate == nil {
Expand Down
52 changes: 27 additions & 25 deletions internal/config/config_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"path/filepath"
"strings"

"moonbridge/internal/util"

"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -444,26 +446,26 @@ func FromFileConfigWithOptions(fileConfig FileConfig, opts LoadOptions) (Config,

cfg := Config{
Mode: mode,
Addr: valueOrDefault(strings.TrimSpace(fileConfig.Server.Addr), DefaultAddr),
Addr: util.OrDefault(strings.TrimSpace(fileConfig.Server.Addr), DefaultAddr),
AuthToken: strings.TrimSpace(fileConfig.Server.AuthToken),
MaxSessions: intOrDefault(fileConfig.Server.MaxSessions, 0),
SessionTTL: valueOrDefault(strings.TrimSpace(fileConfig.Server.SessionTTL), "24h"),
MaxSessions: util.OrDefault(fileConfig.Server.MaxSessions, 0),
SessionTTL: util.OrDefault(strings.TrimSpace(fileConfig.Server.SessionTTL), "24h"),
TraceRequests: traceEnabled,
LogLevel: valueOrDefault(strings.TrimSpace(fileConfig.Log.Level), "info"),
LogFormat: valueOrDefault(strings.TrimSpace(fileConfig.Log.Format), "text"),
LogLevel: util.OrDefault(strings.TrimSpace(fileConfig.Log.Level), "info"),
LogFormat: util.OrDefault(strings.TrimSpace(fileConfig.Log.Format), "text"),
SystemPrompt: defaults.SystemPrompt,
DefaultModel: defaults.Model,
Defaults: defaults,
Models: models,
Routes: routes,
ProviderDefs: providerDefs,
WebSearchSupport: webSearchSupport,
WebSearchMaxUses: intOrDefault(fileConfig.WebSearch.MaxUses, 8),
WebSearchMaxUses: util.OrDefault(fileConfig.WebSearch.MaxUses, 8),
TavilyAPIKey: strings.TrimSpace(fileConfig.WebSearch.TavilyAPIKey),
FirecrawlAPIKey: strings.TrimSpace(fileConfig.WebSearch.FirecrawlAPIKey),
SearchMaxRounds: intOrDefault(fileConfig.WebSearch.SearchMaxRounds, 5),
SearchMaxRounds: util.OrDefault(fileConfig.WebSearch.SearchMaxRounds, 5),
WebSearchExtra: cloneAnyMap(fileConfig.WebSearch.Extra),
DefaultMaxTokens: intOrDefault(defaults.MaxTokens, 1024),
DefaultMaxTokens: util.OrDefault(defaults.MaxTokens, 1024),
Cache: fromCacheFileConfig(fileConfig.Cache),
Persistence: FromPersistenceFileConfig(fileConfig.Persistence),
ResponseProxy: responseProxy,
Expand Down Expand Up @@ -514,11 +516,11 @@ func fromModelDefFileConfig(fileConfig map[string]ModelDefFileConfig, specs exte
Description: strings.TrimSpace(p.Description),
})
}
supportsReasoning := boolOrDefault(
supportsReasoning := util.Deref(
m.SupportsReasoning,
len(reasoningPresets) > 0 ||
strings.TrimSpace(m.DefaultReasoningLevel) != "" ||
boolOrDefault(m.SupportsReasoningSummaries, false) ||
util.Deref(m.SupportsReasoningSummaries, false) ||
strings.TrimSpace(m.DefaultReasoningSummary) != "",
)
models[trimmedSlug] = ModelDef{
Expand All @@ -530,10 +532,10 @@ func fromModelDefFileConfig(fileConfig map[string]ModelDefFileConfig, specs exte
SupportsReasoning: supportsReasoning,
DefaultReasoningLevel: strings.TrimSpace(m.DefaultReasoningLevel),
SupportedReasoningLevels: reasoningPresets,
SupportsReasoningSummaries: boolOrDefault(m.SupportsReasoningSummaries, false),
SupportsReasoningSummaries: util.Deref(m.SupportsReasoningSummaries, false),
DefaultReasoningSummary: strings.TrimSpace(m.DefaultReasoningSummary),
InputModalities: m.InputModalities,
SupportsImageDetailOriginal: boolOrDefault(m.SupportsImageDetailOriginal, false),
SupportsImageDetailOriginal: util.Deref(m.SupportsImageDetailOriginal, false),
WebSearch: ws,
Extensions: modelExtensions,
}
Expand Down Expand Up @@ -635,7 +637,7 @@ func fromProviderDefFileConfig(fileConfig map[string]ProviderDefFileConfig, spec
pd := ProviderDef{
BaseURL: strings.TrimRight(strings.TrimSpace(def.BaseURL), "/"),
APIKey: strings.TrimSpace(def.APIKey),
Version: valueOrDefault(strings.TrimSpace(def.Version), "2023-06-01"),
Version: util.OrDefault(strings.TrimSpace(def.Version), "2023-06-01"),
UserAgent: strings.TrimSpace(def.UserAgent),
Protocol: strings.TrimSpace(def.Protocol),
WebSearchSupport: wsSupport,
Expand Down Expand Up @@ -952,7 +954,7 @@ func FromAnthropicProxyFileConfig(fileConfig ProxyTargetFileConfig) AnthropicPro
Model: strings.TrimSpace(fileConfig.Model),
ProviderBaseURL: strings.TrimRight(strings.TrimSpace(fileConfig.BaseURL), "/"),
ProviderAPIKey: strings.TrimSpace(fileConfig.APIKey),
ProviderVersion: valueOrDefault(strings.TrimSpace(fileConfig.Version), "2023-06-01"),
ProviderVersion: util.OrDefault(strings.TrimSpace(fileConfig.Version), "2023-06-01"),
}
}

Expand All @@ -964,16 +966,16 @@ func FromPersistenceFileConfig(fileConfig PersistenceFileConfig) PersistenceConf

func fromCacheFileConfig(fileConfig CacheFileConfig) CacheConfig {
return CacheConfig{
Mode: valueOrDefault(strings.TrimSpace(fileConfig.Mode), "automatic"),
TTL: valueOrDefault(strings.TrimSpace(fileConfig.TTL), "5m"),
PromptCaching: boolOrDefault(fileConfig.PromptCaching, true),
AutomaticPromptCache: boolOrDefault(fileConfig.AutomaticPromptCache, true),
ExplicitCacheBreakpoints: boolOrDefault(fileConfig.ExplicitCacheBreakpoints, true),
AllowRetentionDowngrade: boolOrDefault(fileConfig.AllowRetentionDowngrade, false),
MaxBreakpoints: intOrDefault(fileConfig.MaxBreakpoints, 4),
MinCacheTokens: intOrDefault(fileConfig.MinCacheTokens, 1024),
ExpectedReuse: intOrDefault(fileConfig.ExpectedReuse, 2),
MinimumValueScore: intOrDefault(fileConfig.MinimumValueScore, 2048),
MinBreakpointTokens: intOrDefault(fileConfig.MinBreakpointTokens, 1024),
Mode: util.OrDefault(strings.TrimSpace(fileConfig.Mode), "automatic"),
TTL: util.OrDefault(strings.TrimSpace(fileConfig.TTL), "5m"),
PromptCaching: util.Deref(fileConfig.PromptCaching, true),
AutomaticPromptCache: util.Deref(fileConfig.AutomaticPromptCache, true),
ExplicitCacheBreakpoints: util.Deref(fileConfig.ExplicitCacheBreakpoints, true),
AllowRetentionDowngrade: util.Deref(fileConfig.AllowRetentionDowngrade, false),
MaxBreakpoints: util.OrDefault(fileConfig.MaxBreakpoints, 4),
MinCacheTokens: util.OrDefault(fileConfig.MinCacheTokens, 1024),
ExpectedReuse: util.OrDefault(fileConfig.ExpectedReuse, 2),
MinimumValueScore: util.OrDefault(fileConfig.MinimumValueScore, 2048),
MinBreakpointTokens: util.OrDefault(fileConfig.MinBreakpointTokens, 1024),
}
}
20 changes: 9 additions & 11 deletions internal/config/convert.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package config

import (
"moonbridge/internal/util"

"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -122,12 +124,12 @@ func toModelDefFileConfig(def ModelDef) ModelDefFileConfig {
WebSearch: toWebSearchFileConfig(def.WebSearch),
}

m.SupportsReasoning = boolPtr(def.SupportsReasoning)
m.SupportsReasoning = util.Ptr(def.SupportsReasoning)
if def.SupportsReasoningSummaries {
m.SupportsReasoningSummaries = boolPtr(true)
m.SupportsReasoningSummaries = util.Ptr(true)
}
if def.SupportsImageDetailOriginal {
m.SupportsImageDetailOriginal = boolPtr(true)
m.SupportsImageDetailOriginal = util.Ptr(true)
}

if len(def.Extensions) > 0 {
Expand Down Expand Up @@ -251,18 +253,14 @@ func toCacheFileConfig(c CacheConfig) CacheFileConfig {
return CacheFileConfig{
Mode: c.Mode,
TTL: c.TTL,
PromptCaching: boolPtr(c.PromptCaching),
AutomaticPromptCache: boolPtr(c.AutomaticPromptCache),
ExplicitCacheBreakpoints: boolPtr(c.ExplicitCacheBreakpoints),
AllowRetentionDowngrade: boolPtr(c.AllowRetentionDowngrade),
PromptCaching: util.Ptr(c.PromptCaching),
AutomaticPromptCache: util.Ptr(c.AutomaticPromptCache),
ExplicitCacheBreakpoints: util.Ptr(c.ExplicitCacheBreakpoints),
AllowRetentionDowngrade: util.Ptr(c.AllowRetentionDowngrade),
MaxBreakpoints: c.MaxBreakpoints,
MinCacheTokens: c.MinCacheTokens,
ExpectedReuse: c.ExpectedReuse,
MinimumValueScore: c.MinimumValueScore,
MinBreakpointTokens: c.MinBreakpointTokens,
}
}

func boolPtr(v bool) *bool {
return &v
}
10 changes: 2 additions & 8 deletions internal/extension/codex/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"moonbridge/internal/config"
"moonbridge/internal/extension/visual"
"moonbridge/internal/modelref"
"moonbridge/internal/util"
)

// ModelInfo represents a model entry in the OpenAI /v1/models response.
Expand Down Expand Up @@ -469,13 +470,6 @@ func WriteModelsCatalog(path string, providerCfg config.ProviderConfig, pluginCf
return os.WriteFile(path, data, 0644)
}

func valueOrDefault(value string, fallback string) string {
if value == "" {
return fallback
}
return value
}

// routeFor resolves a model alias to a RouteEntry from a ProviderConfig.
func routeFor(providerCfg config.ProviderConfig, modelAlias string) config.RouteEntry {
if provider, upstream := modelref.Parse(modelAlias); provider != "" {
Expand Down Expand Up @@ -547,7 +541,7 @@ func GenerateConfigToml(output io.Writer, modelAlias string, baseURL string, cod
fmt.Fprintln(output)
fmt.Fprintln(output, "[model_providers.moonbridge]")
fmt.Fprintln(output, `name = "Moon Bridge"`)
fmt.Fprintf(output, "base_url = %q\n", valueOrDefault(baseURL, "http://"+config.DefaultAddr+"/v1"))
fmt.Fprintf(output, "base_url = %q\n", util.OrDefault(baseURL, "http://"+config.DefaultAddr+"/v1"))
if serverCfg.AuthToken != "" {
fmt.Fprintln(output, `requires_openai_auth = true`)
}
Expand Down
12 changes: 2 additions & 10 deletions internal/extension/deepseek_v4/deepseek_v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"moonbridge/internal/format"
"moonbridge/internal/protocol/anthropic"
"moonbridge/internal/protocol/openai"
"moonbridge/internal/util"
)

// StripReasoningContent removes the reasoning_content field from message
Expand Down Expand Up @@ -112,7 +113,7 @@ func StreamDeltaForReasoning(delta anthropic.StreamDelta) string {
return delta.Text
}
if delta.Type == "thinking_delta" {
return firstNonEmpty(delta.Thinking, delta.Text)
return util.FirstNonEmpty(delta.Thinking, delta.Text)
}
return ""
}
Expand All @@ -126,15 +127,6 @@ func IsReasoningContentBlock(block *format.CoreContentBlock) bool {
return block.Type == "reasoning" || block.Type == "reasoning_content"
}

func firstNonEmpty(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return ""
}

// ToAnthropicRequest mutates an Anthropic request for DeepSeek V4 quirks.
// DeepSeek-compatible providers may reject or mis-handle sampling knobs here.
// Codex/OpenAI reasoning effort maps to DeepSeek's Anthropic-compatible
Expand Down
7 changes: 4 additions & 3 deletions internal/extension/deepseek_v4/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"moonbridge/internal/format"
"moonbridge/internal/protocol/anthropic"
"moonbridge/internal/util"
)

const persistedThinkingSummaryPrefix = "moonbridge:deepseek_v4_thinking:v1:"
Expand Down Expand Up @@ -173,7 +174,7 @@ func (stream *StreamState) Start(index int, block *format.CoreContentBlock) bool
if stream == nil || block == nil || !IsReasoningContentBlock(block) {
return false
}
stream.thinkingText[index] = firstNonEmpty(block.ReasoningText, block.Text)
stream.thinkingText[index] = util.FirstNonEmpty(block.ReasoningText, block.Text)
stream.thinkingSignature[index] = block.ReasoningSignature
return true
}
Expand All @@ -184,10 +185,10 @@ func (stream *StreamState) Delta(index int, delta anthropic.StreamDelta) bool {
}
switch delta.Type {
case "thinking_delta", "reasoning_content_delta":
stream.thinkingText[index] += firstNonEmpty(delta.Thinking, delta.Text)
stream.thinkingText[index] += util.FirstNonEmpty(delta.Thinking, delta.Text)
return true
case "signature_delta":
stream.thinkingSignature[index] += firstNonEmpty(delta.Signature, delta.Text)
stream.thinkingSignature[index] += util.FirstNonEmpty(delta.Signature, delta.Text)
return true
default:
return false
Expand Down
15 changes: 3 additions & 12 deletions internal/protocol/openai/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"moonbridge/internal/extension/codextool"
"moonbridge/internal/format"
"moonbridge/internal/util"
)

// ============================================================================
Expand Down Expand Up @@ -1262,7 +1263,7 @@ func convertInput(raw json.RawMessage, model string) ([]format.CoreMessage, []fo
}
pendingFCBlocks = append(pendingFCBlocks, format.CoreContentBlock{
Type: "tool_use",
ToolUseID: firstNonEmpty(item.CallID, item.ID),
ToolUseID: util.FirstNonEmpty(item.CallID, item.ID),
ToolName: item.Name,
ToolNamespace: item.Namespace,
ToolInput: toolInput,
Expand All @@ -1287,7 +1288,7 @@ func convertInput(raw json.RawMessage, model string) ([]format.CoreMessage, []fo
}
pendingFCBlocks = append(pendingFCBlocks, format.CoreContentBlock{
Type: "tool_use",
ToolUseID: firstNonEmpty(item.CallID, item.ID),
ToolUseID: util.FirstNonEmpty(item.CallID, item.ID),
ToolName: item.Name,
ToolNamespace: item.Namespace,
ToolInput: toolInput,
Expand Down Expand Up @@ -1488,16 +1489,6 @@ func convertToolChoice(raw json.RawMessage) (*format.CoreToolChoice, error) {
// Utility
// ============================================================================

// firstNonEmpty returns the first non-empty string from the list.
func firstNonEmpty(vals ...string) string {
for _, v := range vals {
if v != "" {
return v
}
}
return ""
}

// copyContentParts returns a shallow copy of a ContentPart slice.
func copyContentParts(parts []ContentPart) []ContentPart {
out := make([]ContentPart, len(parts))
Expand Down
7 changes: 0 additions & 7 deletions internal/service/provider/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,6 @@ func newHTTPClient(cfg HTTPConfig) *http.Client {
}
}

func valueOrDefault(value, fallback string) string {
if value == "" {
return fallback
}
return value
}

// ClientForKey returns the anthropic.Client for a given provider key.
func (pm *ProviderManager) ClientForKey(key string) (ProviderClient, error) {
pm.mu.RLock()
Expand Down
12 changes: 2 additions & 10 deletions internal/service/server/adapter_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"moonbridge/internal/service/stats"
mbtrace "moonbridge/internal/service/trace"
"moonbridge/internal/session"
"moonbridge/internal/util"
)

// ============================================================================
Expand Down Expand Up @@ -827,7 +828,7 @@ func streamOutputItemToCoreBlocks(item openai.OutputItem) []format.CoreContentBl
case "reasoning":
return reasoningBlocksFromStreamOutput(item.Summary)
case "function_call", "custom_tool_call", "local_shell_call":
toolUseID := firstNonEmptyString(item.CallID, item.ID)
toolUseID := util.FirstNonEmpty(item.CallID, item.ID)
if toolUseID == "" {
return nil
}
Expand Down Expand Up @@ -890,15 +891,6 @@ func streamOutputToolInput(item openai.OutputItem) json.RawMessage {
return payload
}

func firstNonEmptyString(vals ...string) string {
for _, v := range vals {
if v != "" {
return v
}
}
return ""
}

// handleAdapterStream handles the streaming path through adapter dispatch.
func (s *Server) handleAdapterStream(
w http.ResponseWriter,
Expand Down
Loading