返回 DeepSeek-Reasonix
stepfun.go
根目录 / internal / provider / openai / stepfun.go
1 package openai
2
3 import (
4 "net/url"
5 "strings"
6 )
7
8 // StepFun's coding-plan surface lives under /step_plan/v1 on both official
9 // regional hosts. The docs hand Anthropic-SDK users a base without /v1
10 // (https://api.stepfun.com/step_plan) because that SDK appends /v1/messages
11 // itself; pasting the same base into an OpenAI-compatible client yields
12 // {base}/chat/completions — a 404, while the model probe's {base}/v1/models
13 // fallback still succeeds, so setup looks healthy and every call fails.
14 // Canonicalization preserves the user's regional host; only the path moves.
15 const (
16 stepFunPlanChatPath = "/step_plan/v1/chat/completions"
17 stepFunPlanModelsPath = "/step_plan/v1/models"
18 )
19
20 // canonicalStepFunPlanEndpoint rewrites known official StepFun step_plan URLs
21 // to their complete form. Unknown or unsafe URLs stay untouched.
22 func canonicalStepFunPlanEndpoint(raw, canonicalPath string) (string, bool) {
23 raw = strings.TrimSpace(raw)
24 if raw == "" {
25 return "", false
26 }
27 if strings.ContainsAny(raw, "?#") {
28 return "", false
29 }
30 u, err := url.Parse(raw)
31 if err != nil || u.Scheme != "https" || u.Host == "" || u.Opaque != "" {
32 return "", false
33 }
34 if !isStepFunPlanHost(u.Hostname()) {
35 return "", false
36 }
37 if port := u.Port(); port != "" && port != "443" {
38 return "", false
39 }
40 if u.User != nil || u.RawPath != "" {
41 return "", false
42 }
43 if !stepFunPlanKnownPath(u.Path) {
44 return "", false
45 }
46 return "https://" + strings.ToLower(u.Hostname()) + canonicalPath, true
47 }
48
49 func canonicalStepFunPlanChatURL(raw string) (string, bool) {
50 return canonicalStepFunPlanEndpoint(raw, stepFunPlanChatPath)
51 }
52
53 // CanonicalStepFunPlanModelsURL rewrites known official StepFun step_plan URLs
54 // to GET /step_plan/v1/models.
55 func CanonicalStepFunPlanModelsURL(raw string) (string, bool) {
56 return canonicalStepFunPlanEndpoint(raw, stepFunPlanModelsPath)
57 }
58
59 // isStepFunPlanHost matches the two official API hosts exactly. Both stepfun.com
60 // (China) and stepfun.ai (global) serve step_plan; relays and lookalikes stay
61 // untouched.
62 func isStepFunPlanHost(host string) bool {
63 switch strings.ToLower(strings.TrimSpace(host)) {
64 case "api.stepfun.com", "api.stepfun.ai":
65 return true
66 default:
67 return false
68 }
69 }
70
71 func stepFunPlanKnownPath(path string) bool {
72 if path != "/" {
73 path = strings.TrimSuffix(path, "/")
74 }
75 switch path {
76 case "/step_plan", "/step_plan/v1", "/step_plan/v1/v1":
77 return true
78 }
79 for _, leaf := range []string{"/chat/completions", "/models"} {
80 for _, prefix := range []string{"/step_plan", "/step_plan/v1", "/step_plan/v1/v1"} {
81 if path == prefix+leaf || path == prefix+leaf+leaf {
82 return true
83 }
84 }
85 }
86 return false
87 }
88
88 lines GO