fix: harden speech and document interactions
Cross-platform packages / Validate source (push) Waiting to run
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (arm64, linux, ubuntu-24.04-arm) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (arm64, macos, macos-15) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (arm64, windows, windows-2025) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (x64, linux, ubuntu-24.04) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (x64, macos, macos-15-intel) (push) Blocked by required conditions
Cross-platform packages / ${{ matrix.platform }} ${{ matrix.arch }} (x64, windows, windows-2025) (push) Blocked by required conditions
Cross-platform packages / Publish GitHub Release (push) Blocked by required conditions

This commit is contained in:
lofyer
2026-08-05 21:53:35 +08:00
parent 1094751d4b
commit cd7c10d17c
13 changed files with 1015 additions and 128 deletions
+67 -1
View File
@@ -20,6 +20,7 @@ import type {
AgentImage
} from './agent/runtime'
import { encodeBoundedJpeg } from './bounded-jpeg'
import { parseDocument } from './knowledge/document-parser'
type StoredTextContext = ContextAttachment & {
kind: 'text'
@@ -35,6 +36,7 @@ type StoredImageContext = ContextAttachment & {
type StoredContext = StoredTextContext | StoredImageContext
const maximumFileSize = 256 * 1024
const maximumDocumentFileSize = 20 * 1024 * 1024
const maximumContextBytes = 12 * 1024 * 1024
const maximumContextCount = 16
const maximumAttachmentsPerMessage = 8
@@ -68,6 +70,36 @@ const supportedImageExtensions = new Set([
'.png',
'.webp'
])
const supportedDocumentExtensions = new Set([
'.docx',
'.pdf',
'.pptx',
'.xlsx'
])
function truncateUtf8(value: string, maximumBytes: number): string {
const buffer = Buffer.from(value)
if (buffer.byteLength <= maximumBytes) {
return value
}
const marker = '\n\n[文档内容过长,已截断]'
const markerBytes = Buffer.byteLength(marker)
return `${buffer
.subarray(0, maximumBytes - markerBytes)
.toString('utf8')
.replace(/\uFFFD$/u, '')}${marker}`
}
function formatParsedDocument(
sections: Awaited<ReturnType<typeof parseDocument>>['sections']
): string {
return sections
.map(
(section) =>
`[${section.locator}]\n${section.content}`
)
.join('\n\n')
}
export class ContextManager {
private readonly contexts = new Map<string, StoredContext>()
@@ -161,6 +193,12 @@ export class ContextManager {
extensions: [...supportedImageExtensions].map((extension) =>
extension.slice(1)
)
},
{
name: 'PDF 和 Office 文档',
extensions: [...supportedDocumentExtensions].map((extension) =>
extension.slice(1)
)
}
]
})
@@ -178,7 +216,8 @@ export class ContextManager {
const extension = extname(canonicalPath).toLowerCase()
if (
!supportedExtensions.has(extension) &&
!supportedImageExtensions.has(extension)
!supportedImageExtensions.has(extension) &&
!supportedDocumentExtensions.has(extension)
) {
throw new Error(`${extension || '未知'}`)
}
@@ -204,6 +243,33 @@ export class ContextManager {
}
continue
}
if (supportedDocumentExtensions.has(extension)) {
try {
const fileStat = await handle.stat()
if (
!fileStat.isFile() ||
fileStat.size > maximumDocumentFileSize
) {
throw new Error('PDF 或 Office 文档必须小于 20MB 且不能是目录')
}
const parsed = await parseDocument(
basename(canonicalPath),
await handle.readFile()
)
attachments.push(
this.storeText(
basename(canonicalPath),
truncateUtf8(
formatParsedDocument(parsed.sections),
maximumFileSize
)
)
)
} finally {
await handle.close()
}
continue
}
let content: string
try {
const fileStat = await handle.stat()