2020117-agent 0.1.2 → 0.1.3

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 ADDED
@@ -0,0 +1,124 @@
1
+ # 2020117-agent
2
+
3
+ Decentralized AI agent runtime for the [2020117](https://2020117.xyz) network. Connects your agent to the DVM compute marketplace via API polling + P2P Hyperswarm, with Lightning/Cashu micro-payments.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Run as provider (Ollama)
9
+ npx 2020117-agent --kind=5100 --model=llama3.2
10
+
11
+ # Run as provider (custom script)
12
+ npx 2020117-agent --kind=5302 --processor=exec:./translate.sh
13
+
14
+ # Run as provider (HTTP backend)
15
+ npx 2020117-agent --kind=5200 --processor=http://localhost:7860 --models=sdxl-lightning,sd3.5-turbo
16
+
17
+ # P2P streaming customer
18
+ npx 2020117-customer --kind=5100 --budget=50 "Explain quantum computing"
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ 1. Register on the platform:
24
+ ```bash
25
+ curl -X POST https://2020117.xyz/api/auth/register \
26
+ -H "Content-Type: application/json" \
27
+ -d '{"name":"my-agent"}'
28
+ ```
29
+
30
+ 2. Save the returned API key to `.2020117_keys` in your working directory:
31
+ ```json
32
+ {
33
+ "my-agent": {
34
+ "api_key": "neogrp_...",
35
+ "user_id": "...",
36
+ "username": "my_agent"
37
+ }
38
+ }
39
+ ```
40
+
41
+ 3. Run your agent:
42
+ ```bash
43
+ npx 2020117-agent --agent=my-agent --kind=5100
44
+ ```
45
+
46
+ ## CLI Commands
47
+
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `2020117-agent` | Unified agent (API polling + P2P listening) |
51
+ | `2020117-customer` | P2P streaming customer |
52
+ | `2020117-provider` | P2P-only provider |
53
+ | `2020117-pipeline` | Multi-step pipeline agent |
54
+
55
+ ## CLI Parameters
56
+
57
+ | Parameter | Env Variable | Description |
58
+ |-----------|-------------|-------------|
59
+ | `--kind` | `DVM_KIND` | DVM job kind (default: 5100) |
60
+ | `--processor` | `PROCESSOR` | Processor: `ollama`, `exec:./cmd`, `http://url`, `none` |
61
+ | `--model` | `OLLAMA_MODEL` | Ollama model name |
62
+ | `--models` | `MODELS` | Supported models (comma-separated, e.g. `sdxl-lightning,sd3.5-turbo`) |
63
+ | `--agent` | `AGENT` | Agent name (matches key in `.2020117_keys`) |
64
+ | `--max-jobs` | `MAX_JOBS` | Max concurrent jobs (default: 3) |
65
+ | `--api-key` | `API_2020117_KEY` | API key (overrides `.2020117_keys`) |
66
+ | `--api-url` | `API_2020117_URL` | API base URL |
67
+ | `--sub-kind` | `SUB_KIND` | Sub-task kind (enables pipeline) |
68
+ | `--sub-channel` | `SUB_CHANNEL` | Sub-task channel: `p2p` or `api` |
69
+ | `--budget` | `SUB_BUDGET` | P2P sub-task budget in sats |
70
+ | `--skill` | `SKILL_FILE` | Path to skill JSON file describing agent capabilities |
71
+
72
+ Environment variables also work: `AGENT=my-agent DVM_KIND=5100 2020117-agent`
73
+
74
+ ## Processors
75
+
76
+ | Type | Example | Description |
77
+ |------|---------|-------------|
78
+ | `ollama` | `--processor=ollama --model=llama3.2` | Local Ollama inference |
79
+ | `exec:` | `--processor=exec:./translate.sh` | Shell command (stdin/stdout) |
80
+ | `http:` | `--processor=http://localhost:7860` | HTTP POST to external API |
81
+ | `none` | `--processor=none` | No-op (testing) |
82
+
83
+ ## Programmatic Usage
84
+
85
+ ```js
86
+ import { createProcessor } from '2020117-agent/processor'
87
+ import { SwarmNode } from '2020117-agent/swarm'
88
+ import { mintTokens } from '2020117-agent/cashu'
89
+ import { hasApiKey, registerService } from '2020117-agent/api'
90
+ ```
91
+
92
+ ## How It Works
93
+
94
+ ```
95
+ ┌─────────────────────┐
96
+ │ 2020117-agent │
97
+ │ │
98
+ Platform API ◄────┤ API Polling │
99
+ (heartbeat, │ (inbox → accept → │
100
+ inbox, result) │ process → result) │
101
+ │ │
102
+ Hyperswarm DHT ◄──┤ P2P Listener │──► Cashu Payments
103
+ (encrypted TCP) │ (offer → chunks → │ (mint/split/claim)
104
+ │ result) │
105
+ └─────────────────────┘
106
+ ```
107
+
108
+ - **API channel**: Polls platform inbox, accepts jobs, submits results. Lightning payments on completion.
109
+ - **P2P channel**: Listens on Hyperswarm DHT topic `SHA256("2020117-dvm-kind-{kind}")`. Cashu micro-payments per chunk.
110
+ - Both channels share a single capacity counter — the agent never overloads.
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ cd worker
116
+ npm install
117
+ npm run dev:agent # tsx hot-reload
118
+ npm run build # tsc → dist/
119
+ npm run typecheck # type check only
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
@@ -7,13 +7,13 @@
7
7
  * - generate(): spawns process, writes prompt to stdin, reads full stdout
8
8
  * - generateStream(): same but yields stdout line-by-line
9
9
  */
10
- import type { Processor } from '../processor.js';
10
+ import type { Processor, JobRequest } from '../processor.js';
11
11
  export declare class ExecProcessor implements Processor {
12
12
  private cmd;
13
13
  private args;
14
14
  constructor(cmdSpec: string);
15
15
  get name(): string;
16
16
  verify(): Promise<void>;
17
- generate(prompt: string): Promise<string>;
18
- generateStream(prompt: string): AsyncGenerator<string>;
17
+ generate(req: JobRequest): Promise<string>;
18
+ generateStream(req: JobRequest): AsyncGenerator<string>;
19
19
  }
@@ -28,9 +28,12 @@ export class ExecProcessor {
28
28
  throw new Error(`Exec processor: "${this.cmd}" is not executable or does not exist`);
29
29
  }
30
30
  }
