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