From de6f4677ca837e7099a64c48e16dc876e5deda5f Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Mon, 6 Apr 2026 13:53:25 +0800 Subject: [PATCH] Fix chat.send metadata regression across gateway clients --- lib/runtime/gateway_runtime_api.dart | 1 - lib/web/web_relay_gateway_client.dart | 5 --- test/runtime/gateway_runtime_suite.dart | 37 ++++++++++++++++ test/web/web_relay_gateway_client_test.dart | 49 +++++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 test/web/web_relay_gateway_client_test.dart diff --git a/lib/runtime/gateway_runtime_api.dart b/lib/runtime/gateway_runtime_api.dart index 692eb2b2..8bf8eed2 100644 --- a/lib/runtime/gateway_runtime_api.dart +++ b/lib/runtime/gateway_runtime_api.dart @@ -146,7 +146,6 @@ extension GatewayRuntimeApiInternal on GatewayRuntime { 'idempotencyKey': runId, if (agentId != null && agentId.trim().isNotEmpty) 'agentId': agentId.trim(), - if (metadata != null && metadata.isNotEmpty) 'metadata': metadata, if (attachments.isNotEmpty) 'attachments': attachments .map((attachment) => attachment.toJson()) diff --git a/lib/web/web_relay_gateway_client.dart b/lib/web/web_relay_gateway_client.dart index b0e81ac2..15a8d4c7 100644 --- a/lib/web/web_relay_gateway_client.dart +++ b/lib/web/web_relay_gateway_client.dart @@ -294,10 +294,6 @@ class WebRelayGatewayClient { Map metadata = const {}, }) async { final runId = randomIdInternal(); - final normalizedMetadata = { - for (final entry in metadata.entries) - if (entry.key.trim().isNotEmpty) entry.key: entry.value, - }; final payload = asMapInternal( await request( 'chat.send', @@ -309,7 +305,6 @@ class WebRelayGatewayClient { 'attachments': attachments .map((item) => item.toJson()) .toList(growable: false), - if (normalizedMetadata.isNotEmpty) 'metadata': normalizedMetadata, 'timeoutMs': 30000, 'idempotencyKey': runId, }, diff --git a/test/runtime/gateway_runtime_suite.dart b/test/runtime/gateway_runtime_suite.dart index bb0ec716..19171df7 100644 --- a/test/runtime/gateway_runtime_suite.dart +++ b/test/runtime/gateway_runtime_suite.dart @@ -34,6 +34,24 @@ void main() { ); }); + test('GatewayRuntime omits metadata from chat.send payloads', () async { + SharedPreferences.setMockInitialValues({}); + final store = createIsolatedTestStore(); + final runtime = _FakeGatewayRuntimeForSendChat(store: store); + + final runId = await runtime.sendChat( + sessionKey: 'thread-1', + message: 'hello', + thinking: 'medium', + metadata: const {'threadMode': 'test'}, + ); + + expect(runId, 'run-send-chat'); + expect(runtime.lastMethod, 'chat.send'); + expect(runtime.lastParams, isNotNull); + expect(runtime.lastParams, isNot(contains('metadata'))); + }); + test( 'GatewayRuntime uses explicit shared token override for the initial connect handshake', () async { @@ -669,6 +687,25 @@ class _FakeGatewayRuntimeForChatController extends GatewayRuntime { } } +class _FakeGatewayRuntimeForSendChat extends GatewayRuntime { + _FakeGatewayRuntimeForSendChat({required super.store}) + : super(identityStore: DeviceIdentityStore(store)); + + String? lastMethod; + Map? lastParams; + + @override + Future request( + String method, { + Map? params, + Duration timeout = const Duration(seconds: 15), + }) async { + lastMethod = method; + lastParams = params == null ? null : Map.from(params); + return const {'runId': 'run-send-chat'}; + } +} + class FakeGatewayRuntimeServerInternal { FakeGatewayRuntimeServerInternal._( this.serverInternal, { diff --git a/test/web/web_relay_gateway_client_test.dart b/test/web/web_relay_gateway_client_test.dart new file mode 100644 index 00000000..a23b2616 --- /dev/null +++ b/test/web/web_relay_gateway_client_test.dart @@ -0,0 +1,49 @@ +@TestOn('vm') +library; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:xworkmate/runtime/runtime_models.dart'; +import 'package:xworkmate/web/web_relay_gateway_client.dart'; +import 'package:xworkmate/web/web_store.dart'; + +class _FakeWebRelayGatewayClient extends WebRelayGatewayClient { + _FakeWebRelayGatewayClient() : super(WebStore()); + + String? lastMethod; + Map? lastParams; + + @override + bool get isConnected => true; + + @override + Future request( + String method, { + Map? params, + Duration timeout = const Duration(seconds: 15), + }) async { + lastMethod = method; + lastParams = params == null ? null : Map.from(params); + return const {'runId': 'relay-run'}; + } +} + +void main() { + test('WebRelayGatewayClient omits metadata from chat.send payloads', () async { + SharedPreferences.setMockInitialValues({}); + final client = _FakeWebRelayGatewayClient(); + + final runId = await client.sendChat( + sessionKey: 'thread-1', + message: 'hello', + thinking: 'medium', + metadata: const {'threadMode': 'test'}, + attachments: const [], + ); + + expect(runId, 'relay-run'); + expect(client.lastMethod, 'chat.send'); + expect(client.lastParams, isNotNull); + expect(client.lastParams, isNot(contains('metadata'))); + }); +}