Update proxy to Zhanlu v1.4.2 provider flow

The 1.4.2 extension replaced the old signed/encrypted chat gateway with an
OpenAI-compatible aigateway. Align the proxy with the new flow:

- Use ecloud.10086.cn login/model base URLs, zhanlu_ide plugin headers and
  v1.4.2 plugin version
- Provision the model API key via SM2-signed get-or-create after v1/login
  profile fetch; store api_key/model_base_url/email in credentials
- Chat via Bearer apiKey against {modelBaseUrl}/chat/completions with plain
  OpenAI SSE passthrough; fetch /v1/models from the gateway model-info endpoint
- Force HTTP/1.1 upstream (gateway drops HTTP/2 ALPN negotiation with EOF)
- Drop obsolete AES body encryption, model name mapping and vscode headers
This commit is contained in:
2026-08-05 14:55:28 +08:00
parent c3af3caa5b
commit 7d8a5b6f74
18 changed files with 1122 additions and 232 deletions
+52 -34
View File
@@ -10,50 +10,57 @@ import (
)
type Config struct {
ListenAddr string
ServerBaseURL string
UpstreamPath string
CredentialsPath string
SSOExchangeURL string
SSOBaseURL string
TokenDecryptKey string
PublicKeyPEM string
PhonePublicKeyPEM string
Models []string
DefaultModel string
OpenAIAPIKey string
LoginPassword string
UpstreamTimeout time.Duration
StreamIdleTimout time.Duration
Debug bool
Credentials auth.Credentials
ListenAddr string
MobileLoginBaseURL string
MobileModelBaseURL string
UpstreamPath string
CredentialsPath string
SSOExchangeURL string
SSOBaseURL string
TokenDecryptKey string
PublicKeyPEM string
PhonePublicKeyPEM string
SM2PrivateKey string
Models []string
DefaultModel string
OpenAIAPIKey string
LoginPassword string
PluginVersion string
UpstreamTimeout time.Duration
StreamIdleTimout time.Duration
Debug bool
Credentials auth.Credentials
}
func Load() (Config, error) {
cfg := Config{
ListenAddr: getenv("ZHANLU_LISTEN_ADDR", ":8080"),
ServerBaseURL: getenv("ZHANLU_SERVER_BASE_URL", "https://api-wuxi-1.cmecloud.cn:8443"),
UpstreamPath: getenv("ZHANLU_UPSTREAM_PATH", "/api/acepilot/zhanlu/aiDeveloper/chat"),
CredentialsPath: getenv("ZHANLU_CREDENTIALS_FILE", defaultCredentialsPath()),
SSOBaseURL: getenv("ZHANLU_SSO_BASE_URL", "http://rdcloud.4c.hq.cmcc"),
SSOExchangeURL: getenv("ZHANLU_SSO_EXCHANGE_URL", "https://api-wuxi-1.cmecloud.cn:8443/cmdevops-aiplus-agent-gateway/api/acepilot/zhanlu/checkoutCode"),
TokenDecryptKey: os.Getenv("ZHANLU_TOKEN_DECRYPT_KEY"),
PublicKeyPEM: getenv("ZHANLU_PUBLIC_KEY_PEM", defaultPublicKeyPEM),
PhonePublicKeyPEM: getenv("ZHANLU_PHONE_PUBLIC_KEY_PEM", defaultPhonePublicKeyPEM),
DefaultModel: getenv("ZHANLU_DEFAULT_MODEL", "minimax-m25"),
OpenAIAPIKey: os.Getenv("OPENAI_COMPAT_API_KEY"),
LoginPassword: os.Getenv("ZHANLU_LOGIN_PASSWORD"),
UpstreamTimeout: durationEnv("ZHANLU_UPSTREAM_TIMEOUT", 300*time.Second),
StreamIdleTimout: durationEnv("ZHANLU_STREAM_IDLE_TIMEOUT", 300*time.Second),
Debug: strings.EqualFold(os.Getenv("ZHANLU_DEBUG"), "true"),
ListenAddr: getenv("ZHANLU_LISTEN_ADDR", ":8080"),
MobileLoginBaseURL: firstNonEmpty(os.Getenv("ZHANLU_MOBILE_LOGIN_BASE_URL"), getenv("ZHANLU_SERVER_BASE_URL", "https://ecloud.10086.cn")),
MobileModelBaseURL: getenv("ZHANLU_MOBILE_MODEL_BASE_URL", "https://ecloud.10086.cn/api/query/aigateway"),
UpstreamPath: getenv("ZHANLU_UPSTREAM_PATH", "/chat/completions"),
CredentialsPath: getenv("ZHANLU_CREDENTIALS_FILE", defaultCredentialsPath()),
SSOBaseURL: getenv("ZHANLU_SSO_BASE_URL", "http://4c.hq.cmcc"),
SSOExchangeURL: getenv("ZHANLU_SSO_EXCHANGE_URL", "http://rdcloud.4c.hq.cmcc/cmdevops-aiplus-agent-gateway/api/acepilot/zhanlu/authToken"),
TokenDecryptKey: getenv("ZHANLU_TOKEN_DECRYPT_KEY", "3jw7woww2rvhla6k"),
PublicKeyPEM: getenv("ZHANLU_PUBLIC_KEY_PEM", defaultPublicKeyPEM),
PhonePublicKeyPEM: getenv("ZHANLU_PHONE_PUBLIC_KEY_PEM", defaultPhonePublicKeyPEM),
SM2PrivateKey: getenv("ZHANLU_APIKEY_AUTH_SM2_PRIVATE_KEY", defaultSM2PrivateKey),
DefaultModel: getenv("ZHANLU_DEFAULT_MODEL", "GLM-4.7"),
OpenAIAPIKey: os.Getenv("OPENAI_COMPAT_API_KEY"),
LoginPassword: os.Getenv("ZHANLU_LOGIN_PASSWORD"),
PluginVersion: getenv("ZHANLU_PLUGIN_VERSION", "1.4.2"),
UpstreamTimeout: durationEnv("ZHANLU_UPSTREAM_TIMEOUT", 300*time.Second),
StreamIdleTimout: durationEnv("ZHANLU_STREAM_IDLE_TIMEOUT", 300*time.Second),
Debug: strings.EqualFold(os.Getenv("ZHANLU_DEBUG"), "true"),
}
cfg.Models = splitCSV(getenv("ZHANLU_MODELS", "glm47,minimax-m25"))
cfg.Models = splitCSV(getenv("ZHANLU_MODELS", "GLM-4.7,MiniMax-M2.5"))
cfg.Credentials = auth.Credentials{
AccessKey: os.Getenv("ZHANLU_ACCESS_KEY"),
SecretKey: os.Getenv("ZHANLU_SECRET_KEY"),
Token: os.Getenv("ZHANLU_TOKEN"),
APIKey: os.Getenv("ZHANLU_API_KEY"),
}
if cfg.Credentials.Validate() == nil {
if cfg.Credentials.Validate() == nil || cfg.Credentials.HasAPIKey() {
return cfg, nil
}
if creds, err := auth.LoadCredentials(cfg.CredentialsPath); err == nil {
@@ -93,6 +100,15 @@ func durationEnv(key string, fallback time.Duration) time.Duration {
return d
}
func firstNonEmpty(values ...string) string {
for _, v := range values {
if strings.TrimSpace(v) != "" {
return v
}
}
return ""
}
func defaultCredentialsPath() string {
return filepath.Join(".", "credentials.json")
}
@@ -104,3 +120,5 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhxudxTewPgljUHEZHkusP7m3I+zA4/RGvuUM
const defaultPhonePublicKeyPEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnqiA2qP9BNvKw5DnVnrBVBhd+5gJDVn3mDemCfq/AN1cdaHV57hQo6R1ufp45mOkSwLaJcTE82zFKmgKoEAKwD1SR10rp0xJC7x3yvx2FbpEsiW9TeZlvJdri1BYKUMS8OP8ykjHSJoy0oMaV6e95R2rsu4DEH7JuA9+Bt0sOoLewvHx/fs1e28tH+928uUEKdLug+cv/XTKjLudpLjiSMPZU6EHFqrUhA9zmEasOMmg9Dj0j4sChBooCeCGnh/pYHJaosH5amhlSQ8FnEG0BQBrQbZ+qhRH4LYyqGYN8grDNeSnPj7vPDcwiEm++85i5AngZfEMnGWZg5jYDhO9+QIDAQAB
-----END PUBLIC KEY-----`
const defaultSM2PrivateKey = "8d6ee90b3c4d299ae5abd655dbc3547c110ae8aeff1de18b0df241f215f90748"