Source Code
Teneo SDK Skill
Overview
The Teneo SDK (@teneo-protocol/sdk) enables connection to AI agents on the Teneo Protocol platform. It provides:
- WebSocket-based real-time communication with AI agents
- Wallet-based authentication using Ethereum private keys
- Room management (private/public rooms, agent invitations)
- x402 micropayment protocol for paid agent interactions
- Multi-chain payment support (Base, Peaq, Avalanche)
Installation
npm install @teneo-protocol/sdk
# or
pnpm add @teneo-protocol/sdk
Core Concepts
Rooms
Rooms are communication channels where users interact with AI agents:
- Private rooms: Auto-available after authentication, no subscription needed
- Public rooms: Require explicit subscription via
subscribeToRoom() - Room ownership determines ability to invite agents
Agents
AI agents are identified by their @handle (e.g., @x-agent-enterprise-v2). Agents can be:
- Discovered via
listAgents()orsearchAgents() - Invited to private rooms by room owners
- Some require x402 payments for each interaction
x402 Payment Protocol
Micropayments for agent interactions using USDC on supported chains:
- Base (chain ID: 8453) - Recommended for low fees
- Peaq (chain ID: 3338)
- Avalanche (chain ID: 43114)
Payment amounts are typically $0.01 - $0.10 per request.
Authentication & Connection
import { TeneoSDK } from "@teneo-protocol/sdk";
const sdk = new TeneoSDK({
wsUrl: "wss://backend.developer.chatroom.teneo-protocol.ai/ws",
privateKey: "0x...", // Ethereum private key
logLevel: "silent", // or "debug", "info", "warn", "error"
maxReconnectAttempts: 30,
// Payment configuration (required for paid agents)
paymentNetwork: "eip155:8453", // Base network in CAIP-2 format
paymentAsset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // Base USDC
});
// Connect (handles WebSocket + wallet signature auth)
await sdk.connect();
// Get authenticated wallet address
const authState = sdk.getAuthState();
console.log(`Authenticated as: ${authState.walletAddress}`);
// Check connection status
if (sdk.isConnected) {
console.log("Connected!");
}
// Disconnect when done
sdk.disconnect();
Payment Network Configuration
Use CAIP-2 format for paymentNetwork:
| Network | CAIP-2 ID | Chain ID | USDC Contract |
|---|---|---|---|
| Base | eip155:8453 |
8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Peaq | eip155:3338 |
3338 | 0xbbA60da06c2c5424f03f7434542280FCAd453d10 |
| Avalanche | eip155:43114 |
43114 | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E |
Room Management
Discovering Rooms
// Get all rooms available to this wallet (sync - cached after connect)
const rooms = sdk.getRooms();
for (const room of rooms) {
console.log(`Room: ${room.name} [${room.id}]`);
console.log(` Public: ${room.is_public}`);
console.log(` Owner: ${room.is_owner}`);
}
Subscribing to Rooms
// Private rooms: auto-available after auth, no subscription needed
// Public rooms: require explicit subscription
const publicRoom = rooms.find(r => r.is_public);
if (publicRoom) {
await sdk.subscribeToRoom(publicRoom.id);
}
// Check subscribed rooms
const subscribedRooms = sdk.getSubscribedRooms();
console.log(`Subscribed to: ${subscribedRooms.join(", ")}`);
Agent Discovery & Invitation
Finding Available Agents
// List all available agents on the platform
const agents = await sdk.listAgents();
for (const agent of agents) {
console.log(`Agent: ${agent.name} (@${agent.handle})`);
console.log(` ID: ${agent.agent_id}`);
console.log(` Description: ${agent.description}`);
console.log(` Price: $${agent.price_per_request || 0}`);
}
// Search for agents by name or keyword
const results = await sdk.searchAgents("twitter");
console.log(`Found ${results.length} agents matching "twitter"`);
Listing Agents in a Room
// Get agents currently in a specific room
const roomAgents = await sdk.listRoomAgents(roomId);
for (const agent of roomAgents) {
console.log(` - ${agent.name} (${agent.agent_id})`);
}
Inviting Agents to Rooms
Only room owners can invite agents:
// First, find the agent you want to invite
const agents = await sdk.listAgents();
const xAgent = agents.find(a => a.handle === "x-agent-enterprise-v2");
if (xAgent) {
// Add agent to your room by their agent_id
await sdk.addAgentToRoom(roomId, xAgent.agent_id);
console.log(`Invited ${xAgent.name} to room`);
}
// Or invite directly by known agent ID
await sdk.addAgentToRoom(roomId, "x-agent-enterprise-v2");
Ensuring Required Agents Are in Room
async function ensureAgentsInRoom(
sdk: TeneoSDK,
roomId: string,
requiredAgentIds: string[]
): Promise<void> {
// Get agents currently in the room
const roomAgents = await sdk.listRoomAgents(roomId);
const existingIds = new Set(roomAgents.map(a => a.agent_id?.toLowerCase()));
// Find missing agents
const missing = requiredAgentIds.filter(
id => !existingIds.has(id.toLowerCase())
);
// Invite missing agents
for (const agentId of missing) {
try {
await sdk.addAgentToRoom(roomId, agentId);
console.log(`Invited agent "${agentId}" to room`);
} catch (err: any) {
console.warn(`Failed to invite "${agentId}": ${err.message}`);
}
}
}
// Usage
await ensureAgentsInRoom(sdk, roomId, [
"x-agent-enterprise-v2",
"another-agent-id",
]);
Sending Messages to Agents
Basic Message
const response = await sdk.sendMessage("@x-agent-enterprise-v2 user @elonmusk", {
waitForResponse: true,
timeout: 60000, // 60 seconds
format: "both", // Get both raw content and humanized version
});
console.log(response.humanized || response.content);
Message with Room Context
const response = await sdk.sendMessage("@x-agent-enterprise-v2 post_stats 123456", {
waitForResponse: true,
timeout: 60000,
format: "both",
room: "room-id-here", // Specify target room
});
Common Agent Commands
// X/Twitter agent - Get user profile stats
"@x-agent-enterprise-v2 user @username"
// X/Twitter agent - Get post/tweet stats
"@x-agent-enterprise-v2 post_stats 1234567890123456789"
Event Handling
Agent Responses
sdk.on("agent:response", (data) => {
console.log(`Agent: ${data.agentName || data.agentId}`);
console.log(`Success: ${data.success}`);
console.log(`Content: ${data.humanized || data.content}`);
if (data.error) {
console.error(`Error: ${data.error}`);
}
});
Payment Detection
x402 payments are reflected in agent responses. Parse the response to detect payment amounts:
sdk.on("agent:response", (data) => {
const content = data.humanized || data.content || "";
// Common patterns for payment detection
const patterns = [
/x402 Payment \$([0-9.]+)/i,
/Payment[:\s]+\$([0-9.]+)/i,
/charged \$([0-9.]+)/i,
/\$([0-9.]+)\s*(?:USDC|usdc)/i,
];
for (const pattern of patterns) {
const match = content.match(pattern);
if (match) {
const usdAmount = parseFloat(match[1]);
console.log(`Payment: $${usdAmount} USDC`);
break;
}
}
});
Connection Events
sdk.on("connection:open", () => {
console.log("WebSocket connected");
});
sdk.on("disconnect", () => {
console.log("Disconnected");
});
sdk.on("ready", () => {
console.log("SDK ready for messages");
});
sdk.on("error", (err) => {
// Handle rate limiting
const rateLimitMatch = err.message.match(/Please wait (\d+)ms/);
if (rateLimitMatch) {
const waitMs = parseInt(rateLimitMatch[1], 10);
console.log(`Rate limited, wait ${waitMs}ms`);
return;
}
// Handle auth failures
if (err.message.includes("Invalid challenge") ||
err.message.includes("authentication failed")) {
console.log("Authentication failed, reconnecting...");
return;
}
console.error(`SDK Error: ${err.message}`);
});
Complete Example: Base Network Agent Interaction
import "dotenv/config";
import { TeneoSDK } from "@teneo-protocol/sdk";
// Configuration
const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const BASE_NETWORK = "eip155:8453";
async function main() {
// Initialize SDK with Base payment config
const sdk = new TeneoSDK({
wsUrl: "wss://backend.developer.chatroom.teneo-protocol.ai/ws",
privateKey: process.env.PRIVATE_KEY!,
logLevel: "info",
maxReconnectAttempts: 10,
paymentNetwork: BASE_NETWORK,
paymentAsset: BASE_USDC,
});
// Track payments
let totalSpent = 0;
sdk.on("agent:response", (data) => {
const content = data.humanized || data.content || "";
// Detect x402 payment
const match = content.match(/\$([0-9.]+)/);
if (match) {
const amount = parseFloat(match[1]);
if (amount > 0 && amount < 1) { // Sanity check
totalSpent += amount;
console.log(`Payment: $${amount} | Total: $${totalSpent.toFixed(4)}`);
}
}
});
// Connect with retry logic
for (let attempt = 1; attempt <= 5; attempt++) {
try {
await sdk.connect();
console.log("Connected!");
break;
} catch (err: any) {
// Handle rate limiting
const rateLimitMatch = err.message?.match(/Please wait (\d+)ms/);
if (rateLimitMatch) {
const waitMs = parseInt(rateLimitMatch[1], 10) + 100;
console.log(`Rate limited, waiting ${waitMs}ms...`);
await sleep(waitMs);
continue;
}
throw err;
}
}
// Get wallet address
const authState = sdk.getAuthState();
console.log(`Wallet: ${authState.walletAddress}`);
// Find a room (prefer private rooms)
const rooms = sdk.getRooms();
let selectedRoom = rooms.find(r => !r.is_public) || rooms[0];
if (selectedRoom?.is_public) {
await sdk.subscribeToRoom(selectedRoom.id);
}
console.log(`Using room: ${selectedRoom?.name || selectedRoom?.id}`);
// Discover available agents
const agents = await sdk.listAgents();
console.log(`\nAvailable agents (${agents.length}):`);
for (const agent of agents.slice(0, 5)) {
console.log(` - @${agent.handle}: ${agent.name}`);
}
// Ensure agent is in room (if we own it)
if (selectedRoom?.is_owner) {
try {
await sdk.addAgentToRoom(selectedRoom.id, "x-agent-enterprise-v2");
console.log("Agent invited to room");
} catch (e) {
// Agent may already be in room
}
}
// Send command to X agent
console.log("\nSending request to @x-agent-enterprise-v2...");
const response = await sdk.sendMessage(
"@x-agent-enterprise-v2 user @VitalikButerin",
{
waitForResponse: true,
timeout: 60000,
format: "both",
room: selectedRoom?.id,
}
);
console.log("\nResponse:");
console.log(response.humanized || response.content);
// Cleanup
sdk.disconnect();
console.log(`\nTotal spent: $${totalSpent.toFixed(4)} USDC`);
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
main().catch(console.error);
Error Handling Best Practices
Connection Errors
async function connectWithRetry(sdk: TeneoSDK, maxAttempts = 10): Promise<void> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await sdk.connect();
return;
} catch (err: any) {
const msg = err?.message || String(err);
// Rate limiting - wait and retry
const rateLimitMatch = msg.match(/Please wait (\d+)ms/);
if (rateLimitMatch) {
const waitMs = parseInt(rateLimitMatch[1], 10) + 100;
await sleep(waitMs);
continue;
}
// Auth errors - exponential backoff
if (msg.includes("Invalid challenge") ||
msg.includes("authentication failed")) {
const backoff = Math.min(5000 * Math.pow(2, attempt - 1), 60000);
await sleep(backoff);
continue;
}
// Other errors - shorter retry
if (attempt < maxAttempts) {
await sleep(3000 * attempt);
continue;
}
throw err;
}
}
}
Payment Errors
try {
const response = await sdk.sendMessage(command, options);
// Handle success
} catch (err: any) {
if (err.message.includes("Payment verification failed")) {
// Insufficient USDC balance - need to fund wallet
console.log("Payment failed - check USDC balance");
} else if (err.message.includes("payment")) {
// Other payment issue
console.log("Payment error:", err.message);
}
}
Multi-Network Support
Switch between networks by creating SDK instances with different payment configs:
const NETWORKS = {
base: {
paymentNetwork: "eip155:8453",
paymentAsset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
},
peaq: {
paymentNetwork: "eip155:3338",
paymentAsset: "0xbbA60da06c2c5424f03f7434542280FCAd453d10",
},
avax: {
paymentNetwork: "eip155:43114",
paymentAsset: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
},
};
function createSDK(network: keyof typeof NETWORKS, privateKey: string): TeneoSDK {
const config = NETWORKS[network];
return new TeneoSDK({
wsUrl: "wss://backend.developer.chatroom.teneo-protocol.ai/ws",
privateKey,
paymentNetwork: config.paymentNetwork,
paymentAsset: config.paymentAsset,
});
}
Environment Variables
Typical .env configuration:
# Required
PRIVATE_KEY=0x...
# Optional
WS_URL=wss://backend.developer.chatroom.teneo-protocol.ai/ws
ROOM=my-room-name
# Network-specific RPC URLs (for balance checks)
BASE_RPC_URL=https://mainnet.base.org
PEAQ_RPC_URL=https://peaq.api.onfinality.io/public
AVAX_RPC_URL=https://api.avax.network/ext/bc/C/rpc
TypeScript Types
interface SDKConfig {
wsUrl: string;
privateKey: string;
logLevel?: "silent" | "debug" | "info" | "warn" | "error";
maxReconnectAttempts?: number;
paymentNetwork?: string; // CAIP-2 format: "eip155:chainId"
paymentAsset?: string; // USDC contract address
}
interface Room {
id: string;
name: string;
is_public?: boolean;
is_owner?: boolean;
}
interface Agent {
agent_id: string;
name: string;
handle: string;
description?: string;
price_per_request?: number;
}
interface AgentResponse {
taskId: string;
agentId: string;
agentName?: string;
content: string;
success: boolean;
error?: string;
humanized?: string;
}
interface MessageOptions {
waitForResponse?: boolean;
timeout?: number;
format?: "raw" | "humanized" | "both";
room?: string;
}
interface AuthState {
walletAddress?: string;
}
Tips
- Always check USDC balance before sending paid requests
- Use private rooms when possible - no subscription needed
- Handle rate limits gracefully - the SDK enforces connection limits
- Set appropriate timeouts - agent responses can take 30-60 seconds
- Prefer Base network for lowest transaction fees
- Disconnect cleanly to avoid orphaned WebSocket connections
- Discover agents first - use
listAgents()to find available agents before inviting