fix(proxy): 修复 Trae Authorization 头发成字面量 jwt + 补全 SOLO 头
Some checks are pending
Release / build (push) Waiting to run

- 上一版把 \ 写成了字符串 jwt,Trae 收到 Cloud-IDE-JWT jwt 必然被网关拒绝
- 完整 SOLO 头集合对齐 trae2api-cn:x-app-version/x-lscbd-*/x-ttnet-* 等
- 版本升至 1.7.0
This commit is contained in:
Liuxinyu176 2026-10-09 10:02:07 +08:00
parent 8e692a0c6e
commit fa796d57b5
2 changed files with 54 additions and 49 deletions

View File

@ -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 {

View File

@ -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<String, String> = 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