@lelemon-ai/sdk

中文文档
0.1.1发布于 2026/11/01MITTypeScriptESM + CJS零依赖

TypeScript SDK for the Lelemon API v2

原始使用文档
文档目录17 节
  1. Install
  2. Quickstart
  3. Supported runtimes
  4. Client options
  5. Calling the API
  6. Idempotency
  7. Pagination
  8. Errors
  9. Webhooks
  10. Signed requests to your actions and connectors
  11. MCP assertions
  12. Data connectors
  13. Subaccounts
  14. Test mode
  15. Types
  16. Development
  17. License

@lelemon-ai/sdk

TypeScript SDK for the Lelemon API v2. It is generated from the API contract, so every method, parameter and response type matches the API reference.

  • Zero runtime dependencies.
  • ESM and CommonJS, with types for both.
  • Automatic retries, an Idempotency-Key on every POST, automatic pagination.
  • Webhook, signed-request and MCP assertion verification built on Web Crypto.
  • Typed errors, one class per HTTP family.

Install

npm install @lelemon-ai/sdk

Quickstart

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 defaults to the LELEMON_API_KEY environment variable. The constructor throws if there is no key.

Method names are the operationId of each route in the API reference: agents.create, cases.takeControl, webhookEndpoints.create. The JSDoc of each method comes from the contract, so your editor shows what the route does and the scopes it needs.

Supported runtimes

RuntimeSupport
Node.js 20 or laterESM (import) and CommonJS (require). Tested
Bundlers (esbuild, webpack, Vite)Tested with esbuild for a neutral platform
Cloudflare Workers, Deno, Bun, edge runtimesThe code only uses Web APIs (fetch, Web Crypto, URL). The bundle loads and runs in a context with no Node globals. Not run on those runtimes themselves
BrowsersThe code runs, but never ship an API key to a browser

Under TypeScript the package resolves with moduleResolution set to node10, node16, nodenext or bundler. A CommonJS project gets index.d.cts and an ESM project gets index.d.ts.

The SDK sends User-Agent: lelemon-sdk-node/<version> on every request. Chrome silently drops that header in fetch, so the API cannot identify the SDK from a Chrome page.

Client options

OptionDefault
apiKeyLELEMON_API_KEYThe key
baseUrlhttps://api.lelemon.cl
subaccountnoneSends Lelemon-Subaccount on every call
maxRetries2Retries on 429, 500, 502, 503 and 504, and on network errors and timeouts
timeoutMs60000Per attempt
retryDelayMs500Base of the exponential backoff. Retry-After wins when present
fetchglobal fetchYour own implementation

Every option accepts undefined, so new Lelemon({ apiKey: process.env.LELEMON_API_KEY }) compiles under exactOptionalPropertyTypes.

Calling the API

Every method has the same shape:

method(...pathParams, body?, options?)

Operations without a body are method(...pathParams, options?). The body is required in the types when the contract says so.

options is the only options object. It holds the query, the idempotency key, the subaccount, the abort signal and extra headers:

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' },
);
Option
queryThe query parameters of the route, typed from the contract
idempotencyKeySent as Idempotency-Key
subaccountSent as Lelemon-Subaccount. Wins over the client option
signalAn AbortSignal
headersExtra headers

Idempotency-Key and Lelemon-Subaccount are never method arguments, even when the contract documents them as header parameters.

Idempotency

Every POST carries an Idempotency-Key. The SDK generates a UUID when you do not pass idempotencyKey, and a retry reuses the same key, so a retried request is never executed twice.

Pass your own key when the same business operation can be triggered more than once (a queue that redelivers, a user who double clicks):

await lelemon.contacts.create(
  { name: 'Ana Rojas', externalRef: 'cust-88123' },
  { idempotencyKey: 'create-cust-88123' },
);

Pagination

A list returns a promise of the first page that is also an async iterable of every item:

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 });

The iterator asks for the next page only when you reach the end of the current one, and keeps your query filters.

Errors

Every error extends LelemonError.

ClassWhen
LelemonApiErrorAny non-2xx answer. The base of the classes below
BadRequestError400
AuthenticationError401
PermissionError403
NotFoundError404
ConflictError409
UnprocessableError422
RateLimitError429, after the retries ran out
ApiServerError5xx, after the retries ran out
LelemonConnectionErrorThe API could not be reached after the retries
LelemonTimeoutErrorA timeout. Extends LelemonConnectionError
LelemonAbortErrorYour signal aborted. cause is the abort reason
LelemonSignatureVerificationErrorA webhook, signed request or MCP assertion did not verify

