@codespar/sdk

中文文档
0.16.28发布于 2026/10/31MITTypeScriptES Module构建来源已验证近 30 天下载 3983

Commerce SDK for AI agents — sessions, managed auth, Complete Loop orchestration for Latin American APIs

原始使用文档
文档目录19 节
  1. What shipped in 0.9
  2. Crypto + KYC meta-tools
  3. Install
  4. Usage
  5. API
  6. new CodeSpar(config)
  7. cs.create(userId, config)
  8. Session methods
  9. Free functions
  10. Pay documents and batches
  11. REST client generated from the OpenAPI document
  12. Migrating from 0.2.x
  13. Multi-environment (projects)
  14. Test-mode mocks
  15. Type aliases
  16. Typed errors
  17. Tool-result guards
  18. Need more?
  19. License

@codespar/sdk

Commerce SDK for AI agents — sessions, managed auth, Complete Loop orchestration for Latin American commercial APIs.

What shipped in 0.9

The current release line is 0.10; the notes below are the 0.9 changelog, kept for reference.

Eight new typed methods on Session, grouped by capability:

Meta-tool wrappers — neutral input shape, typed payload back, same wire as session.execute(...):

  • session.charge(args) — INBOUND charges (codespar_charge). Buyer pays merchant. Pix BRL via Asaas / MP / iugu / Stone; card USD via Stripe. Distinct from codespar_pay (outbound transfers).
  • session.ship(args) — shipping (codespar_ship). One typed entry into Melhor Envio's 3 rails (action: "label" | "quote" | "track").
  • session.shop(args) — buy-side shopping (codespar_shop). Catalog search → async checkout → Pix mint (action: "search" | "checkout" | "checkout_status"). Discriminated ShopArgs/ShopResult give the action-correct result type. Checkout is async: start, then poll checkout_status until ready_for_payment (carries pix_copia_e_cola) or canceled. Settle the returned Pix via a separate payment tool — settlement and governance are out of this contract. Full spec: docs/codespar-shop-contract.md.

Async settlement — codespar_charge / codespar_pay return synchronously, but real settlement lands via webhook:

  • session.paymentStatus(toolCallId) — poll the latest known status (pending → succeeded / failed / refunded). Correlates via the response idempotency_key ↔ provider external_reference.
  • session.paymentStatusStream(toolCallId, { onUpdate?, signal? }) — SSE variant. Snapshot on open, an envelope per state change, heartbeat every 15s, auto-closes 5s after a terminal state. signal aborts from the caller side.

Async verification — codespar_kyc returns the inquiry id; the buyer finishes the hosted flow off-platform:

  • session.verificationStatus(toolCallId) — poll the disposition (pending → approved / rejected / review / expired).
  • session.verificationStatusStream(toolCallId, { onUpdate?, signal? }) — SSE variant. Same lifecycle as paymentStatusStream.

Tool discovery + connection wizard (already in 0.4.0, restated for completeness):

  • session.discover(query, options?) — semantic + lexical tool search across the catalog (codespar_discover, pgvector + pg_trgm).
  • session.connectionWizard(options) — connect deep-link backend (codespar_manage_connections). Credentials never travel through this method.

All wrappers call into the same runtime as session.execute(...); you get typed payloads instead of hand-rolling the meta-tool envelope.

The typed meta-tool facades (shop(), charge(), ship(), …) are clients, not implementations. Each is a thin wrapper over execute("codespar_<tool>", args) and requires a runtime that implements that meta-tool (a registered implementation behind the contract). Against a self-hosted runtime with no registered implementation, the call returns Tool not registered. The contract + the typed facade ship here MIT; the implementation is registered by the runtime you point baseUrl at.

Crypto + KYC meta-tools

codespar_crypto_pay (Coinbase Commerce + Bitso + UnblockPay + Foxbit) and codespar_kyc (Persona, Sift, Konduto, Truora) are routable today. The verification half now has a typed wrapper (session.verificationStatus / verificationStatusStream); a session.cryptoPay(...) typed wrapper is not in 0.9.0 — call via raw session.execute("codespar_crypto_pay", {...}) for now.

// Crypto: receive a USDC payment via Coinbase Commerce hosted checkout
const charge = await session.execute("codespar_crypto_pay", {
  amount: 29.9,
  currency: "USDC",
  direction: "receive",
  description: "Order #42",
});
// charge.output.hosted_url → redirect buyer here

