npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dp-bohrium/widget-sdk

v0.1.0

Published

Bohrium Widget 开发者 SDK:在 Widget Bundle 内与 Host 建立 Bridge,并暴露生命周期与能力协议。

Downloads

7,365

Readme

@dp-bohrium/widget-sdk

Widget 开发者 SDK。普通作者只实现 activate(context) 和可选的 deactivate();CLI 会生成 Widget Host 入口,负责向 Host 宣告 Bundle、接收转移过来的 MessagePort,并暴露共享的 生命周期与能力协议。作者无需直接处理 window.postMessage,也无需手动启动插件。

激活事件目录、context.events 与低层 topic 的边界见 Widget 激活事件与事件查询。

环境要求

| 项 | 要求 | | --- | --- | | Node.js | ≥ 20(仅构建期;产物运行在浏览器 iframe 内) | | 源码格式 | 纯 ESM,必须经打包器处理后交付 | | 运行环境 | 必须由 Bohrium Host 加载,脱离 Host 无法完成握手 | | 产物格式 | Host iframe 为 sandbox="allow-scripts" 且无 allow-same-origin 时,打成 classic script / IIFE(隔离源下 type="module" 需要 CORS) |

安装

写 Widget 装本包。Host 与控制面只消费协议类型,装 @dp-bohrium/widget-contracts 即可。

npm install @dp-bohrium/widget-sdk
pnpm add @dp-bohrium/widget-sdk
yarn add @dp-bohrium/widget-sdk

需要在 TypeScript 中直接声明或在编辑器中解析 Manifest Schema 时,再把 @dp-bohrium/widget-contracts 作为开发依赖安装;它是独立的类型/Schema 包,不需要通过 defineManifest() 工厂获得提示。

在自己的 package.json 里钉死精确 SemVer,不要用 ^ 或裸 latest。SDK 运行时会从已安装 包自身读取版本,插件源码和 Manifest 不需要再次填写 SDK 版本。

快速开始

import type { WidgetContext } from "@dp-bohrium/widget-sdk";

export function activate(context: WidgetContext) {
  context.render((request, renderContext) => {
    renderSlot(request.slot, request.content, renderContext.phase);
  });
  context.events.on("tool-call-result", (event) => showToolResult(event.payload));
}

CLI 生成的 src/widget-host.ts 会调用平台入口 runWidgetHost(widget)。 这与 VS Code 的激活入口一致:开发者实现 activate(context),可选实现 deactivate(); Widget Host/SDK 负责加载、等待初始化、报告启动失败和执行清理。普通插件代码不需要调用 ready(),也不应该写 plugin.ready().catch(() => {})。whenReady 只保留给测试和高级宿主编排。

context.signal 会在 Host 停用插件或 Bridge 断开时 abort。context.render 和 context.events 会自动登记到当前插件生命周期;subscriptions 只用于 第三方计时器、观察器或其它需要在停用时释放的资源。

三条时序约定:

  • ready() 只在 activate(context) 返回(含其 Promise settle)之后才发给 Host, handshakeTimeoutMs 同时约束这一步;
  • 初始化失败时,SDK 先报结构化错误,再调用插件资源清理;
  • context.render 首帧和更新都会收到完整快照,通过 renderContext.phase 区分。

从 Manifest 到插件运行

Manifest 是插件的静态声明,SDK 代码是隔离 iframe 内的运行时实现,Host 负责二者之间的生命周期编排。一个插件可以这样声明:

{
  "$schema": "@dp-bohrium/widget-contracts/schemas/widget-plugin-manifest.schema.json",
  "name": "research",
  "version": "1.0.0",
  "entry": "src/widget.ts",
  "displayName": "研究面板",
  "description": "展示研究工具的结构化结果",
  "activationEvents": ["session.start", "tool-call-result:ScholarSearch", "ui-tool-opened"],
  "contributes": {
    "widget": { "type": "panel", "id": "research", "surfaces": ["workspace", "floating"] }
  },
  "capabilities": {
    "files": { "read": true },
    "events": { "topics": ["tool-call-result", "workspace-file-opened"] }
  }
}

