Skip to content
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,91 @@ npm install agent0-sdk
import { SDK } from 'agent0-sdk';
```

### Adapters (Extensible Search + Chat Backends)

Agent0 SDK uses an adapter pattern for agent discovery (search/vector search) and agent communication (chat). Register one or many adapters, and the SDK will use the first registered adapter by default (or you can choose one explicitly per call).

To plug in your own backend, implement `AgentSearchAdapter` and/or `AgentChatAdapter` and register them:

```ts
import { SDK, type AgentChatAdapter, type AgentSearchAdapter } from 'agent0-sdk';

const mySearchAdapter: AgentSearchAdapter = {
id: 'my-search',
async search() {
return { hits: [], total: 0 };
},
};

const myChatAdapter: AgentChatAdapter = {
id: 'my-chat',
async message() {
return {
sessionId: 'example-session-id',
mode: 'plaintext',
send: async () => ({ message: '' }),
};
},
};

const sdk = new SDK({ chainId: 11155111, rpcUrl: 'http://127.0.0.1:8545' });
sdk.registerSearchAdapter(mySearchAdapter);
sdk.registerChatAdapter(myChatAdapter);

await sdk.search({ query: 'hello', limit: 5 }, 'my-search');
const agent = await sdk.loadAgent('11155111:28');
await agent.message('hi', {}, 'my-chat');
```

### Registry Broker Adapter (ERC-8004 Discovery + Chat)

The Registry Broker API provides indexed discovery of **ERC‑8004 agents** and broker-managed chat sessions with those agents (including encrypted sessions when available). This is useful when you want discovery, routing, and session lifecycle handled by a broker rather than calling an agent endpoint directly.

Agent0 SDK ships a Registry Broker adapter entrypoint. Install the client package, register the adapters, and use the standard `SDK` + `Agent` methods.

```bash
npm install @hol-org/rb-client
```

Register adapters (search + chat), then use `sdk.search(...)` and `agent.message(...)`:

```ts
import { SDK } from 'agent0-sdk';
import { registerHashgraphRegistryBrokerAdapters } from 'agent0-sdk/registry-broker';

const sdk = new SDK({
chainId: 11155111,
rpcUrl: process.env.RPC_URL || 'https://ethereum-sepolia-rpc.publicnode.com',
});
const broker = {
baseUrl: process.env.REGISTRY_BROKER_BASE_URL || 'https://hol.org/registry/api/v1',
apiKey: process.env.REGISTRY_BROKER_API_KEY, // optional (depends on broker config)
};
registerHashgraphRegistryBrokerAdapters(sdk, broker);

const agentId = process.env.ERC8004_AGENT_ID?.trim();
if (!agentId) throw new Error('Set ERC8004_AGENT_ID (e.g. "11155111:28")');

const agent = await sdk.loadAgent(agentId);
await agent.message('hi');
```

If you register multiple adapters in the same category, pass the adapter ID explicitly (second argument to `sdk.search(...)`, third argument to `agent.message(...)`):

```ts
import {
HASHGRAPH_REGISTRY_BROKER_CHAT_ADAPTER_ID,
HASHGRAPH_REGISTRY_BROKER_SEARCH_ADAPTER_ID,
} from 'agent0-sdk/registry-broker';

await sdk.search({ query: 'hello', limit: 5 }, HASHGRAPH_REGISTRY_BROKER_SEARCH_ADAPTER_ID);
await (
await sdk
.createAgent('Remote Agent', 'Remote Agent handle')
.setA2A('https://example.com/agent-card.json', '0.30', false)
).message('hi', {}, HASHGRAPH_REGISTRY_BROKER_CHAT_ADAPTER_ID);
```

### Install from Source

```bash
Expand Down
123 changes: 123 additions & 0 deletions examples/broker-chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import 'dotenv/config';
import { SDK } from '../src/index.js';
import {
HashgraphRegistryBrokerChatAdapter,
HashgraphRegistryBrokerSearchAdapter,
} from '../src/adapters/registry-broker.js';

