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
12 changes: 10 additions & 2 deletions internal/config/extension_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"fmt"
"log/slog"

"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -239,7 +240,14 @@ func decodeTypedExtensionConfig(spec ExtensionConfigSpec, raw map[string]any) an
if typed == nil {
return cloneAnyMap(raw)
}
data, _ := json.Marshal(raw)
_ = json.Unmarshal(data, typed)
data, err := json.Marshal(raw)
if err != nil {
slog.Warn("扩展配置序列化失败,回退到原始配置", "extension", spec.Name, "error", err)
return cloneAnyMap(raw)
}
if err := json.Unmarshal(data, typed); err != nil {
slog.Warn("扩展配置解码失败,回退到原始配置", "extension", spec.Name, "error", err)
return cloneAnyMap(raw)
}
return typed
}
37 changes: 37 additions & 0 deletions internal/extension/codex/auth_json_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package codex

import (
"encoding/json"
"os"
"path/filepath"
"testing"
)

func TestWriteAuthJSONWritesValidContent(t *testing.T) {
path := filepath.Join(t.TempDir(), "nested", "auth.json")
if err := writeAuthJSON(path, "sk-test-token"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read auth.json: %v", err)
}
var parsed map[string]string
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("auth.json is not valid JSON: %v", err)
}
if parsed["openai_api_key"] != "sk-test-token" {
t.Fatalf("unexpected token: %q", parsed["openai_api_key"])
}
}

func TestWriteAuthJSONReturnsErrorOnUnwritablePath(t *testing.T) {
// A path whose parent is an existing regular file cannot be created.
file := filepath.Join(t.TempDir(), "not-a-dir")
if err := os.WriteFile(file, []byte("x"), 0600); err != nil {
t.Fatalf("setup: %v", err)
}
if err := writeAuthJSON(filepath.Join(file, "auth.json"), "tok"); err == nil {
t.Fatal("expected error when parent path is a file, got nil")
}
}
13 changes: 10 additions & 3 deletions internal/extension/codex/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ func GenerateConfigToml(output io.Writer, modelAlias string, baseURL string, cod

// writeAuthJSON writes the API key into Codex's auth.json so that model_providers
// using requires_openai_auth can find the bearer token.
func writeAuthJSON(path, token string) error {
func writeAuthJSON(path, token string) (err error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
Expand All @@ -571,6 +571,13 @@ func writeAuthJSON(path, token string) error {
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(map[string]string{"openai_api_key": token})
defer func() {
if closeErr := f.Close(); closeErr != nil && err == nil {
err = fmt.Errorf("close %s: %w", path, closeErr)
}
}()
if err := json.NewEncoder(f).Encode(map[string]string{"openai_api_key": token}); err != nil {
return fmt.Errorf("encode auth.json: %w", err)
}
return nil
}
7 changes: 5 additions & 2 deletions internal/extension/metrics/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,11 @@ func (p *Plugin) handleQuery(w http.ResponseWriter, r *http.Request) {
var cfg *Config
if setting, ok := p.pluginCfg.Extensions[PluginName]; ok && len(setting.RawConfig) > 0 {
data, err := json.Marshal(setting.RawConfig)
if err == nil {
_ = json.Unmarshal(data, &cfg)
if err != nil {
slog.Warn("metrics 配置序列化失败,使用默认值", "error", err)
} else if err := json.Unmarshal(data, &cfg); err != nil {
slog.Warn("metrics 配置解码失败,使用默认值", "error", err)
cfg = nil
}
}
defaultLimit := 100
Expand Down
8 changes: 6 additions & 2 deletions internal/extension/visual/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package visual
import (
"encoding/json"
"fmt"
"log/slog"
"strings"

"moonbridge/internal/config"
Expand Down Expand Up @@ -98,8 +99,11 @@ func ConfigForModel(pluginCfg config.PluginConfig, modelAlias string) (Config, b
var cfg *Config
if setting, ok := pluginCfg.Extensions[PluginName]; ok && len(setting.RawConfig) > 0 {
data, err := json.Marshal(setting.RawConfig)
if err == nil {
_ = json.Unmarshal(data, &cfg)
if err != nil {
slog.Warn("visual 配置序列化失败,使用默认值", "error", err)
} else if err := json.Unmarshal(data, &cfg); err != nil {
slog.Warn("visual 配置解码失败,使用默认值", "error", err)
cfg = nil
}
}
if cfg == nil {
Expand Down
4 changes: 3 additions & 1 deletion internal/service/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,9 @@ func runHTTPServer(ctx context.Context, addr string, handler http.Handler, error
httpServer := &http.Server{Addr: addr, Handler: handler}
defer func() {
if closer, ok := handler.(io.Closer); ok {
_ = closer.Close()
if err := closer.Close(); err != nil {
slog.Error("关闭 HTTP handler 失败", "error", err)
}
}
}()
errCh := make(chan error, 1)
Expand Down
10 changes: 8 additions & 2 deletions internal/service/server/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ func traceError(stage string, err error) map[string]string {
func writeJSON(writer http.ResponseWriter, status int, payload any) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(status)
_ = json.NewEncoder(writer).Encode(payload)
if err := json.NewEncoder(writer).Encode(payload); err != nil {
slog.Warn("写入 JSON 响应失败", "status", status, "error", err)
}
}
func writeOpenAIError(writer http.ResponseWriter, status int, payload openai.ErrorResponse) {
writeJSON(writer, status, payload)
Expand All @@ -259,7 +261,11 @@ func writeSSE(writer http.ResponseWriter, event openai.StreamEvent) error {
if event.Data == nil {
payload = []byte("{}")
} else {
payload, _ = json.Marshal(event.Data)
marshaled, err := json.Marshal(event.Data)
if err != nil {
return fmt.Errorf("marshal SSE event %q: %w", event.Event, err)
}
payload = marshaled
}
if _, err := writer.Write([]byte("event: " + event.Event + "\n")); err != nil {
return err
Expand Down
42 changes: 42 additions & 0 deletions internal/service/server/dispatch_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package server

import (
"net/http/httptest"
"strings"
"testing"

"moonbridge/internal/protocol/openai"
)

// writeSSE must propagate marshalling failures instead of silently emitting a
// truncated/empty data frame.
func TestWriteSSEPropagatesMarshalError(t *testing.T) {
rec := httptest.NewRecorder()
// channels are not JSON-serializable, forcing json.Marshal to fail.
event := openai.StreamEvent{Event: "response.output_text.delta", Data: make(chan int)}

err := writeSSE(rec, event)
if err == nil {
t.Fatal("expected error when event data cannot be marshalled, got nil")
}
if !strings.Contains(err.Error(), "marshal SSE event") {
t.Fatalf("expected marshal error, got %q", err.Error())
}
}

// writeSSE serializes a well-formed event into the expected SSE frame.
func TestWriteSSEWritesFrame(t *testing.T) {
rec := httptest.NewRecorder()
event := openai.StreamEvent{Event: "ping", Data: map[string]string{"k": "v"}}

if err := writeSSE(rec, event); err != nil {
t.Fatalf("unexpected error: %v", err)
}
body := rec.Body.String()
if !strings.Contains(body, "event: ping\n") {
t.Fatalf("missing event line in %q", body)
}
if !strings.Contains(body, `data: {"k":"v"}`) {
t.Fatalf("missing data line in %q", body)
}
}