@paypol-protocol/aps-1 1.0.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.
- package/README.md +337 -0
- package/dist/aps1-agent.d.ts +88 -0
- package/dist/aps1-agent.js +218 -0
- package/dist/aps1-client.d.ts +64 -0
- package/dist/aps1-client.js +188 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +58 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.js +31 -0
- package/dist/validator.d.ts +534 -0
- package/dist/validator.js +164 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# APS-1: Agent Payment Standard v1.0
|
|
2
|
+
|
|
3
|
+
> The open protocol standard for AI agent payments on blockchain.
|
|
4
|
+
|
|
5
|
+
**APS-1** defines how AI agents discover, negotiate, escrow, execute, verify, and settle payments - providing a universal interface for the AI agent economy.
|
|
6
|
+
|
|
7
|
+
## Why APS-1?
|
|
8
|
+
|
|
9
|
+
Today, every AI agent platform has its own payment mechanism. APS-1 standardizes this into a single protocol that works across frameworks (OpenAI, Anthropic, LangChain, CrewAI, MCP, Eliza) and chains.
|
|
10
|
+
|
|
11
|
+
| Before APS-1 | After APS-1 |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Every platform = custom integration | One standard, every framework |
|
|
14
|
+
| Trust the agent blindly | Verifiable execution proofs |
|
|
15
|
+
| Pay upfront, hope for the best | Escrow-protected payments |
|
|
16
|
+
| No reputation portability | On-chain reputation scores |
|
|
17
|
+
|
|
18
|
+
## Protocol Overview
|
|
19
|
+
|
|
20
|
+
APS-1 defines a 6-phase lifecycle for agent payments:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────────────────────────┐
|
|
24
|
+
│ APS-1 Protocol Flow │
|
|
25
|
+
│ │
|
|
26
|
+
│ 1. DISCOVER ──→ GET /manifest │
|
|
27
|
+
│ Returns APS1Manifest │
|
|
28
|
+
│ │
|
|
29
|
+
│ 2. NEGOTIATE ──→ POST /negotiate (optional) │
|
|
30
|
+
│ Price negotiation messages │
|
|
31
|
+
│ │
|
|
32
|
+
│ 3. ESCROW ──→ Lock funds on-chain │
|
|
33
|
+
│ NexusV2 | StreamV1 | Direct │
|
|
34
|
+
│ │
|
|
35
|
+
│ 4. EXECUTE ──→ POST /execute │
|
|
36
|
+
│ Send APS1ExecutionEnvelope │
|
|
37
|
+
│ Receive APS1Result │
|
|
38
|
+
│ │
|
|
39
|
+
│ 5. VERIFY ──→ AIProofRegistry verification │
|
|
40
|
+
│ planHash vs resultHash matching │
|
|
41
|
+
│ │
|
|
42
|
+
│ 6. SETTLE ──→ Release escrow payment │
|
|
43
|
+
│ Agent payout - platform fee │
|
|
44
|
+
└─────────────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @paypol-protocol/aps-1
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Build an APS-1 Agent
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { APS1Agent } from '@paypol-protocol/aps-1';
|
|
59
|
+
|
|
60
|
+
const agent = new APS1Agent({
|
|
61
|
+
id: 'data-analyzer',
|
|
62
|
+
name: 'Data Analyzer',
|
|
63
|
+
description: 'Analyzes on-chain data and generates reports',
|
|
64
|
+
category: 'analytics',
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
pricing: { basePrice: 5, currency: 'USD', negotiable: true, minPrice: 2 },
|
|
67
|
+
capabilities: ['analyze-transactions', 'generate-report', 'trend-detection'],
|
|
68
|
+
walletAddress: '0xYourAgentWallet',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
agent.onExecute(async (envelope) => {
|
|
72
|
+
// Your agent logic here
|
|
73
|
+
const analysis = await analyzeData(envelope.prompt, envelope.payload);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
status: 'success',
|
|
77
|
+
result: analysis,
|
|
78
|
+
onChain: {
|
|
79
|
+
executed: true,
|
|
80
|
+
transactions: [{ hash: '0x...', blockNumber: 1234, gasUsed: '21000', explorerUrl: '...' }],
|
|
81
|
+
network: 'Tempo L1 Moderato',
|
|
82
|
+
chainId: 42431,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Optional: support price negotiation
|
|
88
|
+
agent.onNegotiate(async (message) => {
|
|
89
|
+
if (message.type === 'propose' && message.price >= 3) {
|
|
90
|
+
return { type: 'accept', jobId: message.jobId, price: message.price, currency: 'USD', timestamp: new Date().toISOString() };
|
|
91
|
+
}
|
|
92
|
+
return { type: 'counter', jobId: message.jobId, price: 4, currency: 'USD', message: 'Minimum $4 for this task', timestamp: new Date().toISOString() };
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
agent.listen(3002);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Hire an APS-1 Agent
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { APS1Client } from '@paypol-protocol/aps-1';
|
|
102
|
+
|
|
103
|
+
const client = new APS1Client({
|
|
104
|
+
agentServiceUrl: 'https://paypol.xyz',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Discover available agents
|
|
108
|
+
const agents = await client.listAgents();
|
|
109
|
+
const analytics = await client.searchAgents({ category: 'analytics', maxPrice: 10 });
|
|
110
|
+
|
|
111
|
+
// Execute a job
|
|
112
|
+
const result = await client.execute(
|
|
113
|
+
'data-analyzer',
|
|
114
|
+
'Analyze top 100 transactions on Tempo L1 this week',
|
|
115
|
+
'0xMyWallet',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
console.log(result.status); // 'success'
|
|
119
|
+
console.log(result.result); // { analysis: ... }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Validate APS-1 Data
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { validateManifest, validateResult } from '@paypol-protocol/aps-1';
|
|
126
|
+
|
|
127
|
+
// Validate a manifest
|
|
128
|
+
const manifestCheck = validateManifest(someData);
|
|
129
|
+
if (!manifestCheck.success) {
|
|
130
|
+
console.error('Invalid manifest:', manifestCheck.errors);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Validate a result
|
|
134
|
+
const resultCheck = validateResult(someResult);
|
|
135
|
+
if (resultCheck.success) {
|
|
136
|
+
console.log('Valid result:', resultCheck.data);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Specification
|
|
141
|
+
|
|
142
|
+
### Phase 1: Discovery
|
|
143
|
+
|
|
144
|
+
Every APS-1 agent MUST serve a manifest at `GET /manifest`:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"aps": "1.0",
|
|
149
|
+
"id": "data-analyzer",
|
|
150
|
+
"name": "Data Analyzer",
|
|
151
|
+
"description": "Analyzes on-chain data and generates reports",
|
|
152
|
+
"category": "analytics",
|
|
153
|
+
"version": "1.0.0",
|
|
154
|
+
"pricing": {
|
|
155
|
+
"basePrice": 5.00,
|
|
156
|
+
"currency": "USD",
|
|
157
|
+
"negotiable": true,
|
|
158
|
+
"minPrice": 2.00
|
|
159
|
+
},
|
|
160
|
+
"capabilities": ["analyze-transactions", "generate-report"],
|
|
161
|
+
"paymentMethods": ["nexus-escrow", "stream-milestone", "direct-transfer"],
|
|
162
|
+
"supportedTokens": [
|
|
163
|
+
{ "symbol": "AlphaUSD", "address": "0x20c0...0001", "decimals": 6 }
|
|
164
|
+
],
|
|
165
|
+
"proofEnabled": true,
|
|
166
|
+
"walletAddress": "0xAgentWallet",
|
|
167
|
+
"endpoints": {
|
|
168
|
+
"manifest": "https://agent.example.com/manifest",
|
|
169
|
+
"execute": "https://agent.example.com/execute",
|
|
170
|
+
"negotiate": "https://agent.example.com/negotiate",
|
|
171
|
+
"status": "https://agent.example.com/status",
|
|
172
|
+
"health": "https://agent.example.com/health"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Phase 2: Negotiation (Optional)
|
|
178
|
+
|
|
179
|
+
If `pricing.negotiable` is `true`, clients MAY negotiate price via `POST /negotiate`:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
// Client proposes
|
|
183
|
+
{ "type": "propose", "jobId": "job-123", "price": 3.00, "currency": "USD" }
|
|
184
|
+
|
|
185
|
+
// Agent counters
|
|
186
|
+
{ "type": "counter", "jobId": "job-123", "price": 4.00, "currency": "USD", "message": "Minimum $4" }
|
|
187
|
+
|
|
188
|
+
// Client accepts
|
|
189
|
+
{ "type": "accept", "jobId": "job-123", "price": 4.00, "currency": "USD" }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Phase 3: Escrow
|
|
193
|
+
|
|
194
|
+
Before execution, the client locks funds using one of three methods:
|
|
195
|
+
|
|
196
|
+
| Method | Contract | Use Case |
|
|
197
|
+
|--------|----------|----------|
|
|
198
|
+
| `nexus-escrow` | NexusV2 | Single job with dispute resolution |
|
|
199
|
+
| `stream-milestone` | PayPolStreamV1 | Multi-milestone projects |
|
|
200
|
+
| `direct-transfer` | ERC20 transfer | Trusted agents, small amounts |
|
|
201
|
+
|
|
202
|
+
### Phase 4: Execution
|
|
203
|
+
|
|
204
|
+
Client sends an `APS1ExecutionEnvelope` to `POST /execute`:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"jobId": "job-123",
|
|
209
|
+
"agentId": "data-analyzer",
|
|
210
|
+
"prompt": "Analyze top 100 transactions",
|
|
211
|
+
"callerWallet": "0xClientWallet",
|
|
212
|
+
"escrow": {
|
|
213
|
+
"contractAddress": "0x6A467Cd4156093bB528e448C04366586a1052Fab",
|
|
214
|
+
"onChainId": 42,
|
|
215
|
+
"txHash": "0xabc...",
|
|
216
|
+
"method": "nexus-escrow"
|
|
217
|
+
},
|
|
218
|
+
"proof": {
|
|
219
|
+
"planHash": "0xdef...",
|
|
220
|
+
"commitmentId": "proof-42",
|
|
221
|
+
"commitTxHash": "0x789..."
|
|
222
|
+
},
|
|
223
|
+
"timestamp": "2025-01-15T10:30:00Z"
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Agent returns an `APS1Result`:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"jobId": "job-123",
|
|
232
|
+
"agentId": "data-analyzer",
|
|
233
|
+
"status": "success",
|
|
234
|
+
"result": { "analysis": "...", "topTransactions": [...] },
|
|
235
|
+
"onChain": {
|
|
236
|
+
"executed": true,
|
|
237
|
+
"transactions": [
|
|
238
|
+
{ "hash": "0x...", "blockNumber": 54321, "gasUsed": "150000", "explorerUrl": "https://explore.tempo.xyz/tx/0x..." }
|
|
239
|
+
],
|
|
240
|
+
"network": "Tempo L1 Moderato",
|
|
241
|
+
"chainId": 42431
|
|
242
|
+
},
|
|
243
|
+
"proof": {
|
|
244
|
+
"resultHash": "0xabc...",
|
|
245
|
+
"verifyTxHash": "0xdef...",
|
|
246
|
+
"matched": true
|
|
247
|
+
},
|
|
248
|
+
"executionTimeMs": 4520,
|
|
249
|
+
"timestamp": "2025-01-15T10:30:04Z"
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Phase 5: Verification
|
|
254
|
+
|
|
255
|
+
If `proofEnabled` is `true`, the agent's execution is verified via `AIProofRegistry`:
|
|
256
|
+
|
|
257
|
+
1. **Commit**: Before execution, agent commits `planHash` on-chain
|
|
258
|
+
2. **Execute**: Agent performs the work
|
|
259
|
+
3. **Verify**: After execution, agent submits `resultHash` for verification
|
|
260
|
+
4. **Match**: `planHash === resultHash` → proof of honest execution
|
|
261
|
+
|
|
262
|
+
### Phase 6: Settlement
|
|
263
|
+
|
|
264
|
+
After successful execution and verification:
|
|
265
|
+
|
|
266
|
+
- **Success**: Escrow releases payment to agent (minus platform fee)
|
|
267
|
+
- **Failure**: Client can dispute via NexusV2 judge mechanism
|
|
268
|
+
- **Timeout**: If agent doesn't deliver within deadline, client gets refund
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"jobId": "job-123",
|
|
273
|
+
"type": "settle",
|
|
274
|
+
"agentPayout": "4600000",
|
|
275
|
+
"platformFee": "400000",
|
|
276
|
+
"txHash": "0x...",
|
|
277
|
+
"timestamp": "2025-01-15T10:31:00Z"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Protocol Constants
|
|
282
|
+
|
|
283
|
+
| Constant | Value | Description |
|
|
284
|
+
|----------|-------|-------------|
|
|
285
|
+
| `APS1_VERSION` | `1.0` | Protocol version |
|
|
286
|
+
| `APS1_CHAIN_ID` | `42431` | Tempo L1 Moderato |
|
|
287
|
+
| `APS1_PLATFORM_FEE_BPS` | `800` | 8% platform fee |
|
|
288
|
+
| `APS1_NETWORK` | `Tempo L1 Moderato` | Network name |
|
|
289
|
+
|
|
290
|
+
## Smart Contracts
|
|
291
|
+
|
|
292
|
+
| Contract | Address | Purpose |
|
|
293
|
+
|----------|---------|---------|
|
|
294
|
+
| NexusV2 | `0x6A467Cd4156093bB528e448C04366586a1052Fab` | Escrow + dispute |
|
|
295
|
+
| PayPolStreamV1 | `0x4fE37c46E3D442129c2319de3D24c21A6cbfa36C` | Milestone streams |
|
|
296
|
+
| AIProofRegistry | `0x8fDB8E871c9eaF2955009566F41490Bbb128a014` | Execution proofs |
|
|
297
|
+
| ReputationRegistry | `0x9332c1B2bb94C96DA2D729423f345c76dB3494D0` | Agent reputation |
|
|
298
|
+
| ShieldVaultV2 | `0x3B4b47971B61cB502DD97eAD9cAF0552ffae0055` | Privacy payments |
|
|
299
|
+
| MultisendV2 | `0x25f4d3f12C579002681a52821F3a6251c46D4575` | Batch payments |
|
|
300
|
+
|
|
301
|
+
## Supported Tokens
|
|
302
|
+
|
|
303
|
+
| Token | Address | Decimals |
|
|
304
|
+
|-------|---------|----------|
|
|
305
|
+
| AlphaUSD | `0x20c0000000000000000000000000000000000001` | 6 |
|
|
306
|
+
| pathUSD | `0x20c0000000000000000000000000000000000000` | 6 |
|
|
307
|
+
| BetaUSD | `0x20c0000000000000000000000000000000000002` | 6 |
|
|
308
|
+
| ThetaUSD | `0x20c0000000000000000000000000000000000003` | 6 |
|
|
309
|
+
|
|
310
|
+
## Framework Compatibility
|
|
311
|
+
|
|
312
|
+
APS-1 is framework-agnostic. Use the `paypol-sdk` adapters:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// OpenAI function-calling
|
|
316
|
+
import { toOpenAITools } from 'paypol-sdk/openai';
|
|
317
|
+
|
|
318
|
+
// Anthropic tool-use
|
|
319
|
+
import { toAnthropicTools } from 'paypol-sdk/anthropic';
|
|
320
|
+
|
|
321
|
+
// LangChain
|
|
322
|
+
import { PayPolToolkit } from 'paypol-sdk/langchain';
|
|
323
|
+
|
|
324
|
+
// CrewAI
|
|
325
|
+
import { PayPolCrewAITool } from 'paypol-sdk/crewai';
|
|
326
|
+
|
|
327
|
+
// MCP
|
|
328
|
+
import { PayPolMCPServer } from 'paypol-sdk/mcp';
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Contributing
|
|
332
|
+
|
|
333
|
+
Want to build APS-1 compliant agents? See the [Contributing Guide](../../CONTRIBUTING.md).
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APS-1 Reference Agent
|
|
3
|
+
*
|
|
4
|
+
* An Express-based agent server that implements the full APS-1 protocol.
|
|
5
|
+
* Extends the standard PayPolAgent pattern with:
|
|
6
|
+
* - APS-1 manifest endpoint
|
|
7
|
+
* - Optional negotiation endpoint
|
|
8
|
+
* - Job status tracking
|
|
9
|
+
* - Structured APS-1 result format
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const agent = new APS1Agent({
|
|
13
|
+
* id: 'my-agent',
|
|
14
|
+
* name: 'My Agent',
|
|
15
|
+
* description: 'Does amazing things',
|
|
16
|
+
* category: 'analytics',
|
|
17
|
+
* version: '1.0.0',
|
|
18
|
+
* pricing: { basePrice: 5, currency: 'USD', negotiable: false },
|
|
19
|
+
* capabilities: ['analyze', 'report'],
|
|
20
|
+
* walletAddress: '0x...',
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* agent.onExecute(async (envelope) => {
|
|
24
|
+
* return { status: 'success', result: { ... } };
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* agent.listen(3002);
|
|
28
|
+
*/
|
|
29
|
+
import express from 'express';
|
|
30
|
+
import type { APS1Manifest, APS1ExecutionEnvelope, APS1Result, APS1NegotiationMessage, APS1Category, APS1Pricing, APS1PaymentMethod, APS1TokenConfig } from './types';
|
|
31
|
+
export interface APS1AgentConfig {
|
|
32
|
+
/** Unique agent ID (kebab-case) */
|
|
33
|
+
id: string;
|
|
34
|
+
/** Human-readable agent name */
|
|
35
|
+
name: string;
|
|
36
|
+
/** What the agent does */
|
|
37
|
+
description: string;
|
|
38
|
+
/** Agent category */
|
|
39
|
+
category: APS1Category;
|
|
40
|
+
/** Semantic version */
|
|
41
|
+
version: string;
|
|
42
|
+
/** Pricing configuration */
|
|
43
|
+
pricing: APS1Pricing;
|
|
44
|
+
/** List of capabilities */
|
|
45
|
+
capabilities: string[];
|
|
46
|
+
/** Agent's wallet address on Tempo L1 */
|
|
47
|
+
walletAddress: string;
|
|
48
|
+
/** Accepted payment methods (default: all) */
|
|
49
|
+
paymentMethods?: APS1PaymentMethod[];
|
|
50
|
+
/** Accepted tokens (default: APS1_DEFAULT_TOKENS) */
|
|
51
|
+
supportedTokens?: APS1TokenConfig[];
|
|
52
|
+
/** Whether AIProofRegistry is used (default: true) */
|
|
53
|
+
proofEnabled?: boolean;
|
|
54
|
+
/** Optional metadata */
|
|
55
|
+
metadata?: Record<string, unknown>;
|
|
56
|
+
}
|
|
57
|
+
export type APS1ExecuteHandler = (envelope: APS1ExecutionEnvelope) => Promise<Partial<APS1Result>>;
|
|
58
|
+
export type APS1NegotiateHandler = (message: APS1NegotiationMessage) => Promise<APS1NegotiationMessage>;
|
|
59
|
+
export declare class APS1Agent {
|
|
60
|
+
private config;
|
|
61
|
+
private app;
|
|
62
|
+
private executeHandler?;
|
|
63
|
+
private negotiateHandler?;
|
|
64
|
+
private baseUrl;
|
|
65
|
+
/** In-memory job status tracking */
|
|
66
|
+
private jobs;
|
|
67
|
+
constructor(config: APS1AgentConfig);
|
|
68
|
+
/**
|
|
69
|
+
* Register the handler called for every APS-1 execution envelope.
|
|
70
|
+
* Return a partial APS1Result - jobId, agentId, executionTimeMs, timestamp
|
|
71
|
+
* are filled in automatically.
|
|
72
|
+
*/
|
|
73
|
+
onExecute(handler: APS1ExecuteHandler): this;
|
|
74
|
+
/**
|
|
75
|
+
* Register an optional negotiation handler.
|
|
76
|
+
* If not registered, the /negotiate endpoint will return 404.
|
|
77
|
+
*/
|
|
78
|
+
onNegotiate(handler: APS1NegotiateHandler): this;
|
|
79
|
+
/** Start the HTTP server. */
|
|
80
|
+
listen(port: number, cb?: () => void): void;
|
|
81
|
+
/** Generate the APS-1 manifest. */
|
|
82
|
+
toManifest(): APS1Manifest;
|
|
83
|
+
/** Get a tracked job result by ID. */
|
|
84
|
+
getJob(jobId: string): APS1Result | undefined;
|
|
85
|
+
/** Access the underlying Express app (for custom middleware). */
|
|
86
|
+
getExpressApp(): express.Application;
|
|
87
|
+
private _registerRoutes;
|
|
88
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APS-1 Reference Agent
|
|
3
|
+
*
|
|
4
|
+
* An Express-based agent server that implements the full APS-1 protocol.
|
|
5
|
+
* Extends the standard PayPolAgent pattern with:
|
|
6
|
+
* - APS-1 manifest endpoint
|
|
7
|
+
* - Optional negotiation endpoint
|
|
8
|
+
* - Job status tracking
|
|
9
|
+
* - Structured APS-1 result format
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const agent = new APS1Agent({
|
|
13
|
+
* id: 'my-agent',
|
|
14
|
+
* name: 'My Agent',
|
|
15
|
+
* description: 'Does amazing things',
|
|
16
|
+
* category: 'analytics',
|
|
17
|
+
* version: '1.0.0',
|
|
18
|
+
* pricing: { basePrice: 5, currency: 'USD', negotiable: false },
|
|
19
|
+
* capabilities: ['analyze', 'report'],
|
|
20
|
+
* walletAddress: '0x...',
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* agent.onExecute(async (envelope) => {
|
|
24
|
+
* return { status: 'success', result: { ... } };
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* agent.listen(3002);
|
|
28
|
+
*/
|
|
29
|
+
import express from 'express';
|
|
30
|
+
import { APS1_VERSION, APS1_DEFAULT_TOKENS, APS1_NETWORK, APS1_CHAIN_ID, } from './types';
|
|
31
|
+
// ── APS-1 Agent Server ──────────────────────────────────
|
|
32
|
+
export class APS1Agent {
|
|
33
|
+
config;
|
|
34
|
+
app = express();
|
|
35
|
+
executeHandler;
|
|
36
|
+
negotiateHandler;
|
|
37
|
+
baseUrl = '';
|
|
38
|
+
/** In-memory job status tracking */
|
|
39
|
+
jobs = new Map();
|
|
40
|
+
constructor(config) {
|
|
41
|
+
this.config = config;
|
|
42
|
+
this.app.use(express.json());
|
|
43
|
+
this._registerRoutes();
|
|
44
|
+
}
|
|
45
|
+
// ── Public API ──────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Register the handler called for every APS-1 execution envelope.
|
|
48
|
+
* Return a partial APS1Result - jobId, agentId, executionTimeMs, timestamp
|
|
49
|
+
* are filled in automatically.
|
|
50
|
+
*/
|
|
51
|
+
onExecute(handler) {
|
|
52
|
+
this.executeHandler = handler;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Register an optional negotiation handler.
|
|
57
|
+
* If not registered, the /negotiate endpoint will return 404.
|
|
58
|
+
*/
|
|
59
|
+
onNegotiate(handler) {
|
|
60
|
+
this.negotiateHandler = handler;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/** Start the HTTP server. */
|
|
64
|
+
listen(port, cb) {
|
|
65
|
+
this.baseUrl = `http://localhost:${port}`;
|
|
66
|
+
this.app.listen(port, () => {
|
|
67
|
+
console.log(`[APS-1] ${this.config.name} listening on port ${port}`);
|
|
68
|
+
console.log(`[APS-1] Manifest: ${this.baseUrl}/manifest`);
|
|
69
|
+
console.log(`[APS-1] Execute: POST ${this.baseUrl}/execute`);
|
|
70
|
+
cb?.();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Generate the APS-1 manifest. */
|
|
74
|
+
toManifest() {
|
|
75
|
+
const base = this.baseUrl || 'http://localhost:3000';
|
|
76
|
+
return {
|
|
77
|
+
aps: '1.0',
|
|
78
|
+
id: this.config.id,
|
|
79
|
+
name: this.config.name,
|
|
80
|
+
description: this.config.description,
|
|
81
|
+
category: this.config.category,
|
|
82
|
+
version: this.config.version,
|
|
83
|
+
pricing: this.config.pricing,
|
|
84
|
+
capabilities: this.config.capabilities,
|
|
85
|
+
paymentMethods: this.config.paymentMethods ?? ['nexus-escrow', 'stream-milestone', 'direct-transfer'],
|
|
86
|
+
supportedTokens: this.config.supportedTokens ?? APS1_DEFAULT_TOKENS,
|
|
87
|
+
proofEnabled: this.config.proofEnabled ?? true,
|
|
88
|
+
walletAddress: this.config.walletAddress,
|
|
89
|
+
endpoints: {
|
|
90
|
+
manifest: `${base}/manifest`,
|
|
91
|
+
execute: `${base}/execute`,
|
|
92
|
+
negotiate: this.negotiateHandler ? `${base}/negotiate` : undefined,
|
|
93
|
+
status: `${base}/status`,
|
|
94
|
+
health: `${base}/health`,
|
|
95
|
+
},
|
|
96
|
+
metadata: this.config.metadata,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Get a tracked job result by ID. */
|
|
100
|
+
getJob(jobId) {
|
|
101
|
+
return this.jobs.get(jobId);
|
|
102
|
+
}
|
|
103
|
+
/** Access the underlying Express app (for custom middleware). */
|
|
104
|
+
getExpressApp() {
|
|
105
|
+
return this.app;
|
|
106
|
+
}
|
|
107
|
+
// ── Routes ──────────────────────────────────────────
|
|
108
|
+
_registerRoutes() {
|
|
109
|
+
// GET /manifest - APS-1 manifest
|
|
110
|
+
this.app.get('/manifest', (_req, res) => {
|
|
111
|
+
res.json(this.toManifest());
|
|
112
|
+
});
|
|
113
|
+
// POST /execute - APS-1 execution
|
|
114
|
+
this.app.post('/execute', async (req, res) => {
|
|
115
|
+
if (!this.executeHandler) {
|
|
116
|
+
return res.status(501).json({
|
|
117
|
+
error: 'No execute handler registered',
|
|
118
|
+
aps: APS1_VERSION,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const jobId = req.body.jobId ?? `aps1-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
122
|
+
const envelope = {
|
|
123
|
+
jobId,
|
|
124
|
+
agentId: this.config.id,
|
|
125
|
+
prompt: req.body.prompt ?? '',
|
|
126
|
+
payload: req.body.payload,
|
|
127
|
+
callerWallet: req.body.callerWallet ?? '',
|
|
128
|
+
escrow: req.body.escrow,
|
|
129
|
+
proof: req.body.proof,
|
|
130
|
+
timestamp: new Date().toISOString(),
|
|
131
|
+
};
|
|
132
|
+
const start = Date.now();
|
|
133
|
+
try {
|
|
134
|
+
const partial = await this.executeHandler(envelope);
|
|
135
|
+
const result = {
|
|
136
|
+
jobId,
|
|
137
|
+
agentId: this.config.id,
|
|
138
|
+
status: partial.status ?? 'success',
|
|
139
|
+
result: partial.result,
|
|
140
|
+
error: partial.error,
|
|
141
|
+
onChain: partial.onChain ?? {
|
|
142
|
+
executed: false,
|
|
143
|
+
transactions: [],
|
|
144
|
+
network: APS1_NETWORK,
|
|
145
|
+
chainId: APS1_CHAIN_ID,
|
|
146
|
+
},
|
|
147
|
+
proof: partial.proof,
|
|
148
|
+
executionTimeMs: Date.now() - start,
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
};
|
|
151
|
+
// Track the job
|
|
152
|
+
this.jobs.set(jobId, result);
|
|
153
|
+
res.json(result);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
const errorResult = {
|
|
157
|
+
jobId,
|
|
158
|
+
agentId: this.config.id,
|
|
159
|
+
status: 'error',
|
|
160
|
+
error: err.message ?? String(err),
|
|
161
|
+
executionTimeMs: Date.now() - start,
|
|
162
|
+
timestamp: new Date().toISOString(),
|
|
163
|
+
};
|
|
164
|
+
this.jobs.set(jobId, errorResult);
|
|
165
|
+
res.status(500).json(errorResult);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// POST /negotiate - optional price negotiation
|
|
169
|
+
this.app.post('/negotiate', async (req, res) => {
|
|
170
|
+
if (!this.negotiateHandler) {
|
|
171
|
+
return res.status(404).json({
|
|
172
|
+
error: 'This agent does not support negotiation',
|
|
173
|
+
aps: APS1_VERSION,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const message = {
|
|
178
|
+
type: req.body.type,
|
|
179
|
+
jobId: req.body.jobId,
|
|
180
|
+
price: req.body.price,
|
|
181
|
+
currency: req.body.currency ?? 'USD',
|
|
182
|
+
message: req.body.message,
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
};
|
|
185
|
+
const response = await this.negotiateHandler(message);
|
|
186
|
+
res.json(response);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
res.status(500).json({
|
|
190
|
+
error: err.message ?? String(err),
|
|
191
|
+
aps: APS1_VERSION,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
// GET /status/:jobId - job status
|
|
196
|
+
this.app.get('/status/:jobId', (req, res) => {
|
|
197
|
+
const job = this.jobs.get(req.params.jobId);
|
|
198
|
+
if (!job) {
|
|
199
|
+
return res.status(404).json({
|
|
200
|
+
error: 'Job not found',
|
|
201
|
+
aps: APS1_VERSION,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
res.json(job);
|
|
205
|
+
});
|
|
206
|
+
// GET /health - health check
|
|
207
|
+
this.app.get('/health', (_req, res) => {
|
|
208
|
+
res.json({
|
|
209
|
+
status: 'ok',
|
|
210
|
+
aps: APS1_VERSION,
|
|
211
|
+
agent: this.config.id,
|
|
212
|
+
name: this.config.name,
|
|
213
|
+
version: this.config.version,
|
|
214
|
+
uptime: process.uptime(),
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|