npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@agentoffernetwork/sdk

v0.1.1

Published

TypeScript SDK for Agent Offer Network — intent-driven offer matching, click/conversion tracking, and recommendation formatting for LLM agents.

Readme

@agentoffernetwork/sdk

TypeScript SDK for Agent Offer Network — connect your LLM agent to a marketplace of product and service offers.

⚠️ v0.1.1 Breaking:完全对齐 Protocol Offer Schema v0.1。从内部预览版 0.0.1 升级需改代码,见 Migration Guide

🛑 0.1.0 已废,请勿安装 — 发包时 CI 缓存命中旧 dist 导致 runtime import 失败。详见 CHANGELOG。安装请用 @agentoffernetwork/sdk@^0.1.1

Features

  • Intent-driven search — describe what the user wants in natural language, get matched offers
  • Multimodal intent — text + image support (OpenAI-compatible content parts format)
  • Click & conversion tracking — full attribution pipeline for monetization
  • Recommendation formatting — ready-to-display output with disclosure labels
  • Strong typingCategory.attributes is a discriminated union (33 sub_type interfaces); zero cast needed thanks to TypeScript native narrowing
  • Zero dependencies — uses native fetch (Node 20+)
  • Dual modemock for development, live for production

Install

npm install @agentoffernetwork/sdk

Requires Node 20+.

Fastest Start

Start in mock mode first. It returns built-in sample offers locally, so you can verify your integration before requesting a live API key.

import { initialize } from '@agentoffernetwork/sdk';

const client = await initialize({
  apiKey: 'test-key',
  mode: 'mock',
});

const result = await client.queryOffers({
  intent: {
    content: [{ type: 'input_text', text: 'noise-cancelling headphones under $300' }],
  },
  context: {
    userProfile: {},
  },
});

const top = result.offers[0];
if (top) {
  console.log(top.offerInfo.title);
}

To switch to production later, keep the same code and only change:

const client = await initialize({
  apiKey: process.env.AGENTOFFERNETWORK_API_KEY!,
  mode: 'live',
  appId: process.env.AON_APP_ID,   // optional — identifies your app for attribution
});

Quick Start

import { initialize, formatPrice } from '@agentoffernetwork/sdk';

const client = await initialize({
  apiKey: 'test-key',
  mode: 'mock',
});

// Search offers by intent
const result = await client.queryOffers({
  intent: {
    content: [{ type: 'input_text', text: 'noise-cancelling headphones under $300' }],
  },
  context: {
    userProfile: {},
  },
  pagination: { limit: 5 },
});

// Access nested fields per Protocol v0.1
for (const offer of result.offers) {
  const info = offer.offerInfo;
  const price = info.commercial?.price;
  console.log(`${info.title} — ${formatPrice(price) ?? 'N/A'}`);
  console.log(client.formatRecommendation(offer, { style: 'markdown' }));
}

API

initialize(config): Promise<AgentOfferClient>

Create an SDK client instance.

const client = await initialize({
  apiKey: 'your-api-key',        // Required
  mode: 'mock',                  // 'mock' | 'live'
  baseUrl: 'https://api.agentoffernetwork.com', // Optional, default shown
  timeout: 5000,                 // Optional, ms
  appId: 'my-app-id',            // Optional — sent as `x-aon-app-id` header
});

client.queryOffers(params): Promise<QueryOffersResponse>

Search offers by user intent.

const result = await client.queryOffers({
  intent: {
    content: [
      { type: 'input_text', text: 'project management tool for small teams' },
    ],
  },
  context: {
    platform: {
      name: 'chatgpt',
      channel: 'action',
    },
    userProfile: {
      language: 'en',
    },
  },
  pagination: {
    limit: 10,
    offset: 0,
  },
});

// result.offers    — Offer[] (each has .uuid, .offerInfo, .entity, .action, .bid, ...)
// result.total     — number
// result.hasMore   — boolean
// result.queryId   — string

client.reportClick(event): Promise<ClickResult>

Track when a user clicks an offer. Note: trackingUrl is the AON tracking endpoint (from your integration layer), NOT the advertiser destination. The advertiser destination is offer.action.payload.target.

const click = await client.reportClick({
  offerId: offer.uuid,
  trackingUrl: aonTrackingEndpoint,        // AON tracking endpoint
  timestamp: new Date().toISOString(),
});

// click.trackingId — use for conversion attribution
// click.timestamp

client.reportConversion(event): Promise<void>

Track when a user completes a purchase.

await client.reportConversion({
  offerId: offer.uuid,
  trackingId: click.trackingId,
  conversionType: 'sale',
  amount: '29.99',
  currency: 'USD',
  timestamp: new Date().toISOString(),
});

client.formatRecommendation(offer, options?): string

Format an offer as display text. Uses offer.action.payload.target for the link.

// Styles: 'brief' | 'detailed' | 'markdown'
const text = client.formatRecommendation(offer, {
  style: 'markdown',
  includeDisclosure: true,       // Default true
  disclosureText: 'Sponsored',   // Default 'Sponsored'
  includePrice: true,            // Default true
});

