From 4b62300e40c1b8bdb36fa4dac4527bc3625f5916 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Fri, 12 Jun 2026 16:58:24 +0800 Subject: [PATCH] feat(assistant): include attachment source paths in gateway prompts --- .../app_controller_desktop_thread_actions.dart | 5 ++++- .../assistant/assistant_attachment_payloads.dart | 1 + lib/runtime/go_task_service_client.dart | 4 +++- lib/runtime/runtime_models_gateway_entities.dart | 4 ++++ lib/runtime/runtime_models_runtime_payloads.dart | 4 ++++ .../assistant_attachment_payloads_test.dart | 1 + .../runtime/assistant_execution_target_test.dart | 16 +++++++++++++++- 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/app/app_controller_desktop_thread_actions.dart b/lib/app/app_controller_desktop_thread_actions.dart index ad65345f..e0ecffdf 100644 --- a/lib/app/app_controller_desktop_thread_actions.dart +++ b/lib/app/app_controller_desktop_thread_actions.dart @@ -577,6 +577,7 @@ extension AppControllerDesktopThreadActions on AppController { sha256: key, type: attachment.type.trim(), uploadedAtMs: uploadedAtMs, + sourcePath: attachment.sourcePath.trim(), ), ); } @@ -998,8 +999,10 @@ extension AppControllerDesktopThreadActions on AppController { if (visibleTaskInputAttachments.isNotEmpty) { buffer.writeln('- taskInputAttachments:'); for (final attachment in visibleTaskInputAttachments) { + final sourcePath = attachment.sourcePath.trim(); + final pathSuffix = sourcePath.isEmpty ? '' : ', path: $sourcePath'; buffer.writeln( - ' - ${attachment.name.trim()} (${attachment.mimeType.trim()}, sha256: ${attachment.key})', + ' - ${attachment.name.trim()} (${attachment.mimeType.trim()}, sha256: ${attachment.key}$pathSuffix)', ); } } diff --git a/lib/features/assistant/assistant_attachment_payloads.dart b/lib/features/assistant/assistant_attachment_payloads.dart index b95d46a6..be57fd22 100644 --- a/lib/features/assistant/assistant_attachment_payloads.dart +++ b/lib/features/assistant/assistant_attachment_payloads.dart @@ -84,6 +84,7 @@ buildAssistantAttachmentPayloadsInternal( fileName: attachment.name, content: base64Encode(bytes), sha256: crypto.sha256.convert(bytes).toString(), + sourcePath: attachment.path, ), ); } diff --git a/lib/runtime/go_task_service_client.dart b/lib/runtime/go_task_service_client.dart index 6a07fa3a..fa7128c5 100644 --- a/lib/runtime/go_task_service_client.dart +++ b/lib/runtime/go_task_service_client.dart @@ -300,7 +300,7 @@ class GoTaskServiceRequest { (item) => { 'name': item.fileName, 'description': item.mimeType, - 'path': '', + 'path': item.sourcePath.trim(), }, ), ], @@ -314,6 +314,8 @@ class GoTaskServiceRequest { 'sizeBytes': goTaskServiceBase64Size(item.content), if (goTaskServiceAttachmentSha256(item).isNotEmpty) 'sha256': goTaskServiceAttachmentSha256(item), + if (item.sourcePath.trim().isNotEmpty) + 'sourcePath': item.sourcePath.trim(), }, ) .toList(growable: false), diff --git a/lib/runtime/runtime_models_gateway_entities.dart b/lib/runtime/runtime_models_gateway_entities.dart index a9a71e2d..62b06490 100644 --- a/lib/runtime/runtime_models_gateway_entities.dart +++ b/lib/runtime/runtime_models_gateway_entities.dart @@ -17,6 +17,7 @@ class GatewayChatAttachmentPayload { required this.fileName, required this.content, this.sha256 = '', + this.sourcePath = '', }); final String type; @@ -24,6 +25,7 @@ class GatewayChatAttachmentPayload { final String fileName; final String content; final String sha256; + final String sourcePath; Map toJson() { return { @@ -32,6 +34,7 @@ class GatewayChatAttachmentPayload { 'fileName': fileName, 'content': content, if (sha256.trim().isNotEmpty) 'sha256': sha256.trim(), + if (sourcePath.trim().isNotEmpty) 'sourcePath': sourcePath.trim(), }; } } @@ -360,6 +363,7 @@ class LocalDeviceIdentity { ); } } + class CollaborationAttachment { const CollaborationAttachment({ required this.name, diff --git a/lib/runtime/runtime_models_runtime_payloads.dart b/lib/runtime/runtime_models_runtime_payloads.dart index 07de2ced..5035fc14 100644 --- a/lib/runtime/runtime_models_runtime_payloads.dart +++ b/lib/runtime/runtime_models_runtime_payloads.dart @@ -855,6 +855,7 @@ class TaskInputAttachmentRecord { required this.sha256, required this.type, required this.uploadedAtMs, + this.sourcePath = '', }); final String name; @@ -862,6 +863,7 @@ class TaskInputAttachmentRecord { final String sha256; final String type; final double uploadedAtMs; + final String sourcePath; String get key => sha256.trim().toLowerCase(); @@ -872,6 +874,7 @@ class TaskInputAttachmentRecord { 'sha256': key, 'type': type.trim(), 'uploadedAtMs': uploadedAtMs, + if (sourcePath.trim().isNotEmpty) 'sourcePath': sourcePath.trim(), }; } @@ -889,6 +892,7 @@ class TaskInputAttachmentRecord { sha256: json['sha256']?.toString().trim().toLowerCase() ?? '', type: json['type']?.toString().trim() ?? '', uploadedAtMs: uploadedAtMs(json['uploadedAtMs']), + sourcePath: json['sourcePath']?.toString().trim() ?? '', ); } } diff --git a/test/features/assistant/assistant_attachment_payloads_test.dart b/test/features/assistant/assistant_attachment_payloads_test.dart index d6bd4ffc..6ff85719 100644 --- a/test/features/assistant/assistant_attachment_payloads_test.dart +++ b/test/features/assistant/assistant_attachment_payloads_test.dart @@ -34,6 +34,7 @@ void main() { expect(payloads.single.fileName, 'note.txt'); expect(payloads.single.mimeType, 'text/plain'); expect(payloads.single.type, 'file'); + expect(payloads.single.sourcePath, file.path); expect( base64Decode(payloads.single.content), utf8.encode('hello attachment'), diff --git a/test/runtime/assistant_execution_target_test.dart b/test/runtime/assistant_execution_target_test.dart index 166f59be..a161af4c 100644 --- a/test/runtime/assistant_execution_target_test.dart +++ b/test/runtime/assistant_execution_target_test.dart @@ -1591,6 +1591,7 @@ void main() { mimeType: 'text/plain', fileName: 'note.txt', content: base64Encode(utf8.encode('note body')), + sourcePath: '/Users/shenlan/Desktop/note.txt', ), ], ); @@ -1615,7 +1616,11 @@ void main() { final attachments = params['attachments'] as List; final attachment = attachments.single as Map; expect(attachment['name'], 'note.txt'); - expect(attachment['path'], isEmpty); + expect(attachment['path'], '/Users/shenlan/Desktop/note.txt'); + expect( + inlineAttachment['sourcePath'], + '/Users/shenlan/Desktop/note.txt', + ); }, ); @@ -1636,6 +1641,7 @@ void main() { mimeType: 'image/png', fileName: 'diagram.png', content: base64Encode(utf8.encode('image bytes')), + sourcePath: '/Users/shenlan/Pictures/diagram.png', ); await controller.sendChatMessage( @@ -1662,11 +1668,19 @@ void main() { fakeGoTaskService.requests.last.prompt, contains('diagram.png (image/png, sha256:'), ); + expect( + fakeGoTaskService.requests.last.prompt, + contains('path: /Users/shenlan/Pictures/diagram.png'), + ); final thread = controller.requireTaskThreadForSessionInternal( fakeGoTaskService.requests.last.sessionId, ); expect(thread.taskInputAttachments, hasLength(1)); expect(thread.taskInputAttachments.single.name, 'diagram.png'); + expect( + thread.taskInputAttachments.single.sourcePath, + '/Users/shenlan/Pictures/diagram.png', + ); }, );