Skip to content

Commit f189bbd

Browse files
xgopilotphantom5099
andcommitted
fix(provider): auto-fill chat endpoint by chat_api_mode when path is empty
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
1 parent 70b204e commit f189bbd

7 files changed

Lines changed: 105 additions & 10 deletions

File tree

internal/provider/openaicompat/generate_sdk.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,30 @@ func (p *Provider) newSDKClient() openai.Client {
116116
}
117117

118118
func resolveChatEndpoint(cfg provider.RuntimeConfig) (string, error) {
119-
endpoint, err := provider.ResolveChatEndpointURL(cfg.BaseURL, cfg.ChatEndpointPath)
119+
chatEndpointPath := resolveChatEndpointPathByMode(cfg.ChatEndpointPath, cfg.ChatAPIMode)
120+
endpoint, err := provider.ResolveChatEndpointURL(cfg.BaseURL, chatEndpointPath)
120121
if err != nil {
121122
return "", fmt.Errorf("%sinvalid chat endpoint configuration: %w", errorPrefix, err)
122123
}
123124
return endpoint, nil
124125
}
125126

127+
// resolveChatEndpointPathByMode 在 chat endpoint 为空时,根据 chat_api_mode 自动回填默认端点路径。
128+
func resolveChatEndpointPathByMode(rawPath string, chatAPIMode string) string {
129+
if strings.TrimSpace(rawPath) != "" {
130+
return rawPath
131+
}
132+
133+
mode, err := provider.NormalizeProviderChatAPIMode(chatAPIMode)
134+
if err != nil || mode == "" {
135+
mode = provider.DefaultProviderChatAPIMode()
136+
}
137+
if mode == provider.ChatAPIModeResponses {
138+
return chatEndpointPathResponses
139+
}
140+
return chatEndpointPathCompletions
141+
}
142+
126143
// validateStreamingResponse 校验流式响应协议,避免非 SSE 响应被误交给流解析器。
127144
func validateStreamingResponse(resp *http.Response) error {
128145
if resp == nil || resp.Body == nil {

internal/provider/openaicompat/generate_sdk_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,41 @@ func TestValidateStreamingResponseAllowsMissingContentTypeForNonHTMLBody(t *test
120120
t.Fatalf("expected body stream to remain readable, got %q", string(restoredBody))
121121
}
122122
}
123+
124+
func TestResolveChatEndpointPathByMode(t *testing.T) {
125+
t.Parallel()
126+
127+
tests := []struct {
128+
name string
129+
path string
130+
mode string
131+
want string
132+
}{
133+
{
134+
name: "preserves explicit path",
135+
path: "/gateway/chat/completions",
136+
mode: "responses",
137+
want: "/gateway/chat/completions",
138+
},
139+
{
140+
name: "fills chat completions path by default mode",
141+
path: "",
142+
mode: "",
143+
want: "/chat/completions",
144+
},
145+
{
146+
name: "fills responses path for responses mode",
147+
path: "",
148+
mode: "responses",
149+
want: "/responses",
150+
},
151+
}
152+
153+
for _, tt := range tests {
154+
t.Run(tt.name, func(t *testing.T) {
155+
if got := resolveChatEndpointPathByMode(tt.path, tt.mode); got != tt.want {
156+
t.Fatalf("resolveChatEndpointPathByMode(%q, %q) = %q, want %q", tt.path, tt.mode, got, tt.want)
157+
}
158+
})
159+
}
160+
}

internal/provider/openaicompat/openaicompat_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,11 +1232,11 @@ data: [DONE]
12321232
}
12331233
}
12341234

