跳到内容
CADCAMFUN
文档

文档

插件

扩展清单、工作主机 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 清单

辅助扩展的plugin.json
{
  "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。顶级模块代码在导入时运行;声明的查找函数在调用时运行。

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."
    ]
  })
}

这些是样本车间说明,而不是内置的检查标准。将它们替换为您的扩展程序实际拥有的内容。为了在下次服务初始化时接纳该模块,启用的文件是:

<dataDir>/plugins/enabled.json
["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非空显示名称。
versionmajor.minor.patch,可选地后跟预发布后缀,例如 -beta.1.
apiVersion必须是字符串 "1";它是唯一的插件 API 版本。
entryES 模块相对于插件文件夹的路径。
capabilities功能字符串数组(见下文)。关闭列表之外的任何内容都是明显错误。
contributes选修的 commands, panels, postProcessors, operations, 和 aiTools.
signatureEd25519 对存档哈希进行签名;打包安装所需。
devtrue 标记本地开发插件:跳过签名检查并且 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:readctx.drawing.read()阅读打开的图纸(或 undefined 当没有人打开时)。—
drawing:writectx.drawing.patch(patch)申请 AddEntity, UpdateEntity, 或者 RemoveEntity 补丁;返回新的修订版本。drawing:read
toolpath:readctx.toolpath.list(), .program(id)读取刀具路径和 G 代码程序。—
storagectx.storage命名空间键值字符串: get, set, delete, keys.—
ui:panelctx.uiregisterCommand 和 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 在工作区中。
frame-plugin.test.ts
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) 显示命名空间存储键,以便测试可以断言隔离。

应用程序如何加载辅助插件

  1. 启动时 sidecar 会读取 <data dir>/plugins/enabled.json,插件 ID 的 JSON 数组。除非在那里列出,否则不会加载任何内容。
  2. 对于每个 ID,它读取 <data dir>/plugins/<id>/plugin.json 并解析它。文件夹名称必须等于清单 id.
  3. 仅有的 ai:tool 已连接至该主机。请求绘图、工具路径、存储或 UI 访问的清单会被跳过并发出警告,而不是加载不会执行任何操作的表面。
  4. 该插件已安装到工作主机并启用。无法加载日志的插件 Plugin <id> was not loaded: ... 并且永远不会停止应用程序的启动。
  5. 每个贡献的工具都提供给助手 plugin_<slug of plugin id>_<tool id>,最多 80 个字符,其中 slug 小写 ID 并用下划线替换其他字符。 com.example.bolt-circle 带工具 bolt_circle 变成 plugin_com_example_bolt_circle_bolt_circle.
  6. 在工作人员看到参数之前,会根据工具的 JSON 架构对参数进行验证。如果架构超过 20,000 个字符,不是对象架构,或者无法转换,则主机接受任何对象,并且插件必须进行验证。
  7. 抛出、超时或发现插件消失的调用返回 { 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 个字符,因为模型会读取它们。

贡献类型

钥匙领域目的
commandsid, title, 选修的 keybinding (e.g. Mod+Shift+G)调色板中的命令
panelsid, title, entry从指定函数返回的声明树呈现的面板;没有 DOM 访问权限
postProcessorsid, title, entryA (program, options) => string 在运行时注册的帖子
operationsid, title, entry出口的 { schema, generate } 记录
aiToolsid, 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
表面上的上下文授予时出席方法
drawingdrawing:read 或 drawing:writeread() → 绘图或 undefined; patch(p) → 新修订版,其中 AddEntity, UpdateEntity, RemoveEntity 补丁
toolpathtoolpath:readlist(), program(toolpathId)
storagestorageget, set, delete, keys;字符串值
uiui:panelregisterCommand, registerPanel
log总是debug, info, warn, error

在 3D

工作区中的插件包 拖动进行轨道旋转·滚动进行缩放·右键拖动进行平移
All 13 workspace packages from their package.json files; each line is a workspace:* dependency (17 in total). Rows are dependency depth: packages with no workspace dependencies at the front, the sidecar, which depends on 8 of them, at the back. Orange boxes are apps. Highlighted: plugin-sdk (depends on core) and the nikcli plugin, both loaded by the sidecar.