refactor(acp): unify ingress topology and migrate all services to xworkmate-bridge.svc.plus

This commit is contained in:
Haitao Pan 2026-04-20 11:30:42 +08:00
parent 69045c6684
commit 104ab26356
10 changed files with 70 additions and 30 deletions

View File

@ -32,6 +32,7 @@ Verified public HTTP JSON-RPC endpoints:
- Codex: `https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc`
- OpenCode: `https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc`
- Gemini: `https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc`
- OpenClaw: `https://xworkmate-bridge.svc.plus/gateway/openclaw/`
The `.../acp` path remains reserved for WebSocket ACP.
@ -46,11 +47,12 @@ Missing bearer auth returns a JSON-RPC error envelope with code `-32001`.
## Public Validation Results
The ingress returned `200 OK` on all three public routes after re-apply, and the deployment response confirmed the active upstream mappings:
The ingress returned `200 OK` on all public routes after re-apply, and the deployment response confirmed the active upstream mappings:
- `codex` -> `127.0.0.1:9010`
- `opencode` -> `127.0.0.1:3910`
- `gemini` -> `127.0.0.1:8791`
- `openclaw` -> `127.0.0.1:18789` (Host process)
### Codex

View File

@ -58,7 +58,7 @@ flowchart TD
C1["https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc"]
C2["https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc"]
C3["https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc"]
C4["wss://openclaw.svc.plus"]
C4["https://xworkmate-bridge.svc.plus/gateway/openclaw/"]
end
B7 --> C1
@ -110,7 +110,7 @@ flowchart LR
U1["https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc"]
U2["https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc"]
U3["https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc"]
U4["wss://openclaw.svc.plus<br/>reported as openclaw.svc.plus:443"]
U4["https://xworkmate-bridge.svc.plus/gateway/openclaw/<br/>reported as xworkmate-bridge.svc.plus:443"]
end
APPMETHODS --> BRIDGE
@ -138,7 +138,7 @@ Important distinction:
- `availableExecutionTargets` tells the app which first-level task dialog modes
are currently available
- for `gatewayProviderId=openclaw`, the bridge rewrites the upstream target to
`wss://openclaw.svc.plus`
`https://xworkmate-bridge.svc.plus/gateway/openclaw/`
## Production Truth
当前 production forwarding 事实:
@ -152,7 +152,7 @@ Important distinction:
- `opencode`
- `gemini`
- current production gateway forwarding target:
- `openclaw -> wss://openclaw.svc.plus`
- `openclaw -> https://xworkmate-bridge.svc.plus/gateway/openclaw/`
对 app 而言:
@ -163,7 +163,7 @@ Important distinction:
## Invariants
- app traffic reaches upstream ACP and gateway services only through the bridge
- app does not call `xworkmate-bridge.svc.plus/acp-server/*` or `openclaw.svc.plus` directly
- app does not call `xworkmate-bridge.svc.plus/acp-server/*` or `xworkmate-bridge.svc.plus/gateway/openclaw/` directly
- upstream auth stays bridge-internal:
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
- `acp.capabilities` is the provider / capability discovery source

View File

@ -127,7 +127,7 @@ Use these terms consistently:
- `canonical app-facing path`: `/acp/rpc` and `/acp`
- `gateway runtime method family`: `xworkmate.gateway.*`
- `independent upstream service`: `xworkmate-bridge.svc.plus/acp-server/*`, `wss://openclaw.svc.plus`
- `independent upstream service`: `xworkmate-bridge.svc.plus/acp-server/*`, `xworkmate-bridge.svc.plus/gateway/openclaw/`
- `bridge-owned routing`: provider / gateway selection performed inside bridge
- `routing metadata`: execution target and resolved provider/gateway identifiers returned to the app

View File

