feat(assistant): include attachment source paths in gateway prompts

This commit is contained in:
Haitao Pan 2026-06-12 16:58:24 +08:00
parent a1d905f272
commit 4b62300e40
7 changed files with 32 additions and 3 deletions

View File

@ -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)',
);
}
}

View File

@ -84,6 +84,7 @@ buildAssistantAttachmentPayloadsInternal(
fileName: attachment.name,
content: base64Encode(bytes),
sha256: crypto.sha256.convert(bytes).toString(),
sourcePath: attachment.path,
),
);
}

View File

@ -300,7 +300,7 @@ class GoTaskServiceRequest {
(item) => <String, dynamic>{
'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),

View File

@ -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<String, dynamic> 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,

View File

@ -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() ?? '',
);
}
}

View File

@ -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'),

View File

@ -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<dynamic>;
final attachment = attachments.single as Map<String, dynamic>;
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',
);
},
);