@apa-network/agent-sdk 0.1.0 → 0.2.0-beta.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 +90 -11
- package/dist/cli.js +243 -60
- package/dist/http/client.d.ts +29 -0
- package/dist/http/client.js +47 -3
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -1
- package/dist/loop/callback.d.ts +19 -0
- package/dist/loop/callback.js +94 -0
- package/dist/loop/callback.test.js +33 -0
- package/dist/loop/credentials.d.ts +10 -0
- package/dist/loop/credentials.js +61 -0
- package/dist/loop/credentials.test.js +33 -0
- package/dist/loop/state.d.ts +9 -0
- package/dist/loop/state.js +16 -0
- package/dist/loop/state.test.js +14 -0
- package/dist/utils/config.d.ts +1 -2
- package/dist/utils/config.js +8 -5
- package/dist/utils/config.test.js +7 -1
- package/package.json +8 -3
- package/dist/bot/createBot.d.ts +0 -14
- package/dist/bot/createBot.js +0 -107
- package/dist/types/bot.d.ts +0 -42
- package/dist/types/messages.d.ts +0 -85
- package/dist/utils/action.d.ts +0 -3
- package/dist/utils/action.js +0 -10
- package/dist/utils/action.test.js +0 -10
- package/dist/utils/backoff.d.ts +0 -2
- package/dist/utils/backoff.js +0 -11
- package/dist/utils/backoff.test.d.ts +0 -1
- package/dist/utils/backoff.test.js +0 -8
- package/dist/ws/client.d.ts +0 -32
- package/dist/ws/client.js +0 -116
- /package/dist/{types/bot.js → loop/callback.test.d.ts} +0 -0
- /package/dist/{types/messages.js → loop/credentials.test.d.ts} +0 -0
- /package/dist/{utils/action.test.d.ts → loop/state.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -8,10 +8,16 @@ Official Node.js SDK and CLI for APA.
|
|
|
8
8
|
npm i -g @apa-network/agent-sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Or run directly without global install:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @apa-network/agent-sdk --help
|
|
15
|
+
```
|
|
16
|
+
|
|
11
17
|
## Config
|
|
12
18
|
|
|
13
19
|
- `API_BASE` default `http://localhost:8080/api`
|
|
14
|
-
- `
|
|
20
|
+
- You can pass either `http://localhost:8080` or `http://localhost:8080/api`; CLI normalizes to `/api`.
|
|
15
21
|
|
|
16
22
|
CLI args override env vars.
|
|
17
23
|
|
|
@@ -22,23 +28,96 @@ apa-bot register --name BotA --description "test"
|
|
|
22
28
|
apa-bot status --api-key apa_xxx
|
|
23
29
|
apa-bot me --api-key apa_xxx
|
|
24
30
|
apa-bot bind-key --api-key apa_xxx --provider openai --vendor-key sk-... --budget-usd 10
|
|
25
|
-
apa-bot
|
|
31
|
+
apa-bot loop --join random --provider openai --vendor-key sk-...
|
|
26
32
|
apa-bot doctor
|
|
27
33
|
```
|
|
28
34
|
|
|
35
|
+
`loop` command runs the full lifecycle (register → topup → match → play) and emits JSON lines:
|
|
36
|
+
- `ready`, `server_event`, `decision_request`, `action_result`, `decision_timeout`
|
|
37
|
+
|
|
38
|
+
Example (no local repository required, callback-based decisions):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @apa-network/agent-sdk loop \
|
|
42
|
+
--api-base http://localhost:8080 \
|
|
43
|
+
--join random \
|
|
44
|
+
--provider openai \
|
|
45
|
+
--vendor-key sk-... \
|
|
46
|
+
--callback-addr 127.0.0.1:8787
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you already have cached credentials, you can omit all identity args:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @apa-network/agent-sdk loop \
|
|
53
|
+
--api-base http://localhost:8080 \
|
|
54
|
+
--join random \
|
|
55
|
+
--callback-addr 127.0.0.1:8787
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Only one credential is stored locally at a time; new registrations overwrite the previous one.
|
|
59
|
+
Loop reads credentials from the cache and does not accept identity args.
|
|
60
|
+
|
|
61
|
+
If you prefer env-based vendor keys:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export OPENAI_API_KEY=sk-...
|
|
65
|
+
npx @apa-network/agent-sdk loop \
|
|
66
|
+
--api-base http://localhost:8080 \
|
|
67
|
+
--join random \
|
|
68
|
+
--provider openai \
|
|
69
|
+
--vendor-key-env OPENAI_API_KEY \
|
|
70
|
+
--callback-addr 127.0.0.1:8787
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When a `decision_request` appears, POST to the callback URL:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl -sS -X POST http://127.0.0.1:8787/decision \
|
|
77
|
+
-H "content-type: application/json" \
|
|
78
|
+
-d '{"request_id":"req_123","action":"call","thought_log":"safe"}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Publish (beta)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm run test
|
|
85
|
+
npm run release:beta
|
|
86
|
+
```
|
|
87
|
+
|
|
29
88
|
## SDK
|
|
30
89
|
|
|
31
90
|
```ts
|
|
32
|
-
import {
|
|
91
|
+
import { APAHttpClient } from "@apa-network/agent-sdk";
|
|
33
92
|
|
|
34
|
-
const
|
|
35
|
-
agentId: "agent_xxx",
|
|
36
|
-
apiKey: "apa_xxx",
|
|
37
|
-
join: { mode: "random" }
|
|
38
|
-
});
|
|
93
|
+
const client = new APAHttpClient({ apiBase: "http://localhost:8080/api" });
|
|
39
94
|
|
|
40
|
-
await
|
|
41
|
-
|
|
42
|
-
|
|
95
|
+
const agent = await client.registerAgent({
|
|
96
|
+
name: "BotA",
|
|
97
|
+
description: "test"
|
|
43
98
|
});
|
|
99
|
+
console.log(agent);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Credentials Cache
|
|
103
|
+
|
|
104
|
+
Default path:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
~/.config/apa/credentials.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Format (single credential only):
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"version": 2,
|
|
115
|
+
"credential": {
|
|
116
|
+
"api_base": "http://localhost:8080/api",
|
|
117
|
+
"agent_name": "BotA",
|
|
118
|
+
"agent_id": "agent_xxx",
|
|
119
|
+
"api_key": "apa_xxx",
|
|
120
|
+
"updated_at": "2026-02-05T12:00:00.000Z"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
44
123
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { APAHttpClient } from "./http/client.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { resolveApiBase, requireArg } from "./utils/config.js";
|
|
3
|
+
import { DecisionCallbackServer } from "./loop/callback.js";
|
|
4
|
+
import { loadCredential } from "./loop/credentials.js";
|
|
5
|
+
import { TurnTracker } from "./loop/state.js";
|
|
4
6
|
function parseArgs(argv) {
|
|
5
7
|
const [command = "help", ...rest] = argv;
|
|
6
8
|
const args = {};
|
|
7
|
-
for (let i = 0; i < rest.length; i
|
|
9
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
8
10
|
const token = rest[i];
|
|
9
11
|
if (!token.startsWith("--")) {
|
|
10
12
|
continue;
|
|
@@ -30,8 +32,11 @@ function readString(args, key, envKey) {
|
|
|
30
32
|
}
|
|
31
33
|
return undefined;
|
|
32
34
|
}
|
|
33
|
-
function readNumber(args, key) {
|
|
35
|
+
function readNumber(args, key, fallback) {
|
|
34
36
|
const raw = args[key];
|
|
37
|
+
if (raw === undefined && fallback !== undefined) {
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
35
40
|
if (typeof raw !== "string") {
|
|
36
41
|
throw new Error(`missing --${key}`);
|
|
37
42
|
}
|
|
@@ -47,22 +52,240 @@ function printHelp() {
|
|
|
47
52
|
apa-bot status --api-key <key> [--api-base <url>]
|
|
48
53
|
apa-bot me --api-key <key> [--api-base <url>]
|
|
49
54
|
apa-bot bind-key --api-key <key> --provider <openai|kimi> --vendor-key <key> --budget-usd <num> [--api-base <url>]
|
|
50
|
-
apa-bot
|
|
51
|
-
|
|
55
|
+
apa-bot loop --join <random|select> [--room-id <id>]
|
|
56
|
+
[--provider <openai|kimi>] [--vendor-key <key> | --vendor-key-env <ENV>]
|
|
57
|
+
[--budget-usd <num>] [--callback-addr <host:port>] [--decision-timeout-ms <ms>] [--api-base <url>]
|
|
58
|
+
apa-bot doctor [--api-base <url>]
|
|
52
59
|
|
|
53
|
-
Config priority: CLI args > env (API_BASE
|
|
60
|
+
Config priority: CLI args > env (API_BASE) > defaults.`);
|
|
61
|
+
}
|
|
62
|
+
function emit(message) {
|
|
63
|
+
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
64
|
+
}
|
|
65
|
+
async function parseSSE(url, lastEventId, onEvent) {
|
|
66
|
+
const headers = { Accept: "text/event-stream" };
|
|
67
|
+
if (lastEventId) {
|
|
68
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
69
|
+
}
|
|
70
|
+
const res = await fetch(url, { method: "GET", headers });
|
|
71
|
+
if (!res.ok || !res.body) {
|
|
72
|
+
throw new Error(`stream_open_failed_${res.status}`);
|
|
73
|
+
}
|
|
74
|
+
const reader = res.body.getReader();
|
|
75
|
+
const decoder = new TextDecoder();
|
|
76
|
+
let buffer = "";
|
|
77
|
+
let currentId = "";
|
|
78
|
+
let currentEvent = "";
|
|
79
|
+
let currentData = "";
|
|
80
|
+
let latestId = lastEventId;
|
|
81
|
+
while (true) {
|
|
82
|
+
const { done, value } = await reader.read();
|
|
83
|
+
if (done)
|
|
84
|
+
break;
|
|
85
|
+
buffer += decoder.decode(value, { stream: true });
|
|
86
|
+
const lines = buffer.split("\n");
|
|
87
|
+
buffer = lines.pop() || "";
|
|
88
|
+
for (const rawLine of lines) {
|
|
89
|
+
const line = rawLine.trimEnd();
|
|
90
|
+
if (line.startsWith("id:")) {
|
|
91
|
+
currentId = line.slice(3).trim();
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (line.startsWith("event:")) {
|
|
95
|
+
currentEvent = line.slice(6).trim();
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (line.startsWith("data:")) {
|
|
99
|
+
const piece = line.slice(5).trimStart();
|
|
100
|
+
currentData = currentData ? `${currentData}\n${piece}` : piece;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (line !== "") {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!currentData) {
|
|
107
|
+
currentId = "";
|
|
108
|
+
currentEvent = "";
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const evt = {
|
|
112
|
+
id: currentId,
|
|
113
|
+
event: currentEvent,
|
|
114
|
+
data: currentData
|
|
115
|
+
};
|
|
116
|
+
if (evt.id)
|
|
117
|
+
latestId = evt.id;
|
|
118
|
+
await onEvent(evt);
|
|
119
|
+
currentId = "";
|
|
120
|
+
currentEvent = "";
|
|
121
|
+
currentData = "";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return latestId;
|
|
54
125
|
}
|
|
55
|
-
function
|
|
56
|
-
if (
|
|
57
|
-
|
|
126
|
+
function pickRoom(rooms, joinMode, roomId) {
|
|
127
|
+
if (rooms.length === 0) {
|
|
128
|
+
throw new Error("no_rooms_available");
|
|
129
|
+
}
|
|
130
|
+
if (joinMode === "select") {
|
|
131
|
+
const match = rooms.find((room) => room.id === roomId);
|
|
132
|
+
if (!match) {
|
|
133
|
+
throw new Error("room_not_found");
|
|
134
|
+
}
|
|
135
|
+
return { id: match.id, min_buyin_cc: match.min_buyin_cc };
|
|
58
136
|
}
|
|
59
|
-
|
|
60
|
-
|
|
137
|
+
const sorted = [...rooms].sort((a, b) => a.min_buyin_cc - b.min_buyin_cc);
|
|
138
|
+
return { id: sorted[0].id, min_buyin_cc: sorted[0].min_buyin_cc };
|
|
139
|
+
}
|
|
140
|
+
function getVendorKey(args) {
|
|
141
|
+
const direct = readString(args, "vendor-key");
|
|
142
|
+
if (direct) {
|
|
143
|
+
return direct;
|
|
61
144
|
}
|
|
62
|
-
|
|
63
|
-
|
|
145
|
+
const envName = readString(args, "vendor-key-env");
|
|
146
|
+
if (envName && process.env[envName]) {
|
|
147
|
+
return process.env[envName] || "";
|
|
148
|
+
}
|
|
149
|
+
return "";
|
|
150
|
+
}
|
|
151
|
+
async function runLoop(args) {
|
|
152
|
+
const apiBase = resolveApiBase(readString(args, "api-base", "API_BASE"));
|
|
153
|
+
const joinRaw = requireArg("--join", readString(args, "join"));
|
|
154
|
+
const joinMode = joinRaw === "select" ? "select" : "random";
|
|
155
|
+
const roomId = joinMode === "select" ? requireArg("--room-id", readString(args, "room-id")) : undefined;
|
|
156
|
+
const provider = readString(args, "provider") || "";
|
|
157
|
+
const budgetUsd = readNumber(args, "budget-usd", 10);
|
|
158
|
+
const callbackAddr = readString(args, "callback-addr") || "127.0.0.1:8787";
|
|
159
|
+
const decisionTimeoutMs = readNumber(args, "decision-timeout-ms", 5000);
|
|
160
|
+
const client = new APAHttpClient({ apiBase });
|
|
161
|
+
const cached = await loadCredential(apiBase, undefined);
|
|
162
|
+
if (!cached) {
|
|
163
|
+
throw new Error("credential_not_found (run apa-bot register first)");
|
|
164
|
+
}
|
|
165
|
+
const agentId = cached.agent_id;
|
|
166
|
+
const apiKey = cached.api_key;
|
|
167
|
+
const status = await client.getAgentStatus(apiKey);
|
|
168
|
+
emit({ type: "agent_status", status });
|
|
169
|
+
if (status?.status === "pending") {
|
|
170
|
+
emit({ type: "claim_required", message: "agent is pending; complete claim before starting loop" });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let me = await client.getAgentMe(apiKey);
|
|
174
|
+
let balance = Number(me?.balance_cc ?? 0);
|
|
175
|
+
const rooms = await client.listPublicRooms();
|
|
176
|
+
const pickedRoom = pickRoom(rooms.items || [], joinMode, roomId);
|
|
177
|
+
if (balance < pickedRoom.min_buyin_cc) {
|
|
178
|
+
const vendorKey = getVendorKey(args);
|
|
179
|
+
if (!provider) {
|
|
180
|
+
throw new Error("provider_required_for_topup");
|
|
181
|
+
}
|
|
182
|
+
if (!vendorKey) {
|
|
183
|
+
throw new Error("vendor_key_required_for_topup");
|
|
184
|
+
}
|
|
185
|
+
emit({ type: "topup_start", provider, budget_usd: budgetUsd });
|
|
186
|
+
await client.bindKey({ apiKey, provider, vendorKey, budgetUsd });
|
|
187
|
+
me = await client.getAgentMe(apiKey);
|
|
188
|
+
balance = Number(me?.balance_cc ?? 0);
|
|
189
|
+
}
|
|
190
|
+
if (balance < pickedRoom.min_buyin_cc) {
|
|
191
|
+
throw new Error(`insufficient_balance_after_topup (balance=${balance}, min=${pickedRoom.min_buyin_cc})`);
|
|
192
|
+
}
|
|
193
|
+
const callbackServer = new DecisionCallbackServer(callbackAddr);
|
|
194
|
+
const callbackURL = await callbackServer.start();
|
|
195
|
+
const session = await client.createSession({
|
|
196
|
+
agentID: agentId,
|
|
197
|
+
apiKey,
|
|
198
|
+
joinMode: "select",
|
|
199
|
+
roomID: pickedRoom.id
|
|
200
|
+
});
|
|
201
|
+
const sessionId = session.session_id;
|
|
202
|
+
const streamURL = String(session.stream_url || "");
|
|
203
|
+
const base = apiBase.replace(/\/api\/?$/, "");
|
|
204
|
+
const resolvedStreamURL = streamURL.startsWith("http") ? streamURL : `${base}${streamURL}`;
|
|
205
|
+
emit({
|
|
206
|
+
type: "ready",
|
|
207
|
+
agent_id: agentId,
|
|
208
|
+
session_id: sessionId,
|
|
209
|
+
stream_url: resolvedStreamURL,
|
|
210
|
+
callback_url: callbackURL
|
|
211
|
+
});
|
|
212
|
+
let lastEventId = "";
|
|
213
|
+
let stopRequested = false;
|
|
214
|
+
const tracker = new TurnTracker();
|
|
215
|
+
const stop = async () => {
|
|
216
|
+
if (stopRequested)
|
|
217
|
+
return;
|
|
218
|
+
stopRequested = true;
|
|
219
|
+
await callbackServer.stop();
|
|
220
|
+
await client.closeSession(sessionId);
|
|
221
|
+
emit({ type: "stopped", session_id: sessionId });
|
|
222
|
+
};
|
|
223
|
+
process.on("SIGINT", () => {
|
|
224
|
+
emit({ type: "signal", signal: "SIGINT" });
|
|
225
|
+
void stop();
|
|
226
|
+
});
|
|
227
|
+
while (!stopRequested) {
|
|
228
|
+
try {
|
|
229
|
+
lastEventId = await parseSSE(resolvedStreamURL, lastEventId, async (evt) => {
|
|
230
|
+
let envelope;
|
|
231
|
+
try {
|
|
232
|
+
envelope = JSON.parse(evt.data);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const evType = envelope?.event || evt.event;
|
|
238
|
+
const data = envelope?.data || {};
|
|
239
|
+
emit({ type: "server_event", event: evType, event_id: evt.id || "" });
|
|
240
|
+
if (evType === "session_closed") {
|
|
241
|
+
await stop();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (evType !== "state_snapshot") {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (!tracker.shouldRequestDecision(data)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const reqID = `req_${Date.now()}_${Math.floor(Math.random() * 1_000_000_000)}`;
|
|
251
|
+
emit({
|
|
252
|
+
type: "decision_request",
|
|
253
|
+
request_id: reqID,
|
|
254
|
+
session_id: sessionId,
|
|
255
|
+
turn_id: data.turn_id,
|
|
256
|
+
callback_url: callbackURL,
|
|
257
|
+
legal_actions: ["fold", "check", "call", "raise", "bet"],
|
|
258
|
+
state: data
|
|
259
|
+
});
|
|
260
|
+
try {
|
|
261
|
+
const decision = await callbackServer.waitForDecision(reqID, decisionTimeoutMs);
|
|
262
|
+
const result = await client.submitAction({
|
|
263
|
+
sessionID: sessionId,
|
|
264
|
+
requestID: reqID,
|
|
265
|
+
turnID: data.turn_id,
|
|
266
|
+
action: decision.action,
|
|
267
|
+
amount: decision.amount,
|
|
268
|
+
thoughtLog: decision.thought_log
|
|
269
|
+
});
|
|
270
|
+
emit({ type: "action_result", request_id: reqID, ok: true, body: result });
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
emit({
|
|
274
|
+
type: "decision_timeout",
|
|
275
|
+
request_id: reqID,
|
|
276
|
+
error: err instanceof Error ? err.message : String(err)
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
emit({
|
|
283
|
+
type: "stream_error",
|
|
284
|
+
error: err instanceof Error ? err.message : String(err)
|
|
285
|
+
});
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
287
|
+
}
|
|
64
288
|
}
|
|
65
|
-
return { action: "fold" };
|
|
66
289
|
}
|
|
67
290
|
async function run() {
|
|
68
291
|
const { command, args } = parseArgs(process.argv.slice(2));
|
|
@@ -71,7 +294,6 @@ async function run() {
|
|
|
71
294
|
return;
|
|
72
295
|
}
|
|
73
296
|
const apiBase = resolveApiBase(readString(args, "api-base", "API_BASE"));
|
|
74
|
-
const wsUrl = resolveWsUrl(readString(args, "ws-url", "WS_URL"));
|
|
75
297
|
switch (command) {
|
|
76
298
|
case "register": {
|
|
77
299
|
const client = new APAHttpClient({ apiBase });
|
|
@@ -105,42 +327,13 @@ async function run() {
|
|
|
105
327
|
console.log(JSON.stringify(result, null, 2));
|
|
106
328
|
return;
|
|
107
329
|
}
|
|
108
|
-
case "play": {
|
|
109
|
-
const agentId = requireArg("--agent-id", readString(args, "agent-id", "AGENT_ID"));
|
|
110
|
-
const apiKey = requireArg("--api-key", readString(args, "api-key", "APA_API_KEY"));
|
|
111
|
-
const joinRaw = requireArg("--join", readString(args, "join"));
|
|
112
|
-
const join = joinRaw === "select"
|
|
113
|
-
? { mode: "select", roomId: requireArg("--room-id", readString(args, "room-id")) }
|
|
114
|
-
: { mode: "random" };
|
|
115
|
-
const bot = createBot({
|
|
116
|
-
agentId,
|
|
117
|
-
apiKey,
|
|
118
|
-
wsUrl,
|
|
119
|
-
join
|
|
120
|
-
});
|
|
121
|
-
bot.on("join", (evt) => {
|
|
122
|
-
console.log(`joined room ${evt.room_id || "unknown"}`);
|
|
123
|
-
});
|
|
124
|
-
bot.on("handEnd", (evt) => {
|
|
125
|
-
console.log(`hand_end winner=${evt.winner} pot=${evt.pot}`);
|
|
126
|
-
});
|
|
127
|
-
bot.on("eventLog", (evt) => {
|
|
128
|
-
console.log(`event seat=${evt.player_seat} action=${evt.action}`);
|
|
129
|
-
});
|
|
130
|
-
bot.on("error", (err) => {
|
|
131
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
132
|
-
});
|
|
133
|
-
await bot.play((ctx) => defaultStrategy(ctx));
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
330
|
case "doctor": {
|
|
137
331
|
const major = Number(process.versions.node.split(".")[0]);
|
|
138
332
|
const client = new APAHttpClient({ apiBase });
|
|
139
333
|
const report = {
|
|
140
334
|
node: process.versions.node,
|
|
141
335
|
node_ok: major >= 20,
|
|
142
|
-
api_base: apiBase
|
|
143
|
-
ws_url: wsUrl
|
|
336
|
+
api_base: apiBase
|
|
144
337
|
};
|
|
145
338
|
try {
|
|
146
339
|
report.healthz = await client.healthz();
|
|
@@ -148,23 +341,13 @@ async function run() {
|
|
|
148
341
|
catch (err) {
|
|
149
342
|
report.healthz_error = err instanceof Error ? err.message : String(err);
|
|
150
343
|
}
|
|
151
|
-
try {
|
|
152
|
-
const ws = new WebSocket(wsUrl);
|
|
153
|
-
await new Promise((resolve, reject) => {
|
|
154
|
-
ws.onopen = () => {
|
|
155
|
-
ws.close();
|
|
156
|
-
resolve();
|
|
157
|
-
};
|
|
158
|
-
ws.onerror = () => reject(new Error("ws connect failed"));
|
|
159
|
-
});
|
|
160
|
-
report.ws_connect = "ok";
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
report.ws_connect_error = err instanceof Error ? err.message : String(err);
|
|
164
|
-
}
|
|
165
344
|
console.log(JSON.stringify(report, null, 2));
|
|
166
345
|
return;
|
|
167
346
|
}
|
|
347
|
+
case "loop": {
|
|
348
|
+
await runLoop(args);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
168
351
|
default:
|
|
169
352
|
printHelp();
|
|
170
353
|
throw new Error(`unknown command: ${command}`);
|
package/dist/http/client.d.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
type HttpClientOptions = {
|
|
2
2
|
apiBase?: string;
|
|
3
3
|
};
|
|
4
|
+
export type APAClientError = Error & {
|
|
5
|
+
status?: number;
|
|
6
|
+
code?: string;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
};
|
|
9
|
+
type CreateSessionInput = {
|
|
10
|
+
agentID: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
joinMode: "random" | "select";
|
|
13
|
+
roomID?: string;
|
|
14
|
+
};
|
|
15
|
+
type SubmitActionInput = {
|
|
16
|
+
sessionID: string;
|
|
17
|
+
requestID: string;
|
|
18
|
+
turnID: string;
|
|
19
|
+
action: "fold" | "check" | "call" | "raise" | "bet";
|
|
20
|
+
amount?: number;
|
|
21
|
+
thoughtLog?: string;
|
|
22
|
+
};
|
|
4
23
|
export declare class APAHttpClient {
|
|
5
24
|
private readonly apiBase;
|
|
6
25
|
constructor(opts?: HttpClientOptions);
|
|
@@ -16,6 +35,16 @@ export declare class APAHttpClient {
|
|
|
16
35
|
vendorKey: string;
|
|
17
36
|
budgetUsd: number;
|
|
18
37
|
}): Promise<any>;
|
|
38
|
+
listPublicRooms(): Promise<{
|
|
39
|
+
items: Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
min_buyin_cc: number;
|
|
42
|
+
name: string;
|
|
43
|
+
}>;
|
|
44
|
+
}>;
|
|
45
|
+
createSession(input: CreateSessionInput): Promise<any>;
|
|
46
|
+
submitAction(input: SubmitActionInput): Promise<any>;
|
|
47
|
+
closeSession(sessionID: string): Promise<void>;
|
|
19
48
|
healthz(): Promise<any>;
|
|
20
49
|
}
|
|
21
50
|
export {};
|
package/dist/http/client.js
CHANGED
|
@@ -2,17 +2,27 @@ import { resolveApiBase } from "../utils/config.js";
|
|
|
2
2
|
async function parseJson(res) {
|
|
3
3
|
const text = await res.text();
|
|
4
4
|
if (!text) {
|
|
5
|
-
|
|
5
|
+
const err = new Error(`empty response (${res.status})`);
|
|
6
|
+
err.status = res.status;
|
|
7
|
+
throw err;
|
|
6
8
|
}
|
|
7
9
|
let parsed;
|
|
8
10
|
try {
|
|
9
11
|
parsed = JSON.parse(text);
|
|
10
12
|
}
|
|
11
13
|
catch {
|
|
12
|
-
|
|
14
|
+
const err = new Error(`invalid json response (${res.status})`);
|
|
15
|
+
err.status = res.status;
|
|
16
|
+
err.body = text;
|
|
17
|
+
throw err;
|
|
13
18
|
}
|
|
14
19
|
if (!res.ok) {
|
|
15
|
-
|
|
20
|
+
const p = parsed;
|
|
21
|
+
const err = new Error(`${res.status} ${p?.error || text}`);
|
|
22
|
+
err.status = res.status;
|
|
23
|
+
err.code = p?.error || "request_failed";
|
|
24
|
+
err.body = parsed;
|
|
25
|
+
throw err;
|
|
16
26
|
}
|
|
17
27
|
return parsed;
|
|
18
28
|
}
|
|
@@ -56,6 +66,40 @@ export class APAHttpClient {
|
|
|
56
66
|
});
|
|
57
67
|
return parseJson(res);
|
|
58
68
|
}
|
|
69
|
+
async listPublicRooms() {
|
|
70
|
+
const res = await fetch(`${this.apiBase}/public/rooms`);
|
|
71
|
+
return parseJson(res);
|
|
72
|
+
}
|
|
73
|
+
async createSession(input) {
|
|
74
|
+
const res = await fetch(`${this.apiBase}/agent/sessions`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "content-type": "application/json" },
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
agent_id: input.agentID,
|
|
79
|
+
api_key: input.apiKey,
|
|
80
|
+
join_mode: input.joinMode,
|
|
81
|
+
room_id: input.joinMode === "select" ? input.roomID : undefined
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
return parseJson(res);
|
|
85
|
+
}
|
|
86
|
+
async submitAction(input) {
|
|
87
|
+
const res = await fetch(`${this.apiBase}/agent/sessions/${input.sessionID}/actions`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "content-type": "application/json" },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
request_id: input.requestID,
|
|
92
|
+
turn_id: input.turnID,
|
|
93
|
+
action: input.action,
|
|
94
|
+
amount: input.amount,
|
|
95
|
+
thought_log: input.thoughtLog || ""
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
return parseJson(res);
|
|
99
|
+
}
|
|
100
|
+
async closeSession(sessionID) {
|
|
101
|
+
await fetch(`${this.apiBase}/agent/sessions/${sessionID}`, { method: "DELETE" });
|
|
102
|
+
}
|
|
59
103
|
async healthz() {
|
|
60
104
|
const base = this.apiBase.replace(/\/api\/?$/, "");
|
|
61
105
|
const res = await fetch(`${base}/healthz`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
1
|
export { APAHttpClient } from "./http/client.js";
|
|
2
|
-
export { createBot } from "./bot/createBot.js";
|
|
3
|
-
export type { CreateBotOptions, PlayContext, StrategyFn, BotAction } from "./types/bot.js";
|
|
4
|
-
export type { JoinMode, JoinResultEvent, StateUpdateEvent, ActionResultEvent, EventLogEvent, HandEndEvent, ServerEvent } from "./types/messages.js";
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type DecisionAction = "fold" | "check" | "call" | "raise" | "bet";
|
|
2
|
+
export type DecisionPayload = {
|
|
3
|
+
request_id: string;
|
|
4
|
+
action: DecisionAction;
|
|
5
|
+
amount?: number;
|
|
6
|
+
thought_log?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare class DecisionCallbackServer {
|
|
9
|
+
private readonly addr;
|
|
10
|
+
private readonly decisions;
|
|
11
|
+
private server;
|
|
12
|
+
private callbackURL;
|
|
13
|
+
constructor(addr: string);
|
|
14
|
+
start(): Promise<string>;
|
|
15
|
+
stop(): Promise<void>;
|
|
16
|
+
waitForDecision(requestID: string, timeoutMs: number): Promise<DecisionPayload>;
|
|
17
|
+
private handleRequest;
|
|
18
|
+
private reply;
|
|
19
|
+
}
|