fix hermes provider command dispatch
This commit is contained in:
parent
5b5ffa86cd
commit
bf1fcac528
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
build/
|
||||
.env
|
||||
xworkmate-go-core-linux
|
||||
|
||||
@ -183,7 +183,7 @@ HTTP 与 WebSocket 统一使用 **JSON-RPC 2.0** 作为默认通信协议。为
|
||||
|
||||
说明:
|
||||
|
||||
- `bridgeOrigin` 读取 `BRIDGE_SERVER_URL`
|
||||
- `bridgeOrigin` 反映 bootstrap 阶段的 bridge metadata
|
||||
- 默认值:`https://xworkmate-bridge.svc.plus`
|
||||
|
||||
## 4. Bridge JSON-RPC Methods
|
||||
@ -838,7 +838,7 @@ bridge 在 session 执行期间会通过 `session.update` 推送通知,统一
|
||||
| `ACP_ALLOWED_ORIGINS` | `https://xworkmate.svc.plus,http://localhost:*,http://127.0.0.1:*` | bridge allowed origins |
|
||||
| `ACP_MULTI_AGENT_ENABLED` | `true` | `acp.capabilities` 中的 `multiAgent` 开关 |
|
||||
| `ACP_MULTI_AGENT_MODEL` | `gpt-4o` | multi-agent 默认模型 |
|
||||
| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` | bootstrap health 与外部调用统一使用的 bridge base URL |
|
||||
| `BRIDGE_SERVER_URL` | `https://xworkmate-bridge.svc.plus` | bootstrap health 的 metadata 默认值,不作为 app runtime 真源 |
|
||||
| `INTERNAL_SERVICE_TOKEN` | 空 | upstream provider / gateway 优先使用的内部服务 token |
|
||||
| `BRIDGE_AUTH_TOKEN` | 空 | bridge 入站 bearer token;当 `INTERNAL_SERVICE_TOKEN` 为空时也作为 upstream forwarding token |
|
||||
| `IMAGE` | 空 | `/api/ping` 版本信息来源 |
|
||||
|
||||
@ -16,7 +16,7 @@ See also:
|
||||
- `assistant` surface 进入 ACP control-plane:`acp.capabilities`、`xworkmate.routing.resolve`、`session.*`
|
||||
- `settings` surface 进入 gateway runtime / connection flow:`acp.capabilities`、`xworkmate.gateway.*`
|
||||
|
||||
不管 bridge 内部还保留哪些 provider / gateway mode / capability flag,app-facing 公共入口都只有 bridge origin。
|
||||
不管 bridge 内部还保留哪些 provider / gateway mode / capability flag,app-facing 公共入口都只有 bridge origin;`/acp-server/*` 和 `/gateway/openclaw` 属于 bridge-owned routing facts,不是 app-owned truth。
|
||||
|
||||
## Topology
|
||||
|
||||
|
||||
@ -144,6 +144,17 @@ func ResolveProviderCommand(
|
||||
"-p",
|
||||
prompt,
|
||||
}
|
||||
case "hermes":
|
||||
binary := strings.TrimSpace(EnvOrDefault("ACP_HERMES_BIN", "hermes"))
|
||||
if strings.TrimSpace(model) == "" {
|
||||
return binary, []string{"-p", prompt}
|
||||
}
|
||||
return binary, []string{
|
||||
"--model",
|
||||
strings.TrimSpace(model),
|
||||
"-p",
|
||||
prompt,
|
||||
}
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@ -36,3 +36,19 @@ func TestNormalizeProviderWorkingDirectorySkipsUnknownProvider(t *testing.T) {
|
||||
t.Fatalf("expected unknown provider to keep dir, got %q %q", got, effective)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveProviderCommandSupportsHermes(t *testing.T) {
|
||||
t.Setenv("ACP_HERMES_BIN", "/usr/local/bin/hermes")
|
||||
|
||||
command, args := ResolveProviderCommand("hermes", "sonnet", "hello world", "/tmp/work")
|
||||
|
||||
if command != "/usr/local/bin/hermes" {
|
||||
t.Fatalf("expected hermes binary override, got %q", command)
|
||||
}
|
||||
if len(args) != 4 {
|
||||
t.Fatalf("expected hermes args with model and prompt, got %#v", args)
|
||||
}
|
||||
if args[0] != "--model" || args[1] != "sonnet" || args[2] != "-p" || args[3] != "hello world" {
|
||||
t.Fatalf("unexpected hermes args: %#v", args)
|
||||
}
|
||||
}
|
||||
|
||||
78
scripts/ci/verify_api_interface_contract.sh
Executable file
78
scripts/ci/verify_api_interface_contract.sh
Executable file
@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/ci/verify_api_interface_contract.sh
|
||||
set -euo pipefail
|
||||
|
||||
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||
BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
||||
|
||||
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
||||
echo "Error: BRIDGE_AUTH_TOKEN is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- Verifying API Interface Contract for $BRIDGE_SERVER_URL ---"
|
||||
|
||||
check_endpoint() {
|
||||
local name=$1
|
||||
local path=$2
|
||||
local expected_status=$3
|
||||
local content_type=$4
|
||||
|
||||
echo -n "Checking $name ($path)... "
|
||||
local response_info
|
||||
response_info=$(curl -s -o /tmp/resp.body -w "%{http_code} %{content_type}" \
|
||||
-H "Authorization: Bearer $BRIDGE_AUTH_TOKEN" \
|
||||
"$BRIDGE_SERVER_URL$path")
|
||||
|
||||
local status=$(echo "$response_info" | cut -d' ' -f1)
|
||||
local actual_ct=$(echo "$response_info" | cut -d' ' -f2-)
|
||||
|
||||
if [[ "$status" == "$expected_status" ]]; then
|
||||
if [[ "$actual_ct" == *"$content_type"* ]]; then
|
||||
# 验证是否为有效的 JSON 且包含 ok: true
|
||||
if jq -e '.ok == true' /tmp/resp.body >/dev/null 2>&1; then
|
||||
echo "✅ OK ($status, application/json)"
|
||||
else
|
||||
echo "❌ Failed (Invalid Bridge Response Structure)"
|
||||
cat /tmp/resp.body
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Failed: Wrong Content-Type (Expected $content_type, got $actual_ct)"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Failed (Expected $expected_status, got $status)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 现在的架构下,所有路径都应该由 Bridge 统一处理并返回 200 JSON
|
||||
check_endpoint "OpenClaw" "/gateway/openclaw" "200" "application/json"
|
||||
check_endpoint "OpenCode" "/acp-server/opencode" "200" "application/json"
|
||||
check_endpoint "Codex" "/acp-server/codex" "200" "application/json"
|
||||
check_endpoint "Gemini" "/acp-server/gemini" "200" "application/json"
|
||||
check_endpoint "Hermes" "/acp-server/hermes" "200" "application/json"
|
||||
|
||||
# 6. Aggregate RPC Endpoint
|
||||
echo -n "Checking Aggregate RPC (/acp/rpc)... "
|
||||
rpc_status=$(curl -s -o /tmp/rpc.resp -w "%{http_code}" \
|
||||
-X POST -H "Authorization: Bearer $BRIDGE_AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","method":"acp.capabilities","params":{},"id":1}' \
|
||||
"$BRIDGE_SERVER_URL/acp/rpc")
|
||||
|
||||
if [[ "$rpc_status" == "200" ]]; then
|
||||
if jq -e '.ok == true' /tmp/rpc.resp >/dev/null 2>&1; then
|
||||
echo "✅ OK (200 + Valid JSON-RPC Result)"
|
||||
else
|
||||
echo "❌ Failed (Invalid JSON-RPC Response)"
|
||||
cat /tmp/rpc.resp
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Failed ($rpc_status)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Interface contract verification completed."
|
||||
60
scripts/ci/verify_api_scenario_contract.sh
Executable file
60
scripts/ci/verify_api_scenario_contract.sh
Executable file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/ci/verify_api_scenario_contract.sh
|
||||
set -euo pipefail
|
||||
|
||||
BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}"
|
||||
BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}"
|
||||
|
||||
if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then
|
||||
echo "Error: BRIDGE_AUTH_TOKEN is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--- Verifying API Scenario Contract for $BRIDGE_SERVER_URL ---"
|
||||
|
||||
# Scenario: Discovery -> Initialize Session
|
||||
# 1. Discover capabilities and find a provider
|
||||
echo "Step 1: Discovery"
|
||||
CAPS=$(curl -s -X POST -H "Authorization: Bearer $BRIDGE_AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","method":"acp.capabilities","params":{},"id":"scen-1"}' \
|
||||
"$BRIDGE_SERVER_URL/acp/rpc")
|
||||
|
||||
FIRST_PROVIDER=$(echo "$CAPS" | python3 -c 'import json, sys; d=json.load(sys.stdin); print(d.get("result", {}).get("providerCatalog", [{}])[0].get("providerId", ""))')
|
||||
|
||||
if [[ -z "$FIRST_PROVIDER" ]]; then
|
||||
echo "❌ Error: No providers found in catalog"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Found provider: $FIRST_PROVIDER"
|
||||
|
||||
# 2. Attempt session.start
|
||||
echo "Step 2: Session Initialization"
|
||||
SESSION_ID="test-scenario-$(date +%s)"
|
||||
START_RESP=$(curl -s -X POST -H "Authorization: Bearer $BRIDGE_AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"jsonrpc\":\"2.0\",\"method\":\"session.start\",\"params\":{\"sessionId\":\"$SESSION_ID\",\"routing\":{\"routingMode\":\"explicit\",\"explicitExecutionTarget\":\"singleAgent\",\"explicitProviderId\":\"$FIRST_PROVIDER\"}},\"id\":\"scen-2\"}" \
|
||||
"$BRIDGE_SERVER_URL/acp/rpc")
|
||||
|
||||
# Check if response is valid JSON-RPC
|
||||
if ! echo "$START_RESP" | jq . >/dev/null 2>&1; then
|
||||
echo "❌ Error: session.start returned invalid JSON"
|
||||
echo "$START_RESP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Analyze result
|
||||
SUCCESS=$(echo "$START_RESP" | python3 -c 'import json, sys; d=json.load(sys.stdin); print(d.get("result", {}).get("success", "false"))')
|
||||
ERROR_MSG=$(echo "$START_RESP" | python3 -c 'import json, sys; d=json.load(sys.stdin); print(d.get("error", {}).get("message", d.get("result", {}).get("error", "")))')
|
||||
|
||||
if [[ "$SUCCESS" == "True" ]]; then
|
||||
echo "✅ Session started successfully"
|
||||
elif [[ "$ERROR_MSG" == *"connection refused"* || "$ERROR_MSG" == *"ROUTING_REQUIRED"* ]]; then
|
||||
echo "✅ Scenario logic verified (Gateway correctly routed but backend agent is unreachable: $ERROR_MSG)"
|
||||
else
|
||||
echo "❌ Session start failed with unexpected error: $ERROR_MSG"
|
||||
echo "$START_RESP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Scenario contract verification completed."
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user