@teneo-protocol/sdk
v3.2.0
Published
TypeScript SDK for external platforms to interact with Teneo agents
Maintainers
Readme
Teneo Protocol SDK
Connect your app to the Teneo AI Agent Network
The Teneo Protocol SDK lets you connect your application to a decentralized network of specialized AI agents. Instead of calling a single AI model, your app taps into an entire ecosystem where:
- 🤖 Multiple AI agents with different specializations handle your requests
- 🧠 Intelligent routing automatically selects the best agent for each query
- 🔐 Web3-native authentication using Ethereum wallet signatures (no API keys!)
🎉 What's New in v3.1.1
🤖 Pre-Flight Auto-Summon
The SDK now automatically detects when an agent is missing from your room and adds it before sending your command — no more failed requests or retry cycles:
const sdk = new TeneoSDK({
wsUrl: process.env.WS_URL,
privateKey: process.env.PRIVATE_KEY,
autoSummon: true // Enable auto-summon
});
// Agent not in room? SDK handles it automatically:
// 1. Checks local cache → agent missing
// 2. Fires autosummon:start
// 3. Adds agent to room
// 4. Fires autosummon:success
// 5. Sends your command
await sdk.sendDirectCommand({
agent: "example-agent",
command: "latest 2h",
room: roomId
}, true);Lifecycle events for full visibility:
autosummon:start— agent addition initiatedautosummon:success— agent added, command proceedingautosummon:failed— agent not found or addition failed, falls back to coordinator
🔐 Private Key Validation
Constructor now validates private key format immediately, catching empty or malformed keys before they cause cryptic signing errors downstream.
🎉 What's New in v3.0
Version 3.0 introduces API Naming Improvements for better clarity and consistency:
📝 Clearer Method Names
All method names have been improved to be more explicit and intuitive:
- Room subscriptions -
subscribeToPublicRoom()makes it clear these only work for public rooms - Cache-only methods -
getCached*()prefix makes sync vs async operations obvious - Boolean semantics -
checkAgentInRoom()clearly indicates it may returnundefined - Search scope -
findAvailableAgentsBy*()clarifies network-wide search
🔄 Fully Backward Compatible
All old method names still work! They're deprecated with helpful aliases:
- Old names forward to new implementations
- TypeScript shows deprecation warnings
- Migrate at your convenience
Jump to Migration Guide | See Full CHANGELOG
🚀 Quickstart
Installation
pnpm install @teneo-protocol/sdkYour First Connection
import { TeneoSDK } from "@teneo-protocol/sdk";
// 1. Initialize with your Ethereum private key
const sdk = new TeneoSDK({
wsUrl: "wss://backend.developer.chatroom.teneo-protocol.ai/ws",
privateKey: "0x..." // Your private key with 0x prefix
});
// 2. Listen for responses
sdk.on("agent:response", (response) => {
console.log(`${response.agentName}: ${response.humanized}`);
});
// 3. Connect (authenticates automatically)
await sdk.connect();
// 4. Get your private rooms (auto-available after auth)
const rooms = sdk.getRooms();
const roomId = rooms[0].id;
// 5. Send a message to a room
// WITH COORDINATOR (when available):
await sdk.sendMessage("Give me the last 5 tweets from @elonmusk", {
room: roomId
});
// → Coordinator selects best agent automatically
// WITHOUT COORDINATOR (direct command required):
await sdk.sendMessage("@X Platform Agent search bitcoin 5", {
room: roomId
});
// → Direct command to specific agent by nameThat's it! After authentication:
- Your private rooms are automatically available
- Send messages to any room by ID
- With coordinator: Use natural language - coordinator routes to best agent
- Without coordinator: Use
@Agent Name command paramssyntax to target specific agents - Responses arrive via the event listener
Message Routing Flow
The SDK supports two routing approaches depending on your environment:
graph TB
UserMessage[User Message]
CheckEnv{Environment Has<br/>Coordinator?}
CoordPath[Coordinator Routing]
DirectPath[Direct Routing]
CoordSelect[Coordinator Selects<br/>Best Agent]
DirectTarget[Direct to<br/>Named Agent]
Agent[Agent Processes<br/>Request]
UserMessage --> CheckEnv
CheckEnv -->|Yes| CoordPath
CheckEnv -->|No| DirectPath
CoordPath -->|"Natural language<br/>supported"| CoordSelect
DirectPath -->|"@Agent Name required"| DirectTarget
CoordSelect --> Agent
DirectTarget --> AgentKey Differences:
- Coordinator environments: Both natural language and direct commands work
- Non-coordinator environments: Must use
@Agent Name command paramssyntax
How It Works
1. Agent Network Architecture
Your App
↓
Teneo SDK (This library)
↓
WebSocket Connection
↓
Teneo Coordinator ──→ Selects best agent
↓
┌─────────┬─────────┬─────────┬─────────┐
│ X │Analytics│ Reddit │ Custom │
│ Agent │ Agent │ Agent │ Agents │
└─────────┴─────────┴─────────┴─────────┘3. Web3 Authentication
Unlike traditional APIs with API keys, Teneo uses Ethereum wallet signatures:
// Challenge-response authentication flow:
// 1. SDK connects to Teneo network
// 2. Server sends random challenge string
// 3. SDK signs: "Teneo authentication challenge: {challenge}"
// 4. Server verifies signature against your wallet address
// 5. ✅ Authenticated! You can now send messages
// Your private key never leaves your machineThis enables:
- 🔐 No API keys to manage - Your wallet IS your identity
🎯 Running the Examples
Setup
git clone https://github.com/TeneoProtocolAI/teneo-sdk.git
cd teneo-sdk
pnpm install
pnpm run build
# Set credentials
export PRIVATE_KEY=your_private_key
export TENEO_WS_URL=wss://backend.developer.chatroom.teneo-protocol.ai/wsProduction Dashboard Example
The Production Dashboard is a comprehensive example showcasing ALL SDK features in a real-world web application:
pnpm example:dashboard
# or
cd examples/production-dashboard && bun run server.tsThen open: http://localhost:3000
What it demonstrates:
- ✅ Full WebSocket Integration - Connection, authentication, auto-reconnection
- ✅ Room Management (v2.0) - Create, update, delete rooms with ownership tracking
- ✅ Agent-Room Management (v2.0) - Add/remove agents, list room agents with caching
- ✅ Message Sending - Coordinator-based and direct agent commands
- ✅ Real-time Updates - Server-Sent Events (SSE) for live dashboard
- ✅ Agent Discovery - List agents with capabilities and status
- ✅ Secure Private Key Handling - AES-256-GCM encryption in memory
- ✅ Webhook Integration - Real-time event streaming with circuit breaker
- ✅ Health Monitoring -
/healthand/metricsendpoints - ✅ Complete Event System - All SDK events with real-time UI updates
Built with Hono (fast web framework) and Bun (fast JavaScript runtime). See examples/production-dashboard/README.md for details.
📖 Complete Examples
Example 1: Request-Response Pattern
Wait for specific responses with timeout:
const sdk = new TeneoSDK({
wsUrl: process.env.TENEO_WS_URL!,
privateKey: process.env.PRIVATE_KEY!
});
await sdk.connect();
// Wait for response (blocks until agent responds or timeout)
const response = await sdk.sendMessage("Give me the last 5 tweets from @elonmusk?", {
waitForResponse: true,
timeout: 30000, // 30 seconds
format: "both" // Get both raw data and humanized text
});
console.log("Agent:", response.agentName);
console.log("Answer:", response.humanized);
console.log("Raw data:", response.raw);
// Output:
// Agent: X Agent
// Answer: Timeline for @elonmusk (5 tweets) ...Example 2: Multi-Room System
Organize agents by context using rooms:
const sdk = new TeneoSDK({
wsUrl: process.env.TENEO_WS_URL!,
privateKey: process.env.PRIVATE_KEY!,
autoJoinPublicRooms: ["crawler-room-id", "kol-tracker-room-id"] // public rooms only
});
// Each room may have different agents available
// Note: Private rooms are automatically available after auth
await sdk.connect();
// Send to specific room contexts
// WITH COORDINATOR:
await sdk.sendMessage("Get latest tweets from @elonmusk", { room: "kol-tracker-room-id" });
// → Coordinator routes to best agent in room
await sdk.sendMessage("Crawl this website for data", { room: "crawler-room-id" });
// → Coordinator routes to best agent in room
// WITHOUT COORDINATOR (direct commands required):
await sdk.sendMessage("@X Platform Agent timeline @elonmusk 5", { room: "kol-tracker-room-id" });
// → Direct to X Platform Agent in room
await sdk.sendMessage("@Web Crawler Agent crawl https://example.com", { room: "crawler-room-id" });
// → Direct to Web Crawler Agent in room
// Manage rooms dynamically
const rooms = sdk.getSubscribedRooms();
console.log("Active rooms:", rooms);
// Output: Active rooms: ['crawler-room-id', 'kol-tracker-room-id']Example 3: Webhook Integration
Receive agent responses via HTTP POST to your server:
// Your webhook endpoint (Express)
import express from "express";
const app = express();
app.use(express.json());
app.post("/teneo-webhook", (req, res) => {
const { event, data, timestamp } = req.body;
if (event === "task_response") {
console.log(`Agent: ${data.agentName}`);
console.log(`Message: ${data.content}`);
// Save to your database
db.saveAgentResponse({
agentId: data.agentId,
content: data.content,
timestamp: new Date(timestamp)
});
}
res.sendStatus(200);
});
app.listen(8080);
// Teneo SDK with webhook
const sdk = new TeneoSDK({
wsUrl: process.env.TENEO_WS_URL!,
privateKey: process.env.PRIVATE_KEY!,
webhookUrl: "https://your-webhook.com/",
webhookHeaders: {
Authorization: "Bearer your-secret-token"
}
});
// Monitor webhook delivery
sdk.on("webhook:sent", () => console.log("📤 Webhook sent"));
sdk.on("webhook:success", () => console.log("✅ Webhook delivered"));
sdk.on("webhook:error", (error) => {
console.error("❌ Webhook failed:", error.message);
// Circuit breaker will automatically retry
});
await sdk.connect();
// Check webhook health
const status = sdk.getWebhookStatus();
console.log("Pending:", status.queue.pending);
console.log("Circuit state:", status.queue.circuitState); // OPEN/CLOSED/HALF_OPENWebSocket Endpoints
The Teneo SDK supports two deployment environments via different WebSocket endpoints:
Development Platform (Testing)
- URL:
wss://backend.developer.chatroom.teneo-protocol.ai/ws - Whitelist: Not required - open for testing
- Use case: Development, testing, and experimentation
Configuration:
# .env file
TENEO_WS_URL=wss://backend.developer.chatroom.teneo-protocol.ai/wsProduction Platform (B2B)
- URL:
wss://backend.chatroom.teneo-protocol.ai/ws - Whitelist: Required - you must be whitelisted to use this endpoint
- Use case: Production applications and B2B integrations
- Get access: Request whitelist access at https://teneo-protocol.ai/data-access
Configuration:
# .env file
TENEO_WS_URL=wss://backend.chatroom.teneo-protocol.ai/wsNote: The SDK uses the development endpoint by default. For production use, you need to be whitelisted and explicitly configure the production URL. Use the development endpoint for testing without restrictions.
🏠 Room Management API
Create and manage multiple rooms for different contexts and use cases.
Creating Rooms
// Create a new room
const room = await sdk.createRoom({
name: "Crypto Research",
description: "Room for crypto analysis"
});
console.log(`Created room: ${room.id}`);
// Check if you can create more rooms
if (sdk.canCreateRoom()) {
await sdk.createRoom({ name: "Gaming Room" });
} else {
console.log(`Room limit reached: ${sdk.getOwnedRoomCount()}/${sdk.getRoomLimit()}`);
}Querying Rooms
// Get all owned rooms (rooms you created)
const ownedRooms = sdk.getOwnedRooms();
console.log(`You own ${ownedRooms.length} rooms:`);
ownedRooms.forEach((room) => {
console.log(` - ${room.name} (${room.is_public ? "Public" : "Private"})`);
});
// Get shared rooms (rooms you were invited to)
const sharedRooms = sdk.getSharedRooms();
console.log(`You have access to ${sharedRooms.length} shared rooms`);
// Get all rooms (owned + shared)
const allRooms = sdk.getAllRooms();
console.log(`Total rooms accessible: ${allRooms.length}`);
// Get specific room by ID
const room = sdk.getRoom("room-123");
if (room) {
console.log(`Room: ${room.name}`);
console.log(`Created by: ${room.created_by}`);
console.log(`You are ${room.is_owner ? "owner" : "member"}`);
}
// Check room limits
console.log(`Room capacity: ${sdk.getOwnedRoomCount()}/${sdk.getRoomLimit()}`);Updating Rooms
// Update room details (owner only)
const updated = await sdk.updateRoom("room-123", {
name: "Updated Room Name",
description: "New description"
});
console.log(`Room updated: ${updated.name}`);Deleting Rooms
// Delete a room you own
await sdk.deleteRoom("room-123");
console.log("Room deleted");
// Listen for deletion events
sdk.on("room:deleted", (roomId) => {
console.log(`Room ${roomId} was deleted`);
});Room Events
// Room lifecycle events
sdk.on("room:created", (room) => {
console.log(`✅ Created: ${room.name}`);
});
sdk.on("room:updated", (room) => {
console.log(`📝 Updated: ${room.name}`);
});
sdk.on("room:deleted", (roomId) => {
console.log(`🗑️ Deleted: ${roomId}`);
});
// Error handling
sdk.on("room:create_error", (error) => {
console.error(`Failed to create room: ${error.message}`);
});
sdk.on("room:update_error", (error, roomId) => {
console.error(`Failed to update room ${roomId}: ${error.message}`);
});
sdk.on("room:delete_error", (error, roomId) => {
console.error(`Failed to delete room ${roomId}: ${error.message}`);
});🤖 Agent Room Management API
Customize which agents are available in each of your rooms.
Adding Agents to Rooms
// Add an agent to your room (owner only)
await sdk.addAgentToRoom("room-123", "agent-456");
console.log("Agent added to room");
// Listen for confirmation
sdk.on("agent_room:agent_added", (roomId, agentId) => {
console.log(`✅ Agent ${agentId} added to room ${roomId}`);
});Removing Agents from Rooms
// Remove an agent from your room (owner only)
await sdk.removeAgentFromRoom("room-123", "agent-456");
console.log("Agent removed from room");
// Listen for confirmation
sdk.on("agent_room:agent_removed", (roomId, agentId) => {
console.log(`🗑️ Agent ${agentId} removed from room ${roomId}`);
});Listing Room Agents
// List all agents in a room (with 5-minute cache)
const roomAgents = await sdk.listRoomAgents("room-123");
console.log(`${roomAgents.length} agents in this room:`);
roomAgents.forEach((agent) => {
console.log(` - ${agent.agent_name} (${agent.status})`);
if (agent.capabilities) {
console.log(` Capabilities: ${agent.capabilities.map((c) => c.name).join(", ")}`);
}
});
// Force refresh (bypass cache)
const freshAgents = await sdk.listRoomAgents("room-123", false);Listing Available Agents
// List agents NOT yet in the room (available to add)
const available = await sdk.listAvailableAgents("room-123");
console.log(`${available.length} agents available to add:`);
available.forEach((agent) => {
console.log(` - ${agent.agent_name}`);
if (agent.description) {
console.log(` ${agent.description}`);
}
});Query Methods (Synchronous - from cache)
// Check if specific agent is in room (instant, no network call)
const inRoom = sdk.checkAgentInRoom("room-123", "agent-456");
if (inRoom === true) {
console.log("Agent is in the room");
} else if (inRoom === false) {
console.log("Agent is NOT in the room");
} else {
console.log("Cache not available - call listRoomAgents() first");
}
// Get room agent count (instant)
const count = sdk.getCachedRoomAgentCount("room-123");
if (count !== undefined) {
console.log(`Room has ${count} agents`);
}
// Get cached room agents (instant)
const cached = sdk.getCachedRoomAgents("room-123");
if (cached) {
console.log("Agents:", cached.map((a) => a.agent_name).join(", "));
} else {
console.log("No cached data - call listRoomAgents() first");
}
// Get cached available agents (instant)
const cachedAvailable = sdk.getCachedAvailableAgents("room-123");Cache Management
// Caching behavior:
// - listRoomAgents() and listAvailableAgents() cache results for 5 minutes
// - Cache automatically invalidated when you add/remove agents
// - Cache automatically invalidated on agent status updates
// Manual cache invalidation (if needed)
sdk.invalidateAgentRoomCache("room-123");
console.log("Cache cleared for room-123");Agent Room Events
// Agent add/remove events
sdk.on("agent_room:agent_added", (roomId, agentId) => {
console.log(`✅ Agent ${agentId} added to ${roomId}`);
});
sdk.on("agent_room:agent_removed", (roomId, agentId) => {
console.log(`🗑️ Agent ${agentId} removed from ${roomId}`);
});
// List events
sdk.on("agent_room:agents_listed", (roomId, agents) => {
console.log(`📋 ${agents.length} agents in room ${roomId}`);
});
sdk.on("agent_room:available_agents_listed", (agents) => {
console.log(`📋 ${agents.length} agents available to add`);
});
// Status updates (real-time)
sdk.on("agent_room:status_update", (data) => {
console.log(`🔄 Agent ${data.agentId} status updated in room ${data.roomId}`);
console.log(` New status: ${data.status}`);
});
// Error handling
sdk.on("agent_room:add_error", (error, roomId) => {
console.error(`Failed to add agent to ${roomId}: ${error.message}`);
});
sdk.on("agent_room:remove_error", (error, roomId) => {
console.error(`Failed to remove agent from ${roomId}: ${error.message}`);
});
sdk.on("agent_room:list_error", (error, roomId) => {
console.error(`Failed to list agents in ${roomId}: ${error.message}`);
});
sdk.on("agent_room:list_available_error", (error) => {
console.error(`Failed to list available agents: ${error.message}`);
});Auto-Summon
When you send a command to an agent that isn't in your room yet, the SDK can automatically add it for you. No manual addAgentToRoom() call needed — just send your command and the SDK handles the rest.
Setup
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PRIVATE_KEY!)
.withAutoSummon(true) // Enable auto-summon
.withNetwork("base")
.build()
);
await sdk.connect();How It Works
// Just send a command — the agent doesn't need to be in your room
const response = await sdk.sendDirectCommand({
agent: "example-agent",
command: "latest 2h",
room: roomId
}, true);
// Behind the scenes:
// 1. SDK checks if agent is in the room (instant cache lookup)
// 2. If not -> automatically adds the agent to your room
// 3. Sends your command to the agent
// 4. Returns the response
console.log(response.humanized);Auto-Summon Events
Track the auto-summon lifecycle to show loading states in your UI:
// Agent is being added to the room
sdk.on("autosummon:start", (agentName, roomId) => {
showLoadingState(`Adding ${agentName} to your room...`);
});
// Agent was successfully added — command is now being processed
sdk.on("autosummon:success", (agentName, agentId, roomId) => {
showNotification(`${agentName} joined your room`);
});
// Agent could not be added (doesn't exist, is offline, etc.)
sdk.on("autosummon:failed", (agentName, roomId, reason) => {
showError(`Could not add ${agentName}: ${reason}`);
});Behavior Notes
- No duplicates: If the agent is already in the room, the SDK skips the summon and sends your command directly.
- Works with any message method: Auto-summon triggers from both
sendDirectCommand()andsendMessage()when the message targets an@agent. - Graceful fallback: If the local cache is empty (e.g., first connection), the SDK falls back to server-side detection — your command still works, just with a small extra round trip.
- Room ownership required: Auto-summon only works in rooms you own, since adding agents requires owner permissions.
Complete Example: Room Setup
// Complete workflow: Create room and customize agents
async function setupCustomRoom() {
// 1. Create a new room
const room = await sdk.createRoom({
name: "My Custom Room",
description: "Specialized agent room"
});
console.log(`✅ Created room: ${room.id}`);
// 2. See what agents are available
const available = await sdk.listAvailableAgents(room.id);
console.log(`${available.length} agents available`);
// 3. Add specific agents you want
const cryptoAgent = available.find((a) => a.agent_name?.includes("Crypto"));
if (cryptoAgent) {
await sdk.addAgentToRoom(room.id, cryptoAgent.agent_id);
console.log(`✅ Added ${cryptoAgent.agent_name}`);
}
const analyticsAgent = available.find((a) => a.agent_name?.includes("Analytics"));
if (analyticsAgent) {
await sdk.addAgentToRoom(room.id, analyticsAgent.agent_id);
console.log(`✅ Added ${analyticsAgent.agent_name}`);
}
// 4. Verify room configuration
const roomAgents = await sdk.listRoomAgents(room.id);
console.log(`\n🎯 Room configured with ${roomAgents.length} agents:`);
roomAgents.forEach((agent) => {
console.log(` - ${agent.agent_name}`);
});
// 5. Now send messages to this room
await sdk.sendMessage("Analyze BTC price trends", { room: room.id });
}
setupCustomRoom();💰 Payment Flow API
The SDK supports automatic micropayments to AI agents using the x402 protocol.
Basic Usage (Auto-Approve)
import { TeneoSDK } from "@teneo-protocol/sdk";
// Enable payments with auto-approve
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PRIVATE_KEY!)
.withPayments({
autoApprove: true, // Automatically approve quotes
maxPricePerRequest: 1000000 // Max 1 USDC per request (in micro-units)
})
.build()
);
await sdk.connect();
// Send message - payment handled automatically
const response = await sdk.sendMessage("@X Platform Agent user @elonmusk", {
room: roomId,
waitForResponse: true
});
console.log(response.humanized);
// Output: User profile for @elonmusk...Note: The builder uses
withPayments({ autoApprove: true })while the plain config object usesautoApproveQuotes: true. Both control the same behavior. The builder also acceptsmaxPricePerRequestandquoteTimeout.
Multi-Network Support (v2.3)
The SDK supports USDC payments across multiple EVM networks with dynamic configuration. Network configurations are automatically fetched from the backend during connect(), enabling the protocol to add new networks without requiring SDK updates.
Dynamic Network Configuration
Networks are initialized automatically when you connect:
import { TeneoSDK, NETWORKS, getSupportedNetworks } from "@teneo-protocol/sdk";
const sdk = new TeneoSDK({
wsUrl: "wss://teneo.network/ws",
privateKey: "0x..."
});
// Before connect: NETWORKS is empty
console.log(NETWORKS); // {}
// Networks fetched during connect from backend /api/networks
await sdk.connect();
// After connect: NETWORKS populated with backend configuration
console.log(NETWORKS); // { peaq: {...}, base: {...}, avalanche: {...} }
const networks = getSupportedNetworks(); // ["peaq", "base", "avalanche"]Key Features:
- 🔄 Automatic fetch from backend
/api/networksendpoint duringconnect() - ⚡ 60-minute cache with automatic refresh
- 🛡️ Offline resilience - falls back to cached configs if backend temporarily unavailable
- 🚀 Future-proof - new networks can be added server-side without SDK updates
Querying Network Information
import {
NETWORKS,
getNetwork,
getDefaultNetwork,
getSupportedNetworks,
isNetworkSupported,
createChainDefinition
} from "@teneo-protocol/sdk";
// Must be called after connect()
await sdk.connect();
// Get all supported networks (dynamically loaded from backend)
const networks = getSupportedNetworks();
console.log(networks); // e.g., ["peaq", "base", "avalanche"]
// Get network by name
const baseNetwork = getNetwork("base");
// Get network by chain ID
const peaqNetwork = getNetwork(3338);
// Get network by CAIP-2 identifier
const avaxNetwork = getNetwork("eip155:43114");
console.log(baseNetwork);
// {
// chainId: 8453,
// name: "Base Mainnet",
// caip2: "eip155:8453",
// rpcUrl: "https://mainnet.base.org",
// usdcContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
// settlementRouter: "0x73fc659Cd5494E69852bE8D9D23FE05Aab14b29B",
// transferHook: "0x081258287F692D61575387ee2a4075f34dd7Aef7",
// eip712: { name: "USD Coin", version: "2" }
// }
// Check if a network is supported
if (isNetworkSupported("base")) {
// Create a viem chain definition
const baseChain = createChainDefinition(baseNetwork);
// Use with PaymentClient or other viem-based operations
}
// Get default network (prefers PEAQ, falls back to first available)
const defaultNetwork = getDefaultNetwork();Current Networks
These networks are currently supported (fetched dynamically from backend):
PEAQ Mainnet (chainId: 3338)
- Original Teneo network
- USDC contract, settlement router, transfer hook configured via backend
Base Mainnet (chainId: 8453)
- Layer 2 scaling solution
- Lower gas fees, faster transactions
Avalanche Mainnet (chainId: 43114)
- High-throughput blockchain
- Sub-second finality
Note: Network configurations are fetched from the backend and may change. Use
getSupportedNetworks()to get the current list. The SDK automatically handles network selection based on agent requirements.
Settlement Router Integration (x402 v2.5)
Payment quotes now include settlement router information for enhanced payment routing:
// Request a quote
const quote = await sdk.requestQuote("Analyze this data", "room-id");
// Quote includes settlement router fields (x402 v2.5)
console.log(quote.data.settlement_router); // Router contract address
console.log(quote.data.salt); // Unique transaction salt
console.log(quote.data.facilitator_fee); // Facilitator fee amount
console.log(quote.data.hook); // Transfer hook address
console.log(quote.data.hook_data); // Optional hook data (default: "0x")Note: The SDK automatically handles network selection. The payment server determines which network to use based on the agent's configuration. You don't need to manually configure networks unless you're using the
PaymentClientdirectly for custom payment operations.
Configuring Default Payment Network
You can configure which network to use for all payments in three ways:
Option 1: Using Environment Variable (Global Default)
# Set default network for all payments
export TENEO_NETWORK=base
# Or use chain ID
export TENEO_NETWORK=8453
# Or use CAIP-2 format
export TENEO_NETWORK=eip155:8453import { TeneoSDK } from "@teneo-protocol/sdk";
// SDK automatically uses TENEO_NETWORK env var
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PRIVATE_KEY!)
.withPayments({ autoApprove: true })
.build()
);
// All payments will use Base network (from TENEO_NETWORK)Option 2: Using Config Builder (Per-SDK Instance)
import { TeneoSDK } from "@teneo-protocol/sdk";
// Configure Base network for this SDK instance
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PRIVATE_KEY!)
.withPayments({ autoApprove: true })
.withNetwork("base") // Use Base Mainnet
.build()
);
// All payments from this SDK will use Base networkOr using chain ID:
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PRIVATE_KEY!)
.withPayments({ autoApprove: true })
.withNetworkChainId(43114) // Use Avalanche (chain ID 43114)
.build()
);Network Selection Priority:
The SDK uses this priority order for network selection:
- Per-request network override via
sendDirectCommand({ network: "base" })orsendMessage({ network: "base" }) - Builder
.withNetwork()or.withNetworkChainId()setting TENEO_NETWORKenvironment variable- Default: PEAQ network (eip155:3338)
Example: Per-Request Network Override (Recommended)
Send commands to different chains from a single SDK instance:
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PRIVATE_KEY!)
.withPayments({ autoApprove: true, maxPricePerRequest: 1000000 })
.build()
);
await sdk.connect();
const roomId = sdk.getRooms()[0].id;
// Pay on PEAQ
await sdk.sendDirectCommand({
agent: "x-agent-enterprise-v2",
command: "user @elonmusk",
room: roomId,
network: "peaq", // Per-request network override
}, true);
// Pay on Base
await sdk.sendDirectCommand({
agent: "x-agent-enterprise-v2",
command: "user @elonmusk",
room: roomId,
network: "base", // Per-request network override
}, true);
// Pay on Avalanche
await sdk.sendDirectCommand({
agent: "x-agent-enterprise-v2",
command: "user @elonmusk",
room: roomId,
network: "avalanche", // Per-request network override
}, true);You can also pass a chain ID instead of a name:
await sdk.sendDirectCommand({
agent: "x-agent-enterprise-v2",
command: "user @elonmusk",
room: roomId,
network: 8453, // Base by chain ID
}, true);The same works with sendMessage:
await sdk.sendMessage("@X Platform Agent user @elonmusk", {
room: roomId,
waitForResponse: true,
network: "avalanche", // Per-request network override
});Example: Multiple SDK Instances with Different Networks
For cases where you want a fixed default per SDK instance:
// Production bot uses PEAQ
const peaqBot = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.PEAQ_PRIVATE_KEY!)
.withPayments({ autoApprove: true })
.withNetwork("peaq")
.build()
);
// Experimental bot uses Base
const baseBot = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.BASE_PRIVATE_KEY!)
.withPayments({ autoApprove: true })
.withNetwork("base")
.build()
);
// High-throughput bot uses Avalanche
const avalancheBot = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(process.env.AVALANCHE_PRIVATE_KEY!)
.withPayments({ autoApprove: true })
.withNetwork("avalanche")
.build()
);Direct Agent Commands
When to Use Direct Commands
Direct agent commands allow you to target a specific agent by name using the @Agent Name command params syntax:
- Required in non-coordinator environments (direct commands are the only way to communicate)
- Optional in coordinator environments (gives you explicit control over which agent handles the request)
Syntax Options
// Option 1: Use @mention syntax via sendMessage (recommended)
await sdk.sendMessage("@X Platform Agent search bitcoin 5", {
room: roomId,
waitForResponse: true
});
// Option 2: Use sendDirectCommand for programmatic control
const response = await sdk.sendDirectCommand({
agent: "X Platform Agent",
command: "timeline @elonmusk 5",
room: roomId
}, true); // waitForResponse
console.log(response.humanized);
// Option 3: With per-request network override
const response = await sdk.sendDirectCommand({
agent: "X Platform Agent",
command: "user @elonmusk",
room: roomId,
network: "base", // Per-request network override (peaq, base, avalanche, or chain ID)
}, true);
// Option 4: Fire-and-forget (no wait for response)
await sdk.sendDirectCommand({
agent: "X Platform Agent",
command: "user @elonmusk",
room: roomId
});Environment-Specific Examples
// IN COORDINATOR ENVIRONMENTS (both work):
// Natural language - coordinator selects best agent
await sdk.sendMessage("Get me the latest tweets from @elonmusk", {
room: roomId,
waitForResponse: true
});
// Direct command - explicit agent selection
await sdk.sendMessage("@X Platform Agent timeline @elonmusk 5", {
room: roomId,
waitForResponse: true
});
// IN NON-COORDINATOR ENVIRONMENTS (direct commands required):
// Direct command - MUST specify agent name
await sdk.sendMessage("@X Platform Agent search bitcoin 5", {
room: roomId,
waitForResponse: true
});
// This will NOT work without coordinator:
// ❌ await sdk.sendMessage("Get bitcoin info", { room: roomId });Payment Events
// Quote received from agent
sdk.on("quote:received", (quote) => {
console.log(`Quote: ${quote.data.pricing.pricePerUnit} USDC`);
console.log(`Agent: ${quote.data.agent_name}`);
console.log(`Expires: ${quote.data.expires_at}`);
});
// Payment attached to request
sdk.on("payment:attached", (data) => {
console.log(`Paid ${data.amount / 1_000_000} USDC to ${data.agentId}`);
});
// Payment blocked (price exceeds maxPricePerRequest)
sdk.on("payment:blocked", (data) => {
console.warn(`Payment blocked: agent ${data.agentId} charges ${data.agentPrice} but max is ${data.maxPrice}`);
});
// Payment errors
sdk.on("payment:error", (error) => {
console.error(`Payment failed: ${error.message}`);
});Manual Quote Approval
const sdk = new TeneoSDK(
TeneoSDK.builder()
.withWebSocketUrl("wss://...")
.withAuthentication(privateKey)
.withPayments({
autoApprove: false // Require manual approval
})
.build()
);
// Listen for quotes
sdk.on("quote:received", async (quote) => {
const price = quote.data.pricing.pricePerUnit;
// Check price before approving
if (price <= 0.01) {
// Approve and send payment
await sdk.confirmQuote(quote.data.task_id);
} else {
console.log(`Quote too expensive: $${price}`);
}
});
// Request triggers quote
await sdk.sendMessage("@X Platform Agent user @elonmusk", { room: roomId });Payment Flow Diagram
Your App Teneo Backend Agent
│ │ │
│──── request_task ──────────>│ │
│ │────── select agent ─────>│
│<───── task_quote ───────────│<────── pricing ──────────│
│ │ │
│ [Auto-approve or manual] │ │
│ │ │
│──── confirm_task ──────────>│ │
│ + x402 payment header │────── execute task ─────>│
│ │ │
│<───── task_response ────────│<────── response ─────────│
│ │ │
│ │──── settle payment ─────>│
└ └ └🔗 Wallet Transaction Flow (On-Chain Actions)
Some agents (e.g., Squid Router for cross-chain swaps) need your wallet to sign and submit on-chain transactions. The SDK handles this through a simple event-driven flow.
How It Works
- Agent requests a transaction — the SDK emits a
wallet:tx_requestedevent with the transaction details - Your app signs and submits — using your wallet/signer of choice (ethers, viem, web3.js, etc.)
- Your app reports the result — call
sendTxResult()so the agent knows what happened - Agent may request more transactions — for multi-step operations (e.g., approve + swap), the agent sends additional
trigger_wallet_txmessages after each result
Your App Teneo Backend Agent
│ │ │
│ │<── trigger_wallet_tx ────│ (e.g., "approve USDC")
│<── wallet:tx_requested ─────│ │
│ │ │
│ [Sign & submit tx] │ │
│ │ │
│──── tx_result ─────────────>│──── tx_result ──────────>│
│ (confirmed + txHash) │ │
│ │ │
│ │<── trigger_wallet_tx ────│ (e.g., "execute swap")
│<── wallet:tx_requested ─────│ │
│ │ │
│ [Sign & submit tx] │ │
│ │ │
│──── tx_result ─────────────>│──── tx_result ──────────>│
│ │ │
│<───── task_response ────────│<────── response ─────────│
└ └ └Basic Usage
sdk.on("wallet:tx_requested", async (data) => {
console.log(`Transaction requested by ${data.agentName}: ${data.description}`);
console.log(`Chain: ${data.tx.chainId}, To: ${data.tx.to}, Value: ${data.tx.value}`);
try {
// Sign and submit using your preferred library
const txHash = await wallet.sendTransaction({
to: data.tx.to,
value: data.tx.value,
data: data.tx.data,
chainId: data.tx.chainId,
});
// Wait for confirmation
const receipt = await provider.waitForTransaction(txHash);
// Report success — include room and chainId for proper routing and tx hash formatting
await sdk.sendTxResult(data.taskId, "confirmed", txHash, undefined, data.room, data.tx.chainId);
} catch (err) {
// Report failure
await sdk.sendTxResult(data.taskId, "failed", undefined, err.message, data.room, data.tx.chainId);
}
});Rejecting a Transaction
If the transaction is optional (data.optional === true) or you don't want to sign:
await sdk.sendTxResult(data.taskId, "rejected", undefined, undefined, data.room, data.tx.chainId);Event: wallet:tx_requested
| Field | Type | Description |
|-------|------|-------------|
| taskId | string | Task identifier — pass this back in sendTxResult() |
| agentName | string? | Name of the agent requesting the transaction |
| tx.to | string | Target contract/address |
| tx.value | string | Value in wei |
| tx.data | string? | Encoded calldata (for contract interactions) |
| tx.chainId | number | Target chain (e.g., 8453 for Base) |
| description | string? | Human-readable description of the transaction |
| optional | boolean | Whether the user can skip this transaction |
| room | string? | Room ID — must be passed back in sendTxResult() for routing |
Method: sendTxResult()
await sdk.sendTxResult(taskId, status, txHash?, error?, room?, chainId?)| Parameter | Type | Description |
|-----------|------|-------------|
| taskId | string | The taskId from the wallet:tx_requested event |
| status | "confirmed" \| "rejected" \| "failed" | Transaction outcome |
| txHash | string? | On-chain transaction hash (required for "confirmed") |
| error | string? | Error message (for "failed" status) |
| room | string? | Room ID from the wallet:tx_requested event (required for routing) |
| chainId | number? | Chain ID from data.tx.chainId — formats txHash as hash\|network (e.g., 0xabc...\|base) |
Important: Always pass
data.roomanddata.tx.chainIdfrom the event back intosendTxResult(). The server usesroomto route the result to the correct agent, andchainIdformats the tx hash to match the expected format. Without these, the agent may not receive your response correctly.
Multi-Step Transactions
Some operations require multiple sequential transactions (e.g., ERC-20 approve followed by a swap). The agent handles sequencing — your code just needs to keep listening:
- Agent sends
trigger_wallet_tx#1 (approve) → you sign → you callsendTxResult() - Agent receives the result, then sends
trigger_wallet_tx#2 (swap) → you sign → you callsendTxResult() - Agent sends
task_responsewith the final outcome
Your wallet:tx_requested listener stays active for the entire lifecycle. Each transaction arrives as a separate event with the same taskId but different tx data. The task is only resolved when the agent sends the final task_response.
🎨 Event System
The SDK is fully event-driven. Subscribe to what matters:
Connection & Authentication
sdk.on("connection:open", () => console.log("🔌 WebSocket connected"));
sdk.on("connection:close", (code, reason) => console.log(`❌ Disconnected: ${reason}`));
sdk.on("connection:reconnecting", (attempt) => console.log(`🔄 Reconnecting (attempt ${attempt})`));
sdk.on("auth:challenge", (challenge) =>
console.log("🔐 Challenge received, signing with wallet...")
);
sdk.on("auth:success", (state) => {
console.log(`✅ Authenticated as ${state.walletAddress}`);
console.log(`Whitelisted: ${state.isWhitelisted}`);
});
sdk.on("auth:error", (error) => console.error("❌ Auth failed:", error));Agent Events
sdk.on("agent:selected", (selection) => {
console.log(`🤖 ${selection.agentName} was selected by coordinator`);
console.log(`Reasoning: ${selection.reasoning}`);
});
sdk.on("agent:response", (response) => {
console.log(`💬 ${response.agentName}: ${response.humanized}`);
});
sdk.on("agent:list", (agents) => {
console.log(`📋 Agent list updated: ${agents.length} agents available`);
agents.forEach((agent) => {
console.log(` - ${agent.name}: ${agent.capabilities?.map(c => c.name).join(", ")}`);
});
});Room Events
sdk.on("room:subscribed", (data) => {
console.log(`✅ Joined room: ${data.roomId}`);
console.log(`All subscribed rooms: ${data.subscriptions.join(", ")}`);
});
sdk.on("room:unsubscribed", (data) => {
console.log(`👋 Left room: ${data.roomId}`);
});Wallet Transaction Events
sdk.on("wallet:tx_requested", async (data) => {
console.log(`🔗 ${data.agentName} requests tx on chain ${data.tx.chainId}`);
console.log(` ${data.description}`);
// See "Wallet Transaction Flow" section for full handling
});⚙️ Configuration
Simple Configuration
const sdk = new TeneoSDK({
wsUrl: "wss://backend.developer.chatroom.teneo-protocol.ai/ws",
privateKey: "0x...", // Your private key
autoJoinPublicRooms: ["room-id-1"],
reconnect: true,
logLevel: "info"
});Advanced Configuration (Builder Pattern)
import { SDKConfigBuilder, SecurePrivateKey } from "@teneo-protocol/sdk";
// Encrypt private key in memory (AES-256-GCM)
const secureKey = new SecurePrivateKey(process.env.PRIVATE_KEY!);
const config = new SDKConfigBuilder()
// Required
.withWebSocketUrl(process.env.TENEO_WS_URL!)
.withAuthentication(secureKey) // Encrypted key
// Rooms - auto-join these public rooms on connect
.withAutoJoinPublicRooms(["room-id-1", "room-id-2"]) // Public rooms only (private rooms auto-available)
// Reconnection strategy
.withReconnectionStrategy({
type: "exponential",
baseDelay: 3000, // Start at 3 seconds
maxDelay: 120000, // Cap at 2 minutes
maxAttempts: 20,
jitter: true // Prevent thundering herd
})
// Webhook with retry
.withWebhook("https://your-server.com/webhook", {
Authorization: "Bearer token"
})
.withWebhookRetryStrategy({
type: "exponential",
baseDelay: 1000,
maxDelay: 30000,
maxAttempts: 5
})
// Response formatting
.withResponseFormat({
format: "both", // 'raw' | 'humanized' | 'both'
includeMetadata: true
})
// Security
.withSignatureVerification({
enabled: true,
trustedAddresses: ["0xAgent1...", "0xAgent2..."],
requireFor: ["task_response"]
})
// Performance
.withMessageDeduplication(true, 60000, 10000)
.withLogging("debug")
.build();
const sdk = new TeneoSDK(config);Environment Variables
Create .env:
# Required
TENEO_WS_URL=wss://backend.developer.chatroom.teneo-protocol.ai/ws
PRIVATE_KEY=0xYourPrivateKey
# Optional
LOG_LEVEL=info
# Payment network (v2.3.0) - Optional, defaults to PEAQ
TENEO_NETWORK=base # By name: peaq, base, avalanche
# Or by chain ID: TENEO_NETWORK=8453
# Or CAIP-2: TENEO_NETWORK=eip155:8453Load them:
import * as dotenv from "dotenv";
dotenv.config();
const sdk = new TeneoSDK({
wsUrl: process.env.TENEO_WS_URL!,
privateKey: process.env.PRIVATE_KEY!,
logLevel: (process.env.LOG_LEVEL as any) || "info"
// TENEO_NETWORK is automatically used by SDK for payment network
});🔄 v3.0.0 Migration Guide
All v3.0.0 changes are backward compatible. Old method names still work via deprecated aliases.
Quick Migration (Optional)
Search and replace across your codebase to use the new, clearer names:
# Config property
autoJoinRooms: → autoJoinPublicRooms:
# Room subscription methods
.subscribeToRoom( → .subscribeToPublicRoom(
.unsubscribeFromRoom( → .unsubscribeFromPublicRoom(
# Cache-only methods (sync)
.getRoomAgents( → .getCachedRoomAgents(
.getAvailableAgents( → .getCachedAvailableAgents(
.getRoomAgentCount( → .getCachedRoomAgentCount(
# Boolean check methods
.isAgentInRoom( → .checkAgentInRoom(
# Network-wide search methods
.findAgentsByCapability( → .findAvailableAgentsByCapability(
.findAgentsByName( → .findAvailableAgentsByName(
.findAgentsByStatus( → .findAvailableAgentsByStatus(Why These Changes?
Room Subscriptions: subscribeToPublicRoom() clarifies that only public rooms need subscription. Private rooms are automatically available after authentication.
Cache Methods: The getCached* prefix makes it obvious these are synchronous, cache-only operations. Async methods like listRoomAgents() still fetch from server.
Boolean Checks: checkAgentInRoom() returns boolean | undefined, not just boolean. The name clarifies this uncertainty.
Search Scope: findAvailableAgentsBy*() methods search ALL available agents network-wide, not just room-specific agents.
No Rush!
- ✅ Old names work indefinitely
- ✅ TypeScript/JSDoc shows deprecation hints
- ✅ Migrate on your schedule
- ✅ Zero functional changes
See CHANGELOG.md for detailed explanations and examples.
🛡️ Production Features
1. Secure Private Key Management
Your Ethereum private key is encrypted in memory with AES-256-GCM:
import { SecurePrivateKey } from "@teneo-protocol/sdk";
// Immediately encrypted on construction
const secureKey = new SecurePrivateKey(process.env.PRIVATE_KEY!);
const sdk = new TeneoSDK({
wsUrl: "...",
privateKey: secureKey // Pass encrypted key
});
// Key lifecycle:
// 1. Encrypted in memory with AES-256-GCM
// 2. Only decrypted temporarily during signing
// 3. Zeroed from memory immediately after use
// 4. Auto-cleanup on disconnect2. Circuit Breaker Pattern
Prevents cascading failures in webhook delivery:
const status = sdk.getWebhookStatus();
console.log("Circuit state:", status.queue.circuitState);
// CLOSED = Normal operation, webhooks being delivered
// OPEN = Too many failures, failing fast (60s timeout)
// HALF_OPEN = Testing recovery (2 successes → CLOSED)
// Circuit opens after 5 consecutive failures
// Automatically retries after 60 seconds
// Closes after 2 successful deliveries
// State transitions:
// CLOSED --[5 failures]--> OPEN --[60s]--> HALF_OPEN --[2 successes]--> CLOSED3. Retry Strategies
Configurable exponential backoff, linear, or constant delays:
| Strategy | Formula | Example (base=2s, mult=2) |
| --------------- | --------------------- | ------------------------- |
| Exponential | base * mult^attempt | 2s, 4s, 8s, 16s, 32s |
| Linear | base * attempt | 2s, 4s, 6s, 8s, 10s |
| Constant | base | 2s, 2s, 2s, 2s, 2s |
const config = new SDKConfigBuilder()
.withReconnectionStrategy({
type: "exponential",
baseDelay: 3000,
maxDelay: 120000,
maxAttempts: 20,
jitter: true // Add 0-1000ms randomness
})
.build();4. Message Deduplication
Prevents duplicate message processing with TTL-based cache:
const config = new SDKConfigBuilder()
.withMessageDeduplication(
true, // Enable
300000, // 5 minute TTL
10000 // Cache up to 10k message IDs
)
.build();
// Duplicate messages are automatically filtered
// Useful for preventing replay attacks
// Auto-cleanup at 90% capacity6. Signature Verification
Verify agent messages are authentic:
const config = new SDKConfigBuilder()
.withSignatureVerification({
enabled: true,
trustedAddresses: ["0xAgent1...", "0xAgent2..."],
requireFor: ["task_response", "agent_selected"],
strictMode: false // Only reject types in requireFor
})
.build();
sdk.on("signature:verified", (type, address) => {
console.log(`✅ Verified ${type} from ${address}`);
});
sdk.on("signature:failed", (type, reason) => {
console.warn(`⚠️ Invalid signature on ${type}: ${reason}`);
});📊 Monitoring & Health
Health Check
const health = sdk.getHealth();
console.log("Status:", health.status); // 'healthy' | 'degraded' | 'unhealthy'
console.log("Connected:", health.connection.status);
console.log("Authenticated:", health.connection.authenticated);
if (health.webhook) {
console.log("Webhook pending:", health.webhook.pending);
console.log("Circuit state:", health.webhook.circuitState);
}
if (health.rateLimit) {
console.log("Available tokens:", health.rateLimit.availableTokens);
}Connection State
const state = sdk.getConnectionState();
console.log("Connected:", state.connected);
console.log("Authenticated:", state.authenticated);
console.log("Reconnecting:", state.reconnecting);
console.log("Reconnect attempts:", state.reconnectAttempts);Performance Metrics
// Rate limiter status (via health check)
const health = sdk.getHealth();
if (health.rateLimit) {
console.log("Available:", health.rateLimit.availableTokens);
console.log("Rate:", health.rateLimit.tokensPerSecond, "/sec");
console.log("Burst capacity:", health.rateLimit.maxBurst);
}
// Deduplication cache
const dedup = sdk.getDeduplicationStatus();
if (dedup) {
console.log("Cache size:", dedup.cacheSize);
console.log("Max size:", dedup.maxSize);
console.log("Usage:", Math.round((dedup.cacheSize / dedup.maxSize) * 100), "%");
}🔧 Troubleshooting
Issue: "ERR_REQUIRE_ESM" Error
Problem:
Error [ERR_REQUIRE_ESM]: require() of ES Module node-fetch not supportedSolution: Use the compiled version:
# ✅ Correct
npm run build
node your-app.js
# ❌ Wrong
npx ts-node your-app.tsAlternative: Install node-fetch v2:
npm install [email protected]
npm run buildIssue: Authentication Failed
Problem: Can't authenticate with Teneo network.
Solutions:
Check private key format (64 hex characters, + 0x prefix):
// ✅ Good - 64 hex characters after 0x prefix privateKey: "0x1234567890123456789012345678901234567890123456789012345678901234";Verify key length:
echo -n "0x1234...your_key" | wc -c # Should output: 66 (0x prefix + 64 hex characters)Enable debug logging:
const sdk = new TeneoSDK({ wsUrl: "...", privateKey: "...", logLevel: "debug" });
Issue: Rate Limiting
Problem:
RateLimitError: Rate limit exceededSolutions:
- Slow down requests:
for (const message of messages) { await sdk.sendMessage(message, { room: roomId }); await new Promise((r) => setTimeout(r, 200)); // 200ms delay }
Issue: Webhook Failures
Solutions:
Verify HTTPS (except localhost):
// ✅ Good webhookUrl: "https://your-server.com/webhook"; webhookUrl: "http://localhost:3000/webhook"; // ❌ Bad webhookUrl: "http://your-server.com/webhook";Test manually:
curl -X POST https://your-server.com/webhook \ -H "Content-Type: application/json" \ -d '{"test": true}'Check circuit breaker:
const status = sdk.getWebhookStatus(); if (status.queue.circuitState === "OPEN") { console.log("Circuit open, will retry in 60s"); }
🧪 Testing
npm test # All tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm run test:unit # Unit tests only
npm run test:integration # Integration testsTest Results:
- ✅ 671 unit tests passing
- ✅ 100% pass rate (Phase 0-2 complete)
- ✅ Comprehensive coverage
🤝 Contributing
We welcome contributions!
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests
- Run
npm test - Commit (
git commit -m 'Add amazing feature') - Push (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
AGPL-3.0 License
Built with ❤️ by the Teneo Team