An API error carries the problem details of the answer:

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 is typed as the union of error codes in the contract, fix is the next step the error catalog suggests and budget comes with budget_exceeded. error.name is always the literal class name.

Aborting stops everything at once, including the wait before a retry:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
await lelemon.agents.list({ signal: controller.signal });

Webhooks

Verify the signature with the raw body. constructEvent returns a union of the 36 events of the contract, discriminated by type, and throws LelemonSignatureVerificationError when the signature, the timestamp or the secret do not match. The verification is asynchronous because it uses Web Crypto.

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);
});

The payload is a string, a Uint8Array (a Node Buffer is one) or an ArrayBuffer. A re-serialized body does not verify. The default tolerance is 300 seconds (toleranceSeconds). During a secret rotation the header carries two v1 signatures and either one is enough.

An event type added after your SDK version still parses. Keep a default branch.

Signed requests to your actions and connectors

verifyRequest(headers, method, url, rawBody, secret, options?) verifies the Lelemon-Signature that Lelemon adds to the calls it makes to your actions and data connectors. deriveToolSecret(organizationSecret, toolId) derives the secret of one tool. Both are asynchronous. createReplayGuard() returns an isReplay function for the jti.

MCP assertions

verifyMcpAssertion(token, secrets, { audience, expectedTool, expectedArgs }) checks the JWT that Lelemon sends to your MCP server when its run identity is on. secrets maps each kid to its secret. It pins HS256, checks the issuer, the audience and the expiry, rejects an assertion that lives longer than 60 seconds, requires it to be for the tool and the arguments of the call you received (expectedTool, expectedArgs), and can reject a repeated jti (isReplay).

Data connectors

connector.manifest, connector.account, connector.field, connector.table, connector.badge, connector.action, connector.viewPage and connector.accountList build the documents of a lelemon-connector/v1 connector with types. validateConnectorDocument(type, document) checks one against the JSON Schemas of GET /v2/data-connectors/format (connector-format.json, kept equal to the running API by pnpm spec:update).

The package installs the lelemon command:

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 is a complete connector, and examples/fixture.json a fixture for serve.

Subaccounts

const client = lelemon.withSubaccount('sub_...');
await client.contacts.list();

await lelemon.contacts.list({ subaccount: 'sub_...' });

withSubaccount returns a new client and leaves the original untouched. Events that belong to a subaccount carry customerId.

Test mode

A key that starts with lel_test_ works on a separate copy of your data: agents answer, cases open and webhooks fire, but nothing reaches a real person. Use it for the whole integration:

const lelemon = new Lelemon({ apiKey: process.env.LELEMON_TEST_KEY });

Many routes accept query: { dryRun: true } to validate a request and see what would happen without saving, sending or charging anything. The answer to a dry run carries dryRun: true in its type, so you can tell it from the real one.

Types

The generated types of the contract are exported as a namespace:

import type { Types } from '@lelemon-ai/sdk';

function describe(agent: Types.Agent) {
  return agent.name;
}

Where the contract says "exactly one of", the types are unions that reject invalid combinations at compile time, for example a recipient with both contactId and externalRef.

Development

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 is the contract. generate reads it and writes src/generated. The tree of methods comes from the operationId of each route.

License

MIT. Copyright (c) 2026 Lelemon SpA.

版本与发布记录

@lelemon-ai/sdk 共发布 3 个版本,首次发布于 2026/11/01;过去 12 个月发布了 3 个版本。

版本发布日期距今
0.1.1latest2026/11/011 天前
0.1.02026/11/012 天前
0.0.0-stage2026/11/012 天前

关于 @lelemon-ai/sdk 的常见问题

@lelemon-ai/sdk 是什么?

Lelemon API v2的TypeScript SDK

如何安装 @lelemon-ai/sdk?

在项目目录运行 npm install @lelemon-ai/sdk,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 lelemon,可以用 npx lelemon 直接运行。

@lelemon-ai/sdk 支持 TypeScript 吗?

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

@lelemon-ai/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@lelemon-ai/sdk 需要什么版本的 Node.js?

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

@lelemon-ai/sdk 可以商用吗?

@lelemon-ai/sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@lelemon-ai/sdk 还在维护吗?

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