ai-market 0.1.13 → 0.1.14
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.
|
@@ -118,7 +118,8 @@ var CAPABILITY_LABELS = {
|
|
|
118
118
|
messaging: "Send and receive messages",
|
|
119
119
|
pay: "Make payments and manage wallet",
|
|
120
120
|
manage_listings: "Create and manage listings",
|
|
121
|
-
withdraw: "Withdraw funds to bank account"
|
|
121
|
+
withdraw: "Withdraw funds to bank account",
|
|
122
|
+
serve_listings: "Run an API worker that handles incoming calls"
|
|
122
123
|
};
|
|
123
124
|
function setCallbackPort(port) {
|
|
124
125
|
_callbackPort = port;
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
requireMeta,
|
|
13
13
|
saveCursor,
|
|
14
14
|
saveMeta
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-G27TUULG.js";
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
18
|
import { Command } from "commander";
|
|
@@ -44,12 +44,12 @@ function registerCommands(program2) {
|
|
|
44
44
|
const user = program2.command("user").description("Account & identity");
|
|
45
45
|
user.command("register").description("Register a new agent (opens browser for verification)").requiredOption("--name <name>", "Agent name (lowercase, 2-50 chars)").option("--offers <items>", "Comma-separated list of offerings", splitList).option("--wants <items>", "Comma-separated list of wants", splitList).option("--description <desc>", "Agent description").option("--api-url <url>", "API base URL", DEFAULT_API_URL).action(
|
|
46
46
|
(opts) => run(async () => {
|
|
47
|
-
const { setCallbackPort, setApprovalContext } = await import("./api-
|
|
47
|
+
const { setCallbackPort, setApprovalContext } = await import("./api-NODAAOEI.js");
|
|
48
48
|
const existing = loadMeta();
|
|
49
49
|
if (existing?.activeAgentId) die(`Already registered. Use "ai-market user logout" first.`);
|
|
50
50
|
const apiUrl = opts.apiUrl.replace(/\/+$/, "");
|
|
51
51
|
const client = getClient();
|
|
52
|
-
const capabilities = ["browse_market", "propose_deals", "messaging", "pay", "manage_listings"];
|
|
52
|
+
const capabilities = ["browse_market", "propose_deals", "messaging", "pay", "manage_listings", "serve_listings"];
|
|
53
53
|
const { port, waitForCallback, close } = await startCallbackServer();
|
|
54
54
|
setCallbackPort(port);
|
|
55
55
|
setApprovalContext({ agentName: opts.name, capabilities });
|
|
@@ -67,7 +67,8 @@ Registering agent "${opts.name}"...`);
|
|
|
67
67
|
{ name: "propose_deals" },
|
|
68
68
|
{ name: "messaging" },
|
|
69
69
|
{ name: "pay" },
|
|
70
|
-
{ name: "manage_listings" }
|
|
70
|
+
{ name: "manage_listings" },
|
|
71
|
+
{ name: "serve_listings" }
|
|
71
72
|
],
|
|
72
73
|
mode: "delegated"
|
|
73
74
|
});
|
|
@@ -111,7 +112,7 @@ Registered agent "${opts.name}" (${result.agentId})`);
|
|
|
111
112
|
user.command("me").description("Show your agent profile").action(() => run(async () => print(await api("GET", "/v1/agents/me"))));
|
|
112
113
|
user.command("request-capability").description("Request additional capabilities (opens browser for approval)").requiredOption("--capabilities <caps>", "Comma-separated capabilities to request", splitList).action(
|
|
113
114
|
(opts) => run(async () => {
|
|
114
|
-
const { setCallbackPort, setApprovalContext } = await import("./api-
|
|
115
|
+
const { setCallbackPort, setApprovalContext } = await import("./api-NODAAOEI.js");
|
|
115
116
|
const meta = requireMeta();
|
|
116
117
|
const client = getClient();
|
|
117
118
|
const { port, waitForCallback, close } = await startCallbackServer();
|
|
@@ -173,7 +174,7 @@ Capabilities updated:`);
|
|
|
173
174
|
})
|
|
174
175
|
);
|
|
175
176
|
const listing = program2.command("listing").description("Manage listings");
|
|
176
|
-
listing.command("create").description("Create a new listing").requiredOption("--type <type>", '"offer" or "want"').requiredOption("--category <cat>", "Category (e.g. compute, design)").requiredOption("--title <title>", "Listing title").option("--description <desc>", "Description").option("--price <amount>", "Price in USD").option("--tags <tags>", "Comma-separated tags for search discovery", splitList).action(
|
|
177
|
+
listing.command("create").description("Create a new listing").requiredOption("--type <type>", '"offer" or "want"').requiredOption("--category <cat>", "Category (e.g. compute, design)").requiredOption("--title <title>", "Listing title").option("--description <desc>", "Description").option("--price <amount>", "Price in USD").option("--tags <tags>", "Comma-separated tags for search discovery", splitList).option("--delivery-kind <kind>", '"manual" (default) or "api"').option("--pricing-mode <mode>", '"fixed" (default) or "per_call"').action(
|
|
177
178
|
(opts) => run(async () => {
|
|
178
179
|
const body = {
|
|
179
180
|
type: opts.type,
|
|
@@ -183,9 +184,17 @@ Capabilities updated:`);
|
|
|
183
184
|
if (opts.description) body.description = opts.description;
|
|
184
185
|
if (opts.price) body.price = { amount: opts.price, currency: "USD" };
|
|
185
186
|
if (opts.tags) body.tags = opts.tags;
|
|
187
|
+
if (opts.deliveryKind) body.delivery_kind = opts.deliveryKind;
|
|
188
|
+
if (opts.pricingMode) body.pricing_mode = opts.pricingMode;
|
|
186
189
|
print(await api("POST", "/v1/listings", body));
|
|
187
190
|
})
|
|
188
191
|
);
|
|
192
|
+
listing.command("serve <listing-id>").description("Run as an API worker for an api-delivery listing (long-running)").requiredOption("--exec <cmd>", "Shell command to invoke for each call. Receives the call payload as JSON on stdin and must print the result as JSON on stdout.").action(
|
|
193
|
+
(listingId, opts) => run(async () => {
|
|
194
|
+
const { runServe } = await import("./serve-BMIMXCDR.js");
|
|
195
|
+
await runServe({ listingId, exec: opts.exec });
|
|
196
|
+
})
|
|
197
|
+
);
|
|
189
198
|
listing.command("list").description("Browse all listings").option("--type <type>", "Filter by offer/want").action(
|
|
190
199
|
(opts) => run(async () => {
|
|
191
200
|
const params = new URLSearchParams();
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getClient,
|
|
3
|
+
requireMeta
|
|
4
|
+
} from "./chunk-G27TUULG.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/serve.ts
|
|
7
|
+
import WebSocket from "ws";
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
var CALL_EXEC_TIMEOUT_MS = 28e3;
|
|
10
|
+
var RECONNECT_INITIAL_MS = 1e3;
|
|
11
|
+
var RECONNECT_MAX_MS = 3e4;
|
|
12
|
+
async function runServe(opts) {
|
|
13
|
+
const meta = requireMeta();
|
|
14
|
+
const client = getClient();
|
|
15
|
+
const apiUrl = new URL(meta.apiUrl);
|
|
16
|
+
const wsUrl = new URL(meta.apiUrl);
|
|
17
|
+
wsUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
18
|
+
wsUrl.pathname = "/v1/agent-link";
|
|
19
|
+
let backoff = RECONNECT_INITIAL_MS;
|
|
20
|
+
let stopped = false;
|
|
21
|
+
let currentSocket = null;
|
|
22
|
+
process.on("SIGINT", () => {
|
|
23
|
+
stopped = true;
|
|
24
|
+
console.log("\nDisconnecting...");
|
|
25
|
+
currentSocket?.close(1e3, "shutdown");
|
|
26
|
+
setTimeout(() => process.exit(0), 200);
|
|
27
|
+
});
|
|
28
|
+
console.log(`Worker for listing ${opts.listingId}`);
|
|
29
|
+
console.log(`Handler: ${opts.exec}`);
|
|
30
|
+
console.log(`Connecting to ${wsUrl}...
|
|
31
|
+
`);
|
|
32
|
+
while (!stopped) {
|
|
33
|
+
try {
|
|
34
|
+
await connectAndServe({
|
|
35
|
+
wsUrl: wsUrl.toString(),
|
|
36
|
+
agentId: meta.activeAgentId,
|
|
37
|
+
listingId: opts.listingId,
|
|
38
|
+
exec: opts.exec,
|
|
39
|
+
signJwt: () => client.signJwt({ agentId: meta.activeAgentId, audience: meta.apiUrl }),
|
|
40
|
+
setCurrentSocket: (s) => {
|
|
41
|
+
currentSocket = s;
|
|
42
|
+
},
|
|
43
|
+
onConnected: () => {
|
|
44
|
+
backoff = RECONNECT_INITIAL_MS;
|
|
45
|
+
}
|
|
46
|
+
// reset on successful hello
|
|
47
|
+
});
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error(`[serve] error: ${err?.message || err}`);
|
|
50
|
+
}
|
|
51
|
+
if (stopped) break;
|
|
52
|
+
console.error(`[serve] reconnecting in ${backoff}ms...`);
|
|
53
|
+
await sleep(backoff);
|
|
54
|
+
backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function connectAndServe(args) {
|
|
58
|
+
return new Promise(async (resolve, reject) => {
|
|
59
|
+
let token;
|
|
60
|
+
try {
|
|
61
|
+
({ token } = await args.signJwt());
|
|
62
|
+
} catch (e) {
|
|
63
|
+
reject(new Error(`Failed to sign JWT: ${e?.message || e}`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const ws = new WebSocket(args.wsUrl);
|
|
67
|
+
args.setCurrentSocket(ws);
|
|
68
|
+
let helloAcked = false;
|
|
69
|
+
ws.on("open", () => {
|
|
70
|
+
ws.send(JSON.stringify({ type: "hello", agentId: args.agentId, jwt: token }));
|
|
71
|
+
ws.send(JSON.stringify({ type: "serve", listingIds: [args.listingId] }));
|
|
72
|
+
});
|
|
73
|
+
ws.on("message", async (data) => {
|
|
74
|
+
let frame;
|
|
75
|
+
try {
|
|
76
|
+
frame = JSON.parse(data.toString());
|
|
77
|
+
} catch {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (frame.type === "welcome") {
|
|
81
|
+
if (!helloAcked) {
|
|
82
|
+
helloAcked = true;
|
|
83
|
+
args.onConnected();
|
|
84
|
+
console.log(`[serve] connected. Serving ${frame.servingListings.length} listing(s).`);
|
|
85
|
+
if (!frame.servingListings.includes(args.listingId)) {
|
|
86
|
+
console.error(`[serve] WARNING: server did not accept listing ${args.listingId}. Check that you own it and it is type=offer/delivery_kind=api.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (frame.type === "rejected") {
|
|
92
|
+
console.error(`[serve] server rejected connection: ${frame.reason}`);
|
|
93
|
+
ws.close();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (frame.type === "call") {
|
|
97
|
+
const { reqId, listingId, input, callerId } = frame;
|
|
98
|
+
console.log(`[call ${reqId.slice(0, 8)}] \u2190 ${callerId}`);
|
|
99
|
+
try {
|
|
100
|
+
const output = await runHandler(args.exec, { reqId, listingId, input, callerId });
|
|
101
|
+
ws.send(JSON.stringify({ type: "result", reqId, output }));
|
|
102
|
+
console.log(`[call ${reqId.slice(0, 8)}] \u2192 ok`);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
const code = e?.code === "timeout" ? "timeout" : "handler_error";
|
|
105
|
+
ws.send(JSON.stringify({ type: "error", reqId, code, message: e?.message || "Handler failed" }));
|
|
106
|
+
console.error(`[call ${reqId.slice(0, 8)}] \u2192 error: ${e?.message}`);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
ws.on("close", (code, reason) => {
|
|
112
|
+
args.setCurrentSocket(null);
|
|
113
|
+
if (helloAcked) {
|
|
114
|
+
console.error(`[serve] disconnected (${code} ${reason || ""})`);
|
|
115
|
+
} else {
|
|
116
|
+
console.error(`[serve] connection closed before hello (${code} ${reason || ""})`);
|
|
117
|
+
}
|
|
118
|
+
resolve();
|
|
119
|
+
});
|
|
120
|
+
ws.on("error", (err) => {
|
|
121
|
+
console.error(`[serve] socket error: ${err.message}`);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function runHandler(exec, payload) {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const child = spawn("/bin/sh", ["-c", exec], { stdio: ["pipe", "pipe", "inherit"] });
|
|
128
|
+
let stdout = "";
|
|
129
|
+
const timer = setTimeout(() => {
|
|
130
|
+
child.kill("SIGKILL");
|
|
131
|
+
reject(Object.assign(new Error("Handler timed out"), { code: "timeout" }));
|
|
132
|
+
}, CALL_EXEC_TIMEOUT_MS);
|
|
133
|
+
child.stdout.on("data", (chunk) => {
|
|
134
|
+
stdout += chunk.toString();
|
|
135
|
+
});
|
|
136
|
+
child.on("error", (err) => {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
reject(err);
|
|
139
|
+
});
|
|
140
|
+
child.on("close", (code) => {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
if (code !== 0) {
|
|
143
|
+
reject(new Error(`Handler exited with code ${code}`));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const trimmed = stdout.trim();
|
|
147
|
+
if (!trimmed) {
|
|
148
|
+
resolve({});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
resolve(JSON.parse(trimmed));
|
|
153
|
+
} catch {
|
|
154
|
+
resolve({ text: trimmed });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
child.stdin.write(JSON.stringify(payload));
|
|
158
|
+
child.stdin.end();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function sleep(ms) {
|
|
162
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
runServe
|
|
166
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-market",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "AI Market — Agent Trading Platform CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
"@auth/agent": "^0.4.5",
|
|
19
19
|
"commander": "^13.0.0",
|
|
20
20
|
"jose": "^6.2.2",
|
|
21
|
-
"open": "^11.0.0"
|
|
21
|
+
"open": "^11.0.0",
|
|
22
|
+
"ws": "^8.20.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@types/node": "^22.19.15",
|
|
26
|
+
"@types/ws": "^8.18.1",
|
|
25
27
|
"tsup": "^8.0.0",
|
|
26
28
|
"typescript": "^5.7.0"
|
|
27
29
|
},
|