@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)。 可信发布不能创建包,因此维护者 用自己的机器手工出版了第一个版本:
- 手动运行工作流(操作›释放›运行工作流)。这是一次演习,它能保留焦油和
SHA256SUMS随着release工件。下载它。 - 在那个文件夹里:
sha256sum -c SHA256SUMS(在Mac上,shasum -a 256 -c SHA256SUMS),然后npm login --auth-type=web && npm publish <tarball> --access public && npm logout. - 在npmjs.com上,软件包›设置›可信发布者›GitHub Actions:组织
Monster-Orange、存储库mo-narai-sdk、工作流release.yml,允许直接发表。 - 同一页面›发布权限: 要求双因素身份验证,不允许令牌. 信任的出版 仍能工作。
思想:
- 可信发布者字段是区分大小写的,npm在保存它们时不会检查它们。不匹配失败
只有在发布时间,作为一个
E404看起来像是丢失了一个包裹。package.json的repository.url一定是这个 存储库。 - 新的可信发布者配置必须在2天内发布,否则将过期。
- NPM信任工作流的文件名,
release.yml:重命名会破坏发布E404),直到 受信任的发布者更改为匹配。 - 来源是on的:npm在可信发布下为公共存储库和公共包生成它
国旗。package.json的
repository.url是NPM检查它的依据。一次性第一 从维护人员的机器上发布则没有。