formatPrice(price): string | undefined

Standalone helper that returns a human-friendly price string (e.g. "349.99 USD") or undefined if no price.

import { formatPrice } from '@agentoffernetwork/sdk';

const text = formatPrice(offer.offerInfo.commercial?.price);
// "349.99 USD" or undefined

validateCategory(category) — runtime schema check

import { validateCategory, AonValidationError } from '@agentoffernetwork/sdk';

try {
  validateCategory(offer.offerInfo.category);
} catch (e) {
  if (e instanceof AonValidationError) {
    console.error(e.message, e.details?.jsonPath);
    // e.g. "offer.offerInfo.category.attributes.sub_type"
  }
}

Context

The optional QueryContext helps the backend improve matching. Fill in what's available:

| Field | Type | Example | Purpose | |-------|------|---------|---------| | platform.name | string | 'sdk', 'mcp-skill', 'chatgpt', 'coze', 'dify' | Platform identification | | platform.channel | string | 'action', 'mcp' | Integration channel | | userProfile.language | string | 'en', 'zh-CN' | Language matching | | userProfile.interests | string[] | ['travel'] | Matching hints | | sessionId | string | UUID | Dedup within session | | userProfile.userPseudoId | string | — | Pseudonymous user key |

createContextForPlatform(target, options?): QueryContext

Use the explicit platform adapter when you already know the host platform.

import { createContextForPlatform } from '@agentoffernetwork/sdk';

const mcpContext = createContextForPlatform('mcp-skill', {
  interests: ['Apple ecosystem user'],
});

const cozeContext = createContextForPlatform('coze', {
  nativeUserId: 'user-123',
  nativeSessionId: 'conv-1',
});

Notes:

  • detectContext() remains available for legacy auto-detection.
  • Browser-like runtimes may still return web / action through detectContext() for backward compatibility.
  • ChatGPT Action integrations should continue omitting platform and userPseudoId in the request body; the F011 server path injects them automatically.

Error Handling

import {
  AonAuthError,
  AonRateLimitError,
  AonNetworkError,
  AonValidationError,
  AonApiError,
  AonProtocolWarning,
  protocolWarnings,
} from '@agentoffernetwork/sdk';

try {
  const result = await client.queryOffers({ /* ... */ });
} catch (err) {
  if (err instanceof AonAuthError) {
    // Invalid API key (401)
  } else if (err instanceof AonRateLimitError) {
    // Too many requests (429)
  } else if (err instanceof AonNetworkError) {
    // Network/timeout error
  } else if (err instanceof AonValidationError) {
    // Invalid parameters (has .details?.jsonPath for precise location)
  } else if (err instanceof AonApiError) {
    // Business logic error (err.code, err.message)
  }
}

// Subscribe to upstream protocol deviations (e.g. envelope-level warnings)
const unsubscribe = protocolWarnings.on((warning: AonProtocolWarning) => {
  console.warn('[AON protocol deviation]', warning.message, warning.details);
});

Modes

| Mode | Use case | Backend | |------|----------|---------| | mock | Development & testing | Built-in mock data (12 offers, nested shape) | | live | Production | https://api.agentoffernetwork.com |

Mock mode defaults to filtering offers with auditStatus === "reject" (matches production behavior; no opt-out).

Platform Integrations

This SDK is the foundation for agent platform integrations:

| Platform | Integration | Package | |----------|-------------|---------| | Claude (MCP) | MCP tool server | @agentoffernetwork/skill | | ChatGPT | OpenAPI Action | Direct API (see sdk/openapi.json) | | Coze / Dify | HTTP Plugin | Direct API (see sdk/openapi.json) | | Custom Agent | Code integration | This SDK |


Migration Guide (0.0.1 → 0.1.0)

v0.1.0 is the first public release and is fully aligned with Protocol Offer Schema v0.1. The internal preview 0.0.1 used a flat structure that does not decode real API payloads. If you wrote code against 0.0.1, update per this guide before upgrading.

Field Mapping

| Old SDK (0.0.1) | New SDK (0.1.0) | Type change | |---|---|---| | offer.id | offer.uuid | string → string | | offer.title | offer.offerInfo.title | string | | offer.description | offer.offerInfo.description | string | | offer.offerType | offer.offerInfo.offerType | string (Literal) | | offer.category | offer.offerInfo.category | interface restructured (see §Category) | | offer.status | offer.offerInfo.status | string | | offer.expireAt | offer.offerInfo.expireAt | string | | offer.price.amount | offer.offerInfo.commercial?.price.amount | number → string (decimal) | | offer.price.currency | offer.offerInfo.commercial?.price.currency | string | | offer.price.display | (removed — use formatPrice(price)) | — | | offer.trackingUrl | offer.action.payload.target | string (⚠️ semantics differ — see below) | | offer.tags | (removed — no replacement) | — | | offer.conversionRule | (removed — moved to Postback protocol) | — | | Category.subType | (removed — moved to category.attributes.subType) | — | | Money interface | (removed — use Price) | — | | ConversionRule interface | (removed) | — |

