@michinarai/sdk

中文文档
0.3.4发布于 2026/11/02Apache-2.0TypeScriptES Module构建来源已验证

构建在Narai课程中运行的工具:清单、嵌入运行时和本地测试工具。

中文使用文档
文档目录11 节
  1. 安装
  2. 你的第一个工具
  3. 运行它
  4. API,在一个地方
  5. 电线
  6. 测试工具
  7. 这个装具模块
  8. 模式
  9. 开发
  10. 稳定
  11. 释放

@michinarai / sdk

构建在Narai课程中运行的工具。

一个工具是一个小的web应用程序,Narai将其安装在自己的原始沙盒框架中。它显示了学习者的能力 乐器(指板、色轮、画布)并报告学习者所做的。Narai保持着 回答问题,运行每个裁判,拥有学习者模型:工具从不评分,也从不知道学习者是谁 是,除非学习者将他们的帐户与工具的发布者联系起来,并选择让工具知道他们。

  • 学习: 这门课 为Narai做一个工具 从一个空文件夹 到你的第一个工具,一步一步来。
  • 开始: 命令行, @michinarai/cli (narai init, narai dev, narai validate, narai build)。
  • 在课堂上放一个: 一个Narai账户和开发者测试版 narai login, narai tools register 和 narai tools push. 步骤是CLI的 获得. 建筑和当地的线束需要 没有帐户。
  • 链接帐户: 拥有自己账户的出版商可以让学习者将它们链接起来,并提供Sign in with Narai; 账户链接.
  • 阅读真实的: mo-narai-labsNarai自己的工具(指板) 一个间歇训练器,一个节奏敲击器),只建立在这个包。

安装

npm install @michinarai/sdk

每个版本都是从这个存储库的发布工作流中发布的,并且会显示出处(npm会显示出处) 提交它已构建)。每一个变化都在 CHANGELOG.md.

你的第一个工具

清单说明工具显示和报告的内容; connect 等待Narai的主人; answer 报告学习者 所做的。

// tool.js, loaded by your page as <script type="module" src="./tool.js">
import { connect, defineTool, pickLocale } from "@michinarai/sdk";

const manifest = defineTool({
  id: "you/hello", version: "0.1.0", sdk: "^1",
  publisher: { name: "You", url: "https://example.com" },
  surfaces: { embed: { url: "https://example.com/hello/", sdk: "^1" } },
  payloads: { "hello.answer@1": { type: "object", properties: { text: { type: "string" } }, required: ["text"] } },
  answers: { "hello.say": "hello.answer@1" },   // a task kind, and the payload its answer comes in
  locales: ["en", "ja"],
  capabilities: { audio_out: false, microphone: false, network: "none" },
});

const tool = await connect(manifest);                  // only Narai's host may connect
tool.ready(pickLocale(tool.start, manifest.locales));  // rendered, in the learner's language if the tool has it
document.querySelector("#say").onclick = () =>
  tool.answer({ schema: "hello.answer@1", text: "hello" });   // what the learner did, never whether it's right

surfaces.embed.url 是您的页面所在的位置:您托管的https URL,或工具的一个文件(index.html) Narai主机在工具自己的起源。页面导入 @michinarai/sdk 按名称:使用任何捆绑器或导入映射。 narai init 将所有这些(清单)写入 narai.tool.json(页面,构建)。

运行它

narai dev 将工具安装在当地的马具上,就像一节课一样:纳莱的主人 http://localhost, 工具在沙盒框架上 http://127.0.0.1. 马具显示每条消息及其判决,并发送 按照导师的要求来执行命令。从代码来看,它是 startHarness (下面是挽具)。

API,在一个地方

import { connect, defineTool, pickLocale } from "@michinarai/sdk";

const manifest = defineTool({ id: "you/echo", version: "0.1.0", sdk: "^1", /* … */ }); // throws a ManifestError, every reason
// or a manifest kept as JSON: defineTool(json) checks it at runtime and returns a ToolManifest
const tool = await connect(manifest);   // takes Narai's host (NARAI_HOST_ORIGINS), waits CONNECT_TIMEOUT_MS