activationEvents 只决定 Host 何时创建插件运行时;它不授予事件读取权限,也不负责打开思考过程或某个面板。插件激活后通过 context.events 和 context.render 注册监听与渲染器。Host 会对每个事件和能力请求做白名单投影与授权,插件收到的事件可能是有限回放,也可能是实时事件。

实现建议:事件处理器保持幂等,按 toolCallId 或 requestId 去重;不要从 iframe URL、DOM 或宿主绝对路径推断内部数据;当能力未授予时显示结构化空态并继续保留可渲染的主界面。声明但没有实际使用的能力会被 Host 记录为诊断信息,未声明能力的调用会被拒绝并结束当前 Bridge 会话。

统一插件入口

所有插件类型都使用同一个 activate(context) 约定。面板、工具结果和文件预览由 Manifest 的 contributes.widget 决定;运行时代码只认识统一的 context.events、 context.render、context.capabilities 和 context.ui。首帧和后续快照通过 renderContext.phase 区分,不需要维护两套回调。

Manifest 的编辑器提示来自 @dp-bohrium/widget-contracts 发布的 JSON Schema 和 TypeScript 类型,不依赖某个 defineManifest 工厂。构建工具调用 compileWidgetPluginManifest,自动补齐 schemaVersion、hostApiVersion、SDK 版本和 宿主加载字段;它会直接读取 Manifest 内的 samples,只有构建器从别处生成样例时 才需要传入第二个参数。createWidgetClient 是协议级基础实现,不属于 Widget 作者 API;当前根入口只提供统一 Widget API,协议级代码应从 ./runtime 显式引入。

SDK 发布包会生成 dist/index.d.ts 与对应的 declaration map,并通过 package.json 的 types/exports 暴露。插件安装 @dp-bohrium/widget-sdk 后,编辑器会直接获得 WidgetContext、事件名、能力请求、渲染请求和生命周期返回值的类型提示;Manifest 编辑器提示由 @dp-bohrium/widget-contracts 的 JSON Schema 和声明文件提供。

包入口

| 入口 | 内容 | 用途 | | --- | --- | --- | | . | runWidgetHost、compileWidgetPluginManifest 和全部公开类型 | 生成的生产 Bundle | | ./testing | MemoryTransport、MockWidgetHost | 单元测试 | | ./runtime | 低层 WidgetClient、transport 与 bootstrap 常量 | 协议级集成 | | ./package.json | 已安装包的元数据 | SDK 和构建工具自动读取版本,作者无需复制版本字符串 |

根入口只导出插件运行时和 Manifest 编译所需的少量值,其余均为类型。低层 MessagePortTransport 与 bootstrap 消息不属于普通 Widget API。

runWidgetHost 是生成的 widget-host.ts 使用的 Widget Host 启动边界; 插件作者只维护 widget.ts 中的 activate/deactivate。

能力

每项能力都要在 Manifest 的 capabilities 里声明,并由 Host 实际 grant; 缺任一侧,调用在 Widget 本地即被拒绝。所有能力调用都必须在 ready() 之后。

下面七项是当前的全部能力。grant 名与 Manifest 申请键不一定同名 —— ui.presentation 对应的作者侧申请键是 capabilities.presentation(编译后的 宿主格式字段才叫 requestedCapabilities.presentation)。

| grant 名 | Manifest 申请键 | SDK 调用 | | --- | --- | --- | | files.list / files.metadata / files.read | files | context.capabilities.request({ operation, input }) | | files.download | files.download | context.files.download({ file, suggestedName }) | | events.read | events(需列出 topics) | 低层分页订阅;context.events.on(…) 是 Host 主动推送,不需要该 grant | | tasks.watch | tasks.watch | context.tasks.watch({ taskId, cursor, limit }) | | ui.state | state(read / write) | context.ui.state.read(key) / write(key, value) | | actions.invoke | actions | context.capabilities.request({ operation: "actions.invoke", input }) | | network.fetch | network | context.network.fetch(…) | | ui.presentation | presentation | context.ui.present(…) / changeSurface / close |

