文档导出
从代理结构化内容中生成PDF,DOCX,PPTX,CSV和XLSX等文档. 第二个条目, ./transcript,将笔录写为Markdown、纯文本、JSON、CSV、SRT、VTT、DOCX、HTML碎片或拉链;第三, ./archive,同时读取 ZIP 条目, ./calendar,将一个事件写成 iCalendar 文件和 Google Calendar 的模板地址。 三个运行在浏览器以及节点.
安装
npm install @framers/agentos-ext-document-export
扩展包
import { createExtensionPack } from '@framers/agentos-ext-document-export';
const pack = createExtensionPack({ options: { workspaceDir: '/srv/agent' } });
| 工具 | 它会做什么 |
|---|---|
document_export | 结构化内容(有段落、表格、图表和图像的部分)改为 pdf, (中文(简体) ), docx, (中文(简体) ), pptx, (中文(简体) ), csv 或者说 xlsx,将文件保存在 <workspaceDir>/exports/ 并回答其下载和预览链接 |
document_suggest | 读取回复的长度、表格和章节,并说是否提供导出,以及哪种格式,没有模式调用 |
选项 : workspaceDir (默认:进程的工作文件夹), serverPort (违约377), publicBaseUrl 财务报告和审定财务报表 priority (默认50). 本条目运行于节点:它会将文件写入磁盘并用pdfkit使PDF.
标记导出( 浏览器安全)
@framers/agentos-ext-document-export/transcript 写一个记录和时间, 一个会后包 其行带他们的引文, 和显示在行边的支票。 它只进口 倾销 并且从不触及一个文件系统:每个编剧回答一个字符串或字节,被呼叫者保存.
import type { TranscriptExport } from '@framers/agentos-ext-document-export/transcript';
const doc: TranscriptExport = {
title: 'Budget review',
startedAt: '2026-10-08T14:05:00.000Z',
durationSeconds: 754,
tags: ['q3'],
pack: {
summary: 'The team agreed the budget.',
decisions: [{ text: 'Budget grows ten percent.', quotes: [{ seq: 2, text: 'we grow the budget by ten percent' }] }],
actionItems: [{ text: 'Send the slides.', owner: 'Ann', quotes: [{ seq: 3, text: "I'll send the slides" }] }],
openQuestions: [],
},
checks: [{ seq: 2, label: 'Matches your notes', source: 'Note: Budget', sentence: 'Budget up ten percent.' }],
turns: [
{ seq: 1, startMs: 0, endMs: 2_000, text: 'Shall we start?', speaker: 'You' },
{ seq: 2, startMs: 65_000, endMs: 69_500, text: 'Yes, we grow the budget by ten percent.', speaker: 'Other' },
{ seq: 3, startMs: 70_000, endMs: null, text: "I'll send the slides." },
],
};
时间是毫秒 从会话开始。 一个引号 轮到它了 seq作家们也印出时间在旁边 转弯 speaker 是可选的,在显示时会打印.
| 作家 | 答复 |
|---|---|
toMarkdown(doc) | 标记:标题、一行事实、摘要、决定、行动项目和开口问题及其引文、时间和发言者的笔录、每行核对、笔记 |
toPlainText(doc) | 同样的没有Markdown标记 |
toJson(doc) | 每个记录,与 format: 'transcript-export' 财务报告和审定财务报表 v: 1 |
toHtml(doc) | 剪贴板的 HTML 片段: 标题、 有引文的包、 记录和注释、 每行换行符 <br> |
actionItemsCsv(doc) | 作为 CSV 的动作项: 项, 其所有者, 其第一个引用和该引用的时间, 在一个 UTF-8 字节顺序标记之后 |
toSrt(doc) | 子里普提示,按其起始数列出,编号从 1 起 |
toVtt(doc) | 一个WebVTT文件, 它的提示在其启动时列出 |
toDocx(doc) | 带有标题、段落和引用的 Word 文档的字节,每行换行符写为 <w:br/> 每个标签为 <w:tab/> |
writeArchive(entries, options?), (中文(简体) ), readArchive(bytes, options?) | 数个文件的拉链,其文件按顺序读取,文件夹条目被忽略,不超过 maxInflatedBytes (50米B,除非你们经过另一圈),确是膨胀的。 有选项onEntry, (中文(简体) ), signal, (中文(简体) ), mtime, (中文(简体) ), writeArchive 回答承诺并逐项写入条目(下级(中文(简体) ) |
在"Markdown","plain-text","HTML"和"Word"编剧中,一个没有内容的章节被忽略了:摘要的标题需要一个摘要,列出其条目,注释标题是一些笔记,笔录标题是一转,所以 { ...doc, turns: [] } 在没有记录的情况下写记录.
在这四个作者中,一个文本保留了自己的行间断,包括空行. 在Markdown和纯文本中,一行空白将两行相隔开来,文本起端的行间间断被忽略.
上文记录的每个作者的一个例子:
import {
actionItemsCsv,
readArchive,
toDocx,
toHtml,
toJson,
toMarkdown,
toPlainText,
toSrt,
toVtt,
writeArchive,
} from '@framers/agentos-ext-document-export/transcript';
toMarkdown(doc);
// # Budget review
//
// Thu, 08 Oct 2026 14:05:00 GMT | 13 minutes | q3
//
// ## Summary
//
// The team agreed the budget.
//
// ## Decisions
//
// - Budget grows ten percent.
// > "we grow the budget by ten percent" (01:05)
// ...
// **[01:05] Other:** Yes, we grow the budget by ten percent.
// - Matches your notes: Budget up ten percent. (Note: Budget)
toPlainText(doc);
// ...
// [01:05] Other: Yes, we grow the budget by ten percent.
// Matches your notes: Budget up ten percent. (Note: Budget)
JSON.parse(toJson(doc)).format;
// 'transcript-export'
toHtml(doc);
// '<h1>Budget review</h1><h2>Summary</h2><p>The team agreed the budget.</p><h2>Decisions</h2><ul><li>...'
actionItemsCsv(doc);
// U+FEFF, the byte order mark, then:
// "Action item","Owner","Quote","Time"
// "Send the slides.","Ann","I'll send the slides","01:10"
toSrt(doc);
// 1
// 00:00:00,000 --> 00:00:02,000
// You: Shall we start?
// ...
toVtt(doc);
// WEBVTT
//
// 00:00:00.000 --> 00:00:02.000
// You: Shall we start?
// ...
const docx: Uint8Array = toDocx(doc);
const zip = writeArchive([
{ path: 'sessions/budget-review.md', data: toMarkdown(doc) },
{ path: 'sessions/budget-review.docx', data: docx },
]);
readArchive(zip).map((entry) => entry.path);
// ['sessions/budget-review.md', 'sessions/budget-review.docx']
CSV以字节顺序标记(U+FEFF,字节EF BB BF曾被编码为UTF-8)开始: Excel 打开 UTF-8 CSV 文件时通常使用一个文件保存,如果没有一个,它就可以在ASCII之外误读文本,包括一个人的名字。 不剥去标记的阅读器会在第一个标题前发现.
字幕会留下一个没有开始的转弯 WebVTT 要求 每一个提示开始时间都不要早于它之前的提示. 同样的开始转动 保持他们的秩序。 一个没有终点的转弯 在下一个提示的开始 或2秒后,它自己的开始, 以哪个开头.
每种格式都加以防范, 帮助输入输出 :
- XML 和 HTML 文本 (
xmlText: XML 1.0 外的字符Char生产减少,以及&, (中文(简体) ),<, (中文(简体) ),>, (中文(简体) ),"财务报告和审定财务报表'被逃脱. - 一个WebVTT提示 (
vttText:&财务报告和审定财务报表<被逃脱,每一个-->已断开,提示内没有空行。 一个 SRT 提示( A)cueText) 里面没有空行. - 一个 CSV 字段 (
csvField:每个字段的引文翻了一番,开头的字段是:=, (中文(简体) ),+, (中文(简体) ),-, (中文(简体) ),@、标签、车厢返回、线路信号或全线=, (中文(简体) ),+, (中文(简体) ),-或者说@获得一个主引用,所以电子表格不会把它读作公式. - 标记下正文文本( N)
markdownText:标记被逃脱,而GitHub的提及和引用被打破,作为 标出正文文本 描述. - 归档路径: 相对的, 没有空的, 没有
..并且没有反弹.writeArchive拒绝任何其它路径readArchive拒绝一个文件的归档。 文件夹条目( 结束于/,用于文件夹的 zip 工具)readArchive答对. - 归档大小( A)
readArchive:在文件被夸大之前,每个文件的大小被添加到一个运行中的总和中,和一个过去的总和maxInflatedBytes丢出一个RangeError,所以一个小的档案 宣布或持有千兆字节 从来没有充气。 默认值为 52,428,800 字节( 50 MiB); 传递一个更大的链接, 用于一个已知保存较多的档案, 如readArchive(bytes, { maxInflatedBytes: 512 * 1024 * 1024 })。其文件声称其压缩字节比它所持有的更压缩的档案,像共享一个数据段的文件那样,将丢出一个RangeError我也一样。 约束在所持的字节上,而不是在读取的时间.
通过条目写入归档条目
有选择, writeArchive(entries, options) 回答一个承诺, 并一次写一个 zip 条目, 因此一个长的导出可以显示其进展并被停止 :
import { ArchiveError } from '@framers/agentos-ext-document-export/archive';
import { writeArchive } from '@framers/agentos-ext-document-export/transcript';
const controller = new AbortController(); // controller.abort() from a Stop key
try {
const zip = await writeArchive(entries, {
onEntry: (index, path) => console.log(`${index + 1} of ${entries.length}: ${path}`),
signal: controller.signal,
});
} catch (error) {
if (!(error instanceof ArchiveError && error.reason === 'stopped')) throw error;
// Stopped after the entry in hand; no archive was handed back.
}
| 选项 | 它会做什么 |
|---|---|
onEntry(index, path) | 每个条目的编号为: entries 及其路径,依次 |
signal | 每条前读取,后读取一次. 一旦它被中止, 写到手头的输入后就会停止, 而承诺拒绝使用 ArchiveError,原因 stopped,没有归档 |
mtime | 每个条目、毫秒或秒的修改时间 Date 从1980年到2099年,所以同样的条目给出了相同的字节. 默认: 时钟, 如 writeArchive(entries) 写入 |
在两个条目之间, 写入等待计时器( T)setTimeout(fn, 0))),所以发送到线条上的信息,其中工人的停站,是在手入后处理的. 每个路径在第一个条目被写入之前都会被检查,一个坏路径,或者一个两次,拒绝之前的承诺 onEntry 称为; writeArchive(entries) 保存上次输入的路径数据两次。 没有选择, writeArchive(entries) 是上面同步调用, 不变 。 两种表格为相同的条目写出不同的字节:逐个入项表格在数据描述符(APPNOTE 4.3.9)后在数据描述符中给出了每个条目的大小,如流线作家所做的那样,以及 readArchive 财务报告和审定财务报表 openArchive 读两个都一样.
档案(浏览器安全)
@framers/agentos-ext-document-export/archive 读取一个过于庞大的 ZIP 以放大整个, 如一个在手机浏览器中选择的注释文件夹 . openArchive 列出档案中心目录中的条目,并充公其任何数据 bytes() 单从它自己的片段读取 Blob。 。 。 .
import { ArchiveError, openArchive } from '@framers/agentos-ext-document-export/archive';
const archive = await openArchive(file, { maxEntries: 10_000, signal });
try {
for (const entry of archive.entries) {
if (entry.kind !== 'file' || entry.unsafe) continue;
const bytes = await entry.bytes({ maxBytes: 10 * 1024 * 1024, signal });
// one entry's bytes, checked against its recorded length and CRC-32
}
} catch (error) {
if (error instanceof ArchiveError && error.reason === 'size') {
// the entry's recorded size is more than maxBytes
}
throw error;
} finally {
archive.close();
}
| 选项 | 何处 | 它的束缚 |
|---|---|---|
maxEntries | openArchive | 存档的条目可能列出, 在目录读取前在尾端记录上检查 。 默认: 无 |
maxDirectoryBytes | openArchive | 目录大小, 在读取前检查 。 默认: 64 MiB (MAX_DIRECTORY_BYTES(中文(简体) ) |
maxBytes | bytes() | 条目的字节. 过去的记录大小被拒绝为 size 在任何读取之前。 然后在保留前计算出每个 8 KiB 步骤的输出, 将输出超过记录大小的步骤拒绝 format,因此输出永远不会通过 maxBytes。默认:无;仅记录大小即可限制读取内容 |
worker | bytes() | 充气于fflate的工人(违约)或 false在电话线上 工人只有在采取之前的下一步工作。 它的工人来自 blob: URL,所以其内容安全政策不允许的页面 blob: 工人阅读 false |
signal | 两者 | 停止打开或读取,即使读取 Blob 正在等待,或者正在向工人迈出一步 |
多么开放的读取
开放阅读存档的结尾及其目录,并充公并返回没有条目的数据:
- 没有归档注释:最后的22个字节(末端记录),前面的20个字节,一个ZIP64定位器坐在那里(当少于20个字节的字节出现在末端记录之前,如空归档),以及目录.
- 有评论:最后的22个字节,然后最多是最后的65,557个字节(最长的注释和最终记录),最后的记录和目录之前的20个字节. 一个短于65,557字节的归档在第二个读取时被读取全数,包含条目的数据.
- A ZIP64存档增加了其ZIP64端记录的前56个字节.
在检查目录大小后, 其内容以切片形式读取 maxDirectoryBytes。 。 。 .
名称和种类
每个列名都 path, (中文(简体) ), size, (中文(简体) ), compressedSize, (中文(简体) ), method (0个储存,8个DEFLATE), encrypted, (中文(简体) ), kind 财务报告和审定财务报表 unsafe。 。 。 .
path是条目的名称作为文本: 当条目的通用比特 11 设定时的 UTF-8。 没有它, 信息- ZIP Unicode 路径的额外字段的名称(0x7075)其CRC-32与所存储的名称相匹配;而如果其字节为有效的UTF-8,则所存储的名称为UTF-8,因为许多写作者没有旗子就存储了它;否则IBM代码页437,ZIP格式的原字符集.path显示文本,绝不是解决问题的路径.kind实值directory以斜线或文件夹中的属性发誓,link从 Unix 符号链接比特, 否则file。 。 。 .encrypted当条目的目录记录设置了通用比特 0(加密)或比特 6(强加密)时,是真实的。 拒绝这种入境,因为method当读取。 作为 Info-ZIP 的传统加密zip -e写它,将数据的方法保存在它下面,所以它的条目列表方法8与encrypted真实: 在读完之前, 读完两本书后.unsafe标记一条有空白部分的路径,., (中文(简体) ),..,反弹,无效字符或驱动器前缀:已列出,从未读取.
档案必须保存什么
ZIP64档案被读取;一个以上磁盘上的档案不是. ZIP64的收尾记录在定位器正坐在收尾记录之前时被读取,它本身的大小必须至少是44字节并结束于定位器开始或之前的地方. 没有定位器, 最终记录的号码被记为书面, 所以存档 准确 65,535项条目 zipfile 记录为 0xFFFF 没有ZIP64,打开.
该目录持有记录端记录,然后没有记录或一个数字签名记录;其中的任何其他字节拒绝归档. 一个条目的本地头必须重复其目录记录的压缩方法,加密比特和名称,字节的字节,或者在读取其任何数据之前拒绝该条目.
读取
一个档案的条目一次被读取. close() 停止活动读取,结束它的工人 并拒绝以后的读取。 然而读取的结尾(它的最后一步,拒绝,信号, close() 或未读取 Blob它的工人以它结束。 在一个步骤被读取之后的一站,在给工人读取之前它拒绝读取 stopped,而结束的工人 没有得到更多的。 读取的信号停止了保存, 所以下一个读取运行 close(),每个后期读取被拒绝作为 stopped。 。 。 .
在调用线条上,一个读取停止在结束条目的DEFLATE流的阶梯上:fflate的充气器显示每个阶梯之后的终点,该阶梯之后没有任何读取或充气.
fflate的工人无法显示终点,所以一个工人读到的步子一连16次(128 KiB)无所作为,使工人不再进场,工人最多只能得到16步并经过一字节,结束一行:
- 当输出是然后输入的全部记录长度时,工人会被给输入的下个字节作为最后一步. 结束了的溪流没有读取它,读取完成的溪流一次被充气,无论在条目的压缩尺寸内随它而去. 一条溪流仍然拒绝字节作为截断,除非该字节完成工人持有的块和与之相接的溪流:一个字节没有保留DEFLATE块整体. 最后一个步骤是空的,它不会将两者分开:一个没有输入的充气器会随着流的结束,结束或不结束而采取一个空的最后步骤,每当它结束之前的步骤完全在字节边界上结束时,它就没有任何步骤.
- 否则,或当工人因为溪流还去而拒绝该字节时,工人就结束,并在调用线上再读出条目.
有效的流被读取任何空块, 并拒绝提供超过记录大小的流为 format 无论是线条。 如果安装的fflate充气器没有显示其状态为fflate0.8.3的充气器,那么一条连续16次无所事事的流在调用线条上也已完成了,该入段的下个字节是其最后一步,一个仍然在行的流被拒绝,因为 format。 。 。 .
每一次拒绝都是 ArchiveError 其信息是固定的,从未带有条目名称或 Blob自己的错误 :
reason | 何时 |
|---|---|
format | 不是一个 ZIP 此读取器打开: 没有结束记录, 错误的 ZIP64 端记录, 不符合归档的数字, 目录中除了一个数字签名以外的字节, 数据重叠的条目, 缺失的本地标题或不重复其目录记录的条目, 无效的 DEFLATE, 不符合记录长度或 CRC-32 的字节, 或 a Blob 无法还原其字节 |
method | 存储或DEFLATE以外的压缩或加密条目 |
size | 该条目记录的大小大于 maxBytes |
path | 条目是 unsafe |
entries | 条目多于 maxEntries |
directory_size | 大于 maxDirectoryBytes |
busy | 正在读取同一档案的另一个条目 |
stopped | 信号发射,或者归档关闭,读取 Blob 仍待处理 writeArchive 有选项的,其信号在进入之前或最后一个之后发射 |
负数或不数的绑定 RangeError。 。 。 .
日历事件( 浏览器安全)
@framers/agentos-ext-document-export/calendar 将一个事件写为 iCalendar 文件, 并作为 Google Calendar 新事件页面的地址 。 它导入了已内置的无节点和没有软件包,所以一个页面同时构建了下行. 也没有写给任何人的日历:人物打开文件,或者在Google的页面上保存事件.
import { googleCalendarAddress, icsEvent } from '@framers/agentos-ext-document-export/calendar';
const ics = icsEvent({
uid: '[email protected]',
title: 'Send the slides',
description: 'From the budget review: "I\'ll send the slides"',
start: '2026-11-12',
allDay: true,
stamp: new Date(),
});
const href = URL.createObjectURL(new Blob([ics], { type: 'text/calendar' }));
// a link with this href and download="send-the-slides.ics" saves the file
window.open(googleCalendarAddress({ title: 'Send the slides', start: '2026-11-12', allDay: true }), '_blank', 'noopener');
// https://calendar.google.com/calendar/render?action=TEMPLATE&text=Send%20the%20slides&dates=20261112/20261113
| 外地 | icsEvent | googleCalendarAddress | 随你便 |
|---|---|---|---|
uid | UID | 事件标识符. 对于一个事件制作的每个文件都保持相同的状态,所以打开第二个文件的日历会更新事件,而不是添加另一个 | |
title | SUMMARY | text | 标题 |
description, (中文(简体) ), details | DESCRIPTION | details | 笔记, 当不是空时写出, 换行符保留 |
start | DTSTART | dates | 对于一个定时事件,瞬间:a Date,或 ISO 8601日期时间及其冲抵(2026-11-12T10:00:00-05:00),任何长度的一分之一的切到写作时的第二分之一(2026-11-12T15:00:00.123456+00:00) (中文(简体) ). 一日之事,其一日为: YYYY-MM-DD |
end | DTEND | dates | 形式 start 拿着 一个定时事件结束于它,当它开始时没有它,或者当它掉入第二起事件时结束. 一个全天活动把它当作最后一天, 并且只覆盖第一天没有它 |
allDay | 整日而无一日. 默认 false | ||
stamp | DTSTAMP | 当文件制作时, 瞬间 | |
productId | PRODID | 制作文件的产品. 默认 -//Framers//AgentOS Document Export//EN |
文件如下 RFC 5545 (法语)数字 :
- 行以 CRLF 为终点并折叠为75octets,每行从一个空格开始(第3.1节). 一个折叠倒在了字符之间,从不在一个人的UTF-8字节内,所以每行都是有效的UTF-8并正在流出,还原了整个文本.
- 文本逃逸处
\, (中文(简体) ),;财务报告和审定财务报表,用回击键写出每行换行符\n(第3.3.11节)。 控制字符 TEXT 不允许丢弃; 保留标签 . - 一个定时事件的时间是世界协调时的日期时间(
DTSTART:20261112T150000Z页:1 一个没有结束的定时事件, 或者在它开始的第二秒结束, 没有结束DTEND,RFC 5545读作在开始时结束(第3.6.1节),因为aDTEND必须晚于DTSTART(第3.8.2.2节). - 全天活动的时间是日期(
DTSTART;VALUE=DATE:20261112,第3.3.4节),第3.3.4节DTEND是它最后一天的后天,因为DTEND没有包容性(第3.6.1节).
模板地址写入 action=TEMPLATE, (中文(简体) ), text, (中文(简体) ), dates 财务报告和审定财务报表 details,与 text 财务报告和审定财务报表 details 百分之百的编码. dates 世界协调时两次日期( UTC:20261112T150000Z/20261112T153000Z),在未结束或未结束的第二时间开始一次定时活动两次,或全天活动两次日期,即其最后一天后第二天的第二次日期(20261112/20261113) (中文(简体) ). Google没有出版这些参数的参考文献. 他们写成 将事件添加到日历文件 记录,2026年11月10日读取;那些文件也称Google使基本的HTML在 details。 。 。 .
每个助手都会投出 RangeError 当某个时间不是该事件所用的形式时, 命名字段, 时间在 0 到 9999 年以外, 或者 end 跌倒在一秒钟之前 start 坠入,和 icsEvent 当 uid 或者说 productId 是空的或只持有白色空间和控件字符,而TEXT值会下降。 文本中的独一代孕被写作U+FFFD.
标记下体文本( 浏览器安全)
markdownText(text)从软件包中导出并从中导出 ./transcript,使纯文本安全地放入马克下体,比如一个问题,它读作:
import { markdownText } from '@framers/agentos-ext-document-export/transcript';
markdownText('# x [y](z) @a #12');
// '\\# x \\[y\\](z) @a #12'
// GitHub shows "# x [y](z) @a #12" with no heading, link, mention or reference
- 普通马克 标记被反弹所逃脱:
\ ` * _ [ ] < & ~ |在任何地方, 和一行的第一个#, (中文(简体) ),>, (中文(简体) ),+, (中文(简体) ),=或者说-和命令的列表标记(1., (中文(简体) ),12)) (中文(简体) ). 没有开始标题,列表,引文,主题分页,表格,链接,图像,HTML,实体,代码跨度或强调. - GitHub 链接 提及和参考 在读了Markdown之后,无论是反弹还是HTML实体都没有阻止它,作为它的 标记下 API 于2026年11月10日下达. 零宽空格( U+200B) , 它没有显示什么, 在后面
@提及或地址#联 合 国#12财务报告和审定财务报表owner/repo#12,则GH-联 合 国GH-12,则issues/, (中文(简体) ),pull/或者说discussions/在数字之前https://,则www联 合 国www.和emoji短码的开口结肠(:tada:GitHub画成一种情感, 他们中没有人会连结或画画,也没有人被通知. - 换行符被写为 LF,每行的缩进被丢弃,因为有四个空格开始一个代码块,其中出逃会显示.
GitHub身上还有两件事 当存储器持有一个时,一个赤裸的承诺散列与它的承诺相关联。 两个美元符号之间的文字可以做为数学,反弹不能阻止它: GitHub's 数学页面 请求一个 <span> 围绕这样一个美元标志。 一个复制文本的读者用它复制了零宽空格.