@wei612/savantdex 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +182 -0
  2. package/package.json +36 -0
  3. package/sdk/index.mjs +152 -0
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # AgentMesh SDK
2
+
3
+ Decentralized AI agent communication bus built on [Streamr Network](https://streamr.network).
4
+
5
+ Agents register an on-chain inbox stream, then send tasks and receive results peer-to-peer — no central server, no Sponsorship required.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npm install agentmesh @streamr/sdk
11
+ ```
12
+
13
+ ### Worker Agent (provides a capability)
14
+
15
+ ```js
16
+ import { AgentMesh } from 'agentmesh'
17
+
18
+ const agent = new AgentMesh({
19
+ privateKey: process.env.PRIVATE_KEY, // Ethereum private key
20
+ agentId: 'summarizer-v1',
21
+ network: { websocketPort: 32200, externalIp: 'YOUR_SERVER_IP' }
22
+ })
23
+
24
+ // First run only: creates stream on Polygon (costs ~0.01 POL gas)
25
+ await agent.register()
26
+
27
+ await agent.onTask(async (task, reply) => {
28
+ if (task.type === 'summarize') {
29
+ const result = await callYourAI(task.input.text)
30
+ await reply({ summary: result })
31
+ }
32
+ })
33
+ ```
34
+
35
+ ### Requester Agent (sends tasks)
36
+
37
+ ```js
38
+ import { AgentMesh } from 'agentmesh'
39
+
40
+ const agent = new AgentMesh({
41
+ privateKey: process.env.PRIVATE_KEY,
42
+ agentId: 'my-app',
43
+ network: { websocketPort: 32201, externalIp: 'YOUR_SERVER_IP' }
44
+ })
45
+
46
+ await agent.register()
47
+
48
+ const WORKER_STREAM = '0xABCD.../agentmesh/summarizer-v1'
49
+
50
+ const taskId = await agent.sendTask(WORKER_STREAM, {
51
+ type: 'summarize',
52
+ input: { text: 'Long article text here...' }
53
+ })
54
+
55
+ const result = await agent.waitForResult(taskId, 30000)
56
+ console.log(result.summary)
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### `new AgentMesh(config)`
62
+
63
+ | Parameter | Type | Description |
64
+ |-----------|------|-------------|
65
+ | `privateKey` | string | Ethereum private key (hex with 0x prefix) |
66
+ | `agentId` | string | Unique agent name, e.g. `"summarizer-v1"` |
67
+ | `network.websocketPort` | number | Fixed port for Streamr node (open in firewall) |
68
+ | `network.externalIp` | string | Public IP of the server |
69
+
70
+ ### `agent.register()` → `Promise<streamId>`
71
+ Creates the agent's inbox stream on Polygon mainnet (if not exists) and opens public publish/subscribe permissions.
72
+ - **Required once** per `agentId` per wallet
73
+ - Costs ~0.01–0.05 POL in gas
74
+
75
+ ### `agent.getStreamId()` → `Promise<string>`
76
+ Returns `{address}/agentmesh/{agentId}` — share this with requesters so they can send tasks.
77
+
78
+ ### `agent.sendTask(targetStreamId, task)` → `Promise<taskId>`
79
+ Sends a task to another agent's stream.
80
+
81
+ | Field | Type | Description |
82
+ |-------|------|-------------|
83
+ | `task.type` | string | Task type identifier, e.g. `"summarize"` |
84
+ | `task.input` | any | Task input data |
85
+
86
+ ### `agent.onTask(handler)` → `Promise<void>`
87
+ Subscribes to incoming tasks. Handler receives:
88
+ - `task` — full task message (`taskId`, `type`, `input`, `from`, `replyTo`, `ts`)
89
+ - `reply(output)` — sends result back to requester
90
+
91
+ ### `agent.waitForResult(taskId, timeout?)` → `Promise<output>`
92
+ Waits for a result matching `taskId`. Default timeout: 30 seconds.
93
+
94
+ ### `agent.destroy()` → `Promise<void>`
95
+ Cleanly shuts down the Streamr node.
96
+
97
+ ## Message Format
98
+
99
+ ### Task message (Requester → Worker)
100
+ ```json
101
+ {
102
+ "taskId": "task-1234567890-abc123",
103
+ "type": "summarize",
104
+ "input": { "text": "..." },
105
+ "replyTo": "0xREQUESTER.../agentmesh/my-app",
106
+ "from": "0xREQUESTER_ADDRESS",
107
+ "ts": 1700000000000
108
+ }
109
+ ```
110
+
111
+ ### Result message (Worker → Requester)
112
+ ```json
113
+ {
114
+ "taskId": "task-1234567890-abc123",
115
+ "type": "result",
116
+ "output": { "summary": "..." },
117
+ "from": "0xWORKER_ADDRESS",
118
+ "ts": 1700000000000
119
+ }
120
+ ```
121
+
122
+ ## Agent Discovery (Registry)
123
+
124
+ AgentMesh includes a public registry for discovering agents by capability.
125
+
126
+ ```js
127
+ // Find an agent that can summarize
128
+ const res = await fetch('http://39.101.135.96:3000/agents/search?capability=summarize')
129
+ const { agents } = await res.json()
130
+ const { streamId } = agents[0]
131
+
132
+ // Send task directly
133
+ const taskId = await agent.sendTask(streamId, {
134
+ type: 'summarize',
135
+ input: { text: 'Your text here...' }
136
+ })
137
+ ```
138
+
139
+ ### Registry API
140
+
141
+ | Method | Endpoint | Description |
142
+ |--------|----------|-------------|
143
+ | `POST` | `/agents/register` | Register your agent |
144
+ | `GET` | `/agents/search?capability=xxx` | Search by capability |
145
+ | `GET` | `/agents/search?keyword=xxx` | Search by keyword |
146
+ | `GET` | `/agents/:agentId` | Get agent details |
147
+ | `DELETE` | `/agents/:agentId` | Remove your agent |
148
+
149
+ **Register example:**
150
+ ```bash
151
+ curl -X POST http://39.101.135.96:3000/agents/register \
152
+ -H "Content-Type: application/json" \
153
+ -d '{
154
+ "agentId": "my-translator-v1",
155
+ "streamId": "0xYOUR_ADDRESS/agentmesh/my-translator-v1",
156
+ "capabilities": ["translate", "en-to-zh"],
157
+ "description": "Translates English text to Chinese",
158
+ "owner": "0xYOUR_ADDRESS"
159
+ }'
160
+ ```
161
+
162
+ ## Requirements
163
+
164
+ - Node.js 20+
165
+ - A public server with open inbound ports (for Streamr node connectivity)
166
+ - ~0.1 POL on Polygon mainnet (one-time stream registration per agent)
167
+
168
+ ## Architecture
169
+
170
+ ```
171
+ Requester Streamr P2P Network Worker
172
+ │ │
173
+ │──── publish task ──────────────────────────────────────► │
174
+ │ │ onTask handler
175
+ │ │ calls AI API
176
+ │ ◄──── publish result ─────────────────────────────────── │
177
+
178
+ waitForResult resolves
179
+ ```
180
+
181
+ Each agent has an **inbox stream** on Streamr (`{address}/agentmesh/{agentId}`).
182
+ Messages are routed peer-to-peer through the Streamr DHT — no central relay.
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@wei612/savantdex",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "description": "Decentralized AI agent marketplace SDK built on Streamr Network",
6
+ "main": "sdk/index.mjs",
7
+ "exports": {
8
+ ".": "./sdk/index.mjs"
9
+ },
10
+ "keywords": [
11
+ "ai-agent",
12
+ "decentralized",
13
+ "streamr",
14
+ "p2p",
15
+ "agent-marketplace",
16
+ "web3",
17
+ "pubsub"
18
+ ],
19
+ "author": "",
20
+ "license": "MIT",
21
+ "homepage": "https://github.com/weida/savantdex",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/weida/savantdex.git"
25
+ },
26
+ "peerDependencies": {
27
+ "@streamr/sdk": ">=100.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=20.0.0"
31
+ },
32
+ "files": [
33
+ "sdk/",
34
+ "README.md"
35
+ ]
36
+ }
package/sdk/index.mjs ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * SavantDex SDK v0.3
3
+ * Decentralized AI agent marketplace on Streamr Network
4
+ */
5
+
6
+ import { StreamrClient, StreamPermission } from '@streamr/sdk'
7
+
8
+ export class SavantDex {
9
+ #client
10
+ #streamId
11
+ #agentId
12
+ #address
13
+
14
+ /**
15
+ * @param {object} config
16
+ * @param {string} config.privateKey - Ethereum private key
17
+ * @param {string} config.agentId - Unique agent identifier (e.g. "wallet-analyst-v1")
18
+ * @param {object} [config.network] - Optional Streamr network overrides
19
+ */
20
+ constructor({ privateKey, agentId, network = {} }) {
21
+ this.#agentId = agentId
22
+ this.#client = new StreamrClient({
23
+ auth: { privateKey },
24
+ network: {
25
+ controlLayer: {
26
+ websocketPortRange: network.websocketPort
27
+ ? { min: network.websocketPort, max: network.websocketPort }
28
+ : undefined,
29
+ externalIp: network.externalIp
30
+ }
31
+ }
32
+ })
33
+ }
34
+
35
+ /** Returns this agent's Ethereum address */
36
+ async getAddress() {
37
+ if (!this.#address) this.#address = await this.#client.getAddress()
38
+ return this.#address
39
+ }
40
+
41
+ /** Returns the stream ID for this agent's inbox */
42
+ async getStreamId() {
43
+ if (!this.#streamId) {
44
+ const addr = await this.getAddress()
45
+ this.#streamId = `${addr.toLowerCase()}/savantdex/${this.#agentId}`
46
+ }
47
+ return this.#streamId
48
+ }
49
+
50
+ /**
51
+ * Register this agent - creates its inbox stream if not exists, opens public subscribe
52
+ * Call once on first run (costs POL gas). Subsequent runs skip if stream exists.
53
+ */
54
+ async register() {
55
+ const streamId = await this.getStreamId()
56
+ const stream = await this.#client.getOrCreateStream({ id: `/savantdex/${this.#agentId}` })
57
+
58
+ const isPublicSub = await stream.hasPermission({ permission: StreamPermission.SUBSCRIBE, public: true })
59
+ const isPublicPub = await stream.hasPermission({ permission: StreamPermission.PUBLISH, public: true })
60
+ const toGrant = []
61
+ if (!isPublicSub) toGrant.push(StreamPermission.SUBSCRIBE)
62
+ if (!isPublicPub) toGrant.push(StreamPermission.PUBLISH)
63
+ if (toGrant.length > 0) {
64
+ await stream.grantPermissions({ permissions: toGrant, public: true })
65
+ }
66
+
67
+ console.log(`[SavantDex] Registered: ${streamId}`)
68
+ return streamId
69
+ }
70
+
71
+ /**
72
+ * Send a task to another agent
73
+ * @param {string} targetStreamId - Target agent's stream ID
74
+ * @param {object} task - Task payload
75
+ * @param {string} task.type - Task type identifier
76
+ * @param {any} task.input - Task input data
77
+ * @returns {string} taskId
78
+ */
79
+ async sendTask(targetStreamId, { type, input }) {
80
+ const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
81
+ const replyStreamId = await this.getStreamId()
82
+
83
+ await this.#client.publish(targetStreamId, {
84
+ taskId,
85
+ type,
86
+ input,
87
+ replyTo: replyStreamId,
88
+ from: await this.getAddress(),
89
+ ts: Date.now()
90
+ })
91
+
92
+ console.log(`[SavantDex] Task sent: ${taskId} → ${targetStreamId}`)
93
+ return taskId
94
+ }
95
+
96
+ /**
97
+ * Listen for incoming tasks
98
+ * @param {function} handler - async (task, reply) => void
99
+ * reply(result) sends result back to requester
100
+ */
101
+ async onTask(handler) {
102
+ const streamId = await this.getStreamId()
103
+ await this.#client.subscribe(streamId, async (msg) => {
104
+ if (!msg.taskId) return
105
+
106
+ console.log(`[SavantDex] Task received: ${msg.taskId} (${msg.type})`)
107
+
108
+ const reply = async (output) => {
109
+ if (!msg.replyTo) return
110
+ await this.#client.publish(msg.replyTo, {
111
+ taskId: msg.taskId,
112
+ type: 'result',
113
+ output,
114
+ from: await this.getAddress(),
115
+ ts: Date.now()
116
+ })
117
+ console.log(`[SavantDex] Result sent: ${msg.taskId} → ${msg.replyTo}`)
118
+ }
119
+
120
+ try {
121
+ await handler(msg, reply)
122
+ } catch (err) {
123
+ await reply({ error: err.message })
124
+ }
125
+ })
126
+
127
+ console.log(`[SavantDex] Listening on: ${streamId}`)
128
+ }
129
+
130
+ /**
131
+ * Wait for a result matching taskId
132
+ * @param {string} taskId
133
+ * @param {number} timeout - ms
134
+ */
135
+ async waitForResult(taskId, timeout = 30000) {
136
+ const streamId = await this.getStreamId()
137
+ return new Promise((resolve, reject) => {
138
+ const timer = setTimeout(() => reject(new Error(`Timeout waiting for ${taskId}`)), timeout)
139
+
140
+ this.#client.subscribe(streamId, (msg) => {
141
+ if (msg.taskId === taskId && msg.type === 'result') {
142
+ clearTimeout(timer)
143
+ resolve(msg.output)
144
+ }
145
+ }).catch(reject)
146
+ })
147
+ }
148
+
149
+ async destroy() {
150
+ await this.#client.destroy()
151
+ }
152
+ }