集成就是插件。内置的 REST、GraphQL 和 CMS 连接器使用的,正是所有插件都在用的 @malla/plugin-sdk:一个 connection(设置、密钥字段、一个连接测试)和若干 operations(查询和变更),均用 Zod 校验。
定义插件
ts
import { definePlugin, defineOperation } from '@malla/plugin-sdk'
import { z } from 'zod'
const config = z.object({
baseUrl: z.url().describe('API base URL'),
})
export default definePlugin({
id: 'weather',
name: 'Weather',
version: '1.0.0',
description: 'Current conditions by city.',
connection: {
config,
secrets: { apiKey: { label: 'API key' } },
secretBoundFields: ['baseUrl'], // changing it clears the stored key
hosts: (c) => [new URL(c.baseUrl).host], // the only hosts ctx.fetch may reach
test: async (ctx) => {
const res = await ctx.fetch(`${ctx.config.baseUrl}/ping`)
if (!res.ok) throw new Error('Weather API unreachable')
},
},
operations: {
'weather.current': defineOperation({
kind: 'query',
name: 'Current weather',
input: z.object({ city: z.string().min(1) }),
output: z.object({ tempC: z.number(), summary: z.string() }),
run: async (ctx, input) => {
const url = `${ctx.config.baseUrl}/current?city=${encodeURIComponent(input.city)}`
const res = await ctx.fetch(url, { headers: { 'x-api-key': ctx.secrets.apiKey ?? '' } })
return res.json()
},
}),
},
})
- 操作 id 的格式是
<plugin id>.<name>;definePlugin会检查这一点。 input结构会(以 JSON Schema 形式)成为编辑器中的表单,并校验解析后的输入;output用于驱动自动补全。connection: null表示不需要集成记录,就像内置的 CMS。icon设置编辑器中的图标;fetchTimeoutMs可为模型 API 等较慢的上游调高 fetch 超时(最多 120 秒)。
插件上下文
ctx.config集成的设置,按你的 connection 结构解析。
ctx.secrets已设置值的解密后密钥。绝不会发送到浏览器。
ctx.fetch强制执行策略的 fetch:允许的主机、HTTPS、禁止私有地址、超时、大小上限。
ctx.published在线上站点为 true(线上数据),在编辑器中为 false(工作数据)。
ctx.cms仅限官方插件:列出和获取 CMS 条目。
ctx.postgres仅限官方插件:每次调用一个 TLS 连接,地址检查与 fetch 相同。
ctx.signal调用方取消时中止。
出站策略
ctx.fetch 只能通过 HTTPS 访问 connection 声明的主机。每次请求和重定向之前,它都会解析主机名(DNS over HTTPS),并拦截私有、回环和链路本地地址;最多跟随同一主机上的 5 次重定向,10 秒后超时,响应上限为 2 MB。ALLOW_LOCAL_INTEGRATIONS=1 会解除地址检查,仅限本地开发使用。
密钥
密钥值以加密方式存储,API 从不返回它们;编辑器只知道哪些密钥已设置。修改 secretBoundFields 中的设置或主机集合时,会清除已存储的密钥,除非同一次更新中带上了新值,因此令牌永远不会被悄悄地转发到其他主机。
插件目录
插件发布时附带一份声明其权限的清单,经过审核,并按站点以固定版本安装。更新需要手动选择。