Fix bridge ACP provider endpoint normalization
This commit is contained in:
parent
4164c9bad0
commit
ca34af24a7
@ -44,6 +44,12 @@ class AcpEndpointPaths {
|
||||
}
|
||||
|
||||
path = path.replaceFirst(RegExp(r'/+$'), '');
|
||||
if (path == '/acp-server' ||
|
||||
path.startsWith('/acp-server/') ||
|
||||
path == '/gateway' ||
|
||||
path.startsWith('/gateway/')) {
|
||||
return '';
|
||||
}
|
||||
return path == '/' ? '' : path;
|
||||
}
|
||||
}
|
||||
|
||||
48
test/runtime/acp_endpoint_paths_test.dart
Normal file
48
test/runtime/acp_endpoint_paths_test.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xworkmate/runtime/acp_endpoint_paths.dart';
|
||||
|
||||
void main() {
|
||||
group('ACP endpoint path resolution', () {
|
||||
test('resolves managed bridge origin to ACP HTTP RPC path', () {
|
||||
final endpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse('https://xworkmate-bridge.svc.plus'),
|
||||
);
|
||||
|
||||
expect(endpoint.toString(), 'https://xworkmate-bridge.svc.plus/acp/rpc');
|
||||
});
|
||||
|
||||
test('does not preserve provider mapping paths as app RPC bases', () {
|
||||
final codexEndpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse('https://xworkmate-bridge.svc.plus/acp-server/codex'),
|
||||
);
|
||||
final gatewayEndpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse('https://xworkmate-bridge.svc.plus/gateway/openclaw'),
|
||||
);
|
||||
|
||||
expect(
|
||||
codexEndpoint.toString(),
|
||||
'https://xworkmate-bridge.svc.plus/acp/rpc',
|
||||
);
|
||||
expect(
|
||||
gatewayEndpoint.toString(),
|
||||
'https://xworkmate-bridge.svc.plus/acp/rpc',
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'normalizes provider mapping paths even when ACP suffix is present',
|
||||
() {
|
||||
final endpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse(
|
||||
'https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc',
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
endpoint.toString(),
|
||||
'https://xworkmate-bridge.svc.plus/acp/rpc',
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -378,10 +378,7 @@ void main() {
|
||||
);
|
||||
|
||||
expect(fakeGoTaskService.executeCount, 0);
|
||||
expect(
|
||||
controller.chatMessages.last.text,
|
||||
contains('请先登录 svc.plus'),
|
||||
);
|
||||
expect(controller.chatMessages.last.text, contains('请先登录 svc.plus'));
|
||||
},
|
||||
);
|
||||
|
||||
@ -458,24 +455,24 @@ void main() {
|
||||
'session-token';
|
||||
controller.settingsControllerInternal.accountSessionInternal =
|
||||
const AccountSessionSummary(
|
||||
userId: 'user-1',
|
||||
email: 'review@svc.plus',
|
||||
name: 'Review User',
|
||||
role: 'reviewer',
|
||||
mfaEnabled: true,
|
||||
);
|
||||
userId: 'user-1',
|
||||
email: 'review@svc.plus',
|
||||
name: 'Review User',
|
||||
role: 'reviewer',
|
||||
mfaEnabled: true,
|
||||
);
|
||||
controller.settingsControllerInternal.accountSyncStateInternal =
|
||||
AccountSyncState.defaults().copyWith(
|
||||
syncedDefaults: AccountRemoteProfile.defaults().copyWith(
|
||||
bridgeServerUrl: capture.baseEndpoint.toString(),
|
||||
),
|
||||
syncState: 'ready',
|
||||
tokenConfigured: const AccountTokenConfigured(
|
||||
bridge: true,
|
||||
vault: false,
|
||||
apisix: false,
|
||||
),
|
||||
);
|
||||
syncedDefaults: AccountRemoteProfile.defaults().copyWith(
|
||||
bridgeServerUrl: capture.baseEndpoint.toString(),
|
||||
),
|
||||
syncState: 'ready',
|
||||
tokenConfigured: const AccountTokenConfigured(
|
||||
bridge: true,
|
||||
vault: false,
|
||||
apisix: false,
|
||||
),
|
||||
);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
@ -502,19 +499,6 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _waitForRequest(
|
||||
_CapabilityServerCapture capture, {
|
||||
required int minimumCount,
|
||||
}) async {
|
||||
for (var index = 0; index < 20; index += 1) {
|
||||
if (capture.requestCount >= minimumCount) {
|
||||
return;
|
||||
}
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
fail('Timed out waiting for $minimumCount capability requests');
|
||||
}
|
||||
|
||||
Future<_CapabilityServerCapture> _startCapabilityServer() async {
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
final capture = _CapabilityServerCapture._(
|
||||
|
||||
@ -282,6 +282,37 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'desktop task execution normalizes provider endpoint paths back to bridge RPC',
|
||||
() async {
|
||||
final capture = await _startAcpHttpServer();
|
||||
addTearDown(capture.close);
|
||||
final client = GatewayAcpClient(
|
||||
endpointResolver: () => capture.baseEndpoint,
|
||||
authorizationResolver: (_) async => 'bridge-token',
|
||||
);
|
||||
|
||||
final transport = ExternalCodeAgentAcpDesktopTransport(
|
||||
client: client,
|
||||
endpointResolver: (_) => capture.baseEndpoint,
|
||||
taskEndpointResolver: (_) =>
|
||||
capture.baseEndpoint.replace(path: '/acp-server/codex'),
|
||||
);
|
||||
|
||||
await transport.executeTask(
|
||||
_taskRequest(
|
||||
target: AssistantExecutionTarget.agent,
|
||||
provider: SingleAgentProvider.codex,
|
||||
),
|
||||
onUpdate: (_) {},
|
||||
);
|
||||
|
||||
expect(capture.authorizationHeader, 'Bearer bridge-token');
|
||||
expect(capture.requestPath, '/acp/rpc');
|
||||
expect(capture.requestPath, isNot(contains('/acp-server')));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'desktop task execution routes OpenClaw through bridge RPC with gateway params',
|
||||
() async {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user