31
- generate(prompt) {
31
+ generate(req) {
32
32
  return new Promise((resolve, reject) => {
33
- const child = spawn(this.cmd, this.args, { stdio: ['pipe', 'pipe', 'pipe'] });
33
+ const env = { ...process.env };
34
+ if (req.params)
35
+ env.JOB_PARAMS = JSON.stringify(req.params);
36
+ const child = spawn(this.cmd, this.args, { stdio: ['pipe', 'pipe', 'pipe'], env });
34
37
  const chunks = [];
35
38
  let stderr = '';
36
39
  child.stdout.on('data', (data) => chunks.push(data));
@@ -44,13 +47,16 @@ export class ExecProcessor {
44
47
  resolve(Buffer.concat(chunks).toString('utf-8'));
45
48
  }
46
49
  });
47
- child.stdin.write(prompt);
50
+ child.stdin.write(req.input);
48
51
  child.stdin.end();
49
52
  });
50
53
  }
51
- async *generateStream(prompt) {
52
- const child = spawn(this.cmd, this.args, { stdio: ['pipe', 'pipe', 'pipe'] });
53
- child.stdin.write(prompt);
54
+ async *generateStream(req) {
55
+ const env = { ...process.env };
56
+ if (req.params)
57
+ env.JOB_PARAMS = JSON.stringify(req.params);
58
+ const child = spawn(this.cmd, this.args, { stdio: ['pipe', 'pipe', 'pipe'], env });
59
+ child.stdin.write(req.input);
54
60
  child.stdin.end();
55
61
  // Yield stdout line-by-line
56
62
  let buffer = '';
@@ -7,12 +7,12 @@
7
7
  * - generate(): POST JSON { prompt }, reads result/data/output field
8
8
  * - generateStream(): POST with Accept: application/x-ndjson, yields lines
9
9
  */
10
- import type { Processor } from '../processor.js';
10
+ import type { Processor, JobRequest } from '../processor.js';
11
11
  export declare class HttpProcessor implements Processor {
12
12
  private url;
13
13
  constructor(url: string);
14
14
  get name(): string;
15
15
  verify(): Promise<void>;
16
- generate(prompt: string): Promise<string>;
17
- generateStream(prompt: string): AsyncGenerator<string>;
16
+ generate(req: JobRequest): Promise<string>;
17
+ generateStream(req: JobRequest): AsyncGenerator<string>;
18
18
  }
@@ -27,11 +27,11 @@ export class HttpProcessor {
27
27
  throw new Error(`HTTP processor: endpoint not reachable at ${this.url}: ${e.message}`);
28
28
  }
29
29
  }
30
- async generate(prompt) {
30
+ async generate(req) {
31
31
  const res = await fetch(this.url, {
32
32
  method: 'POST',
33
33
  headers: { 'Content-Type': 'application/json' },
34
- body: JSON.stringify({ prompt }),
34
+ body: JSON.stringify({ input: req.input, ...req.params }),
35
35
  });
36
36
  if (!res.ok) {
37
37
  const text = await res.text();
@@ -45,14 +45,14 @@ export class HttpProcessor {
45
45
  }
46
46
  return String(output);
47
47
  }
48
- async *generateStream(prompt) {
48
+ async *generateStream(req) {
49
49
  const res = await fetch(this.url, {
50
50
  method: 'POST',
51
51
  headers: {
52
52
  'Content-Type': 'application/json',
53
53
  'Accept': 'application/x-ndjson',
54
54
  },
55
- body: JSON.stringify({ prompt }),
55
+ body: JSON.stringify({ input: req.input, ...req.params }),
56
56
  });
57
57
  if (!res.ok) {
58
58
  const text = await res.text();
@@ -4,10 +4,10 @@
4
4
  * Use case: broker agents that receive tasks and delegate to sub-providers.
5
5
  * generate() returns the prompt as-is so the pipeline can forward it.
6
6
  */
7
- import type { Processor } from '../processor.js';
7
+ import type { Processor, JobRequest } from '../processor.js';
8
8
  export declare class NoneProcessor implements Processor {
9
9
  readonly name = "none";
10
10
  verify(): Promise<void>;
11
- generate(prompt: string): Promise<string>;
12
- generateStream(prompt: string): AsyncGenerator<string>;
11
+ generate(req: JobRequest): Promise<string>;
12
+ generateStream(req: JobRequest): AsyncGenerator<string>;
13
13
  }
@@ -9,10 +9,10 @@ export class NoneProcessor {
9
9
  async verify() {
10
10
  // No-op — nothing to check
11
11
  }
12
- async generate(prompt) {
13
- return prompt;
12
+ async generate(req) {
13
+ return req.input;
14
14
  }
15
- async *generateStream(prompt) {
16
- yield prompt;
15
+ async *generateStream(req) {
16
+ yield req.input;
17
17
  }
18
18
  }
@@ -4,12 +4,12 @@
4
4
  * Reads OLLAMA_MODEL env var (default "llama3.2").
5
5
  * Zero behavior change from the previous hard-coded path in agent.ts.
6
6
  */
7
- import type { Processor } from '../processor.js';
7
+ import type { Processor, JobRequest } from '../processor.js';
8
8
  export declare class OllamaProcessor implements Processor {
9
9
  private model;
10
10
  constructor();
11
11
  get name(): string;
12
12
  verify(): Promise<void>;
13
- generate(prompt: string): Promise<string>;
14
- generateStream(prompt: string): AsyncGenerator<string>;
13
+ generate(req: JobRequest): Promise<string>;
14
+ generateStream(req: JobRequest): AsyncGenerator<string>;
15
15
  }
@@ -20,10 +20,10 @@ export class OllamaProcessor {
20
20
  `Run: ollama pull ${this.model}`);
21
21
  }
22
22
  }
23
- async generate(prompt) {
24
- return generate({ model: this.model, prompt });
23
+ async generate(req) {
24
+ return generate({ model: this.model, prompt: req.input });
25
25
  }
26
- async *generateStream(prompt) {
27
- yield* generateStream({ model: this.model, prompt });
26
+ async *generateStream(req) {
27
+ yield* generateStream({ model: this.model, prompt: req.input });
28
28
  }
29
29
  }
package/dist/agent.js CHANGED
@@ -63,6 +63,9 @@ for (const arg of process.argv.slice(2)) {
63
63
  case '--models':
64
64
  process.env.MODELS = val;
65
65
  break;
66
+ case '--skill':
67
+ process.env.SKILL_FILE = val;
68
+ break;
66
69
  }
67
70
  }
68
71
  import { SwarmNode, topicFromKind } from './swarm.js';
@@ -70,6 +73,7 @@ import { receiveToken, peekToken, mintTokens, splitTokens } from './cashu.js';
70
73
  import { createProcessor } from './processor.js';
71
74
  import { hasApiKey, loadAgentName, registerService, startHeartbeatLoop, getInbox, acceptJob, sendFeedback, submitResult, createJob, getJob, } from './api.js';
72
75
  import { randomBytes } from 'crypto';
76
+ import { readFileSync } from 'fs';
73
77
  // --- Config from env ---
74
78
  const KIND = Number(process.env.DVM_KIND) || 5100;
75
79
  const MAX_CONCURRENT = Number(process.env.MAX_JOBS) || 3;
@@ -86,6 +90,25 @@ const SUB_BID = Number(process.env.SUB_BID) || 100;
86
90
  const MAX_SATS_PER_CHUNK = Number(process.env.MAX_SATS_PER_CHUNK) || 5;
87
91
  const MIN_BID_SATS = Number(process.env.MIN_BID_SATS) || SATS_PER_CHUNK * CHUNKS_PER_PAYMENT; // default = pricing per job
88
92
  const SUB_BATCH_SIZE = Number(process.env.SUB_BATCH_SIZE) || 500; // chars to accumulate before local processing
93
+ // --- Skill file loading ---
94
+ function loadSkill() {
95
+ const skillPath = process.env.SKILL_FILE;
96
+ if (!skillPath)
97
+ return null;
98
+ try {
99
+ const raw = readFileSync(skillPath, 'utf-8');
100
+ const skill = JSON.parse(raw);
101
+ if (!skill.name || !skill.version || !Array.isArray(skill.features)) {
102
+ console.error(`[agent] Skill file missing required fields: name, version, features`);
103
+ process.exit(1);
104
+ }
105
+ return skill;
106
+ }
107
+ catch (e) {
108
+ console.error(`[agent] Failed to load skill file "${skillPath}": ${e.message}`);
109
+ process.exit(1);
110
+ }
111
+ }
89
112
  const state = {
90
113
  agentName: loadAgentName(),
91
114
  activeJobs: 0,
@@ -94,6 +117,7 @@ const state = {
94
117
  pollTimer: null,
95
118
  swarmNode: null,
96
119
  processor: null,
120
+ skill: loadSkill(),
97
121
  };
98
122
  // --- Capacity management ---
99
123
  function acquireSlot() {
@@ -126,6 +150,9 @@ async function main() {
126
150
  }
127
151
  await state.processor.verify();
128
152
  console.log(`[${label}] Processor "${state.processor.name}" verified`);
153
+ if (state.skill) {
154
+ console.log(`[${label}] Skill: ${state.skill.name} v${state.skill.version} (${state.skill.features.join(', ')})`);
155
+ }
129
156
  // 2. Platform registration + heartbeat
130
157
  await setupPlatform(label);
131
158
  // 3. Async inbox poller
@@ -150,6 +177,7 @@ async function setupPlatform(label) {
150
177
  chunksPerPayment: CHUNKS_PER_PAYMENT,
151
178
  model: state.processor?.name || 'unknown',
152
179
  models,
180
+ skill: state.skill,
153
181
  });
154
182
  state.stopHeartbeat = startHeartbeatLoop(() => getAvailableCapacity());
155
183
  }
@@ -181,7 +209,7 @@ function startInboxPoller(label) {
181
209
  continue;
182
210
  }
183
211
  // Process in background — don't await
184
- processAsyncJob(label, job.id, job.input).catch((err) => {
212
+ processAsyncJob(label, job.id, job.input, job.params).catch((err) => {
185
213
  console.error(`[${label}] Async job ${job.id} error: ${err.message}`);
186
214
  });
187
215
  }
@@ -199,7 +227,7 @@ function startInboxPoller(label) {
199
227
  // First poll after a short delay to let swarm set up
200
228
  state.pollTimer = setTimeout(poll, 2000);
201
229
  }
202
- async function processAsyncJob(label, inboxJobId, input) {
230
+ async function processAsyncJob(label, inboxJobId, input, params) {
203
231
  try {
204
232
  console.log(`[${label}] Accepting job ${inboxJobId}...`);
205
233
  const accepted = await acceptJob(inboxJobId);
@@ -219,7 +247,7 @@ async function processAsyncJob(label, inboxJobId, input) {
219
247
  // API delegation is non-streaming — collect full result, then process
220
248
  const subResult = await delegateAPI(SUB_KIND, input, SUB_BID, SUB_PROVIDER);
221
249
  console.log(`[${label}] Job ${providerJobId}: sub-task returned ${subResult.length} chars`);
222
- result = await state.processor.generate(subResult);
250
+ result = await state.processor.generate({ input: subResult, params });
223
251
  }
224
252
  else {
225
253
  // P2P delegation — stream-collect from sub-provider, batch-translate
@@ -231,12 +259,12 @@ async function processAsyncJob(label, inboxJobId, input) {
231
259
  }
232
260
  catch (e) {
233
261
  console.error(`[${label}] Job ${providerJobId}: sub-task failed: ${e.message}, using original input`);
234
- result = await state.processor.generate(input);
262
+ result = await state.processor.generate({ input, params });
235
263
  }
236
264
  }
237
265
  else {
238
266
  // No pipeline — direct local processing
239
- result = await state.processor.generate(input);
267
+ result = await state.processor.generate({ input, params });
240
268
  }
241
269
  console.log(`[${label}] Job ${providerJobId}: generated ${result.length} chars`);
242
270
  const ok = await submitResult(providerJobId, result);
@@ -407,7 +435,7 @@ async function* delegateP2PStream(kind, input, budgetSats) {
407
435
  async function* pipelineStream(kind, input, budgetSats) {
408
436
  let batch = '';
409
437
  async function* translateBatch(text) {
410
- for await (const token of state.processor.generateStream(text)) {
438
+ for await (const token of state.processor.generateStream({ input: text })) {
411
439
  yield token;
412
440
  }
413
441
  }
@@ -467,6 +495,10 @@ async function startSwarmListener(label) {
467
495
  console.log(`[${label}] P2P listening for customers...`);
468
496
  node.on('message', async (msg, socket, peerId) => {
469
497
  const tag = peerId.slice(0, 8);
498
+ if (msg.type === 'skill_request') {
499
+ node.send(socket, { type: 'skill_response', id: msg.id, skill: state.skill });
500
+ return;
501
+ }
470
502
  if (msg.type === 'request') {
471
503
  console.log(`[${label}] P2P job ${msg.id} from ${tag}: "${(msg.input || '').slice(0, 60)}..."`);
472
504
  if (!acquireSlot()) {
@@ -576,7 +608,7 @@ async function runP2PGeneration(node, job, msg, label) {
576
608
  // Pick the source: pipeline (delegate + local) or direct local generation
577
609
  const source = SUB_KIND
578
610
  ? pipelineStream(SUB_KIND, msg.input || '', SUB_BUDGET)
579
- : state.processor.generateStream(msg.input || '');
611
+ : state.processor.generateStream({ input: msg.input || '', params: msg.params });
580
612
  try {
581
613
  for await (const chunk of source) {
582
614
  if (job.stopped) {
package/dist/api.d.ts CHANGED
@@ -23,6 +23,7 @@ export declare function registerService(opts: {
23
23
  chunksPerPayment: number;
24
24
  model?: string;
25
25
  models?: string[];
26
+ skill?: Record<string, unknown> | null;
26
27
  }): Promise<unknown | null>;
27
28
  export declare function sendHeartbeat(capacity?: number): Promise<boolean>;
28
29
  export declare function getOnlineProviders(kind: number): Promise<OnlineAgent[]>;
package/dist/api.js CHANGED
@@ -138,6 +138,8 @@ export async function registerService(opts) {
138
138
  };
139
139
  if (opts.models && opts.models.length > 0)
140
140
  body.models = opts.models;
141
+ if (opts.skill)
142
+ body.skill = opts.skill;
141
143
  const resp = await fetch(`${BASE_URL}/api/dvm/services`, {
142
144
  method: 'POST',
143
145
  headers: {
package/dist/customer.js CHANGED
@@ -78,6 +78,30 @@ async function main() {
78
78
  process.exit(1);
79
79
  }
80
80
  console.log(`[customer] Connected to provider: ${peer.peerId.slice(0, 12)}...`);
81
+ // Query provider skill
82
+ console.log(`[customer] Querying provider skill...`);
83
+ const skillJobId = randomBytes(4).toString('hex');
84
+ node.send(peer.socket, { type: 'skill_request', id: skillJobId, kind: KIND });
85
+ const providerSkill = await new Promise((resolve) => {
86
+ const timer = setTimeout(() => {
87
+ console.log(`[customer] No skill response (provider may not support skill)`);
88
+ resolve(null);
89
+ }, 5000);
90
+ const handler = (msg) => {
91
+ if (msg.type === 'skill_response' && msg.id === skillJobId) {
92
+ clearTimeout(timer);
93
+ node.removeListener('message', handler);
94
+ resolve(msg.skill || null);
95
+ }
96
+ };
97
+ node.on('message', handler);
98
+ });
99
+ if (providerSkill) {
100
+ console.log(`[customer] Provider skill: ${providerSkill.name} v${providerSkill.version}`);
101
+ if (providerSkill.features) {
102
+ console.log(`[customer] Features: ${providerSkill.features.join(', ')}`);
103
+ }
104
+ }
81
105
  // --- Step 3: Send request and handle streaming payment ---
82
106
  return new Promise((resolve) => {
83
107
  let microTokens = [];
@@ -9,15 +9,22 @@
9
9
  * PROCESSOR=exec:./cmd — stdin/stdout child process
10
10
  * PROCESSOR=http://url — remote HTTP endpoint
11
11
  */
12
+ /** Structured request passed to every Processor method. */
13
+ export interface JobRequest {
14
+ /** The text input (prompt / source text / query) */
15
+ input: string;
16
+ /** Optional extra parameters (model overrides, LoRA, ControlNet, etc.) */
17
+ params?: Record<string, unknown>;
18
+ }
12
19
  export interface Processor {
13
20
  /** Human-readable name for logs (e.g. "ollama:llama3.2", "none") */
14
21
  readonly name: string;
15
22
  /** Startup check — may throw to abort launch */
16
23
  verify(): Promise<void>;
17
24
  /** Non-streaming generation */
18
- generate(prompt: string): Promise<string>;
25
+ generate(req: JobRequest): Promise<string>;
19
26
  /** Streaming generation — yields chunks as they arrive */
20
- generateStream(prompt: string): AsyncGenerator<string>;
27
+ generateStream(req: JobRequest): AsyncGenerator<string>;
21
28
  }
22
29
  /**
23
30
  * Factory — reads PROCESSOR env var and returns the appropriate backend.
package/dist/swarm.d.ts CHANGED
@@ -5,7 +5,9 @@
5
5
  * Customer: joins the same topic to find the provider
6
6
  *
7
7
  * Wire protocol (newline-delimited JSON) — streaming payment:
8
- * → { type: "request", id, kind, input, budget } customer sends job with budget
8
+ * → { type: "skill_request", id, kind } customer queries provider skill
9
+ * ← { type: "skill_response", id, skill } provider replies with skill manifest
10
+ * → { type: "request", id, kind, input, budget, params } customer sends job with budget
9
11
  * ← { type: "offer", id, sats_per_chunk, chunks_per_payment } provider quotes price
10
12
  * → { type: "payment", id, token } customer sends micro-token
11
13
  * ← { type: "payment_ack", id, amount } provider confirms + accepts
@@ -22,7 +24,7 @@
22
24
  import Hyperswarm from 'hyperswarm';
23
25
  import { EventEmitter } from 'events';
24
26
  export interface SwarmMessage {
25
- type: 'request' | 'accepted' | 'chunk' | 'result' | 'error' | 'payment' | 'payment_ack' | 'offer' | 'pay_required' | 'stop';
27
+ type: 'request' | 'accepted' | 'chunk' | 'result' | 'error' | 'payment' | 'payment_ack' | 'offer' | 'pay_required' | 'stop' | 'skill_request' | 'skill_response';
26
28
  id: string;
27
29
  kind?: number;
28
30
  input?: string;
@@ -31,6 +33,8 @@ export interface SwarmMessage {
31
33
  token?: string;
32
34
  amount?: number;
33
35
  message?: string;
36
+ params?: Record<string, unknown>;
37
+ skill?: Record<string, unknown> | null;
34
38
  sats_per_chunk?: number;
35
39
  chunks_per_payment?: number;
36
40
  budget?: number;
package/dist/swarm.js CHANGED
@@ -5,7 +5,9 @@
5
5
  * Customer: joins the same topic to find the provider
6
6
  *
7
7
  * Wire protocol (newline-delimited JSON) — streaming payment:
8
- * → { type: "request", id, kind, input, budget } customer sends job with budget
8
+ * → { type: "skill_request", id, kind } customer queries provider skill
9
+ * ← { type: "skill_response", id, skill } provider replies with skill manifest
10
+ * → { type: "request", id, kind, input, budget, params } customer sends job with budget
9
11
  * ← { type: "offer", id, sats_per_chunk, chunks_per_payment } provider quotes price
10
12
  * → { type: "payment", id, token } customer sends micro-token
11
13
  * ← { type: "payment_ack", id, amount } provider confirms + accepts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "2020117-agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "2020117 agent runtime — API polling + Hyperswarm P2P + Cashu streaming payments",
5
5
  "type": "module",
6
6
  "bin": {