1235-
func TestGenerate_UsesDirectBaseURLWhenChatEndpointPathEmpty(t *testing.T) {
1235+
func TestGenerate_UsesDefaultCompletionsEndpointWhenChatEndpointPathEmpty(t *testing.T) {
12361236
t.Setenv(config.OpenAIDefaultAPIKeyEnv, "test-key")
12371237

12381238
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1239-
if r.URL.Path != "/v1/text/chatcompletion_v2" {
1239+
if r.URL.Path != "/v1/text/chatcompletion_v2/chat/completions" {
12401240
t.Fatalf("unexpected path: %s", r.URL.Path)
12411241
}
12421242
w.Header().Set("Content-Type", "text/event-stream")
@@ -1265,11 +1265,11 @@ data: [DONE]
12651265
}
12661266
}
12671267

1268-
func TestGenerate_UsesDirectBaseURLWithResponsesModeWhenChatEndpointPathEmpty(t *testing.T) {
1268+
func TestGenerate_UsesDefaultResponsesEndpointWhenChatEndpointPathEmpty(t *testing.T) {
12691269
t.Setenv(config.OpenAIDefaultAPIKeyEnv, "test-key")
12701270

12711271
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1272-
if r.URL.Path != "/v1/text/chatcompletion_v2" {
1272+
if r.URL.Path != "/v1/text/chatcompletion_v2/responses" {
12731273
t.Fatalf("unexpected path: %s", r.URL.Path)
12741274
}
12751275
w.Header().Set("Content-Type", "text/event-stream")

internal/tui/core/app/update.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3219,6 +3219,18 @@ func providerAddDefaultChatEndpointPath(driver string) string {
32193219
}
32203220
}
32213221

3222+
// providerAddDefaultOpenAICompatChatEndpointPath 根据 chat_api_mode 返回 openaicompat 的默认聊天端点路径。
3223+
func providerAddDefaultOpenAICompatChatEndpointPath(chatAPIMode string) string {
3224+
mode, err := provider.NormalizeProviderChatAPIMode(chatAPIMode)
3225+
if err != nil || mode == "" {
3226+
mode = provider.DefaultProviderChatAPIMode()
3227+
}
3228+
if mode == provider.ChatAPIModeResponses {
3229+
return "/responses"
3230+
}
3231+
return "/chat/completions"
3232+
}
3233+
32223234
// providerAddDefaultBaseURL 返回 provider add 表单的驱动默认 base URL。
32233235
func providerAddDefaultBaseURL(driver string) string {
32243236
switch provider.NormalizeProviderDriver(driver) {
@@ -3309,7 +3321,11 @@ func buildProviderAddRequest(form providerAddFormState) (providerAddRequest, str
33093321
}
33103322

33113323
if strings.TrimSpace(request.ChatEndpointPath) == "" {
3312-
request.ChatEndpointPath = providerAddDefaultChatEndpointPath(request.Driver)
3324+
if request.Driver == provider.DriverOpenAICompat {
3325+
request.ChatEndpointPath = providerAddDefaultOpenAICompatChatEndpointPath(request.ChatAPIMode)
3326+
} else {
3327+
request.ChatEndpointPath = providerAddDefaultChatEndpointPath(request.Driver)
3328+
}
33133329
}
33143330

33153331
switch request.Driver {

internal/tui/core/app/update_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2568,6 +2568,28 @@ func TestBuildProviderAddRequest(t *testing.T) {
25682568
}
25692569
})
25702570

2571+
t.Run("openai compat fills chat endpoint by chat api mode when empty", func(t *testing.T) {
2572+
req, err := buildProviderAddRequest(providerAddFormState{
2573+
Name: "openai-compat-responses-endpoint",
2574+
Driver: provider.DriverOpenAICompat,
2575+
ModelSource: config.ModelSourceDiscover,
2576+
ChatAPIMode: provider.ChatAPIModeResponses,
2577+
ChatEndpointPath: "",
2578+
APIKey: "k",
2579+
APIKeyEnv: "OPENAI_COMPAT_RESPONSES_ENDPOINT_API_KEY",
2580+
DiscoveryEndpointPath: provider.DiscoveryEndpointPathModels,
2581+
})
2582+
if err != "" {
2583+
t.Fatalf("unexpected error: %s", err)
2584+
}
2585+
if req.ChatAPIMode != provider.ChatAPIModeResponses {
2586+
t.Fatalf("expected chat api mode responses, got %q", req.ChatAPIMode)
2587+
}
2588+
if req.ChatEndpointPath != "/responses" {
2589+
t.Fatalf("expected responses endpoint path, got %q", req.ChatEndpointPath)
2590+
}
2591+
})
2592+
25712593
t.Run("strips control chars from env key before validation", func(t *testing.T) {
25722594
req, err := buildProviderAddRequest(providerAddFormState{
25732595
Name: "openai-compat",

internal/tui/core/app/view.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,10 @@ func (a App) renderProviderAddForm() string {
324324
case providerAddFieldChatEndpointPath:
325325
note := ""
326326
trimmedPath := strings.TrimSpace(a.providerAddForm.ChatEndpointPath)
327-
if trimmedPath == "" || trimmedPath == "/" {
328-
note = "留空或\"/\" 使用直连 base_url"
327+
if trimmedPath == "" {
328+
note = "留空会按 Chat API Mode 自动回填默认端点"
329+
} else if trimmedPath == "/" {
330+
note = "\"/\" 使用直连 base_url"
329331
} else {
330332
note = "以 \"/\" 开头的端点路径"
331333
}

internal/tui/core/app/view_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ func TestRenderProviderAddFormMasksAPIKeyAndShowsHints(t *testing.T) {
293293
if !strings.Contains(form, "留空会自动填充默认地址") {
294294
t.Fatalf("expected base url hint, got %q", form)
295295
}
296-
if !strings.Contains(form, "留空或\"/\" 使用直连 base_url") {
297-
t.Fatalf("expected chat endpoint direct-mode hint, got %q", form)
296+
if !strings.Contains(form, "留空会按 Chat API Mode 自动回填默认端点") {
297+
t.Fatalf("expected chat endpoint autofill hint, got %q", form)
298298
}
299299
if !strings.Contains(form, "[Error] input invalid") {
300300
t.Fatalf("expected hard error label, got %q", form)

0 commit comments

Comments
 (0)