This document records the working solution for Trae International official-login generation forwarding. It exists because Trae looks OpenAI-like from the outside, but its successful generation path is not an OpenAI-compatible HTTP backend.
The proxy originally exposed Trae official-login model discovery and route validation, but generation returned a 501-style failure:
Trae official login is connected for local login state, model discovery, and
route validation; generation still needs a Trae AhaNet official-client adapter.
It cannot be forwarded directly to OpenAI /chat/completions.
The important correction is that Trae and AGY are separate official-login paths. AGY is Google / Code Assist and was already working. Trae must use Trae's own local client path.
Real Trae chat generation uses this path:
Trae renderer chat UI
-> TransportManager
-> ai-agent RPC service=chat method=chat
-> Trae ai-agent native module
-> AhaNet
-> https://coresg-normal.trae.ai/api/agent/v3/create_agent_task
Evidence from a real local Trae send:
- Renderer log showed
send chat,doRequestWithStream start: service=chat, method=chat, andTransportManager executeRequest, chat chat. ai-agentlog showedservice: "chat", method: "chat"and successful cloud task completion.- Trae's model/detail sync calls use
/api/ide/v1/get_detail_param; that endpoint is not the chat generation endpoint. - The backend chat task endpoint observed in logs is
/api/agent/v3/create_agent_task. - Port
127.0.0.1:51160belongs to Trae CKG, not a chat HTTP API. - Trae's current runtime uses Electron
ahaIpc; an external Node@aha-kit/ipcclient could not attach to the same chat path.
The useful internal commands found in Trae are:
wx.bridge.sendAndWaitResponsefeishu.bridge.sendAndWaitResponseim.bridge.sendPromptStreaming
The first two call Trae's internal sendPromptAndWaitResponse, which uses
Trae's own sendToAgent API and official login state.
We bridge through Trae itself instead of trying to clone AhaNet.
flowchart LR
Chat["OpenAI Chat<br/>3447 /v1/chat/completions"]
Responses["OpenAI Responses<br/>3445 /v1/responses"]
Claude["Claude Messages<br/>3443 /v1/messages"]
Route["HTTPS Proxy Manager<br/>Trae route<br/>for example trae/minimax-m3"]
Queue["Trae bridge serial queue<br/>traeOfficialBridgeQueue"]
Adapter["Protocol adapter<br/>OpenAI / Responses / Claude"]
Bridge["Local HTTP bridge<br/>127.0.0.1:17861/v1/chat"]
Extension["Trae extension host<br/>local.https-proxy-manager-trae-bridge"]
Command["wx.bridge.sendAndWaitResponse"]
Login["Trae official login state"]
AhaNet["AhaNet"]
Cloud["Trae cloud<br/>/api/agent/v3/create_agent_task"]
Chat --> Route
Responses --> Route
Claude --> Route
Route --> Queue
Queue --> Adapter
Adapter --> Bridge
Bridge --> Extension
Extension --> Command
Command --> Login
Login --> AhaNet
AhaNet --> Cloud
Cloud -. "final generation" .-> Adapter
Adapter -. "JSON or compatible SSE" .-> Chat
Adapter -. "JSON or compatible SSE" .-> Responses
Adapter -. "JSON or compatible SSE" .-> Claude
OpenAI-compatible client
-> HTTPS Proxy Manager openai-chat / openai-responses / claude entry
-> Trae route, for example trae/minimax-m3
-> local HTTP bridge http://127.0.0.1:17861/v1/chat
-> Trae extension host
-> wx.bridge.sendAndWaitResponse
-> Trae official login + AhaNet
Code locations:
- Proxy adapter:
openai-proxy.js - Trae extension source:
tools/trae-openai-bridge-extension/package.jsontools/trae-openai-bridge-extension/extension.js - Installed Trae extension:
%USERPROFILE%/.trae/extensions/local.https-proxy-manager-trae-bridge-0.1.0 - Tests:
test/adapter.test.js
The bridge listens only on localhost:
http://127.0.0.1:17861/health
http://127.0.0.1:17861/v1/chat
http://127.0.0.1:17861/v1/chat/stream
http://127.0.0.1:17861/debug/commands
Proxy configuration knobs:
TRAE_BRIDGE_HOSTdefaults to127.0.0.1TRAE_BRIDGE_PORTorTRAE_OPENAI_BRIDGE_PORTdefaults to17861TRAE_BRIDGE_URLorTRAE_OPENAI_BRIDGE_URLcan override the base URLTRAE_BRIDGE_TIMEOUT_MSdefaults to 5 minutesTRAE_BRIDGE_LIVE_HEARTBEAT_MScontrols proxy-side live heartbeat interval, default 2 secondsTRAE_BRIDGE_LOG_PROGRESS=0disables Trae local-log progress side-channelTRAE_BRIDGE_LOG_PROGRESS=visiblewrites safe local-log progress summaries into assistant text; regular SDKs can see it, but it changes output contentTRAE_BRIDGE_RAW_PROGRESS=commentor1maps Raw Stream Patch events to API SSE comments, disabled by defaultTRAE_BRIDGE_RAW_PROGRESS=visiblewrites safe Raw Stream Patch summaries into assistant text, for example[Trae plan_item tool=...]TRAE_BRIDGE_BUSY_RETRY_ATTEMPTScontrols retries when the Trae session is busy or running, default 30TRAE_BRIDGE_BUSY_RETRY_DELAY_MScontrols the busy retry delay, default 1000ms
For OpenAI Chat Completions, OpenAI Responses, and Claude Messages:
- Match the public model route to the
trae-officialprofile. - Normalize the model, for example
trae/minimax-m3->minimax-m3. - Convert the incoming protocol request into an OpenAI Chat-like message list.
- Convert messages into a single Trae prompt with role labels:
System,Developer,User,Assistant,Tool. - Extract data URL images into bridge image payloads when present.
- POST
{ prompt, images, model, stream, metadata }to the local Trae bridge. - Wrap Trae's final text as the requested protocol:
- OpenAI Chat
chat.completion - OpenAI Responses
response - Claude Messages
message
- OpenAI Chat
- For
stream: true, emit protocol-compatible SSE chunks.
Streaming requests now use /v1/chat/stream, which calls Trae's
im.bridge.sendPromptStreaming command when it is available. The local bridge
converts Trae's cumulative text callback into text_delta SSE events and
unconfirmed Trae interactions into interaction SSE events. The proxy then
maps those events back to OpenAI Chat, OpenAI Responses, or Claude Messages
streaming responses.
This is still not a raw token-level AhaNet stream. The first implementation does not pretend that Trae's internal tools are client-side tool calls; it emits them as visible text markers so the client can see the Trae-side process without running the same tool twice.
Trae's public streaming command usually calls back with text only after the
final ReplyUser plan item is available, so intermediate UI thinking or tool
execution steps do not always become text_delta events. The bridge emits a
progress SSE heartbeat every 2 seconds while Trae is running. The proxy maps
that heartbeat to OpenAI/Responses SSE comments or Claude ping events. This
heartbeat only proves the Trae-side run is still active; it does not expose
hidden reasoning.
Further investigation found that Trae's renderer and ai-agent logs record some
process state earlier than im.bridge.sendPromptStreaming returns text:
[ai-chat][sendToAgent]: the request entered the Trae session.[tooling] ... start/end: a Trae-side IDE tool or command started/finished.[ai-chat][PlanItemStreamParser] confirm_info received: plan-item confirmation status.first token flushed thought: the model reached first token. The reasoning text in this log line is intentionally not forwarded.[AgentStatus] receivedandplan final token cost: agent completion and timing.
During live streams, the proxy tails only newly appended lines from the current
Trae log files and maps safe summaries to source:"trae-log" progress events.
OpenAI Chat and Responses expose these as SSE comments, for example:
: trae-progress {"source":"trae-log","kind":"tool_start","command":"icube.common.commands.tooling.listFolder"}
Claude remains protocol-compatible and maps these log-progress events to
ping. The side-channel does not forward command output bodies or reasoning
snippets; it only provides earlier operational status from Trae's own local
logs.
If the streaming command returns Now session is not waiting input, busy, or
running, the bridge retries according to TRAE_BRIDGE_BUSY_RETRY_ATTEMPTS and
TRAE_BRIDGE_BUSY_RETRY_DELAY_MS, and emits phase:"busy" SSE progress while
waiting. If streaming still fails, it falls back to wx.bridge.sendAndWaitResponse.
If stream headers have already been sent, later errors are written as visible
bridge busy/error markers and the stream is closed normally, instead of leaving
the client with a silent gap or abrupt disconnect.
The proxy serializes Trae bridge calls. Trae's current chat session can reject
parallel sends with Now session is not waiting input, so concurrent requests
from different proxy ports are queued before hitting the local bridge.
Check that Trae sees the extension:
trae --list-extensions --show-versions | Select-String -Pattern 'https-proxy-manager'Expected:
local.https-proxy-manager-trae-bridge@0.1.0
Check the bridge directly:
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:17861/health' -TimeoutSec 3Expected JSON includes:
{
"ok": true,
"command": "wx.bridge.sendAndWaitResponse",
"port": 17861
}Discover bridge and streaming commands currently visible to Trae's extension host:
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:17861/debug/commands' -TimeoutSec 3Important fields:
waitCommand: the wait/final-response command used for non-streaming calls.streamCommand: the streaming command used for live streaming when present.streamingCommands: whether streaming candidates are available.discoveredCommands: related commands exposed by the current Trae runtime.
Check through the proxy management API:
Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:1430/v1/local/trae/bridge/status' -TimeoutSec 5Direct bridge smoke test:
$payload = @{ prompt = '请只回复 OK'; model = 'minimax-m3' } | ConvertTo-Json
Invoke-WebRequest `
-UseBasicParsing `
-Uri 'http://127.0.0.1:17861/v1/chat' `
-Method POST `
-ContentType 'application/json; charset=utf-8' `
-Body $payload `
-TimeoutSec 240Expected:
{"ok":true,"command":"wx.bridge.sendAndWaitResponse","text":"OK"}Full OpenAI Chat proxy test on port 3447:
$payload = @{
model = 'trae/minimax-m3'
messages = @(@{ role = 'user'; content = '请只回复 OK_FROM_PROXY' })
stream = $false
} | ConvertTo-Json -Depth 8
Invoke-WebRequest `
-UseBasicParsing `
-Uri 'https://127.0.0.1:3447/v1/chat/completions' `
-Method POST `
-ContentType 'application/json; charset=utf-8' `
-Body $payload `
-SkipCertificateCheck `
-TimeoutSec 240Expected response shape:
{
"object": "chat.completion",
"model": "minimax-m3",
"choices": [
{
"message": {
"role": "assistant",
"content": "OK_FROM_PROXY"
}
}
]
}Full OpenAI Responses proxy test on port 3445:
$payload = @{
model = 'trae/minimax-m3'
input = '请只回复 RESPONSES_OK'
stream = $false
} | ConvertTo-Json -Depth 8
Invoke-WebRequest `
-UseBasicParsing `
-Uri 'https://127.0.0.1:3445/v1/responses' `
-Method POST `
-ContentType 'application/json; charset=utf-8' `
-Body $payload `
-SkipCertificateCheck `
-TimeoutSec 240Full Claude Messages proxy test on port 3443:
$payload = @{
model = 'trae/minimax-m3'
max_tokens = 64
messages = @(@{ role = 'user'; content = '请只回复 CLAUDE_OK' })
stream = $false
} | ConvertTo-Json -Depth 8
Invoke-WebRequest `
-UseBasicParsing `
-Uri 'https://127.0.0.1:3443/v1/messages' `
-Method POST `
-ContentType 'application/json; charset=utf-8' `
-Body $payload `
-SkipCertificateCheck `
-TimeoutSec 240Full OpenAI Chat streaming proxy test:
$payload = @{
model = 'trae/minimax-m3'
messages = @(@{ role = 'user'; content = '请只回复 STREAM_OK' })
stream = $true
} | ConvertTo-Json -Depth 8
Invoke-WebRequest `
-UseBasicParsing `
-Uri 'https://127.0.0.1:3447/v1/chat/completions' `
-Method POST `
-ContentType 'application/json; charset=utf-8' `
-Body $payload `
-SkipCertificateCheck `
-TimeoutSec 240Expected content type is text/event-stream, with chunks ending in:
data: [DONE]
The local proxy can expose multiple protocol entries. In the current setup:
https://localhost:3443/v1is the Claude entry.https://localhost:3445/v1is the OpenAI Responses entry.https://localhost:3447/v1is the OpenAI Chat entry.
Trae official bridge now supports:
3443 /v1/messages3445 /v1/responses3447 /v1/chat/completions
Use the protocol path that matches the entry. Calling /v1/chat/completions on
the Claude entry still returns a path mismatch by design.
im.bridge.sendPromptStreaming is the public bridge command. It can reliably
surface cumulative text, interaction events, and heartbeats, but it usually
does not expose the earlier plan/tool/timing events that Trae shows in its own
UI. For deeper debugging, we verified a temporary patch against Trae's installed
workbench bundle.
The deepest working hook found so far is:
Trae workbench.desktop.main.js
-> TransportManager.executeStreamRequest
-> this._client.sendStreamRequest("request_stream", packet, onData, onError, onEnd)
-> onData(f)
-> f.data.event / f.data.payload
This is the first renderer-side stop after the Aha RPC request_stream returns.
It can expose events such as:
metadatamodel_configtoken_usagetiming_eventsplan_itemdone
A minimal direct bridge stream request for 请只回复 OK。 produced:
RAW_EVENT_COUNT=18
TEXT_DELTA_COUNT=1
That confirms raw_event can arrive before the final text_delta.
Install the experimental patch:
node scripts/patch-trae-raw-stream.jsThe script patches and backs up:
%LOCALAPPDATA%/Programs/Trae/resources/app/out/vs/workbench/workbench.desktop.main.js%LOCALAPPDATA%/Programs/Trae/resources/app/node_modules/@byted-icube/ai-modules-chat/dist/index.mjs
Restart Trae after patching, then check command registration:
Invoke-RestMethod http://127.0.0.1:17861/debug/commands |
ConvertTo-Json -Depth 6Expected field:
{
"rawStreamCommand": "im.bridge.subscribeRawStream"
}Inspect the raw queue snapshot:
Invoke-RestMethod http://127.0.0.1:17861/debug/raw-stream |
ConvertTo-Json -Depth 8Check direct SSE output:
$body = @{ prompt = '请只回复 OK。' } | ConvertTo-Json -Compress
$response = Invoke-WebRequest `
-Uri http://127.0.0.1:17861/v1/chat/stream `
-Method POST `
-ContentType 'application/json' `
-Body $body `
-TimeoutSec 120
([regex]::Matches($response.Content, '^event: raw_event', 'Multiline')).CountTo expose these process events at the OpenAI / Responses / Claude API layer, restart the proxy with:
$env:TRAE_BRIDGE_RAW_PROGRESS = 'comment'The proxy keeps them out of assistant text and writes SSE comments instead:
: trae-raw {"source":"trae-raw","event":"plan_item","agentStatus":"running","toolName":"finish"}
Standard SDKs may ignore SSE comments. curl, Invoke-WebRequest, or a custom
SSE client can see them. This is intended for debugging, not as a default mode.
If regular SDKs or clients need to see process summaries, use visible mode:
$env:TRAE_BRIDGE_RAW_PROGRESS = 'visible'
$env:TRAE_BRIDGE_LOG_PROGRESS = 'visible'That writes safe summaries into streamed assistant text:
[Trae tool_start command=icube.common.commands.tooling.listFolder]
[Trae plan_item tool=run_command status=success]
Visible mode feels closer to the Trae UI process stream, but it changes
assistant output text. For automation, prefer comment mode or a dedicated
diagnostic event consumer.
Raw payloads can contain prompt text, model config, token counts, ak / sk,
reasoning_content, thought, and other sensitive internals. The bridge
therefore recursively redacts before forwarding:
- token, authorization, API key, secret,
ak, andskfields become[redacted] - reasoning, thought, prompt, content, and text-like fields keep only
{ redacted: true, chars: N } - large objects are depth, array, and key-count limited
/v1/chat/streamforwards only Aha RPC events withservice:"chat"andmethod:"chat", filtering non-chat RPCs and duplicate UI-layer events
This mode edits Trae's installed files. Trae can show:
Trae 安装似乎损坏。请重新安装。
That is Trae's integrity check detecting the patched install, not a bridge code failure. This mode is useful for temporary diagnostics and proof-of-capability, but it should not be treated as the default product path.
Rollback:
node scripts/unpatch-trae-raw-stream.jsRestart Trae after rollback. The deep rawStreamCommand / raw_event path will
disappear, and the install-integrity warning should disappear as well. For
normal operation, prefer the public bridge command, log side-channel, and
heartbeat progress; keep Raw Stream Patch as a Debug-mode switch.
The existing default paths remain unchanged: public bridge, log side-channel, Raw Stream Patch, and visible summary mode do not depend on native injection. This line is only for checking whether deeper model-protocol traffic inside the Trae agent container is observable.
Install Frida tools once:
python -m pip install --user frida-toolsStatic/runtime scan:
node scripts/trae-deep-probe.js --launch-cmd
python scripts/trae-frida-scan.pyVerified layers so far:
ai_agent.dllcontains protocol strings such asLLMClientRequestRaw,LLMRawChatMessage,api/ide/v2/llm_raw_chat,request body, andresponse body.- The Trae process that loads
ai_agent.dllimportsWinHttpSendRequestandWinHttpWriteData, which are native HTTP outbound boundaries. - The Trae process that loads
aha_net.dllexportsAhaNet_fetchandAhaNet_WsClient_send, then enters Cronet throughsscronet.dll. - Default Trae logs usually expose URL,
body_len, and typed request summaries, not full request bodies.
After identifying PIDs, run the redacted native hook:
python scripts/trae-frida-scan.py
# Pick the PID that has ai_agent.dll to watch WinHTTP boundaries
python scripts/trae-frida-native-probe.py --pid <ai_agent_pid> --duration 45
# Pick the PID that has aha_net.dll to watch AhaNet / Cronet boundaries
python scripts/trae-frida-native-probe.py --pid <aha_net_pid> --duration 45trae-frida-native-probe.py defaults to redacted output: event names, lengths,
HTTP methods, URL paths, JSON-like hints, and protocol-keyword hits. It does not
print header values, request bodies, prompts, tokens, or model messages. Only
explicitly enabling --capture-preview prints a redacted preview:
python scripts/trae-frida-native-probe.py --pid <pid> --duration 20 --capture-preview--capture-preview can still contain sensitive local context. Use it only for a
short local diagnostic run, and do not paste its output into issues, logs, or
chat. Capturing the real model body would require a one-off local hook around
ai_agent.dll WinHTTP writes or aha_net.dll / sscronet.dll upload-provider
callbacks. That is a high-risk Debug path, not a permanent product feature.
If /v1/local/trae/bridge/status returns ECONNREFUSED:
- Confirm the extension is installed:
trae --list-extensions --show-versions - Reload the Trae window or restart Trae.
- Check
http://127.0.0.1:17861/health. - Confirm no other process owns port
17861.
If the bridge is healthy but generation fails:
- Send a tiny direct bridge prompt first.
- Make sure Trae itself is logged in and can send a message in the UI.
- Check the latest Trae logs under:
%APPDATA%/Trae/logs/<timestamp>/window1/renderer.log%APPDATA%/Trae/logs/<timestamp>/Modular/ai-agent_*_stdout.log - Look for:
send chatservice: "chat", method: "chat"ChatStreamFrontResponseReporter status Success
If a request fails with an entry path error:
- Match path to port:
- Claude:
3443 /v1/messages - Responses:
3445 /v1/responses - Chat:
3447 /v1/chat/completions
- Claude:
Run:
npm testThe test suite includes:
- OpenAI Chat request to Trae bridge prompt conversion.
- data URL image extraction for Trae bridge payloads.
- Trae bridge text wrapping into OpenAI
chat.completion.