@ -13,6 +13,15 @@
| `gemini-acp-adapter` | `geminiadapter.Serve` | 启动 Gemini 专用 ACP adapter把 Gemini CLI 包装成 ACP HTTP / WebSocket 服务。 |
| 默认模式 | `toolbridge.Run` | 启动 MCP 风格的本地工具桥,暴露 `chat`、`claude_review`、`vault_kv` 等工具。 |
|服务 │ 外部入口 (HTTPS/WSS) │ 后端转发目标 (Local) │ 部署方式 │
├──────────┼────────────────────────────────────────────────┼──────────────────────────┼─────────────┤
│ Bridge │ xworkmate-bridge.svc.plus/ │ 127.0.0.1:8787 │ Docker 容器 │
│ OpenClaw │ xworkmate-bridge.svc.plus/gateway/openclaw/ │ 127.0.0.1:18789 │ 主机进程 │
│ Codex │ xworkmate-bridge.svc.plus/acp-server/codex/ │ acp-server-codex:3911 │ Docker 容器 │
│ OpenCode │ xworkmate-bridge.svc.plus/acp-server/opencode/ │ acp-server-opencode:3910 │ Docker 容器 │
│ Gemini │ xworkmate-bridge.svc.plus/acp-server/gemini/ │ acp-server-gemini:3912 │ Docker 容器
设计含义:
- `main.go` 不承载业务决策,只做模式分发。

View File

