From fa796d57b5232ea8d37d7d92495f5df058a476db Mon Sep 17 00:00:00 2001 From: Liuxinyu176 <1041316040@qq.com> Date: Fri, 9 Oct 2026 10:02:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(proxy):=20=E4=BF=AE=E5=A4=8D=20Trae=20Autho?= =?UTF-8?q?rization=20=E5=A4=B4=E5=8F=91=E6=88=90=E5=AD=97=E9=9D=A2?= =?UTF-8?q?=E9=87=8F=20jwt=20+=20=E8=A1=A5=E5=85=A8=20SOLO=20=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 上一版把 \ 写成了字符串 jwt,Trae 收到 Cloud-IDE-JWT jwt 必然被网关拒绝 - 完整 SOLO 头集合对齐 trae2api-cn:x-app-version/x-lscbd-*/x-ttnet-* 等 - 版本升至 1.7.0 --- app/build.gradle.kts | 4 +- .../rainy/token/data/proxy/TraeChatProxy.kt | 99 ++++++++++--------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aaea2d1..bbe625d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { applicationId = "com.rainy.token" minSdk = 31 targetSdk = 35 - versionCode = 19 - versionName = "1.6.9" + versionCode = 20 + versionName = "1.7.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/rainy/token/data/proxy/TraeChatProxy.kt b/app/src/main/java/com/rainy/token/data/proxy/TraeChatProxy.kt index c5a718d..ccc5035 100644 --- a/app/src/main/java/com/rainy/token/data/proxy/TraeChatProxy.kt +++ b/app/src/main/java/com/rainy/token/data/proxy/TraeChatProxy.kt @@ -15,6 +15,7 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.put +import okhttp3.Headers.Companion.toHeaders import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request @@ -23,9 +24,9 @@ import okhttp3.RequestBody.Companion.toRequestBody /** * Trae CN 上游 Chat 代理。 * - * 上游为私有协议 POST {base}/api/agent/v3/llm_utils_chat, - * 请求头需要 Cloud-IDE-JWT / x-device-id / x-uid 等设备与身份指纹。 - * M1b 实现最小 body 转换:保留 messages/tools,注入 config_name/function/stream/session。 + * 上游为私有协议 POST {base}/api/agent/v3/llm_utils_chat。 + * CN 模型网关固定走 trae-api-cn.mchost.guru(api.trae.cn 只是账号/OAuth 主机,返回 404)。 + * 请求头/body 对齐 trae2api-cn 参考实现的 SOLO 协议。 */ @Singleton class TraeChatProxy @Inject constructor( @@ -57,30 +58,10 @@ class TraeChatProxy @Inject constructor( } val upstreamBody = buildUpstreamBody(requestBody, credential) val requestId = UUID.randomUUID().toString() - val sessionId = UUID.randomUUID().toString().replace("-", "") val builder = Request.Builder() .url(base + "/api/agent/v3/llm_utils_chat") - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "text/event-stream") - .addHeader("Authorization", "Cloud-IDE-JWT jwt") - .addHeader("X-Cloudide-Token", jwt) - .addHeader("x-uid", credential.userId ?: "") - .addHeader("x-app-id", "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8") - .addHeader("x-device-id", credential.deviceId ?: credential.checkinDeviceId ?: "") - .addHeader("x-machine-id", credential.deviceId ?: credential.checkinDeviceId ?: "") - .addHeader("x-request-id", requestId) - .addHeader("x-ide-version", "3.3.67") - .addHeader("x-ide-version-code", "20260401") - .addHeader("x-ide-version-type", "stable") - .addHeader("x-device-cpu", "AMD") - .addHeader("x-device-brand", "83DG") - .addHeader("x-device-type", "windows") - .addHeader("x-ide-token", jwt) - .addHeader("x-os-version", "Windows 10") - .addHeader("x-system-type", "Windows") - .addHeader("Connection", "keep-alive") - .addHeader("User-Agent", "") + .headers(soloHeaders(jwt, credential, requestId).toHeaders()) .post(upstreamBody.toRequestBody("application/json".toMediaType())) val response = try { @@ -110,36 +91,16 @@ class TraeChatProxy @Inject constructor( ProviderRegion.valueOf(credential.region.uppercase()) }.getOrNull() val base = if (effectiveRegion == ProviderRegion.INTL) { - "https://grow-normal.trae.ai" + "https://a0ai-api-sg.byteintlapi.com" } else { - "https://api.trae.cn" + "https://trae-api-cn.mchost.guru" } val upstreamBody = buildUpstreamBody(requestBody, credential) val requestId = UUID.randomUUID().toString() - val sessionId = UUID.randomUUID().toString().replace("-", "") val builder = Request.Builder() .url(base + "/api/agent/v3/llm_utils_chat") - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "text/event-stream") - .addHeader("Authorization", "Cloud-IDE-JWT jwt") - .addHeader("X-Cloudide-Token", jwt) - .addHeader("x-uid", credential.userId ?: "") - .addHeader("x-app-id", "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8") - .addHeader("x-device-id", credential.deviceId ?: credential.checkinDeviceId ?: "") - .addHeader("x-machine-id", credential.deviceId ?: credential.checkinDeviceId ?: "") - .addHeader("x-request-id", requestId) - .addHeader("x-ide-version", "3.3.67") - .addHeader("x-ide-version-code", "20260401") - .addHeader("x-ide-version-type", "stable") - .addHeader("x-device-cpu", "AMD") - .addHeader("x-device-brand", "83DG") - .addHeader("x-device-type", "windows") - .addHeader("x-ide-token", jwt) - .addHeader("x-os-version", "Windows 10") - .addHeader("x-system-type", "Windows") - .addHeader("Connection", "keep-alive") - .addHeader("User-Agent", "") + .headers(soloHeaders(jwt, credential, requestId).toHeaders()) .post(upstreamBody.toRequestBody("application/json".toMediaType())) val response = try { @@ -159,6 +120,50 @@ class TraeChatProxy @Inject constructor( ) } + private fun soloHeaders( + jwt: String, + credential: Credential.TraeCredential, + requestId: String, + ): Map = linkedMapOf( + "Content-Type" to "application/json", + "Accept" to "text/event-stream", + "Connection" to "keep-alive", + "Authorization" to "Cloud-IDE-JWT $jwt", + "X-Cloudide-Token" to jwt, + "x-ide-token" to jwt, + "x-uid" to (credential.userId ?: ""), + "x-app-id" to "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8", + "x-device-id" to (credential.deviceId ?: credential.checkinDeviceId ?: ""), + "x-machine-id" to (credential.deviceId ?: credential.checkinDeviceId ?: ""), + "x-request-id" to requestId, + "x-ide-version" to "0.1.52", + "x-ide-version-code" to "20260811", + "x-ide-version-type" to "stable", + "x-app-version" to "default", + "x-app-version-code" to "20260811", + "x-version-code" to "20260811", + "x-device-cpu" to "AMD", + "x-device-brand" to "83DG", + "x-device-type" to "windows", + "x-device-platform" to "windows", + "x-os-version" to "Windows 11 Pro", + "x-system-type" to "Windows", + "package-type" to "stable_cn", + "x-lscbd-aid" to "787976", + "x-lscbd-platform" to "windows", + "x-ss-dp" to "787976", + "x-plugin-channel" to "icube-ai", + "app-version" to "0.1.52", + "x-bridge-transport" to "aha", + "x-ahanet-timeout" to "86400", + "x-lgw-req-sdk-type" to "3", + "x-net-sdk-domain-dispatch" to "1", + "x-ttnet-bypass-decompression" to "1", + "x-ttnet-bypass-cookie" to "0", + "request-traffic-type" to "prod", + "User-Agent" to "Trae/0.1.52", + ) + private fun buildUpstreamBody(raw: String, credential: Credential.TraeCredential): String { val src = try { json.parseToJsonElement(raw) as? JsonObject