// Crypto: send a USDC payout to an external wallet (UnblockPay stablecoin-payout)
const payout = await session.execute("codespar_crypto_pay", {
  rail: "stablecoin-payout",
  amount: 100,
  currency: "USDC",
  direction: "send",
  counterparty: {
    address: "0xRecipientWalletAddress...",
    country: "MX", // ISO 3166-1 alpha-2 — required when direction is "send"
  },
});
// payout.output.id / .status → poll via session.paymentStatus(payout.tool_call_id)

// KYC: kick off a Persona identity verification, then poll typed
const inquiry = await session.execute("codespar_kyc", {
  buyer: { email: "[email protected]", first_name: "Alice", last_name: "Smith" },
  check_type: "identity",
});
const v = await session.verificationStatus(inquiry.tool_call_id);
//        ^^ approved | rejected | review | expired | pending

Install

npm install @codespar/sdk

Usage

import { CodeSpar } from "@codespar/sdk";

const cs = new CodeSpar({ apiKey: "ak_..." });

const session = await cs.create("user_123", {
  preset: "brazilian",
  manageConnections: { waitForConnections: true },
  // projectId: "prj_a1b2c3d4e5f6g7h8", // optional — overrides client default, falls back to org's default project
});

// Natural language
const result = await session.send("Charge R$150 via Pix and issue the NF-e");

// Direct tool execution
const charge = await session.execute("asaas/create_payment", {
  customer: "cus_xxx",
  billingType: "PIX",
  value: 150,
});

// Complete Loop — tools, findTools, and loop are free functions
import { tools, findTools, loop } from "@codespar/sdk";

const available = await tools(session);
const payments = await findTools(session, "payment");

const result = await loop(session, {
  steps: [
    { tool: "codespar_charge", params: { amount: 150, currency: "BRL", method: "pix", buyer: { name, document: cpf } } },
    { tool: "codespar_invoice", params: (prev) => ({ rail: "nfe", company_id, payment_id: prev[0].data.id }) },
    { tool: "codespar_ship", params: { action: "label", origin, destination, items } },
    { tool: "codespar_notify", params: { recipient: phone, message: "Your order is on the way!" } },
  ],
  onStepComplete: (step, r) => console.log(`✓ ${step.tool}`),
  retryPolicy: { maxRetries: 3, backoff: "exponential" },
});

API

new CodeSpar(config)

OptionTypeDefaultDescription
apiKeystringCODESPAR_API_KEY envYour API key
baseUrlstringCODESPAR_BASE_URL env, else https://api.codespar.devAPI base URL. Set CODESPAR_BASE_URL=http://localhost:8000 to point the SDK at a local OSS runtime; the managed backend is the default.
managedbooleantrueEnable managed billing/logging
projectIdstring—Optional prj_<16alphanum>. Client-wide default project; sent as x-codespar-project. Falls back to the org's default project when omitted.

cs.create(userId, config)

OptionTypeDescription
serversstring[]MCP servers to connect
presetstring"brazilian", "mexican", "argentinian", "colombian", "all"
manageConnections.waitForConnectionsbooleanBlock until all servers connected
projectIdstringOptional prj_<16alphanum>. Overrides the client-level projectId; falls back to the org's default project when both are unset.
mocksRecord<string, MockValue>Optional test-mode mocks. See Test-mode mocks.

Session methods

MethodDescription
session.execute(tool, params)Execute a specific tool
session.send(message)Send natural language message
session.sendStream(message)Stream events from a natural language message
session.authorize(serverId)Start OAuth flow for a server
session.proxyExecute(request)Proxy a raw HTTP call through the session
session.connections()List connected servers
session.discover(query, options?)Tool search via codespar_discover (typed)
session.connectionWizard(options)Connect deep-link via codespar_manage_connections (typed)
session.charge(args)Inbound charge via codespar_charge (typed) — buyer pays merchant
session.ship(args)Shipping via codespar_ship (typed) — action: "label" | "quote" | "track"
session.paymentStatus(toolCallId)Async settlement status for a codespar_charge / codespar_pay call
session.paymentStatusStream(toolCallId, opts)SSE variant of paymentStatus; resolves on terminal
session.verificationStatus(toolCallId)Async KYC disposition for a codespar_kyc call
session.verificationStatusStream(toolCallId, opts)SSE variant of verificationStatus; resolves on terminal
session.mcpMCP transport URL and headers (when using managed runtime)
session.close()Close session