除 Host 主动推送事件外,其余能力共用同一套调用范式,掌握下面这一个就会调其余能力:

const controller = new AbortController();
const result = await context.network.fetch(
  { url: "https://api.example.org/papers", method: "GET", auth: "optional" },
  { signal: controller.signal, timeoutMs: 15_000 },
);

if (result.ok) {
  consume(result.data.status, result.data.headers, result.data.body);
} else {
  // 策略拒绝、用户拒绝、未授权都走这里,不是 throw。
  showNetworkFailure(result.reason, result.retryable);
}

跨能力约定:

  • 失败以 { ok: false, reason, message, retryable } 正常兑现;只有客户端超时、中止、 dispose 和畸形响应才 reject。
  • 中止与超时统一用 { signal, timeoutMs },复用既有 MessageChannel 能力生命周期。
  • Widget 永远拿不到 Host 凭据,也不应直接调用浏览器 fetch:是否弹窗、记住 Origin 授权、跟随重定向还是拒绝,全部由 Host 决定。
  • context.files.download 只提交文件引用和建议文件名;Host 在用户确认后经宿主 Download Adapter 惰性打开内容源。Widget 不会收到源 URL、保存路径或凭据。
  • context.tasks.watch 只读取一次受限任务快照和事件页;调用方用返回的 cursor 继续轮询。 它不创建、调度或取消任务,服务端负责任务授权、脱敏和终态保留。
  • context.ui.state 只保存按 Widget + Session 隔离的 JSON UI 状态;Host 按 key、单值大小、总字节数和调用 频率限流。它不能保存任务执行、凭据、文件内容或 Workspace 权限状态。
  • Tool Result 的 content.envelope 是可选的结构化投影,包含受约束的状态、数据、指标、产物引用、 provenance、warning 和 missing;产物只携带 opaque id 与展示元数据,不携带下载 URL 或本机路径。
  • 呈现只是请求。Host 可能降级到别的表面,成功结果里的 surface 才是真实表面; Widget 不能指定目标 Panel、身份、运行时 generation、坐标或层级。
  • 高层插件 API 使用 context.events.on(handler) 接收 Host 从当前 Runtime 主动推送的 聚合事件,只按 type 分发,不依赖 Manifest Topic 或 events.read grant。需要直接使用有界 分页读取时,从 @dp-bohrium/widget-sdk/runtime 使用 subscribeEvents / onTool。
  • 低层 subscribeEvents / onTool 的所有订阅共享同一实例的 events 读预算,并各自遵守 topic 最小轮询间隔 —— 订阅越多,单个 topic 刷新越慢。游标只在 onBatch 成功后 提交,重新订阅需显式传回 events.cursor。工具 Hook 观察的是已经发生的服务端 事实,不是执行前拦截器。

测试

import { MockWidgetHost } from "@dp-bohrium/widget-sdk/testing";

MockWidgetHost 负责 init/ready 时序、首帧 render 与 update 选择、resize 事件、 能力 adapter 响应与终态 dispose;capabilityResult 给出确定性的默认允许/拒绝, 同时提供 handleCapability 时后者优先。host.capabilityRequests 返回有序的防御性 拷贝,断言不会改动 Mock 状态。

它使用与生产 Host 相同的公开 contracts,但不等同于生产 iframe、CSP、授权或资源 隔离保证。

限制

  • SDK 不做授权决策,能力是否可用最终由 Host 判定。
  • tasks.watch 是有界观察协议,不是调度器、执行器或取消 API;任务状态和事件由服务端投影。
  • invokeAction 不自动重试,回调完成也不代表工具执行完成。
  • subscribeEvents 的 events.read 是有界分页读取,不是 websocket;实时推送走 onEvent。当前版本没有单请求取消的独立协议消息:unsubscribe 会停止后续轮询并取消 本地等待,已发出的服务端读取可能完成,但结果被忽略。
  • Host 发来畸形 Bridge 信封或载荷时,transport 报结构化 MESSAGE_INVALID 并关闭 连接,不会让 Widget 悬等。
  • Schema 与类型校验以 @dp-bohrium/widget-contracts 为准。

许可证

MIT。