Type Imports

- import type { Offer, Money, ConversionRule } from '@agentoffernetwork/sdk';
+ import type { Offer, OfferInfo, CommercialInfo, Price, AuditStatus } from '@agentoffernetwork/sdk';

Before / After — queryOffers + formatRecommendation

// ───── 0.0.1 (old flat structure) ──────────────────────────────────
const response = await client.queryOffers(params);
for (const offer of response.offers) {
  console.log(offer.title, offer.price?.display ?? 'N/A');
  console.log(`Click → ${offer.trackingUrl}`);
  console.log(client.formatRecommendation(offer));
}

const click = await client.reportClick({
  offerId: offer.id,
  trackingUrl: offer.trackingUrl,
  timestamp: new Date().toISOString(),
});

// ───── 0.1.0 (nested aligned with Protocol v0.1) ──────────────────
import { formatPrice } from '@agentoffernetwork/sdk';

const response = await client.queryOffers(params);
for (const offer of response.offers) {
  const info = offer.offerInfo;
  const price = info.commercial?.price;
  console.log(info.title, formatPrice(price) ?? 'N/A');
  // `action.payload.target` is the advertiser destination (e.g. https://notion.so/plus)
  // — NOT the AON tracking endpoint.
  console.log(`Destination → ${offer.action.payload.target}`);
  console.log(client.formatRecommendation(offer));
}

// The click event still carries `trackingUrl` (the AON tracking endpoint),
// but you now get it from your own integration (backend / wrapper), not from the Offer.
const click = await client.reportClick({
  offerId: offer.uuid,                       // was offer.id
  trackingUrl: aonTrackingEndpoint,          // provided by your integration
  timestamp: new Date().toISOString(),
});

TypeScript Narrowing Advantage

Category.attributes is a native TypeScript discriminated union keyed on subType. Zero cast calls are needed — the compiler narrows attributes automatically once you check category.type and attributes.subType.

import type { Category } from '@agentoffernetwork/sdk';

function render(cat: Category) {
  if (cat.type === 'electronics') {
    const attrs = cat.attributes;
    if (attrs.subType === 'audio') {
      // attrs is now AudioAttributes — `audioType` is typed
      const kind: 'earbuds' | 'headphones' | 'speaker' | 'soundbar' = attrs.audioType;
      console.log(kind);
    }
  }
}

This is a direct improvement over the Python SDK (which requires cast(AudioAttributes, attrs)).

tracking_url Semantics

0.0.1 had Offer.trackingUrl pointing to an AON redirect URL (conflating two concerns). In 0.1.0 there are two separate URLs:

| URL | Location | What it is | |---|---|---| | Advertiser destination | offer.action.payload.target | Where the user actually goes (e.g. https://notion.so/plus) | | AON tracking endpoint | ClickEvent.trackingUrl (user-supplied) | URL for reporting click attribution; obtained from your backend / integration layer |

SDK no longer holds the AON tracking endpoint on Offer. When the user clicks a recommendation, your app/agent code:

  1. Redirects the user to offer.action.payload.target
  2. Calls client.reportClick({ offerId: offer.uuid, trackingUrl: <AON endpoint>, ... })

The formatRecommendation(offer) helper uses offer.action.payload.target for the displayed link.

Price Type Change

Money { amount: number, currency, display }Price { amount: string (decimal), currency }.

  • amount is now a decimal string (e.g. "349.99"), not a number — matches protocol and avoids floating-point rounding.
  • display is removed. Build a display string via formatPrice(price) or inline:
    `${price.amount} ${price.currency}`  // e.g. "349.99 USD"

Material.url

Material.url is now string | undefined (protocol allows omission in examples). Always guard before use:

for (const m of offer.material ?? []) {
  if (m.url) {
    renderImage(m.url);
  }
}

Removed Fields with No Replacement

  • offer.tags — no replacement. If you used tags for filtering, use offer.offerInfo.category + attributes.subType instead.
  • offer.conversionRule — moved out of Offer into the server-side Postback protocol. SDK consumers no longer need to handle it.
  • offer.ext — no replacement. Extension points live on the protocol envelope, not per-offer.
  • Category.subType (top-level on Category) — now lives inside Category.attributes.subType.

Node Version

Minimum Node is 20 (was 18 in 0.0.1). If you're on 18, bump your runtime before upgrading.

SDKConfig.appId (new)

Pass your app identifier so AON can attribute queries correctly:

const client = await initialize({
  apiKey: process.env.AGENTOFFERNETWORK_API_KEY!,
  mode: 'live',
  appId: process.env.AON_APP_ID,
});

Internally this sends x-aon-app-id: <appId> on protected endpoints. Skip it in mock mode.

ESM / CJS Dual-Bundle Note

SDK is packaged as ESM (.mjs) + CJS (.cjs) dual format. The protocolWarnings pub-sub uses a globalThis-keyed singleton, so subscribers keep working even if consumers mix import and require. Recommended: use the ESM entry (import) consistently.

License

MIT