@operon/sdk
v0.2.0
Published
Operon SDK - request placements from the Operon ad network
Downloads
824
Maintainers
Readme
@operon/sdk
Operon's TypeScript SDK. Connect any agent to the ad network.
Install
npm install @operon/sdkWorks in both ESM and CommonJS Node.js projects.
Quickstart
The SDK ships dual ESM + CJS builds, so the same package works in both
project styles. Pick the snippet that matches your project's "type"
field in package.json (default for npm init -y is CommonJS).
ESM ("type": "module", or .mjs):
import { initOperon } from "@operon/sdk";
const operon = initOperon({ url: "https://api.operon.so" });
const result = await operon.getPlacement(userQuery, {
placement_context: "user asked about swapping ETH",
});
if (result.decision === "filled") {
console.log("Recommendation:", result.placement?.service);
}
// Inspect the meta block to see how trust scoring is treating you.
console.log(result._meta);CommonJS (default for npm init -y, no "type": "module"):
const { initOperon } = require("@operon/sdk");
const operon = initOperon({ url: "https://api.operon.so" });
const result = await operon.getPlacement(userQuery, {
placement_context: "user asked about swapping ETH",
});
if (result.decision === "filled") {
console.log("Recommendation:", result.placement?.service);
}
// Inspect the meta block to see how trust scoring is treating you.
console.log(result._meta);Once you've made a call, confirm the SDK is wired up correctly:
npx @operon/sdk test # fire a sandbox getPlacement
npx @operon/sdk status # show local SDK state (UUID, source, registration)Register for production demand
After integrating, register to bump your sandbox quota and join the early-access list for production demand:
npx @operon/sdk registerThe CLI prompts for email, framework name, agent description, and a monthly call volume tier:
Operon Publisher Registration
Registering raises your sandbox quota from 100 to 1000 placements/hr
and puts you on the early-access list for production demand.
We'll only email you about quota changes and demand availability.
We'll use this to follow up about production demand pool access. No marketing spam.
Email: [email protected]
Help us prioritize which framework SDKs to ship next.
Framework (e.g. elizaos): elizaos
Help us understand who's building and what for.
What does your agent do? DeFi research agent
Used to assign appropriate quotas and prioritize early-access invites.
Expected monthly calls:
1) <1K (testing / hobbyist)
2) 1K-10K (small production agent) [default]
3) 10K-100K (mid-tier production)
4) 100K-1M (high-volume)
5) >1M (commercial)
Choice [2]:On success, the CLI echoes back your registration and the new quota:
Done. You're registered.
Email: [email protected]
Framework: elizaos
Agent: DeFi research agent
Expected volume: 1K-10K
Source: (not set, that's fine)
Status: registered
Sandbox quota: 1000 placements/hr (was 100)
Next steps:
- Try a placement (see https://operon.so/developers)
- Watch your impressions log
- Email [email protected] when you're ready for production demandAPI
initOperon(options)
| Option | Type | Description |
|--------|------|-------------|
| url | string | Base URL of the Operon API. |
| publisherName | string? | Display name for your agent. Defaults to operon-sdk. |
| apiKey | string? | Optional Bearer token. Only needed for authenticated publishers. Sandbox usage does not require a key. |
| timeoutMs | number? | Request timeout. Defaults to 10000. |
| source | string? | Marketplace/skill attribution tag ([A-Za-z0-9._-], max 64). First-touch wins: persisted on first valid call, ignored on later inits with a different value. |
| onRetryable | (err: OperonRetryableError) => void? | Observability hook fired when the server returns 503 + Retry-After, before the error is thrown. Use for metrics, Sentry, alerting, etc. Errors thrown by the callback are swallowed. Note that the SDK does not await the callback's return value - async callbacks should not throw; wrap async work in a try/catch internally. |
| maxRetryAfterMs | number? | Cap on Retry-After-derived backoff hints (ms). Defaults to 60_000. Invalid values (0, negative, NaN, Infinity) fall back to the default. |
getPlacement(query, context)
query is the user's natural-language input. context must include placement_context (a one-sentence description of what the user is doing). Optional fields: asset, intent, category, sentiment, actions.
Returns a placement response. Inspect _meta.is_sandbox to know if you got mock demand.
Configuration
| Env var | Purpose |
|---------|---------|
| OPERON_CLIENT_ID | Override the persisted UUID. Use in CI or ephemeral environments without writeable home directories. Also disables source persistence so CI runs don't pollute developer-machine attribution. |
| OPERON_SOURCE | Override the attribution tag at request time. Doesn't write to disk. Use in CI/containers to test attribution behavior without affecting persisted state. |
| OPERON_URL | Default URL for npx @operon/sdk register. Defaults to https://api.operon.so. |
Sandbox vs production
Until production demand is live, all placements are sandbox. Check _meta.is_sandbox in responses. Auction logic, trust scoring, and _meta are real - only the demand pool is mock.
The first 100 calls per UUID return a register prompt. Past 100, the message upgrades to a quota nudge. Past 1000, it includes a personal note pointing to [email protected]. Registered users get 10x quota and a different message.
Circuit breaker
Five failures within 30 seconds opens the circuit. While open, getPlacement throws synchronously without making a request. The window slides, so the circuit auto-closes once 30 seconds pass without new failures.
Handling 503 throttling
When the server returns 503 with a Retry-After header (sandbox-lane rate ceiling), the SDK throws OperonRetryableError carrying retryAfterMs. This does NOT count toward the circuit breaker.
import { initOperon, OperonRetryableError } from "@operon/sdk";
const op = initOperon({ url: "https://api.operon.so" });
try {
const result = await op.getPlacement(query, ctx);
// use result
} catch (err) {
if (err instanceof OperonRetryableError) {
// Server asked us to wait; e.g. skip this placement and continue
console.log(`throttled, retry after ${err.retryAfterMs}ms`);
return null;
}
throw err;
}Observability hooks and backoff tuning
Use onRetryable to pipe throttle events into your metrics or alerting stack. The hook fires before the throw - your try/catch still runs normally. Use maxRetryAfterMs to override the default 60s cap on Retry-After hints (useful when your loop budget is tighter):
import { initOperon, OperonRetryableError } from "@operon/sdk";
const op = initOperon({
url: "https://api.operon.so",
// Fired when 503 + Retry-After comes back. Doesn't gate the throw -
// your try/catch still runs. Use this for metrics, alerts, etc.
onRetryable: (err) => {
metrics.increment("operon.retryable", { retryAfterMs: err.retryAfterMs });
},
// Override the default 60s cap on retry hints.
maxRetryAfterMs: 30_000,
});