For everything the API exposes over REST beyond the session surface, see REST client generated from the OpenAPI document.

Free functions

tools, findTools, and loop are free functions that accept any SessionBase — they work with the managed runtime, Managed Agents sessions, and custom runtimes alike.

FunctionDescription
tools(session)Get all available tools from the session
findTools(session, query)Search tools by name or description
loop(session, config)Run a Complete Loop workflow

Pay documents and batches

cs.payables uploads boleto PDFs, Pix QR images and CSV/XLSX lists, builds a batch, and pays the exact list you reviewed. Destinations are the ones the rail resolved (beneficiary name, masked document), and a bare Pix key never sets the amount.

import { readFile } from "node:fs/promises";
import { CodeSpar, payableBatchFromError } from "@codespar/sdk";

const cs = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });
const file = await cs.payables.files.upload(await readFile("conta.pdf"), { filename: "conta.pdf" });
let batch = await cs.payables.batches.create(
  { fileIds: [file.file_id], items: [{ pixKey: "[email protected]", amountMinor: 5000 }] },
  { idempotencyKey: "batch-2026-10-24-001" },
);
// Complement what the files did not say, then pay the list you reviewed.
const missing = batch.items.find((i) => i.missing.includes("amount"));
if (missing) {
  batch = await cs.payables.batches.updateItem(batch.batch_id, missing.item_id, {
    expectedVersion: batch.version,
    amountMinor: 1990,
  });
}
try {
  batch = await cs.payables.batches.pay(batch.batch_id, {
    version: batch.version,
    listHash: batch.list_hash,
    mandateId: "mdt_example",
  });
} catch (err) {
  const current = payableBatchFromError(err); // 409: the list changed, confirm again
  if (!current) throw err;
}
for (const item of batch.items) console.log(item.item_id, item.payment?.state);

REST client generated from the OpenAPI document

cs.api reaches every operation the API publishes at https://api.codespar.dev/openapi.json: 215 operations across 175 paths at the snapshot this version ships. No method is written by hand. The document is committed as openapi-snapshot.json, the types under src/generated/ are produced from it with openapi-typescript, and the client is typed by path and method: a path that does not declare the method does not compile, a missing required path parameter or body field does not compile, and the result is the documented 2xx shape.

import { CodeSpar } from "@codespar/sdk";

const cs = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });

// 1. List wallets in the project the key is bound to.
const wallets = await cs.api.get("/v1/wallets");

// 2. Create a payment from a wallet. The gateway answers 200 (completed),
//    402 (requires approval), 403 (denied) or 422 (failed) with the same
//    body shape, so read it with response() and branch on status.
const payment = await cs.api.response("post", "/v1/wallets/{id}/execute", {
  path: { id: "wal_example" },
  body: {
    amount: 1,
    currency: "BRL",
    recipient: "[email protected]",
    description: "Example payment",
    mandate_id: "mnd_example",
  },
});
if (payment.status === 402) {
  console.log("approval needed", payment.data);
}

// 3. Read one agentic receipt by id.
const receipt = await cs.api.get("/v1/consumers/receipts/{id}", {
  path: { id: "rcpt_example" },
});
console.log(receipt.state); // "paid" | "delivered" | "exception" | "voided"

get/post/put/patch/delete and request(method, path, options) return the documented 2xx data and throw CodesparApiError on any other status (parsed body on e.body, same as the session methods). response(method, path, options) returns { status, ok, data, response } for every status the document lists, discriminated on status. Options carry path, query, header and body exactly as the document declares them, plus the SDK-wide per-call timeout and signal. Operations documented as text/event-stream resolve to the raw Response once headers arrive.

The client only knows what the document says. A parameter a route accepts but does not declare is not typed here; the fix is in the document, not in this package. createApiClient(config) builds the same client without a CodeSpar instance, and ApiClient.operations() lists what it reaches.

Keeping it current: npm run sdk:spec:refresh (repo root) re-fetches the document and regenerates; npm run sdk:spec:check fails when the snapshot was edited by hand, when the generated files do not match the snapshot, or when the served document no longer matches the snapshot. The test suite pins the first two and dispatches all 215 operations through the client.

Migrating from 0.2.x

tools, findTools, and loop moved from session instance methods to free functions in 0.3.0.

// 0.2.x
const tools = await session.tools();
const found = await session.findTools("payment");
const result = await session.loop({ steps: [...] });

