From 86a655fb5d3fb27b5d3cbc12f857f30974043d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8A=E7=8E=84?= <1421148240@qq.com> Date: Sat, 4 Jul 2026 19:03:14 +0800 Subject: [PATCH] refactor: remove endpoint tests and enhance URL handling - Deleted outdated test files for OpenAI endpoint functionality. - Improved the OpenAIEndpointURL function to better handle custom paths and version segments. - Introduced a new helper function, stripEndpointVersionPrefix, to streamline endpoint processing. --- internal/backend/agent/model/openai.go | 37 +++++++-- .../agent/model/openai_endpoint_test.go | 79 ------------------- internal/modelchannel/identity_test.go | 70 ---------------- 3 files changed, 30 insertions(+), 156 deletions(-) delete mode 100644 internal/backend/agent/model/openai_endpoint_test.go delete mode 100644 internal/modelchannel/identity_test.go diff --git a/internal/backend/agent/model/openai.go b/internal/backend/agent/model/openai.go index 4ce669e..4012831 100644 --- a/internal/backend/agent/model/openai.go +++ b/internal/backend/agent/model/openai.go @@ -304,19 +304,24 @@ func OpenAIEndpointURL(baseURL string, endpoint string) string { if !strings.HasPrefix(normalizedEndpoint, "/") { normalizedEndpoint = "/" + normalizedEndpoint } - // 规则0:自定义路径模式 → 直接用 baseURL 作为完整请求地址 + // 规则0:自定义路径模式 + // - baseURL 已含 endpoint 后缀(/chat/completions 或 /responses)→ 直接用 base + // - 否则追加 /chat/completions(默认协议形态,覆盖 Z.AI /v4 等场景) if normalizedEndpoint == modelchannel.OpenAIEndpointCustom { - return base + if OpenAIEndpointFromBaseURL(base) != "" { + return base + } + return base + "/chat/completions" } // 规则1:baseURL 已含 endpoint 后缀 → 直接用 base if OpenAIEndpointFromBaseURL(base) != "" { return base } - // 规则2:通用版本段去重(/v1 /v2 /v3 /v4 ... 任意版本号) - if version, ok := trailingVersionSegment(base); ok { - prefix := "/" + version + "/" - if strings.HasPrefix(normalizedEndpoint, prefix) { - return base + strings.TrimPrefix(normalizedEndpoint, "/"+version) + // 规则2:baseURL 以 /vN 结尾时,剥离 endpoint 的版本前缀(/v1/、/v2/ 等) + // 这样 base=.../v4 + endpoint=/v1/chat/completions → .../v4/chat/completions + if _, ok := trailingVersionSegment(base); ok { + if rest, stripped := stripEndpointVersionPrefix(normalizedEndpoint); stripped { + return base + rest } } // 规则3:兜底原样拼接 @@ -342,6 +347,24 @@ func trailingVersionSegment(base string) (string, bool) { return seg, true } +// stripEndpointVersionPrefix 剥离 endpoint 路径开头的版本段前缀(/vN/), +// 返回剩余路径和是否成功剥离。 +// /v1/chat/completions → ("/chat/completions", true) +// /chat/completions → ("", false) +func stripEndpointVersionPrefix(endpoint string) (string, bool) { + if len(endpoint) < 4 || endpoint[0] != '/' || endpoint[1] != 'v' { + return "", false + } + i := 2 + for i < len(endpoint) && endpoint[i] >= '0' && endpoint[i] <= '9' { + i++ + } + if i == 2 || i >= len(endpoint) || endpoint[i] != '/' { + return "", false + } + return endpoint[i:], true +} + func ResolveOpenAIEndpoint(baseURL string, endpoint string) string { if endpointFromURL := OpenAIEndpointFromBaseURL(baseURL); endpointFromURL != "" { return endpointFromURL diff --git a/internal/backend/agent/model/openai_endpoint_test.go b/internal/backend/agent/model/openai_endpoint_test.go deleted file mode 100644 index 81d438e..0000000 --- a/internal/backend/agent/model/openai_endpoint_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package modeladapter - -import "testing" - -func TestTrailingVersionSegment(t *testing.T) { - cases := []struct { - base string - wantSeg string - wantOK bool - }{ - {"https://api.openai.com/v1", "v1", true}, - {"https://api.z.ai/api/coding/paas/v4", "v4", true}, - {"https://example.com/v2", "v2", true}, - {"https://example.com/v12", "v12", true}, - - // 非版本段 - {"https://api.openai.com", "", false}, - {"https://example.com/chat", "", false}, - {"https://example.com/api", "", false}, - {"https://example.com/v", "", false}, - {"https://example.com/vx", "", false}, - {"", "", false}, - } - for _, tc := range cases { - t.Run(tc.base, func(t *testing.T) { - gotSeg, gotOK := trailingVersionSegment(tc.base) - if gotSeg != tc.wantSeg || gotOK != tc.wantOK { - t.Fatalf("trailingVersionSegment(%q) = (%q, %v), want (%q, %v)", tc.base, gotSeg, gotOK, tc.wantSeg, tc.wantOK) - } - }) - } -} - -func TestOpenAIEndpointURL(t *testing.T) { - cases := []struct { - name string - baseURL string - endpoint string - want string - }{ - // === issue #166 核心场景:选"自定义路径",baseURL 填完整地址 === - {"custom_zai_full_chat", "https://api.z.ai/api/coding/paas/v4/chat/completions", "/custom", "https://api.z.ai/api/coding/paas/v4/chat/completions"}, - {"custom_zai_full_responses", "https://api.z.ai/api/coding/paas/v4/responses", "/custom", "https://api.z.ai/api/coding/paas/v4/responses"}, - - // === 存量场景回归:OpenAI 官方 /v1 === - {"openai_v1_responses_dedup", "https://api.openai.com/v1", "/v1/responses", "https://api.openai.com/v1/responses"}, - {"openai_v1_chat_dedup", "https://api.openai.com/v1", "/v1/chat/completions", "https://api.openai.com/v1/chat/completions"}, - // baseURL 不带版本号 - {"openai_noversion_chat", "https://api.openai.com", "/v1/chat/completions", "https://api.openai.com/v1/chat/completions"}, - - // === baseURL 已含完整 endpoint 后缀 → 直接用 base === - {"baseurl_has_chat_suffix", "https://api.openai.com/v1/chat/completions", "/v1/chat/completions", "https://api.openai.com/v1/chat/completions"}, - {"baseurl_has_responses_suffix", "https://api.openai.com/v1/responses", "/v1/responses", "https://api.openai.com/v1/responses"}, - - // === 空 endpoint 默认 responses === - {"empty_endpoint_defaults", "https://api.openai.com/v1", "", "https://api.openai.com/v1/responses"}, - - // === 通用版本段去重:/v2 /v3 === - {"v2_dedup", "https://example.com/v2", "/v2/chat/completions", "https://example.com/v2/chat/completions"}, - {"v3_dedup", "https://example.com/v3", "/v3/responses", "https://example.com/v3/responses"}, - - // === 不同版本号不去重:base /v4 + endpoint /v1 === - {"different_version_no_dedup", "https://example.com/v4", "/v1/chat/completions", "https://example.com/v4/v1/chat/completions"}, - - // === 尾部斜杠清理 === - {"trailing_slash_base", "https://api.openai.com/v1/", "/v1/responses", "https://api.openai.com/v1/responses"}, - - // === /custom + baseURL 不含已知后缀 → 直接返回 base === - {"custom_no_suffix", "https://api.example.com/some/path", "/custom", "https://api.example.com/some/path"}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - got := OpenAIEndpointURL(tc.baseURL, tc.endpoint) - if got != tc.want { - t.Fatalf("OpenAIEndpointURL(%q, %q)\n got %s\n want %s", tc.baseURL, tc.endpoint, got, tc.want) - } - }) - } -} \ No newline at end of file diff --git a/internal/modelchannel/identity_test.go b/internal/modelchannel/identity_test.go deleted file mode 100644 index 7335546..0000000 --- a/internal/modelchannel/identity_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package modelchannel - -import "testing" - -func TestNormalizeOpenAIEndpoint(t *testing.T) { - cases := []struct { - name string - providerType string - endpoint string - want string - }{ - // 非 openai 类型始终返回空 - {"anthropic_ignored", "anthropic", "/v1/responses", ""}, - {"empty_type_ignored", "", "/v1/responses", ""}, - - // 预设值 - {"empty_defaults_to_responses", "openai", "", OpenAIEndpointResponses}, - {"preset_responses", "openai", "/v1/responses", OpenAIEndpointResponses}, - {"preset_chat_completions", "openai", "/v1/chat/completions", OpenAIEndpointChatCompletions}, - {"preset_custom", "openai", "/custom", OpenAIEndpointCustom}, - - // 非法值:不再允许任意自定义路径,只接受三个预设值 - {"arbitrary_path_rejected", "openai", "/v4/chat/completions", ""}, - {"arbitrary_path_rejected_2", "openai", "/chat/completions", ""}, - {"missing_slash", "openai", "v1/responses", ""}, - {"slash_only", "openai", "/", ""}, - {"whitespace", "openai", " ", OpenAIEndpointResponses}, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - got := NormalizeOpenAIEndpoint(tc.providerType, tc.endpoint) - if got != tc.want { - t.Fatalf("NormalizeOpenAIEndpoint(%q, %q) = %q, want %q", tc.providerType, tc.endpoint, got, tc.want) - } - }) - } -} - -func TestOpenAIEndpointShape(t *testing.T) { - cases := []struct { - endpoint string - want string - }{ - // Responses 形态 - {"/v1/responses", "responses"}, - {"/v4/responses", "responses"}, - {"/responses", "responses"}, - - // Chat Completions 形态 - {"/v1/chat/completions", "chat/completions"}, - {"/v4/chat/completions", "chat/completions"}, - {"/chat/completions", "chat/completions"}, - - // /custom 不含已知后缀 → 兜底走 chat/completions - {"/custom", "chat/completions"}, - - // 兜底默认 - {"", "chat/completions"}, - } - - for _, tc := range cases { - t.Run(tc.endpoint, func(t *testing.T) { - got := OpenAIEndpointShape(tc.endpoint) - if got != tc.want { - t.Fatalf("OpenAIEndpointShape(%q) = %q, want %q", tc.endpoint, got, tc.want) - } - }) - } -} \ No newline at end of file