tool.start;                       // the locale, its fallbacks, the step's preset, the task (if Narai judges one)
tool.start.subject;               // only when the learner linked their account with your publisher and chose to let
//   its tools know them (the manifest declares surfaces.account_link): their id with you, the same in every tool you
//   publish. Never a name, an email or a Narai id; absent otherwise, so a tool works without it.
//   how a publisher links accounts: the Account linking guide (top of this page)
tool.onCommand("highlight", async (payload) => { /* only commands the manifest declares arrive */ });
//   Narai is answered once the handler is done: an ack, or an error when it throws (`failed`);
//   throw new CommandRefused("unsupported", "No capo on this neck.") to say why it wasn't done
tool.state({ schema: "echo.state@1", shown: "hello" });  // what the tool shows, in the manifest's `state` payload;
//   sent on every change, and the latest answers Narai's requests. Never anything about the learner
await tool.storage.set("last.tempo", { bpm: 90 }); // this launch's own storage, kept by Narai: a key, any JSON ≤ 16 KB
await tool.storage.get("last.tempo");               // { ok: true, value } or { ok: false, error: "not_found" | "quota" | "denied" }
const locale = pickLocale(tool.start, manifest.locales); // the first of the learner's locales the tool has, else its default
tool.onLocale((message) => { /* the learner switched language: pickLocale(message, manifest.locales) */ });
tool.ready(locale);               // rendered, in the locale actually shown
tool.answer({ schema: "echo.answer@1", text: "hello" });   // what the learner did on the task; never a verdict
tool.practice("drill", { duration_s: 30, attempts: 4, payload: { schema: "echo.drill@1", said: 4 } });
tool.close("done");

电线

握手:工具说 tool.hello 当它在听的时候;纳莱的主人在窗户上回答 narai.tool.connect 和一个 MessagePort,只来自列明的产地;以后的每条消息都在端口上传播。一个工具 重新加载的帧再次说hello,并在新的端口上再次连接到相同的启动(其存储空间保留) 出发送再来,准备说再来);老港口关闭了,所以只有一个还活着。从其他页面发来的问候 起源从来没有答案。双方 对照信封检查每封邮件(schema/envelope.schema.json)和清单,并拒绝它不需要的东西 声明:它没有列出的命令,另一个模式中的有效负载,判决(correct, score, passed 任何地方…) 在回复中,一个超过64kb的有效载荷,一条关于另一次发射的消息。

有效载荷是 { schema: "<family>.<name>@<N>", ...body }. 清单在其下声明的JSON模式 payloads 描述了 这个 身体只有: SDK需要 schema 在检查它之前关闭它,所以一个模式需要 schema 每个有效载荷都失败 (manifestWarnings() 这么说)。

"payloads": { "echo.answer@1": { "type": "object", "properties": { "text": { "type": "string" } }, "required": ["text"] } }

Narai的主人需要10分钟的时间 tool.practice,然后每分钟6次,每次发射:批量练习(多余的被拒绝, 和计算)。

每一个 narai.tool.command 携带一个 id,工具回答了一次: tool.command.ack,或者 tool.command.error 带有代码(unknown_command, invalid_payload, unsupported, failed). SDK会帮你发送这些;主机的 command() 求解到答案,并求解到 failed 后 COMMAND_TIMEOUT_MS (10秒)没有。 narai.tool.state.get 请求工具的状态,该状态返回为 tool.state 命名请求(re)。

发射器的存储: tool.storage.get|set|delete 和身着传统的 narai.tool.storage.result. 宿主控制着它 启动时,永远不要一个工具的名字;键是 ^[a-z0-9._-]{1,64}$,值是不超过16kb的任何JSON,启动值为 最多64个键和256 KB。主机通过 storage 来 mountTool: httpStorage({ base }) 通过Narai的API,或者 memoryStorage() (利用)。

@michinarai/sdk/payloads 根据清单声明的JSON模式检查有效负载主体(它对它们进行编译,因此它是 对于主机、线束和CLI,其中允许使用eval)。

