@mottyjs /适配器
Motty SDK的适配器:主机在其主机选项中传递的纯对象,一个导入路径
每一个。模型供应商: openai, anthropic, openrouter, deepseek, moonshot, zai,
dashscope, minimax, groq, mistral, together, fireworks, ollama, local 和
typesafe. 储存: redis,这是Bun自己的客户端,因此只在Bun上运行 cloudflare,
一个持久对象,它只在Cloudflare Workers上运行。身份验证: jwt 和
bearer或者主人自己的物品。凭据保留在主机创建的适配器中
永远不要输入莫蒂的密码。这个包从来没有在motty's bundle中。
bun add @mottyjs/adapters
import { openai } from "@mottyjs/adapters/openai";
服务文件的适配器:
import { serve } from "@mottyjs/serve";
import { jwt } from "@mottyjs/adapters/jwt";
import { openai } from "@mottyjs/adapters/openai";
import { openrouter } from "@mottyjs/adapters/openrouter";
import { redis } from "@mottyjs/adapters/redis";
import desk from "./motty"; // Holds .use(auth())
export default serve(desk, ({ OPENAI_KEY, OPENROUTER_KEY, REDIS_URL, JWKS_URL }) => ({
models: [
openai({ key: OPENAI_KEY }),
// A customer's message goes through a router only to providers that keep none of it
openrouter({ key: OPENROUTER_KEY, body: { provider: { data_collection: "deny" } } }),
],
storage: redis({ url: REDIS_URL }),
auth: jwt({ jwks: JWKS_URL }),
}));
一个 auth 适配器只能证明来电者是谁,所以没有 .use(auth()) 为任何人运行。
在Worker上,存储是宿主自己的Worker的一个持久对象类。工人条目导出
MottyStorage, cloudflare 给出了它的绑定,从 env 那 cloudflare:workers
导出,因为绑定不是字符串,因此不在环境中 mount 写着:
// src/index.ts
import { env } from "cloudflare:workers";
import { mount } from "@mottyjs/serve";
import { cloudflare } from "@mottyjs/adapters/cloudflare";
import { openai } from "@mottyjs/adapters/openai";
import desk from "./motty";
export { MottyStorage } from "@mottyjs/adapters/cloudflare";
export default mount(desk, ({ OPENAI_KEY }) => ({
models: [openai({ key: OPENAI_KEY })],
storage: cloudflare({ binding: env.STORAGE }),
}));
的绑定及其迁移 wrangler.jsonc:
{
"main": "src/index.ts",
"compatibility_date": "2026-02-24",
"compatibility_flags": ["nodejs_compat", "enable_request_signal"],
"durable_objects": {
"bindings": [{ "name": "STORAGE", "class_name": "MottyStorage" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MottyStorage"] }]
}
类型 env.STORAGE 来自于文件 wrangler types 生成,所以运行它一次
那 main 名字存在。
这个 nodejs_compat 标志是必需的,如果没有它,则部署将失败
No such module "node:async_hooks".
第一次部署必须带着它的秘密。 mount 在全局作用域运行,该作用域作为
上载Worker,因此一个还不存在的键停止部署
The openai adapter was given no key 代码10021。把秘密从文件中上传
它不会出现在存储库中:
wrangler deploy --secrets-file <file>
同一个Cloudflare帐户上的上游Worker,到达其workers.dev地址,被看到
在实际部署中回答404,并打开 global_fetch_strictly_public 兼容性
Flag修复了它。为什么它回答404没有确认。
每个键都是自己的对象,所以键之间永远不会等待,对象的键
所有过期的全部删除。如果兼容性日期为2026-02-24或更高,则可以删除该内容
随身携带对象的警报器;较旧的日期只会在空对象上触发一次警报。
希望将相关密钥放在一起的主机通过 name,一个函数从一个键到它的名称
对象,而必须将数据保存在某个区域的对象传递限制到该区域的绑定,例如
env.STORAGE.jurisdiction("eu"). 绑定不是凭证,所以没有任何秘密可以到达motty。
连接需要 headers 对于它提出的每一个请求 body 字段,在提供者自己的
的名字。一个调用可以在它的选项中声明它自己的头文件,对于头文件位于上游的线束
预计。除非连接将其命名为,否则它们都不会被发送 forwardHeaders,在任何信件中
案例:
import { model, motty } from "@mottyjs/core";
import { anthropic } from "@mottyjs/adapters/anthropic";
// In the host
anthropic({ key: ANTHROPIC_KEY, forwardHeaders: ["user-agent", "x-app", "x-session"] });
// In the motty
export default motty({ name: "relay" }).handle(({ request, prompt }) => {
const headers = {
"user-agent": request.headers.get("user-agent"),
"x-app": request.headers.get("x-app"),
"x-session": request.headers.get("x-session"),
};
prompt.options = { ...prompt.options, headers };
return model("anthropic/claude-sonnet-5-5").complete(prompt);
});
没有值的标头会被跳过。 forwardHeaders: true 转发每个头呼叫状态,但
下面这些。它信任motty在固定集之外的每个header,即选择的header
其中包括主机密钥下的帐户或项目,列表是安全的形式。一个网关
可能作用于主机从未设置的标头,例如指定在何处发送请求或
哪个帐户付款,因此列出通过一个帐户的连接的名称。
连接的标头和凭据总是优于同名的调用,也是如此
API自己写的头,比如内容类型。在任何形式的 forwardHeaders,
连接本身的报头、凭据、cookie和描述调用者主体的报头
永远不会发送。说明请求来自何处的通用头被省略在下面 true,
主机列出的名称即使在它说明请求来自何处时也会发送。一个头
未发送的在调用的警告中被命名,宿主的报告携带该警告,因此是作者
明白为什么没到了吧。任何请求都不能携带其名称或值的标头将导致调用失败
作为motty中的一个错误,将被发送的报头也是如此,并且在两个中声明两次
信情况。
在人类活动的上游 anthropic-* header是发送的消息API的调用者,除了
版本,已经带着请求旅行而没有被列出,他们赢得了一个电话的
相同的名字。
jwt 仅针对密钥集地址(jwks),一个公钥
PEM (key)或共享的秘密(secret),并给出它的 sub 作为来电显示。默认情况下
检查签名和令牌本身所声明的时间,以及声明没有到期的令牌
接受。每进一步的检查都是主人的补充:除了验证的方式, jwt 以
选项的 jose的 jwtVerify,都是可选的。标识提供程序为其签名令牌的主机
使用一个密钥集的几个应用程序应该命名发行者和受众:
jwt({
jwks: JWKS_URL,
issuer: "https://id.acme.example/",
audience: "desk",
requiredClaims: ["exp"],
clockTolerance: 30,
});
bearer,从相同的导入路径获取一个普通密钥,并向调用者提供主机的id
命名,用于持有长期凭据的服务:
import { bearer } from "@mottyjs/adapters/jwt";
bearer({ secret: OPS_TOKEN, id: "ops" });
两者都是便利。Auth是主人自己写的 auth 选项接受任何带有
verify 函数,它被赋予调用者发送并返回调用者的令牌,或者 undefined
对任何人,立即或作为一个承诺。在主机自己的表中查找API键,这里是它的
ownerOf:
import { ownerOf } from "./keys";
export default serve(desk, ({ OPENAI_KEY }) => ({
models: [openai({ key: OPENAI_KEY })],
auth: {
verify: async (token) => {
const owner = await ownerOf(token);
return owner === undefined ? undefined : { id: owner };
},
},
}));
使用OpenAI API的服务器可以通过 openai 本身 address. 模型名称
开始 prefix,这是在呼叫之前取下的,它应该是一个独立的词
OpenAI本身也在其中 models. 适配器使用响应API,除非 api 说
"chat-completions":
// In the host
openai({
key: ACME_KEY,
address: "https://llm.acme.example/v1",
prefix: "acme",
api: "chat-completions",
});
// In the motty
model("acme/some-model").complete(prompt);
密钥是必需的,并作为承载令牌发送到该地址。主机本身运行的服务器
想要什么钥匙都没有 local. 适配器所知道的OpenAI自己的模型并不适用于
另一个地址,所以一个选项是按规定发送的,服务器可能会拒绝它。
每个提供程序的适配器都保存一个列表,从该提供程序的引用中读取
只有帮助,如 metadata, seed 和 cacheKey. 一个作者在电话里说了一个,比如
options: { serviceTier: "flex" },无论提供者是谁。在列表中发送一个选项
Provider的名称在不同的地方,其他的都被省略,并在调用时发出警告
命名选项。引用未启用的选项仅在提供者声明的地方才会出现在列表中
它会忽略它不知道的东西,就像OpenRouter一样。值按照写入的方式执行,所以层是
提供商自己的话。更改答案的字段,例如 stop, response_format 或者
parallel_tool_calls总是这样,作者的名字也一样 options.provider 或者在
连接,在上游自己的名字。 openai 没有名单,把他们都发了。 local 有一个
这些服务器共享的默认列表及其 fields 选项替换它,如
fields: { ...localFields, maxTokens: "max_tokens" }. 没有的列表 streamOptions 要求拒绝
用法。
每个模型提供者的适配器经过验证的程度是不同的。 openai, anthropic, openrouter 和
typesafe 通过进行实际调用并在?下运行的测试,针对实时API进行了验证
bun run test:live. 其他的仅针对每个提供者自己编写的存根进行验证
文件; mistral, together 和 fireworks 有一个实时测试,运行后,该提供商
设置Key。
SDK作为一个整体在 根的自述,以及其设计背后的原因 ARCHITECTURE.md.