dsh-pdf-edit
面向 PDF 文档的编辑插件,精确编辑PDF文字
安装
dsh plugin --profile web add dsh-pdf-edit
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✗ |
| 2 列装 | — |
| 3 挂载 | — |
| 4 启动 | — |
dsh: initialized profile web at /home/node/.dsh/profiles/web四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-pdf-edit
[English](./README.en.md) | 中文



🧩 已接入 dsh-std 生态(Community v0.15):本插件随包提供dsh-plugin.json静态清单与标准 FacetModule 入口(dist/std/host.js),可被@dsh-std/adapter-dsh等 dsh-std 标准宿主直接装载,**不依赖任何@deepseek-ai/*官方包**;同时保留 cordis 直连入口。两条入口共用同一套工具实现,编辑行为完全一致。
版本:v0.4.5(当前) | 需要 Node >= 22;兼容 dsh >= 0.1.1-rc.2(cordis 直连)与 dsh-std Community v0.15 宿主
DeepSeek Harness 插件 —— AI 修改 PDF 文字,自动保持原版式不变。
这是什么
一个面向 PDF 文档的 AI 编辑插件。你用自然语言告诉它要改什么,它就会:
- 只改文字,不动排版 —— 字体、字号、颜色、位置全部锁定,改完和原文看起来一模一样
- 自动处理溢出 —— 新文字比原来长时,自动缩小字号或截断,不会撑破版面
- 支持中文 —— 自动识别并嵌入系统中文字体(SimHei / 微软雅黑 / Noto Sans CJK)
适合什么场景
场景 · 举例
术语统一 · 全文把「帐号」改成「账号」、「数据中台」改成「数据平台」
错别字修正 · 让 AI 扫一遍,自动修正拼写和语法错误
合同/报告批量修改 · 多页文档统一替换人名、金额、日期等
格式转换 · 把散乱的 PDF 重新排版成学术论文双栏、手机阅读单栏、商务简报等版式
往 PDF 里补内容 · 把 md/笔记作为新页插到指定页之后(pdf-edit-insert,无需 LLM):自动排版横幅标题/正文/公式框、跨页断页、化学式上下标缺字自动回退
安装
# 通过 Harness 插件 CLI(与官方插件一致) dsh plugin --profile web add dsh-pdf-edit@latest # 或直接通过 npm npm install dsh-pdf-edit
当前版本:v0.4.5(2026-08-25 发布)。主要更新:彩色背景不再留白块(native 直绘按内容流采样实际背景色作补丁底色)、tid 合并阈值自适应排版密度、大文档内存 LRU 淘汰、字体解析结果缓存提速批量编辑。v0.4.3:parsePatchObject按 JSON 解析错误位置截断,彻底处理模型尾部任意说明文字。
dsh-std 标准生态适配(v0.4.0 新增)
本插件同时是 dsh-std Community v0.15 标准插件(dsh-std / dsh-ecosystem-spec):宿主通过静态 dsh-plugin.json 在不执行插件代码的前提下完成兼容性判定,运行期经 adapter 层与 dsh 本体通信——上游接口变更被 adapter 单点吸收,插件免维护。
两种装载方式
方式 · 宿主 · 入口 · 依赖
标准生态(推荐) · 支持 dsh-std 的宿主(如经 @dsh-std/adapter-dsh / dsh-TUI) · 包根 dsh-plugin.json → facets.host.entry(dist/std/host.js) · 无需任何 @deepseek-ai/* 包
cordis 直连(旧) · DeepSeek Harness 原生 profile · cordis.patch.yml + apply(ctx, config) · @deepseek-ai/dsh-tools(peerDep,现为 optional)
# 标准生态宿主内安装(adapter 会扫描 profile 依赖中的 dsh-plugin.json) dsh plugin --profile web add @dsh-std/adapter-dsh dsh plugin --profile web add dsh-pdf-edit
std 宿主下的配置(环境变量)
std 协议没有 cordis config 注入,配置改从环境变量读取:
环境变量 · 说明
DEEPSEEK_API_KEY · LLM Key;AI 精修必需(预览/插入不需要)。也可用 DSH_PDF_EDIT_API_KEY 覆盖
DSH_PDF_EDIT_ALLOWED_ROOTS · 路径白名单,按平台路径分隔符拼接(默认当前工作目录)
DSH_PDF_EDIT_PROVIDER / _MODEL / _BASE_URL · 模型路由(直连 API 时使用)
DSH_PDF_EDIT_RENDER_MODE · native(默认)或 browser
DSH_PDF_EDIT_BROWSER_EXECUTABLE / _BROWSER_CONCURRENCY · 浏览器模式选项
DSH_PDF_EDIT_PATCH_COLOR / _STRICT_TIDS / _MISSING_TIDS_USE_ORIGINAL / _RECOVER_COLOR / _STRICT_COLOR / _OVERFLOW_MODE / _MIN_FONT_SIZE_PT · 其余编辑行为开关
说明:dsh-std tool/model 协议 v1alpha1 尚未向插件开放宿主 LLM 推理通道,因此 std 宿主下 AI 精修暂走 DeepSeek API 直连(Key 必填);工具注册、生命周期、清理语义全部走标准协议。
清单要点
$schema: urn:dsh-std:community-draft:dsh-plugin:0.15,manifestVersion: 0.15- 组件 ID:
io.github.whatsmore-nf.dsh-pdf-edit - 5 个工具以
contributes["x-tools"]声明为tools.dsh/v1alpha1 Tool扩展,activation 时逐一发布本地ToolHandler - 不声明任何 required 契约与权限 → 与任意 std 宿主协商即兼容
遇到 "Cannot read properties of undefined (reading 'prepare')"?
这是 dsh 宿主 rc 阶段的已知问题:当 @deepseek-ai/dsh-tools 在进程内被加载多份时,工具调度器 Symbol 失配。本插件从 v0.1.7 起通过 peerDependencies(精确版本钉死 0.1.1-rc.2)从源头规避;但若曾在 profile 目录下手动执行过 pnpm install,残留副本仍可能触发。
按以下顺序排查:
# 1. 检查是否真的有本地副本(实体目录而非 symlink) ls -l ~/.dsh/profiles/web/node_modules/@deepseek-ai/ # 2. 若有,移除核心包的本地副本 cd ~/.dsh/profiles/web pnpm remove @deepseek-ai/dsh-tools @deepseek-ai/cordis # 3. 重启 dsh,并【新建会话】验证(崩溃过的旧会话日志已损坏,无法恢复)
插件装载时也会主动探测该问题:若命中会直接抛出带上述修复命令的明确报错,而不是静默崩溃。
AI Agent 操作手册(隐藏规则)
在使用本插件前,请先确认以下三个“隐藏规则”——它们在源码中存在,但不在工具接口文档中直接说明:
规则 · 位置 · 影响 · 绕过方式
路径白名单 allowedRoots · src/path-guard.ts · 工具调用 (pdf-edit-preview / pdf-edit-document) 直接失败(文件不存在或不在允许目录) · 编程接口 StyleLockedEditor.open() 仍需配置 allowedRoots;或直接 node -e 调用内部模块绕开工具守卫
sanitizeText 拒收空字符串 · src/validator.ts:26 (t.length === 0 → {ok:false}) · editDocument('去掉所有小标题...') 若 AI 返回 "" 会被拒收,missingTidsUseOriginal: true 回填原文 · 直接操作 unit.text = '',再手动调用 drawPatchedPages()(见示例)
依赖 ctx.llm / DEEPSEEK_API_KEY · src/index.ts:96-121 · 工具接口依赖 DSH LLM 服务或环境变量;无配置时抛错 · 编程接口可传入自定义 chatFn(如 setChatFn),完全脱离工具调度
编程接口简要示例
const { StyleLockedEditor, applyPatches, removeSubheadings } = require('dsh-pdf-edit');
// 1. 打开文档(需 Uint8Array)
const original = new Uint8Array(require('fs').readFileSync('doc.pdf'));
const editor = await StyleLockedEditor.open(original, chatFn, { allowedRoots: ['/workspace'] });
// 2. 预览某页可编辑单元(获取 tid 列表)
const units = await editor.previewPage(1);
console.log(units); // [{ tid: 'p1-0', text: '...' }, ...]
// 3. 全文 AI 修改(标准流程,受 sanitizeText 限制)
const result = await editor.editDocument('修正错别字并统一术语');
require('fs').writeFileSync('doc.edited.pdf', result);
// 4. 手动删除文字(绕过 sanitizeText 空文本限制)—— 完整可运行示例
const fs = require('fs');
// 4a. 提取页面并找到要删除的 tid(getExtract 已开放为公共方法)
const ex = await editor.getExtract(1);
const targetText = '小标题文字'; // 替换为实际要删除的文字
const deleteTid = ex.units.find(u => u.text.includes(targetText))?.tid;
if (deleteTid) {
// 4b. 直接修改 Unit 文本为空(绕过 sanitizeText)
const unit = ex.units.find(u => u.tid === deleteTid);
unit.text = '';
// 4c. 打开原生文档、渲染修改
const { doc, resolver } = await editor.openNativeDoc();
await editor.drawPatchedPages(doc, resolver, [
{ ex, changedTids: new Set([deleteTid]) }
]);
const result = await doc.save();
fs.writeFileSync('deleted.pdf', result);
}
await editor.close();
简化删除包装(v0.2.2 新增)
const { removeSubheadings } = re
…(truncated)以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:12,最后更新 2026-08-25