@ -74,8 +74,9 @@ func applyProductionGatewayRouting(
if strings.TrimSpace(strings.ToLower(request.Mode)) != "openclaw" {
return request
}
// Route through the unified bridge ingress
request.Endpoint = gatewayruntime.Endpoint{
Host: "openclaw.svc.plus",
Host: "xworkmate-bridge.svc.plus",
Port: 443,
TLS: true,
}

View File

@ -36,7 +36,7 @@ func TestResolveGatewayReportedRemoteAddressNormalizesExplicitPublicRemoteHost(
got := resolveGatewayReportedRemoteAddress(server, gatewayruntime.ConnectRequest{
Mode: "openclaw",
Endpoint: gatewayruntime.Endpoint{
Host: "openclaw.svc.plus",
Host: "xworkmate-bridge.svc.plus",
Port: 443,
TLS: true,
},

View File

@ -71,16 +71,34 @@ func newProductionProviderCatalog() (map[string]syncedProvider, []string) {
config := loadBridgeConfig()
authorizationHeader := bridgeUpstreamAuthorizationHeader()
// Map both legacy OPENCLAW_*_URL and new *_RPC_URL patterns for compatibility
providers := []struct {
id string
label string
yaml string
envKeys []string
id string
label string
yaml string
envKeys []string
defaultURL string
}{
{"codex", "Codex", config.Upstream.CodexURL, []string{"CODEX_RPC_URL", "OPENCLAW_CODEX_URL"}},
{"opencode", "OpenCode", config.Upstream.OpenCodeURL, []string{"OPENCODE_RPC_URL", "OPENCLAW_OPENCODE_URL"}},
{"gemini", "Gemini", config.Upstream.GeminiURL, []string{"GEMINI_RPC_URL", "OPENCLAW_GEMINI_URL"}},
{
id: "codex",
label: "Codex",
yaml: config.Upstream.CodexURL,
envKeys: []string{"CODEX_RPC_URL"},
defaultURL: "https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc",
},
{
id: "opencode",
label: "OpenCode",
yaml: config.Upstream.OpenCodeURL,
envKeys: []string{"OPENCODE_RPC_URL"},
defaultURL: "https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc",
},
{
id: "gemini",
label: "Gemini",
yaml: config.Upstream.GeminiURL,
envKeys: []string{"GEMINI_RPC_URL"},
defaultURL: "https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc",
},
}
catalog := make(map[string]syncedProvider)
@ -88,6 +106,9 @@ func newProductionProviderCatalog() (map[string]syncedProvider, []string) {
for _, p := range providers {
endpoint := resolveURL(p.yaml, p.envKeys...)
if endpoint == "" {
endpoint = p.defaultURL
}
catalog[p.id] = syncedProvider{
ProviderID: p.id,
Label: p.label,

View File

@ -25,7 +25,7 @@ func TestManagerConnectUsesReportedRemoteAddressInSnapshot(t *testing.T) {
Port: server.Port(),
TLS: false,
},
ReportedRemoteAddress: "openclaw.svc.plus:443",
ReportedRemoteAddress: "xworkmate-bridge.svc.plus:443",
ConnectAuthMode: "shared-token",
ConnectAuthFields: []string{"token"},
ConnectAuthSources: []string{"shared:form"},
@ -54,7 +54,7 @@ func TestManagerConnectUsesReportedRemoteAddressInSnapshot(t *testing.T) {
t.Fatalf("expected connect success, got %#v", result.Error)
}
if got := result.Snapshot["remoteAddress"]; got != "openclaw.svc.plus:443" {
if got := result.Snapshot["remoteAddress"]; got != "xworkmate-bridge.svc.plus:443" {
t.Fatalf("expected reported remote address, got %#v", got)
}
}

View File

@ -213,11 +213,9 @@ func reconcileCodex(
}
state.DiscoveredMCPCount = discovered
state.ManagedMCPCount = len(managedServers)
if strings.TrimSpace(aiGatewayURL) != "" {
state.Detail = "LLM API uses launch-scoped defaults for collaboration runs."
} else {
state.Detail = "LLM API not configured."
}
state.Detail = "Codex public base URL: https://xworkmate-bridge.svc.plus/acp-server/codex\n" +
"Preferred WebSocket endpoint: https://xworkmate-bridge.svc.plus/acp-server/codex/acp\n" +
"Compatibility HTTP RPC endpoint: https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc"
return state
}
@ -246,8 +244,15 @@ func reconcileCLIListTarget(
}
state.DiscoveredMCPCount = discovered
state.ManagedMCPCount = len(enabledServers(config.ManagedMCPServers))
state.Detail = "MCP discovery uses `" + strings.Join(command, " ") +
"`; LLM API stays launch-scoped."
if targetID == "gemini" {
state.Detail = "Gemini public base URL: https://xworkmate-bridge.svc.plus/acp-server/gemini\n" +
"Preferred WebSocket endpoint: https://xworkmate-bridge.svc.plus/acp-server/gemini/acp\n" +
"Compatibility HTTP RPC endpoint: https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc"
} else {
state.Detail = "MCP discovery uses `" + strings.Join(command, " ") +
"`; LLM API stays launch-scoped."
}
return state
}
@ -286,7 +291,9 @@ func reconcileOpencode(config Config, opencodeHome string) MountTargetState {
}
state.DiscoveredMCPCount = discovered
state.ManagedMCPCount = len(managedServers)
state.Detail = "Managed MCP config is preserved in ~/.opencode/config.toml."
state.Detail = "OpenCode public base URL: https://xworkmate-bridge.svc.plus/acp-server/opencode\n" +
"Preferred WebSocket endpoint: https://xworkmate-bridge.svc.plus/acp-server/opencode/acp\n" +
"Compatibility HTTP RPC endpoint: https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc"
return state
}

View File

@ -54,9 +54,9 @@ fi
BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${2:-https://xworkmate-bridge.svc.plus}}")"
OPENCLAW_BASE_URL="$(normalize_url "${OPENCLAW_URL:-${3:-${BASE_URL}/gateway/openclaw}}")"
CODEX_BASE_URL="$(normalize_url "${CODEX_RPC_URL:-${4:-${BASE_URL}/acp-server/codex}}")"
OPENCODE_BASE_URL="$(normalize_url "${OPENCODE_RPC_URL:-${5:-${BASE_URL}/acp-server/opencode}}")"
GEMINI_BASE_URL="$(normalize_url "${GEMINI_RPC_URL:-${6:-${BASE_URL}/acp-server/gemini}}")"
CODEX_BASE_URL="$(normalize_url "${CODEX_RPC_URL:-${4:-${BASE_URL}/acp-server/codex/acp/rpc}}")"
OPENCODE_BASE_URL="$(normalize_url "${OPENCODE_RPC_URL:-${5:-${BASE_URL}/acp-server/opencode/acp/rpc}}")"
GEMINI_BASE_URL="$(normalize_url "${GEMINI_RPC_URL:-${6:-${BASE_URL}/acp-server/gemini/acp/rpc}}")"
AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-${INTERNAL_SERVICE_TOKEN:-${7:-}}}"
ensure_rpc_path() {