fix: retry ACP websocket after plain-text 404
This commit is contained in:
parent
173ccd83d7
commit
cedf4c481e
@ -321,7 +321,22 @@ class GatewayAcpClient {
|
||||
);
|
||||
} catch (error) {
|
||||
if (error is GatewayAcpException) {
|
||||
rethrow;
|
||||
if (!_shouldRetryViaWebSocketAfterHttpFailure(
|
||||
error,
|
||||
endpoint: resolvedEndpoint,
|
||||
)) {
|
||||
rethrow;
|
||||
}
|
||||
try {
|
||||
return await _requestViaWebSocket(
|
||||
request,
|
||||
onNotification: onNotification,
|
||||
endpointOverride: resolvedEndpoint,
|
||||
authorizationOverride: authorizationOverride,
|
||||
);
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
return _requestViaWebSocket(
|
||||
request,
|
||||
@ -544,6 +559,22 @@ class GatewayAcpClient {
|
||||
return base;
|
||||
}
|
||||
|
||||
bool _shouldRetryViaWebSocketAfterHttpFailure(
|
||||
GatewayAcpException error, {
|
||||
required Uri? endpoint,
|
||||
}) {
|
||||
if (resolveAcpWebSocketEndpoint(endpoint) == null) {
|
||||
return false;
|
||||
}
|
||||
final details = asMap(error.details);
|
||||
final statusCode = intValue(details['statusCode']);
|
||||
final contentType = (stringValue(details['contentType']) ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return statusCode == HttpStatus.notFound &&
|
||||
(contentType.isEmpty || contentType.contains('text/plain'));
|
||||
}
|
||||
|
||||
bool _contentTypeLooksJsonOrSse(String contentType) {
|
||||
return contentType.contains('application/json') ||
|
||||
contentType.contains('application/problem+json') ||
|
||||
|
||||
@ -117,6 +117,27 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'falls back to websocket when HTTP bridge returns plain-text 404',
|
||||
() async {
|
||||
final server = await _AcpFakeServer.start(
|
||||
respondWithPlainTextNotFound: true,
|
||||
);
|
||||
addTearDown(server.close);
|
||||
|
||||
final client = GatewayAcpClient(
|
||||
endpointResolver: () => server.baseHttpUri,
|
||||
);
|
||||
|
||||
final capabilities = await client.loadCapabilities(forceRefresh: true);
|
||||
|
||||
expect(capabilities.singleAgent, isTrue);
|
||||
expect(server.lastHttpRequestPath, '/acp/rpc');
|
||||
expect(server.lastWebSocketRequestPath, '/acp');
|
||||
expect(server.rpcMethods, contains('acp.capabilities'));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'forwards ACP authorization resolver headers over websocket',
|
||||
() async {
|
||||
@ -234,12 +255,14 @@ class _AcpFakeServer {
|
||||
this._server, {
|
||||
required this.disableWebSocket,
|
||||
required this.respondWithHtmlError,
|
||||
required this.respondWithPlainTextNotFound,
|
||||
required this.pathPrefix,
|
||||
});
|
||||
|
||||
final HttpServer _server;
|
||||
final bool disableWebSocket;
|
||||
final bool respondWithHtmlError;
|
||||
final bool respondWithPlainTextNotFound;
|
||||
final String pathPrefix;
|
||||
final List<String> rpcMethods = <String>[];
|
||||
String? lastWebSocketAuthorization;
|
||||
@ -253,6 +276,7 @@ class _AcpFakeServer {
|
||||
static Future<_AcpFakeServer> start({
|
||||
bool disableWebSocket = false,
|
||||
bool respondWithHtmlError = false,
|
||||
bool respondWithPlainTextNotFound = false,
|
||||
String pathPrefix = '',
|
||||
}) async {
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
@ -260,6 +284,7 @@ class _AcpFakeServer {
|
||||
server,
|
||||
disableWebSocket: disableWebSocket,
|
||||
respondWithHtmlError: respondWithHtmlError,
|
||||
respondWithPlainTextNotFound: respondWithPlainTextNotFound,
|
||||
pathPrefix: _normalizePathPrefix(pathPrefix),
|
||||
);
|
||||
unawaited(fake._listen());
|
||||
@ -333,6 +358,13 @@ class _AcpFakeServer {
|
||||
await request.response.close();
|
||||
return;
|
||||
}
|
||||
if (respondWithPlainTextNotFound) {
|
||||
request.response.statusCode = HttpStatus.notFound;
|
||||
request.response.headers.set(HttpHeaders.contentTypeHeader, 'text/plain');
|
||||
request.response.write('not found');
|
||||
await request.response.close();
|
||||
return;
|
||||
}
|
||||
final body = await utf8.decodeStream(request);
|
||||
final envelope = _decodeMap(body);
|
||||
final id = envelope['id'];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user