// 0.3.0
import { tools, findTools, loop } from "@codespar/sdk";
const available = await tools(session);
const found = await findTools(session, "payment");
const result = await loop(session, { steps: [...] });

Multi-environment (projects)

CodeSpar orgs have a second tenancy tier — projects — so dev / staging / prod each get isolated API keys, connected accounts, triggers, and sessions while billing stays at the org level. Every request accepts an optional x-codespar-project: prj_<16 hex> header; omit it and the backend resolves the org's default project (self-healed on first read).

Pin the whole client to one environment:

const cs = new CodeSpar({
  apiKey: process.env.CODESPAR_API_KEY!,
  projectId: "prj_staging0123abcd", // every session this client spawns scopes here
});

Or override per session:

const session = await cs.create("user_123", {
  preset: "brazilian",
  projectId: "prj_prod0123abcd", // overrides the client default
});

Precedence: sessionConfig.projectId > clientConfig.projectId > backend's org default. Format is validated at construction via Zod (/^prj_[A-Za-z0-9]{16}$/) so typos fail fast. See the Projects concept doc for the full tenancy model.

Test-mode mocks

Skip live providers in tests by passing a mocks map to cs.create. Keys are canonical tool names in slash form (asaas/create_payment, melhor-envio/calculate_shipping, …). Values are either a single object — used as the response on every matching call — or an array of objects consumed in order, returning mocks_exhausted once the list drains.

import { CodeSpar } from "@codespar/sdk";

const cs = new CodeSpar({ apiKey: process.env.CODESPAR_API_KEY });

const session = await cs.create("user_test", {
  servers: ["asaas"],
  mocks: {
    "asaas/create_payment": { id: "pay_test", status: "PENDING" },
  },
});

const result = await session.execute("asaas/create_payment", { value: 100 });
// result.data === { id: "pay_test", status: "PENDING" }

Pass an array for stateful mocks:

mocks: {
  "asaas/create_payment": [
    { id: "pay_1", status: "PENDING" },
    { id: "pay_1", status: "RECEIVED" },
  ],
}

Mocks live behind the managed backend's test-mode gate — a csk_test_* API key against a test-environment project. Live keys against the same map return mocks_not_permitted. The SDK forwards keys verbatim; if you send the OSS double-underscore form (asaas__create_payment) the backend rejects with mocks_invalid rather than the SDK silently rewriting.

