diff --git a/cc-haha/src/server/services/providerService.ts b/cc-haha/src/server/services/providerService.ts index 431efb8..e72208a 100644 --- a/cc-haha/src/server/services/providerService.ts +++ b/cc-haha/src/server/services/providerService.ts @@ -418,7 +418,10 @@ export class ProviderService { if (input.apiKey) { headers['Authorization'] = `Bearer ${input.apiKey}` headers['x-api-key'] = input.apiKey - headers['anthropic-version'] = '2023-06-01' + // Note: anthropic-version is intentionally NOT set here. + // Model discovery always uses the OpenAI-compatible format (GET /v1/models → {data:[...]}). + // Sending anthropic-version causes relay servers (NewAPI / heicode Manager) to branch + // into the Anthropic-specific response handler, which panics on an empty model list. } const candidates = [`${base}/v1/models`, `${base}/models`] diff --git a/heicode/controller/heicode_oauth.go b/heicode/controller/heicode_oauth.go index bc01ef7..83a53e7 100644 --- a/heicode/controller/heicode_oauth.go +++ b/heicode/controller/heicode_oauth.go @@ -152,7 +152,7 @@ func renderHeicodeLoginRequired(c *gin.Context, state, redirectURI, providerID s url.QueryEscape(redirectURI), url.QueryEscape(providerID), ) - loginURL := "/sign-in" + loginURL := "/sign-in?redirect=" + url.QueryEscape(authorizeURL) body := fmt.Sprintf(` diff --git a/heicode/controller/model.go b/heicode/controller/model.go index 0145efd..1cd70e6 100644 --- a/heicode/controller/model.go +++ b/heicode/controller/model.go @@ -211,11 +211,16 @@ func ListModels(c *gin.Context, modelType int) { Type: "model", } } + var firstID, lastID interface{} + if len(useranthropicModels) > 0 { + firstID = useranthropicModels[0].ID + lastID = useranthropicModels[len(useranthropicModels)-1].ID + } c.JSON(200, gin.H{ "data": useranthropicModels, - "first_id": useranthropicModels[0].ID, + "first_id": firstID, "has_more": false, - "last_id": useranthropicModels[len(useranthropicModels)-1].ID, + "last_id": lastID, }) case constant.ChannelTypeGemini: userGeminiModels := make([]dto.GeminiModel, len(userOpenAiModels)) diff --git a/heicode/deploy/nginx/heicode-gateway.conf b/heicode/deploy/nginx/heicode-gateway.conf new file mode 100644 index 0000000..780c014 --- /dev/null +++ b/heicode/deploy/nginx/heicode-gateway.conf @@ -0,0 +1,166 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# NewAPI is an independent service. Its process listens on container port 3000. +# The gateway joins the NewAPI Docker network and reaches the service by +# container DNS, so NewAPI does not need a public host port for relay traffic. +upstream newapi_relay { + server new-api:3000; +} + +upstream manager_app { + server heicode:3000; +} + +server { + listen 80; + server_name newapi.xinghanlab.com; + + client_max_body_size 64m; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Keep the same relay-compatible behavior while exposing Manager UI on :80. + location = /v1/models { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/models/ { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/chat/completions { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/messages { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/completions { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/responses { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/embeddings { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/rerank { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/images/ { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/audio/ { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/moderations { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/realtime { + proxy_pass http://newapi_relay; + } + + location ^~ /v1beta/ { + proxy_pass http://newapi_relay; + } + + location / { + proxy_pass http://manager_app; + } +} + +server { + listen 3000; + server_name code.xinghanlab.com newapi.xinghanlab.com _; + + client_max_body_size 64m; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Compatibility for older HeiCode desktop builds that probe /models + # instead of the OpenAI-compatible /v1/models endpoint. + location = /models { + proxy_pass http://newapi_relay/v1/models; + } + + # Only relay-compatible API paths are sent to NewAPI. NewAPI admin/backend + # pages are intentionally not proxied through the public Manager gateway. + location = /v1/models { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/models/ { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/chat/completions { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/messages { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/completions { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/responses { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/embeddings { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/rerank { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/images/ { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/audio/ { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/moderations { + proxy_pass http://newapi_relay; + } + + location ^~ /v1/realtime { + proxy_pass http://newapi_relay; + } + + location ^~ /v1beta/ { + proxy_pass http://newapi_relay; + } + + location / { + proxy_pass http://manager_app; + } +}