文档
插件
扩展清单、工作主机 API、辅助工具和单独的浏览器插件安装合同。
从英文自动翻译。 查看英文原文
选择延伸面
@cadcamfun/plugin-sdk 定义了worker插件合约 plugin.json 清单和 API 版本 1。它的主机公开能力控制的服务并在 Bun 工作线程中运行插件功能。生产助手加载器目前仅接受请求的插件 ai:tool.
这 /api/plugins HTTP 系列使用不同的浏览器插件合约:ZIP 包 manifest.json, a main 条目、浏览器权限、配置和 JavaScript 引导程序。通过该 API 安装包不会使其成为工作 SDK 插件。使用与预期集成相匹配的清单和生命周期。
Worker SDK 清单
{
"id": "com.example.shop-helper",
"name": "Shop helper",
"version": "1.0.0",
"apiVersion": "1",
"entry": "index.js",
"capabilities": ["ai:tool"],
"contributes": {
"aiTools": [{
"id": "lookup",
"description": "Look up the shop's documented inspection checklist.",
"kind": "read",
"parameters": { "type": "object", "properties": {} },
"entry": "lookup"
}]
}
} 标识符使用反向 DNS 形式,版本是语义的 major.minor.patch 带有可选的预发行版本,以及 apiVersion 必须是 "1"。入口路径是相对于插件目录的。贡献可以声明命令、面板、后处理器、操作和辅助工具;声明贡献并不将该功能连接到每个主机。
该架构允许可选 signature 和 dev 字段。当前工作主机记录这些值,但在安装过程中不执行加密签名验证。签名字段本身并不能证明发布者经过验证。
模块入口及AI功能
definePlugin 是具有清单的模块的类型化身份帮助器,并且 activate(ctx)。激活可能会返回清理函数,并且模块可能会暴露 deactivate。 SDK 描述了这些生命周期挂钩;它们的可用性取决于具体的工作引导程序,如下所述。
辅助功能通过一个导出 aiTools 记录;贡献的 entry 命名该记录中的函数。工具贡献包括其描述、JSON 参数模式和 read 或 write 种类。写入工具通过嵌入式助手中的人工批准请求进行路由;外部桥仅接收允许的读取插件工具。
将工作条目捆绑为独立模块。工作主机读取条目源并通过 blob 模块导入它。将相关包或文件导入视为单独的集成问题,而不是假设使用 Node 样式的文件系统加载器。
能力控制的 API
SDK识别 drawing:read, drawing:write, toolpath:read, storage, ui:panel, 和 ai:tool。绘图写入意味着绘图读取。通用主机从安装的manifest中获取有效的能力,并在RPC上进行检查;嵌入应用程序决定它愿意加载哪些清单和表面。
SDK 上下文可以公开绘图读取和添加/更新/删除实体补丁、工具路径/程序读取、私有字符串键值存储以及声明性命令/面板注册。上下文中不存在缺失的服务。存储密钥使用 plugin:<id>: 命名空间并拒绝绝对或父遍历键。
生产辅助加载器仅支持 ai:tool。请求绘图、刀具路径、存储或 UI 功能的清单将被跳过并出现警告。它不会默默地授予这些表面或将其更广泛的功能加载到助理主机中。
装载和工人生命周期
要启用助理扩展,请将其放置在 plugin.json 和捆绑条目下 <dataDir>/plugins/<id>/ 并列出标识符 <dataDir>/plugins/enabled.json,一个 JSON 数组。文件夹标识符必须与清单匹配。加载发生在助手插件服务构建时;失败的插件会在不停止应用程序的情况下记录警告。
通用主机支持已安装、启用、禁用、故障状态,可进行安装、启用、禁用、卸载、命令调用、AI工具调用、配置操作。 Enable创建一个worker并完成其引导握手;禁用将其撕毁。卸载通过主机注入的依赖项删除注册表状态和存储命名空间。
Workers 从一个空的环境开始,引导程序会删除选定的全局 API。这是应用程序级别的限制,而不是操作系统沙箱:Bun 全局变量仍然可访问。仅启用适合在应用程序进程边界内运行的扩展代码。
运行时间限制
主机定义了 256 MB 工作内存设置、每分钟 2、000 次调用的速率限制以及每个插件最多 64 个待处理调用。命令超时行为由嵌入主机提供;辅助加载程序使用 10 秒。 AI 工具调用有单独的 30 秒截止时间。超时、缺少工具、工作线程崩溃、功能故障和队列耗尽会返回类型化的主机故障或可读的工具结果。
十分钟内发生三次崩溃后,崩溃跟踪会导致插件出现故障。这些主机控件绑定了协作 RPC 和生命周期行为;传递给 Bun 的内存设置不能替代单独隔离的可执行文件。
限制定义在 apps/sidecar/src/plugins/host.ts: CALL_TIMEOUT_MS (每次普通调用 5,000 毫秒), AI_TOOL_TIMEOUT_MS (每次助理工具调用 30,000 毫秒), MEMORY_LIMIT_MB (256), CALL_RATE_LIMIT (每个插件每分钟 2,000 次调用), MAX_PENDING_CALLS (64),以及 CRASH_LIMIT (3 在 10 分钟内崩溃 CRASH_WINDOW_MS,之后插件将自动禁用)。
ZIP 安装和浏览器插件 API
POST /api/plugins/install 接受多部分存档。安装程序读取 manifest.json 从 ZIP 根目录或单个顶级目录,验证浏览器插件清单,并将其文件存储在 <dataDir>/storage/plugins/<id>/。浏览器清单使用 main, engines.cadcam,以及浏览器权限列表,而不是SDK entry, apiVersion和能力。
GET /api/plugins 列出那些已安装的条目;详细信息、启用/禁用、卸载、更新和配置端点在其注册表上进行操作。 /api/plugins/bootstrap.js?id=… 服务 JavaScript; /api/plugins/serve?id=…&file=… 提供插件文件。这些是文本/二进制操作及其声明的内容类型。
本地注册表浏览器读取下面的包元数据 <dataDir>/plugins-registry。它是本地清单目录。存档安装、注册表列表和辅助扩展加载是单独的操作。
SDK助手
effectiveCapabilities, hasCapability, 和 missingCapabilities 支持能力检查。 requireCapability 当请求的上下文表面不存在时,会引发功能错误。 storageNamespace 和 storageKey 构建命名空间键; parseManifest 验证 JSON; manifestKey 格式化 ID/版本对。
实施切入点
packages/plugin-sdk/src 拥有公共清单、功能、上下文、插件和测试主机 API。 apps/sidecar/src/plugins/host.ts 拥有worker生命周期和RPC; services/ai-plugins.ts 拥有同意文件加载和辅助注册。浏览器安装实现于 http/plugins.ts 和 repos/plugin-registry-repo.ts.
延期审查
保持贡献模式准确,返回小型结构化结果,并诚实地声明读/写行为。针对实际嵌入主机进行测试:SDK 可用性不会建立对绘图、存储、UI 或后期注册表的生产访问。这 独立 nikcli 插件 是一个 HTTP 操作客户端,与加载到工作线程中的代码具有不同的用途。
完成示例助手模块
这 plugin.json 上面的例子名称 aiTools.lookup。这是对应的自包含 index.js。顶级模块代码在导入时运行;声明的查找函数在调用时运行。
export const aiTools = {
lookup: async () => ({
ok: true,
checklist: [
"Compare the drawing revision with the job traveller.",
"Record the measuring instrument and its calibration status.",
"Record each inspected dimension and the measured value."
]
})
} 这些是样本车间说明,而不是内置的检查标准。将它们替换为您的扩展程序实际拥有的内容。为了在下次服务初始化时接纳该模块,启用的文件是:
["com.example.shop-helper"] 正在运行的目录公开了从插件和工具 ID 派生的标识符。通过发现它 GET /api/ai/actions;不要假设浏览器 ZIP 注册表是助手的启用列表。重新加载应用程序/服务是重新读取生产同意文件的可靠方法;此加载程序没有配置文件系统观察程序。
来源: apps/sidecar/src/services/ai-plugins.ts, packages/ai/src/tools/plugin-tool.ts, 和 apps/sidecar/test/ai-plugin-tools.test.ts.
参数模式和生命周期限制
工具的本地 ID 必须以小写字母开头,并包含小写字母、数字或下划线,最多为 40 个字符。描述包含 10–2,000 个字符。贡献必须命名一个非空入口函数和一个 JSON 参数模式。应用程序适配器在调用工作程序之前验证参数;拒绝可以返回结构化的 ok: false 结果无需终止插件。
公共SDK描述 activate、清理和 deactivate 入口点。当前 Bun 工作引导程序的激活/停用 RPC 处理程序会确认生命周期消息,但不会调用那些导出的模块函数。因此,助理扩展应该将其可调用行为放在 aiTools 并避免依赖 SDK 激活回调来进行设置或清理。
一般工作引导程序还包含占位符绘图/工具路径/UI 处理程序。它们不构成生产图或 UI 集成。辅助装载机的限制 ai:tool 防止这些不受支持的表面被宣传为工作应用程序访问。
来源: packages/plugin-sdk/src/manifest.ts, plugin.ts, apps/sidecar/src/plugins/host.ts (PLUGIN_BOOTSTRAP), 和 services/ai-plugins.ts.
诊断丢失或失败的扩展
- 目录中没有工具: 检查有效启用的 JSON、确切的文件夹/清单 ID 匹配、清单解码以及
ai:tool能力。阅读跳过插件的启动警告。 - 清单请求不受支持的表面: 仅当代码确实不需要时才删除不支持的功能。辅助插件不能仅通过声明来获取绘图写入访问权限。
- 没有这样的工具: 制作
contributes.aiTools[].entry匹配下面的导出函数aiTools。贡献 ID 和条目名称有不同的用途。 - 参数验证失败: 检查 JSON 架构和必填字段。正如真实工作人员和应用程序集成测试所证明的那样,错误的值在执行之前会被拒绝。
- 超时或故障状态: 调查阻塞工作、未解决的承诺或崩溃;重复崩溃导致插件故障。仅在纠正原因后重新启用。
- ZIP 安装程序拒绝清单: 使用浏览器插件
manifest.json该端点的合同。重命名工人plugin.json不翻译其字段。
来源: apps/sidecar/test/plugins-ai-tools.test.ts, ai-plugin-tools.test.ts, plugins-http.test.ts, 和 src/plugins/host.ts.
清单参考
每个插件都会附带一个 plugin.json. parseManifest(json) 一步解析和验证它,将 JSON 语法错误转换为与架构错误相同的错误通道,因此安装屏幕只需要处理一种故障类型。
| 场地 | 规则 |
|---|---|
id | 反向 DNS 标识符:例如,至少两个点分隔标签中的 3–128 字符、小写字母、数字和连字符 com.example.gear-generator. |
name | 非空显示名称。 |
version | major.minor.patch,可选地后跟预发布后缀,例如 -beta.1. |
apiVersion | 必须是字符串 "1";它是唯一的插件 API 版本。 |
entry | ES 模块相对于插件文件夹的路径。 |
capabilities | 功能字符串数组(见下文)。关闭列表之外的任何内容都是明显错误。 |
contributes | 选修的 commands, panels, postProcessors, operations, 和 aiTools. |
signature | Ed25519 对存档哈希进行签名;打包安装所需。 |
dev | true 标记本地开发插件:跳过签名检查并且 UI 显示徽章。 |
| 贡献 | 领域 |
|---|---|
commands[] | id, title, 选修的 keybinding 例如 Mod+Shift+G. |
panels[] | id, title, entry:返回面板声明树的导出函数。永远不会有 DOM 访问。 |
postProcessors[] | id, title, entry: 导出的 (program, options) => string 功能。 |
operations[] | id, title, entry:带有参数的导出记录 schema 和一个纯粹的 generate(params, geometry, tool, stock) 返回刀具路径。 |
aiTools[] | id 匹配 ^[a-z][a-z0-9_]{0,39}$, description (10–2,000 个字符;模型读取它), kind read 或 write, parameters (参数的 JSON 模式),以及 entry. |
帮手: manifestKey(m) 回报 id@version; requestedCapabilities(m) 返回排序后的能力列表进行显示; isDevPlugin(m) 和 isSigned(m) 回答安装策略问题。
功能和插件上下文
功能是插件唯一可以发挥作用的东西。清单声明插件需要什么,用户授予子集,主机构建一个上下文,其中确切包含授予的表面。每个表面在类型中都是可选的,因此缺失的功能在编译时和运行时都是可见的,而不是成为静默的无操作。
| 能力 | 上下文表面 | 允许 | 暗示 |
|---|---|---|---|
drawing:read | ctx.drawing.read() | 阅读打开的图纸(或 undefined 当没有人打开时)。 | — |
drawing:write | ctx.drawing.patch(patch) | 申请 AddEntity, UpdateEntity, 或者 RemoveEntity 补丁;返回新的修订版本。 | drawing:read |
toolpath:read | ctx.toolpath.list(), .program(id) | 读取刀具路径和 G 代码程序。 | — |
storage | ctx.storage | 命名空间键值字符串: get, set, delete, keys. | — |
ui:panel | ctx.ui | registerCommand 和 registerPanel (仅声明性)。 | — |
ai:tool | 无(明确贡献) | 为助手提供工具,并为独立的 nikcli 提供只读工具。 | — |
effectiveCapabilities(granted) 解决影响,所以一个插件声明 drawing:write 不需要声明 drawing:read。每个上下文也有 pluginId,已解决的 capabilities,和一个 log 和 debug, info, warn, 和 error;主机在日志行中添加插件 ID 前缀。使用 requireCapability(ctx, capability, surface) 获得一个表面或失败 CapabilityError 这就是缺失的拨款的名称。
使用 SDK 测试主机测试插件
createTestHost 构建一个与 sidecar 具有相同能力规则的内存主机。只有授予的功能才会出现在上下文中,并且您可以提供绘图、刀具路径和程序。下面的示例是完整的并运行 bun 在工作区中。
import { cad } from "@cadcamfun/core"
import { createTestHost, definePlugin, requireCapability, testManifest } from "@cadcamfun/plugin-sdk"
const manifest = testManifest({
id: "com.example.frame",
capabilities: ["drawing:write", "storage", "ui:panel"],
})
const plugin = definePlugin({
manifest,
activate(ctx) {
const drawing = requireCapability(ctx, "drawing:write", "drawing")
ctx.ui?.registerCommand({ id: "frame.add", title: "Add frame line" })
ctx.storage?.set("runs", "1")
drawing.patch({
_tag: "AddEntity",
entity: { ...cad.baseEntity("frame-1"), _tag: "Line",
start: { x: 0, y: 0 }, end: { x: 100, y: 0 } },
}) // returns revision 1
return () => ctx.log.info("teardown") // runs on deactivation
},
})
const host = createTestHost({ manifest, drawing: cad.emptyDrawing("d1", "Demo") })
const stop = await host.activate(plugin)
host.drawing()?.entities.length // 1
host.context.storage?.get("runs") // "1"
host.ui.commands // [{ id: "frame.add", title: "Add frame line" }]
await stop()
// Without the grant, requireCapability throws:
const denied = createTestHost({ manifest, granted: ["storage"] })
await denied.activate(plugin)
// CapabilityError: Plugin com.example.frame may not use "drawing:write":
// it was not granted at install time testManifest(overrides)从最小有效清单开始(com.example.test-plugin,版本 1.0.0,API 1,条目index.js,没有能力)。granted默认为清单要求的所有内容。传递一个较小的列表来测试被拒绝的路径。host.storage是创建主机时拍摄的快照。通过读取实时值host.context.storage.host.on(commandId, handler)和host.invoke(commandId, args)执行已注册的命令。runPostProcessor针对程序运行贡献的帖子,并且keyOf(pluginId, key)显示命名空间存储键,以便测试可以断言隔离。
应用程序如何加载辅助插件
- 启动时 sidecar 会读取
<data dir>/plugins/enabled.json,插件 ID 的 JSON 数组。除非在那里列出,否则不会加载任何内容。 - 对于每个 ID,它读取
<data dir>/plugins/<id>/plugin.json并解析它。文件夹名称必须等于清单id. - 仅有的
ai:tool已连接至该主机。请求绘图、工具路径、存储或 UI 访问的清单会被跳过并发出警告,而不是加载不会执行任何操作的表面。 - 该插件已安装到工作主机并启用。无法加载日志的插件
Plugin <id> was not loaded: ...并且永远不会停止应用程序的启动。 - 每个贡献的工具都提供给助手
plugin_<slug of plugin id>_<tool id>,最多 80 个字符,其中 slug 小写 ID 并用下划线替换其他字符。com.example.bolt-circle带工具bolt_circle变成plugin_com_example_bolt_circle_bolt_circle. - 在工作人员看到参数之前,会根据工具的 JSON 架构对参数进行验证。如果架构超过 20,000 个字符,不是对象架构,或者无法转换,则主机接受任何对象,并且插件必须进行验证。
- 抛出、超时或发现插件消失的调用返回
{ ok: false, error }(最多 500 个字符的错误文本)发送给助手,而不是导致转弯崩溃。一个write无论其参数如何,在每次通话之前都会与该人员确认该工具。
不是操作系统沙箱
工作线程没有自己的进程、获取或文件句柄,但 Bun 全局变量在其内部仍然可访问。仅启用您信任其代码的插件,并将编写工具保持在最低限度。
标识符和版本规则,已测试
| 价值 | 有效的? | 规则 |
|---|---|---|
com.example.gear-generator | 是的 | 反向 DNS:字母、数字和小写部分 -,至少两个 3–128 个字符 |
a.b | 是的 | 两个单字母段就足够了 |
gear | 否 | 至少需要一个点 |
Com.Example.x | 否 | 不允许大写 |
com.example_x.y | 否 | 不允许使用下划线 |
| 版本 | 有效的? |
|---|---|
1.0.0 | 是的 |
1.2.3-beta.1 | 是的 |
1.0 | 否:需要三个数字 |
v1.0.0 | 否:无前缀 |
apiVersion 必须是字符串 "1",唯一存在的主要 API 插件。 AI 工具 ID 必须匹配 ^[a-z][a-z0-9_]{0,39}$ 并向助理显示为 plugin_<plugin>_<id>;描述必须是 10–2,000 个字符,因为模型会读取它们。
贡献类型
| 钥匙 | 领域 | 目的 |
|---|---|---|
commands | id, title, 选修的 keybinding (e.g. Mod+Shift+G) | 调色板中的命令 |
panels | id, title, entry | 从指定函数返回的声明树呈现的面板;没有 DOM 访问权限 |
postProcessors | id, title, entry | A (program, options) => string 在运行时注册的帖子 |
operations | id, title, entry | 出口的 { schema, generate } 记录 |
aiTools | id, description, kind 读|写, parameters (JSON 架构), entry | 提供给助理的工具;每次调用之前都会确认写入工具 |
其他清单字段: signature (Ed25519 存档哈希,打包安装所需)和 dev (本地开发插件:跳过签名检查并显示徽章)。
能力分辨率,经过测试
import { effectiveCapabilities, missingCapabilities, storageKey, requireCapability } from "@cadcamfun/plugin-sdk"
effectiveCapabilities(["drawing:write", "storage"])
// ["drawing:read", "drawing:write", "storage"] (write implies read; sorted)
missingCapabilities(["drawing:read", "toolpath:read", "ai:tool"], ["drawing:write"])
// ["toolpath:read", "ai:tool"]
storageKey("com.example.gear", "presets/m2") // "plugin:com.example.gear:presets/m2"
storageKey("com.example.gear", "../x")
// RangeError: storage key "../x" may not escape the plugin namespace (same for "/abs")
requireCapability(ctxWithoutDrawing, "drawing:read", "drawing")
// CapabilityError (_tag "CapabilityDenied"):
// Plugin com.example.gear may not use "drawing:read": it was not granted at install time | 表面上的上下文 | 授予时出席 | 方法 |
|---|---|---|
drawing | drawing:read 或 drawing:write | read() → 绘图或 undefined; patch(p) → 新修订版,其中 AddEntity, UpdateEntity, RemoveEntity 补丁 |
toolpath | toolpath:read | list(), program(toolpathId) |
storage | storage | get, set, delete, keys;字符串值 |
ui | ui:panel | registerCommand, registerPanel |
log | 总是 | debug, info, warn, error |