Skip to content
Merged
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
24 changes: 24 additions & 0 deletions constant/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,27 @@ func GetChannelTypeName(channelType int) string {
}
return "Unknown"
}

type ChannelSpecialBase struct {
ClaudeBaseURL string
OpenAIBaseURL string
}
Comment on lines +184 to +187

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add documentation for the public struct.

This public struct should have a godoc comment explaining its purpose, such as how it's used to define alternative base URLs for special channel plans.

+// ChannelSpecialBase defines alternative base URLs for special channel plans
+// that support both Claude and OpenAI formats with different endpoints.
 type ChannelSpecialBase struct {
 	ClaudeBaseURL string
 	OpenAIBaseURL string
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type ChannelSpecialBase struct {
ClaudeBaseURL string
OpenAIBaseURL string
}
// ChannelSpecialBase defines alternative base URLs for special channel plans
// that support both Claude and OpenAI formats with different endpoints.
type ChannelSpecialBase struct {
ClaudeBaseURL string
OpenAIBaseURL string
}
🤖 Prompt for AI Agents
In constant/channel.go around lines 184 to 187, the public struct
ChannelSpecialBase lacks a godoc comment; add a one-line godoc comment
immediately above the type declaration that explains its purpose (e.g., that it
defines alternative base URLs for special channel plans and is used to override
default Claude/OpenAI endpoints), keeping the comment concise and starting with
"ChannelSpecialBase" to satisfy Go documentation conventions.


var ChannelSpecialBases = map[string]ChannelSpecialBase{
"glm-coding-plan": {
ClaudeBaseURL: "https://open.bigmodel.cn/api/anthropic",
OpenAIBaseURL: "https://open.bigmodel.cn/api/coding/paas/v4",
},
"glm-coding-plan-international": {
ClaudeBaseURL: "https://api.z.ai/api/anthropic",
OpenAIBaseURL: "https://api.z.ai/api/coding/paas/v4",
},
"kimi-coding-plan": {
ClaudeBaseURL: "https://api.kimi.com/coding",
OpenAIBaseURL: "https://api.kimi.com/coding/v1",
},
"doubao-coding-plan": {
ClaudeBaseURL: "https://ark.cn-beijing.volces.com/api/coding",
OpenAIBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
},
}
Comment on lines +189 to +206

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add documentation for the public map.

This public map should have a godoc comment explaining that it maps special plan identifiers to their corresponding base URLs for different API formats.

+// ChannelSpecialBases maps special plan identifiers (e.g., "glm-coding-plan") to their
+// corresponding Claude and OpenAI base URLs. When a channel's base URL matches a key in
+// this map, the special URLs are used instead of the default channel base URL.
 var ChannelSpecialBases = map[string]ChannelSpecialBase{
 	"glm-coding-plan": {
 		ClaudeBaseURL: "https://open.bigmodel.cn/api/anthropic",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var ChannelSpecialBases = map[string]ChannelSpecialBase{
"glm-coding-plan": {
ClaudeBaseURL: "https://open.bigmodel.cn/api/anthropic",
OpenAIBaseURL: "https://open.bigmodel.cn/api/coding/paas/v4",
},
"glm-coding-plan-international": {
ClaudeBaseURL: "https://api.z.ai/api/anthropic",
OpenAIBaseURL: "https://api.z.ai/api/coding/paas/v4",
},
"kimi-coding-plan": {
ClaudeBaseURL: "https://api.kimi.com/coding",
OpenAIBaseURL: "https://api.kimi.com/coding/v1",
},
"doubao-coding-plan": {
ClaudeBaseURL: "https://ark.cn-beijing.volces.com/api/coding",
OpenAIBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
},
}
// ChannelSpecialBases maps special plan identifiers (e.g., "glm-coding-plan") to their
// corresponding Claude and OpenAI base URLs. When a channel's base URL matches a key in
// this map, the special URLs are used instead of the default channel base URL.
var ChannelSpecialBases = map[string]ChannelSpecialBase{
"glm-coding-plan": {
ClaudeBaseURL: "https://open.bigmodel.cn/api/anthropic",
OpenAIBaseURL: "https://open.bigmodel.cn/api/coding/paas/v4",
},
"glm-coding-plan-international": {
ClaudeBaseURL: "https://api.z.ai/api/anthropic",
OpenAIBaseURL: "https://api.z.ai/api/coding/paas/v4",
},
"kimi-coding-plan": {
ClaudeBaseURL: "https://api.kimi.com/coding",
OpenAIBaseURL: "https://api.kimi.com/coding/v1",
},
"doubao-coding-plan": {
ClaudeBaseURL: "https://ark.cn-beijing.volces.com/api/coding",
OpenAIBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
},
}
🤖 Prompt for AI Agents
In constant/channel.go around lines 189 to 206, add a Go doc comment for the
exported ChannelSpecialBases map that briefly explains its purpose: that it maps
special plan identifiers (string keys) to ChannelSpecialBase entries containing
the ClaudeBaseURL and OpenAIBaseURL for those plans, and that these base URLs
are used to override default API endpoints for specific plan identifiers; place
the comment immediately above the ChannelSpecialBases declaration, keep it
concise (one to two sentences), and mention the structure of values
(ChannelSpecialBase with ClaudeBaseURL and OpenAIBaseURL) so godoc consumers
understand the mapping.

17 changes: 13 additions & 4 deletions controller/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/relay/channel/volcengine"
"github.com/QuantumNous/new-api/service"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -192,10 +191,20 @@ func FetchUpstreamModels(c *gin.Context) {
case constant.ChannelTypeAli:
url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL)
case constant.ChannelTypeZhipu_v4:
url = fmt.Sprintf("%s/api/paas/v4/models", baseURL)
if plan, ok := constant.ChannelSpecialBases[baseURL]; ok && plan.OpenAIBaseURL != "" {
url = fmt.Sprintf("%s/models", plan.OpenAIBaseURL)
} else {
url = fmt.Sprintf("%s/api/paas/v4/models", baseURL)
}
case constant.ChannelTypeVolcEngine:
if baseURL == volcengine.DoubaoCodingPlan {
url = fmt.Sprintf("%s/v1/models", volcengine.DoubaoCodingPlanOpenAIBaseURL)
if plan, ok := constant.ChannelSpecialBases[baseURL]; ok && plan.OpenAIBaseURL != "" {
url = fmt.Sprintf("%s/v1/models", plan.OpenAIBaseURL)
} else {
url = fmt.Sprintf("%s/v1/models", baseURL)
}
case constant.ChannelTypeMoonshot:
if plan, ok := constant.ChannelSpecialBases[baseURL]; ok && plan.OpenAIBaseURL != "" {
url = fmt.Sprintf("%s/models", plan.OpenAIBaseURL)
} else {
url = fmt.Sprintf("%s/v1/models", baseURL)
}
Expand Down
11 changes: 11 additions & 0 deletions relay/channel/moonshot/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http"

channelconstant "github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/relay/channel"
"github.com/QuantumNous/new-api/relay/channel/claude"
Expand Down Expand Up @@ -44,6 +45,16 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}

func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
baseURL := info.ChannelBaseUrl
if specialPlan, ok := channelconstant.ChannelSpecialBases[baseURL]; ok {
if info.RelayFormat == types.RelayFormatClaude {
return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
}
if info.RelayFormat == types.RelayFormatOpenAI {
return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
}
}
Comment on lines +48 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Handle unsupported relay formats when special base is matched.

When a special base is found but RelayFormat is neither Claude nor OpenAI, the code falls through to the switch statement at line 58. At that point, info.ChannelBaseUrl contains a plan identifier (e.g., "glm-coding-plan") rather than a valid URL, which would result in malformed URLs like "glm-coding-plan/v1/chat/completions".

Apply this diff to handle the case properly:

 	baseURL := info.ChannelBaseUrl
 	if specialPlan, ok := channelconstant.ChannelSpecialBases[baseURL]; ok {
 		if info.RelayFormat == types.RelayFormatClaude {
 			return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
 		}
 		if info.RelayFormat == types.RelayFormatOpenAI {
 			return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
 		}
+		// Special base found but format not supported
+		return "", fmt.Errorf("relay format %s not supported for special base %s", info.RelayFormat, baseURL)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
baseURL := info.ChannelBaseUrl
if specialPlan, ok := channelconstant.ChannelSpecialBases[baseURL]; ok {
if info.RelayFormat == types.RelayFormatClaude {
return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
}
if info.RelayFormat == types.RelayFormatOpenAI {
return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
}
}
baseURL := info.ChannelBaseUrl
if specialPlan, ok := channelconstant.ChannelSpecialBases[baseURL]; ok {
if info.RelayFormat == types.RelayFormatClaude {
return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
}
if info.RelayFormat == types.RelayFormatOpenAI {
return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
}
// Special base found but format not supported
return "", fmt.Errorf("relay format %s not supported for special base %s", info.RelayFormat, baseURL)
}
🤖 Prompt for AI Agents
In relay/channel/moonshot/adaptor.go around lines 48 to 56, when
channelconstant.ChannelSpecialBases matches but info.RelayFormat is neither
RelayFormatClaude nor RelayFormatOpenAI the function currently falls through
leaving baseURL as a plan id and producing malformed URLs; detect this
unsupported format immediately and return a clear error instead of falling
through (e.g., return nil URL with fmt.Errorf("unsupported relay format %q for
special base %q", info.RelayFormat, baseURL)) so the caller can handle the
invalid combination.


switch info.RelayFormat {
case types.RelayFormatClaude:
return fmt.Sprintf("%s/anthropic/v1/messages", info.ChannelBaseUrl), nil
Expand Down
28 changes: 14 additions & 14 deletions relay/channel/volcengine/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,8 @@ import (
)

const (
contextKeyTTSRequest = "volcengine_tts_request"
contextKeyResponseFormat = "response_format"
DoubaoCodingPlan = "doubao-coding-plan"
DoubaoCodingPlanClaudeBaseURL = "https://ark.cn-beijing.volces.com/api/coding"
DoubaoCodingPlanOpenAIBaseURL = "https://ark.cn-beijing.volces.com/api/coding/v3"
contextKeyTTSRequest = "volcengine_tts_request"
contextKeyResponseFormat = "response_format"
)

type Adaptor struct {
Expand All @@ -40,7 +37,7 @@ func (a *Adaptor) ConvertGeminiRequest(*gin.Context, *relaycommon.RelayInfo, *dt
}

func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, req *dto.ClaudeRequest) (any, error) {
if info.ChannelBaseUrl == DoubaoCodingPlan {
if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok {
adaptor := claude.Adaptor{}
return adaptor.ConvertClaudeRequest(c, info, req)
}
Expand Down Expand Up @@ -243,11 +240,12 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
if baseUrl == "" {
baseUrl = channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeVolcEngine]
}
specialPlan, hasSpecialPlan := channelconstant.ChannelSpecialBases[baseUrl]

switch info.RelayFormat {
case types.RelayFormatClaude:
if baseUrl == DoubaoCodingPlan {
return fmt.Sprintf("%s/v1/messages", DoubaoCodingPlanClaudeBaseURL), nil
if hasSpecialPlan && specialPlan.ClaudeBaseURL != "" {
return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
}
if strings.HasPrefix(info.UpstreamModelName, "bot") {
return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil
Expand All @@ -256,8 +254,8 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
default:
switch info.RelayMode {
case constant.RelayModeChatCompletions:
if baseUrl == DoubaoCodingPlan {
return fmt.Sprintf("%s/chat/completions", DoubaoCodingPlanOpenAIBaseURL), nil
if hasSpecialPlan && specialPlan.OpenAIBaseURL != "" {
return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
}
if strings.HasPrefix(info.UpstreamModelName, "bot") {
return fmt.Sprintf("%s/api/v3/bots/chat/completions", baseUrl), nil
Expand Down Expand Up @@ -345,11 +343,13 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request
}

func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) {
if info.RelayFormat == types.RelayFormatClaude && info.ChannelBaseUrl == DoubaoCodingPlan {
if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage)
if info.RelayFormat == types.RelayFormatClaude {
if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok {
if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage)
}
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage)
}
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage)
}

if info.RelayMode == constant.RelayModeAudioSpeech {
Expand Down
22 changes: 19 additions & 3 deletions relay/channel/zhipu_4v/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http"

channelconstant "github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/relay/channel"
"github.com/QuantumNous/new-api/relay/channel/claude"
Expand Down Expand Up @@ -43,15 +44,30 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}

func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
baseURL := info.ChannelBaseUrl
if baseURL == "" {
baseURL = channelconstant.ChannelBaseURLs[channelconstant.ChannelTypeZhipu_v4]
}
specialPlan, hasSpecialPlan := channelconstant.ChannelSpecialBases[baseURL]

switch info.RelayFormat {
case types.RelayFormatClaude:
return fmt.Sprintf("%s/api/anthropic/v1/messages", info.ChannelBaseUrl), nil
if hasSpecialPlan && specialPlan.ClaudeBaseURL != "" {
return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
}
return fmt.Sprintf("%s/api/anthropic/v1/messages", baseURL), nil
default:
switch info.RelayMode {
case relayconstant.RelayModeEmbeddings:
return fmt.Sprintf("%s/api/paas/v4/embeddings", info.ChannelBaseUrl), nil
if hasSpecialPlan && specialPlan.OpenAIBaseURL != "" {
return fmt.Sprintf("%s/embeddings", specialPlan.OpenAIBaseURL), nil
}
return fmt.Sprintf("%s/api/paas/v4/embeddings", baseURL), nil
default:
return fmt.Sprintf("%s/api/paas/v4/chat/completions", info.ChannelBaseUrl), nil
if hasSpecialPlan && specialPlan.OpenAIBaseURL != "" {
return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
}
return fmt.Sprintf("%s/api/paas/v4/chat/completions", baseURL), nil
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion relay/channel/zhipu_4v/constants.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package zhipu_4v

var ModelList = []string{
"glm-4", "glm-4v", "glm-3-turbo", "glm-4-alltools", "glm-4-plus", "glm-4-0520", "glm-4-air", "glm-4-airx", "glm-4-long", "glm-4-flash", "glm-4v-plus",
"glm-4", "glm-4v", "glm-3-turbo", "glm-4-alltools", "glm-4-plus", "glm-4-0520", "glm-4-air", "glm-4-airx", "glm-4-long", "glm-4-flash", "glm-4v-plus", "glm-4.6",
}

var ChannelName = "zhipu_4v"