@lelemon-ai / sdk
Lelemon API v2的TypeScript SDK。它是从API契约生成的,因此每个方法、参数和响应类型都与API引用匹配。
- 零运行时依赖项。
- ESM和CommonJS,以及两者的类型。
- 自动重试
Idempotency-Key在每一个POST,自动分页。 - Webhook,签名请求和基于Web加密的MCP断言验证。
- 输入错误,每个HTTP族一个类。
安装
npm install @lelemon-ai/sdk
快速入门
import { Lelemon } from '@lelemon-ai/sdk';
const lelemon = new Lelemon({ apiKey: process.env.LELEMON_API_KEY });
const agent = await lelemon.agents.create({
name: 'Order bot',
instructions: 'You answer questions about orders in one short sentence.',
llmConfig: { model: 'anthropic/claude-sonnet-5.5' },
});
console.log(agent.id);
apiKey 默认为 LELEMON_API_KEY 环境变量。如果没有键,构造函数将抛出。
方法名是 operationId API引用中每条路由的 agents.create, cases.takeControl, webhookEndpoints.create. 每个方法的JSDoc都来自于契约,所以编辑器会显示路由的功能和它需要的范围。
支持运行时
| 运行时 | 支持 |
|---|---|
| Node.js 20或更高版本 | ESM (import)和CommonJS (require). 测试 |
| 捆绑器(esbuild, webpack, Vite) | 用esbuild测试了一个中立的平台 |
| Cloudflare Workers、Deno、Bun、edge运行时 | 代码只使用Web api (fetch,网络加密; URL). bundle在没有Node全局变量的上下文中加载和运行。而不是运行在这些运行时上 |
| 吃嫩叶的动物 | 代码可以运行,但不会向浏览器提供API密钥 |
在TypeScript下,包解析为 moduleResolution 设置为 node10, node16, nodenext 或者 bundler. 一个CommonJS项目得到 index.d.cts 一个ESM项目得到 index.d.ts.
SDK发送 User-Agent: lelemon-sdk-node/<version> 每一个请求。Chrome默默地滴头在 fetch,因此API无法从Chrome页面识别SDK。
客户的选择
| 选项 | 默认的 | |
|---|---|---|
apiKey | LELEMON_API_KEY | 的关键 |
baseUrl | https://api.lelemon.cl | |
subaccount | 无 | 发送 Lelemon-Subaccount 每次通话 |
maxRetries | 2 | 重试429、500、502、503和504,以及网络错误和超时 |
timeoutMs | 60000 | 每一个尝试 |
retryDelayMs | 500 | 指数回退的底数。 Retry-After 在场时获胜 |
fetch | 全球 fetch | 您自己的实现 |
每个选项都接受 undefined,所以 new Lelemon({ apiKey: process.env.LELEMON_API_KEY }) 编译下 exactOptionalPropertyTypes.
调用API
每种方法都有相同的形状:
method(...pathParams, body?, options?)
没有主体的操作是 method(...pathParams, options?). 当合同规定时,在类型中需要主体。
options 是唯一的选项对象。它包含查询、等幂键、子帐户、中止信号和额外的标头:
const page = await lelemon.agents.list({ query: { limit: 10, status: 'active' } });
const message = await lelemon.whatsapp.messages.create(
{ to: { phone: '+56911112222' }, text: 'Hello' },
{ query: { dryRun: true }, idempotencyKey: 'order-8812-confirmation' },
);
| 选项 | |
|---|---|
query | 路由的查询参数,从合同中键入 |
idempotencyKey | 发送 Idempotency-Key |
subaccount | 发送 Lelemon-Subaccount. 赢得客户端选项 |
signal | 一个 AbortSignal |
headers | 额外的头 |
Idempotency-Key 和 Lelemon-Subaccount 永远不是方法参数,即使契约将它们作为头参数记录。
幂等性
每一个 POST 携带一个 Idempotency-Key. 当不通过时,SDK会生成一个UUID idempotencyKey,而重试则重用相同的键,因此重试的请求永远不会执行两次。
当同一个业务操作可以被触发多次(一个队列重新交付,一个用户双击)时,传递你自己的密钥:
await lelemon.contacts.create(
{ name: 'Ana Rojas', externalRef: 'cust-88123' },
{ idempotencyKey: 'create-cust-88123' },
);
分页
列表返回第一页的承诺,它也是每个项的异步可迭代对象:
for await (const contact of lelemon.contacts.list({ query: { limit: 50 } })) {
console.log(contact.id);
}
const firstPage = await lelemon.contacts.list();
console.log(firstPage.data, firstPage.hasMore, firstPage.nextCursor);
const firstHundred = await lelemon.contacts.list().toArray({ max: 100 });
迭代器只在您到达当前页的末尾时才请求下一页,并保留您的 query 过滤器。
错误
每个错误都会扩展 LelemonError.
| 类 | 当 |
|---|---|
LelemonApiError | 任何非2xx的答案。下面类的基 |
BadRequestError | 400 |
AuthenticationError | 401 |
PermissionError | 403 |
NotFoundError | 404 |
ConflictError | 409 |
UnprocessableError | 422 |
RateLimitError | 重试次数用完后 |
ApiServerError | 5xx,重试次数用完后 |
LelemonConnectionError | 重试后无法访问API |
LelemonTimeoutError | 一个超时。扩展 LelemonConnectionError |
LelemonAbortError | 你的 signal 中止。 cause 流产的原因是什么? |
LelemonSignatureVerificationError | 未验证网络钩子、签名请求或MCP断言 |
API错误携带答案的问题细节:
import { LelemonApiError, NotFoundError, RateLimitError } from '@lelemon-ai/sdk';
try {
await lelemon.agents.retrieve('agt_...');
} catch (error) {
if (error instanceof NotFoundError) {
console.log(error.status, error.code, error.requestId);
} else if (error instanceof RateLimitError) {
console.log(error.retryAfterSeconds);
} else if (error instanceof LelemonApiError) {
console.log(error.code, error.detail, error.param, error.docUrl, error.fix, error.budget);
} else {
throw error;
}
}
code 类型为合同中错误代码的并集, fix 下一步是错误目录所建议的和吗 budget 附带 budget_exceeded. error.name 总是字面意义上的类名。
Aborting会立即停止一切,包括重试前的等待:
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
await lelemon.agents.list({ signal: controller.signal });
人则
用原始体验证签名。 constructEvent 返回合同的36个事件的联合,由 type,并抛出 LelemonSignatureVerificationError 当签名、时间戳或密钥不匹配时。验证是异步的,因为它使用Web加密。
import { Lelemon, type LelemonEvent } from '@lelemon-ai/sdk';
app.post('/webhooks/lelemon', express.raw({ type: 'application/json' }), async (req, res) => {
let event: LelemonEvent;
try {
event = await Lelemon.webhooks.constructEvent(
req.body,
req.header('Lelemon-Signature') ?? '',
process.env.LELEMON_WEBHOOK_SECRET!,
);
} catch {
res.sendStatus(400);
return;
}
switch (event.type) {
case 'case.escalated':
console.log(event.data.case.id, event.customerId);
break;
default:
break;
}
res.sendStatus(200);
});
有效载荷是 string,一个 Uint8Array (一个节点 Buffer 是一)还是一 ArrayBuffer. 重新序列化的主体不进行验证。默认容差为300秒(toleranceSeconds). 在秘密旋转期间,头部携带2 v1 签名和任何一个都足够了。
在SDK版本仍然可以解析后添加的事件类型。保持一个 default 分支。
发送给操作和连接器的签名请求
verifyRequest(headers, method, url, rawBody, secret, options?) 验证 Lelemon-Signature Lelemon添加到对您的操作和数据连接器的调用中。 deriveToolSecret(organizationSecret, toolId) 推导出一种工具的秘密。两者都是异步的。 createReplayGuard() 返回一个 isReplay 的函数 jti.
MCP断言
verifyMcpAssertion(token, secrets, { audience, expectedTool, expectedArgs }) 检查lemon在运行身份开启时发送给MCP服务器的JWT。 secrets 地图每一个 kid 它的秘密。它固定HS256,检查发行者,受众和到期,拒绝寿命超过60秒的断言,要求它用于工具和您收到的呼叫的参数(expectedTool, expectedArgs),并且可以拒绝重复 jti (isReplay)。
数据连接器
connector.manifest, connector.account, connector.field, connector.table, connector.badge, connector.action, connector.viewPage 和 connector.accountList 构建a的文档 lelemon-connector/v1 带类型的连接器。 validateConnectorDocument(type, document) 的JSON schema进行检查 GET /v2/data-connectors/format (connector-format.json,保持与正在运行的API相等 pnpm spec:update)。
这个包安装 lelemon 命令:
lelemon connector validate <url|file> [--type manifest|account|accountSearch|view] [--account <id>] [--header "Name: value"]
lelemon connector serve --fixture <file> [--port 4300] [--secret <signing secret>]
examples/connector.ts 是一个完整的连接器吗 examples/fixture.json 固定装置 serve.
Subaccounts
const client = lelemon.withSubaccount('sub_...');
await client.contacts.list();
await lelemon.contacts.list({ subaccount: 'sub_...' });
withSubaccount 返回一个新客户端,而不修改原来的客户端。属于子帐户的事件携带 customerId.
测试模式
以…开头的钥匙 lel_test_ 在你的数据的单独副本上工作:代理人回答,案件打开,网络钩子启动,但没有任何东西到达真实的人。将其用于整个集成:
const lelemon = new Lelemon({ apiKey: process.env.LELEMON_TEST_KEY });
许多路线都接受 query: { dryRun: true } 验证请求,看看在不保存、发送或收费的情况下会发生什么。演练的答案是有效的 dryRun: true 在它的类型,所以你可以区分它和真正的。
类型
生成的合约类型被导出为一个命名空间:
import type { Types } from '@lelemon-ai/sdk';
function describe(agent: Types.Agent) {
return agent.name;
}
在契约说“恰好是其中之一”的地方,类型是在编译时拒绝无效组合的联合(union),例如两个都有的接收者 contactId 和 externalRef.
开发
pnpm --filter @lelemon-ai/sdk generate
pnpm --filter @lelemon-ai/sdk typecheck
pnpm --filter @lelemon-ai/sdk test
pnpm --filter @lelemon-ai/sdk build
openapi.json 是合同。 generate 读取它并写入 src/generated. 方法树来自 operationId 每条路线。
许可证
麻省理工学院。版权所有(c) 2026柠檬SpA。