The OSS runtime accepts the same mocks shape on its session API (see codespar/codespar#113), so the same test fixtures work whether you point at api.codespar.dev or a self-hosted instance via CODESPAR_BASE_URL. Self-hosted runtimes must additionally set CODESPAR_TEST_MODE_ENABLED=true on the server process; without it, the SDK receives mocks_not_permitted / HTTP 501 instead of fixture responses.

Storage shape differs by runtime — the wire contract does not. The managed backend persists mocks and per-tool consume counters; sessions and their fixtures survive restarts and multi-replica deployments. The OSS runtime holds both in process memory; they are scoped to the HTTP-session process and are lost on restart, and channel-bridge sessions (WhatsApp, Slack, Telegram, Discord) cannot carry mocks under the OSS shape. Response envelopes, status codes, sibling fields, and gate ordering are byte-identical between runtimes regardless. See the test-mode concept doc for the full per-runtime split.

Test mode is a property of the runtime, not the session. On the managed backend it's project.environment === 'test'; on a self-hosted OSS runtime it's CODESPAR_TEST_MODE_ENABLED=true on the server process. When the runtime is in test mode, every external tool call your code or LLM dispatches must match a declared mock — unmatched calls return tool_not_mocked (HTTP 422 on the catalog-routed /execute path; a tool_result block on the chat-loop) and no upstream provider runs. The envelope covers three failure modes: the mocks map has no entry for the canonical name, the session was created with no mocks field, or the canonical name has an unknown server prefix. A session that doesn't declare mocks can't dispatch any tools in test mode; declare the mocks the test will exercise, or run the same code against a live-mode runtime where the real providers handle dispatch. Built-in metadata tools — codespar_list_tools on OSS, codespar_discover and codespar_manage_connections on the managed backend — bypass this gate.

Type aliases

MockObject (Record<string, unknown>) and MockValue (MockObject | MockObject[]) ship from @codespar/types and re-export through @codespar/sdk. Use them when you want to define mock fixtures separately from the create call site.

import type { MockValue } from "@codespar/sdk";

const fixtures: Record<string, MockValue> = {
  "asaas/create_payment": { id: "pay_test", status: "PENDING" },
};

Typed errors

Every transport failure from createSession, proxyExecute, send, sendStream, paymentStatus(Stream), verificationStatus(Stream), and authorize throws a CodesparApiError with a structured code field. The old e.message.includes("foo") pattern is gone — branch on e.code instead.

import { CodesparApiError } from "@codespar/sdk";

try {
  await cs.create("user_test", { mocks: { "asaas/create_payment": {} } });
} catch (err) {
  if (err instanceof CodesparApiError) {
    if (err.code === "mocks_not_permitted") {
      // Live key against a mocks map. Swap to csk_test_*.
    } else if (err.code === "mocks_invalid") {
      // Backend rejected a tool-name key. Check the slash form.
    } else if (err.status === 0) {
      // Network never reached the backend; err.cause has the fetch rejection.
    }
    throw err;
  }
}

session.execute keeps its returns-vs-throws asymmetry: tool failures come back as ToolResult.success === false with the body on error. Only transport-level failures throw.

Tool-result guards

The five reserved tool-result codes (policy_denied, approval_required, mocks_exhausted, mocks_engine_error, tool_not_mocked) ship typed guards plus an exhaustive-match helper. Guards run against any unknown payload — both ToolResult.data from session.execute and ToolCallRecord.output from send / sendStream. Each guard checks the code discriminant AND the variant's required sibling fields, so a malformed payload returns false rather than narrowing positive on the code alone.

import {
  isApprovalRequired,
  isMocksEngineError,
  isMocksExhausted,
  isPolicyDenied,
  isToolNotMocked,
  assertExhaustiveToolResult,
  ToolResultCode,
} from "@codespar/sdk";

const result = await session.execute("asaas/create_payment", { value: 100 });

if (isPolicyDenied(result.data)) {
  console.warn(`blocked by ${result.data.rule_id}: ${result.data.message}`);
} else if (isApprovalRequired(result.data)) {
  console.log(`needs approval ${result.data.approval_id} by ${result.data.expires_at}`);
} else if (isMocksExhausted(result.data)) {
  // Stateful mock array drained — pad it or extend the test.
} else if (isMocksEngineError(result.data)) {
  // Backend-side mocks engine failure; usually a malformed fixture.
} else if (isToolNotMocked(result.data)) {
  console.warn(`no mock for ${result.data.tool_name}`);
}

The same guards apply inside a sendStream loop against event.toolCall.output.

When a switch over ToolResultCode covers every variant, call assertExhaustiveToolResult in the default branch. TypeScript fails to compile if a sixth code lands without a matching arm.

function handle(outcome: ToolResultOutcome): string {
  switch (outcome.code) {
    case ToolResultCode.PolicyDenied: return outcome.rule_id;
    case ToolResultCode.ApprovalRequired: return outcome.approval_id;
    case ToolResultCode.MocksExhausted: return "exhausted";
    case ToolResultCode.MocksEngineError: return "engine";
    case ToolResultCode.ToolNotMocked: return outcome.tool_name;
    default: return assertExhaustiveToolResult(outcome);
  }
}

Need more?

Need governance, budget limits, and audit trails for agent payments? CodeSpar Enterprise adds policy engine, payment routing, and compliance templates on top of these MCP servers.

License

MIT — codespar.dev

版本与发布记录

@codespar/sdk 共发布 40 个版本,首次发布于 2026/04/15;过去 12 个月发布了 40 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.16.28latest2026/10/3110 天前
0.16.272026/10/3010 天前
0.16.262026/10/3010 天前
0.16.232026/10/2813 天前
0.16.222026/10/2713 天前
0.16.212026/10/2714 天前

关于 @codespar/sdk 的常见问题

@codespar/sdk 是什么?

Commerce SDK for AI agents — sessions, managed auth, Complete Loop orchestration for Latin American APIs

如何安装 @codespar/sdk?

在项目目录运行 npm install @codespar/sdk,或使用 pnpm add / yarn add / bun add。

@codespar/sdk 支持 TypeScript 吗?

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

@codespar/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codespar/sdk 需要什么版本的 Node.js?

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

@codespar/sdk 可以商用吗?

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

@codespar/sdk 的下载量有多少?

根据 npm 官方下载统计,@codespar/sdk 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,983 次,日均约 133 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codespar/sdk 还在维护吗?

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