谁可以连接。 一个工具只从它的父窗口进行连接,在Narai的起源之一: NARAI_HOST_ORIGINS (https://michinar.ai, https://beta.michinar.ai),或 hostOrigins 你通过。一个工具 发展,从这台机器或以下服务 local.michinar.ai,也乘坐当地的奈良 (https://local.michinar.ai)和主机;其他地方的工具永远不会。 connect 放弃后 CONNECT_TIMEOUT_MS (10; timeoutMs: Infinity 等待),Narai的主机等待的时间与它说工具没有等待的时间相同 负载。

身着传统的主机 导入 @michinarai/sdk/host: mountTool, toolOrigin, CONNECT_TIMEOUT_MS 和 NARAI_HOST_ORIGINS,而不是工具的运行时。严格CSP下的主机(no 'unsafe-eval')不能编译 有效负载模式,所以它离开了 payloads 不设置并依赖于服务器,它验证每个答案和练习 报告身体与舱单的对比。其他的一切(信封,公布的姓名和图式,判决禁令, 大小上限(这个启动和任务,首先准备好)由预编译代码检查并保持在那里。

测试工具

@michinarai/sdk/testing 启动一个工具的会话对Narai自己的主机在一个真实的 MessageChannel像电线一样 浏览器连接iframe:

import { launch } from "@michinarai/sdk/testing";

const run = await launch(manifest, { locale: "ja", task });
run.tool.ready("ja");
run.tool.answer({ schema: "echo.answer@1", text: "hello" });
await run.settle();
run.received.answers;   // what the host took; run.received.refused: what it didn't, with the reason
run.forge({ type: "tool.answer" /* … */ });   // straight onto the port, around the SDK
run.postToTool({ type: "narai.tool.connect", v: 1 }, { origin: "https://evil.example" });   // as another page
run.postToHost({ type: "tool.hello", v: 1 });   // on the host's window, as the frame (or { origin, source })
run.connects;           // where the host has posted a connect: once, at the tool's origin
run.close();

这个装具模块

import { startHarness } from "@michinarai/sdk/harness";

const harness = await startHarness({ manifest, toolDir: "dist", locale: "ja", task });
// harness.hostUrl: Narai's host on http://localhost; the tool on http://127.0.0.1 in a sandboxed iframe

主机页面显示每条消息,上面有马具的判决,并以导师的身份发送清单的命令 会。 narai dev 运行它。

模式

schema/manifest.schema.json 和 schema/scopes.json 在mo-narai中生成(参见 schema/README.md);从来没有编辑 他们在这里。 schema/envelope.schema.json在服务器像清单一样生成之前,电汇信封一直在这里 然后它,和判决词在一起 src/verdicts.ts,也来自mo-narai)。 npm run generate 写 src/generated/ (类型和预编译的验证器);陈旧的测试会失败。

开发

npm ci
npm test
npm run lint
npm run typecheck

稳定

0.合约(清单、连接、注册中心的API)仍然可以在小版本之间更改 更新日志(CHANGELOG.md)说怎么做。1.0会hold住它。

释放

npm的可信发布:没有npm令牌,无论是在CI中还是在其他任何地方。工作流程如下 .github/workflows/release.yml.

每一个版本。 合并一个有问题的PR version 在包中。Json(并说明在 CHANGELOG.md): 释放。每次推送到main时,工作流都会询问npm该版本是否已发布。当它不是,它 工作流运行闸门,打包tarball及其 SHA256SUMS,标记提交。 v<version>, 发布 它在NPM上 超过OIDC (npm publish),并在GitHub上发布这两个文件。一个已经在npm上的版本:什么都不会发生。 没有人的步骤,没有标签可以推动,也没有什么需要批准。

包的第一次发布,一次(完成:0.1.0)。 可信发布不能创建包,因此维护者 用自己的机器手工出版了第一个版本:

  1. 手动运行工作流(操作›释放›运行工作流)。这是一次演习,它能保留焦油和 SHA256SUMS 随着 release 工件。下载它。
  2. 在那个文件夹里: sha256sum -c SHA256SUMS (在Mac上, shasum -a 256 -c SHA256SUMS),然后 npm login --auth-type=web && npm publish <tarball> --access public && npm logout.
  3. 在npmjs.com上,软件包›设置›可信发布者›GitHub Actions:组织 Monster-Orange、存储库 mo-narai-sdk、工作流 release.yml,允许直接发表。
  4. 同一页面›发布权限: 要求双因素身份验证,不允许令牌. 信任的出版 仍能工作。

思想:

  • 可信发布者字段是区分大小写的,npm在保存它们时不会检查它们。不匹配失败 只有在发布时间,作为一个 E404 看起来像是丢失了一个包裹。package.json的 repository.url 一定是这个 存储库。
  • 新的可信发布者配置必须在2天内发布,否则将过期。
  • NPM信任工作流的文件名, release.yml:重命名会破坏发布 E404),直到 受信任的发布者更改为匹配。
  • 来源是on的:npm在可信发布下为公共存储库和公共包生成它 国旗。package.json的 repository.url 是NPM检查它的依据。一次性第一 从维护人员的机器上发布则没有。

版本与发布记录

@michinarai/sdk 共发布 10 个版本,首次发布于 2026/10/31;过去 12 个月发布了 10 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.4latest2026/11/021 天前
0.3.32026/11/021 天前
0.3.22026/11/021 天前
0.3.12026/11/021 天前
0.3.02026/11/012 天前
0.2.12026/10/313 天前

关于 @michinarai/sdk 的常见问题

@michinarai/sdk 是什么?

构建在Narai课程中运行的工具:清单、嵌入运行时和本地测试工具。

如何安装 @michinarai/sdk?

在项目目录运行 npm install @michinarai/sdk,或使用 pnpm add / yarn add / bun add。

@michinarai/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@michinarai/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@michinarai/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@michinarai/sdk 可以商用吗?

@michinarai/sdk 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@michinarai/sdk 还在维护吗?

最新版本 0.3.4 发布于 2026/11/02(1 天前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/10/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。