feat: add secure remote channel media

This commit is contained in:
lofyer
2026-08-09 20:33:35 +08:00
parent 6c891f3522
commit 66b098ae36
46 changed files with 4403 additions and 1594 deletions
+66
View File
@@ -15,6 +15,7 @@ import type {
ContextAttachment,
WindowCaptureOption
} from '../shared/contracts'
import type { ChannelMediaAttachment } from '../shared/channel-contracts'
import type {
AgentExecutionRequest,
AgentImage
@@ -101,6 +102,20 @@ function formatParsedDocument(
.join('\n\n')
}
function remoteAttachmentName(value: string): string {
const sanitized = [...value]
.map((character) => {
const code = character.codePointAt(0)
return code !== undefined && (code <= 31 || code === 127)
? '_'
: character
})
.join('')
const name = basename(sanitized.replaceAll('\\', '/'))
.trim()
return name.slice(0, 500) || '远程附件'
}
export class ContextManager {
private readonly contexts = new Map<string, StoredContext>()
private totalBytes = 0
@@ -178,6 +193,57 @@ export class ContextManager {
return this.toPublic(context)
}
async ingestRemoteAttachment(
attachment: ChannelMediaAttachment
): Promise<ContextAttachment> {
const data = Buffer.from(attachment.dataBase64, 'base64')
if (
data.byteLength !== attachment.size ||
data.byteLength === 0 ||
data.byteLength > maximumContextBytes
) {
throw new Error('远程附件大小无效')
}
const name = remoteAttachmentName(attachment.name)
const extension = extname(name).toLocaleLowerCase()
if (
attachment.kind === 'image' ||
supportedImageExtensions.has(extension)
) {
if (
attachment.mimeType !== 'image/jpeg' &&
attachment.mimeType !== 'image/png' &&
attachment.mimeType !== 'image/webp'
) {
throw new Error('远程图片格式不受支持')
}
return this.storeImage(
name,
nativeImage.createFromBuffer(data)
)
}
if (supportedDocumentExtensions.has(extension)) {
const parsed = await parseDocument(name, data)
return this.storeText(
name,
truncateUtf8(
formatParsedDocument(parsed.sections),
maximumFileSize
)
)
}
if (!supportedExtensions.has(extension)) {
throw new Error(`${extension || '未知'}`)
}
if (data.byteLength > maximumFileSize) {
throw new Error('远程文本文件不能超过 256KB')
}
const content = new TextDecoder('utf-8', {
fatal: true
}).decode(data)
return this.storeText(name, content)
}
async selectFiles(window: BrowserWindow): Promise<ContextAttachment[]> {
const result = await dialog.showOpenDialog(window, {
properties: ['openFile', 'multiSelections'],