const baseUrl =
process.env.REGISTRY_BROKER_BASE_URL?.trim() || 'https://hol.org/registry/api/v1';
const apiKey =
process.env.REGISTRY_BROKER_API_KEY?.trim() || process.env.RB_API_KEY?.trim() || undefined;
const registry = process.env.ERC8004_REGISTRY?.trim() || 'erc-8004';
const adapters = (() => {
const raw = process.env.ERC8004_ADAPTERS?.trim();
if (!raw) {
return undefined;
}
const parsed = raw
.split(',')
.map((entry) => entry.trim())
.filter((entry) => entry.length > 0);
return parsed.length > 0 ? parsed : undefined;
})();
const searchQuery =
process.env.ERC8004_AGENT_QUERY?.trim() || 'defillama-verifiable-agent';
const targetAgentId = process.env.ERC8004_AGENT_ID?.trim() || '';
const targetAgentUrl = process.env.ERC8004_AGENT_URL?.trim() || '';

const extractReply = (payload: { content?: string; message?: string }): string =>
payload.content?.trim() || payload.message?.trim() || '';

async function main(): Promise<void> {
const sdk = new SDK({
chainId: Number(process.env.CHAIN_ID ?? '11155111'),
rpcUrl: process.env.RPC_URL?.trim() || 'http://127.0.0.1:8545',
});

sdk.registerSearchAdapter(new HashgraphRegistryBrokerSearchAdapter({ apiKey, baseUrl }));
sdk.registerChatAdapter(new HashgraphRegistryBrokerChatAdapter({ apiKey, baseUrl }));

console.log(`Using Registry Broker baseUrl: ${baseUrl}`);

if (targetAgentUrl) {
const agent = await sdk
.createAgent('Remote Agent', 'Remote Agent handle')
.setA2A(targetAgentUrl, '0.30', false);
const chat = await agent.message('Give a one-sentence summary of your capabilities.', {
historyTtlSeconds: 300,
encryption: { preference: 'disabled' },
});

console.log(`Session established: ${chat.sessionId}`);

const firstReply = extractReply(chat.response);
console.log('Agent reply:');
console.log(firstReply || '[empty response]');

return;
}

console.log(`Searching registry "${registry}" for agents (query="${searchQuery || '[empty]'}")`);
let result = await sdk.search({
query: searchQuery,
registry,
adapters,
limit: 200,
sortBy: 'most-recent',
});

if (!result?.hits?.length) {
console.log('No agents found for this query; falling back to most recent agents.');
result = await sdk.search({
registry,
adapters,
limit: 200,
sortBy: 'most-recent',
});
}

if (!result?.hits?.length) {
throw new Error('No ERC-8004 agents found in this registry');
}

console.log(`Found ${result.hits.length} candidates`);

if (!targetAgentId) {
console.log('Set ERC8004_AGENT_ID to open a chat session. Top results:');
result.hits.slice(0, 5).forEach((hit) => {
console.log(`- ${(hit.name ?? '').trim() || '[no name]'} (${hit.id ?? '[no id]'})`);
});
return;
}

console.log(`Loading agent: ${targetAgentId}`);
const agent = await sdk.loadAgent(targetAgentId);
const chat = await agent.message('Give a one-sentence summary of your capabilities.', {
historyTtlSeconds: 300,
encryption: { preference: 'disabled' },
});

console.log(`Session established: ${chat.sessionId}`);

const firstReply = extractReply(chat.response);
console.log('Agent reply:');
console.log(firstReply || '[empty response]');

const followUp = await agent.message(
'Great. Please share one concrete task you can perform and what info you need from me.',
{
sessionId: chat.sessionId,
encryption: { preference: 'disabled' },
},
);

const secondReply = extractReply(followUp.response);
console.log('\nFollow-up reply:');
console.log(secondReply || '[empty response]');
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
7 changes: 5 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ export default {
'^.+\\.ts$': ['ts-jest', {
tsconfig: {
types: ['jest', 'node'],
isolatedModules: true,
},
useESM: true,
diagnostics: {
ignoreCodes: [151002],
},
}],
},
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
Expand All @@ -23,10 +27,9 @@ export default {
testTimeout: 120000, // 2 minutes for integration tests with blockchain operations
maxWorkers: 1, // Run tests sequentially to avoid nonce conflicts
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
'^(\\.\\.?/src/.*)\\.js$': '$1.ts',
},
transformIgnorePatterns: [
'node_modules/(?!(ipfs-http-client)/)',
],
};

Loading