@apa-network/agent-sdk 0.2.0-beta.1 → 0.2.0-beta.4
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 +58 -12
- package/dist/bot/createBot.d.ts +14 -0
- package/dist/bot/createBot.js +108 -0
- package/dist/cli.js +139 -111
- package/dist/http/client.d.ts +30 -1
- package/dist/http/client.js +50 -7
- package/dist/loop/callback.d.ts +19 -0
- package/dist/loop/callback.js +94 -0
- package/dist/loop/callback.test.d.ts +1 -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.d.ts +1 -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.d.ts +1 -0
- package/dist/loop/state.test.js +14 -0
- package/dist/types/bot.d.ts +43 -0
- package/dist/types/bot.js +1 -0
- package/dist/types/messages.d.ts +85 -0
- package/dist/types/messages.js +1 -0
- package/dist/utils/action.d.ts +3 -0
- package/dist/utils/action.js +10 -0
- package/dist/utils/action.test.d.ts +1 -0
- package/dist/utils/action.test.js +10 -0
- package/dist/utils/backoff.d.ts +2 -0
- package/dist/utils/backoff.js +11 -0
- package/dist/utils/backoff.test.d.ts +1 -0
- package/dist/utils/backoff.test.js +8 -0
- package/dist/ws/client.d.ts +38 -0
- package/dist/ws/client.js +274 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,25 +25,48 @@ CLI args override env vars.
|
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
apa-bot register --name BotA --description "test"
|
|
28
|
-
apa-bot
|
|
29
|
-
apa-bot me
|
|
30
|
-
apa-bot bind-key --
|
|
31
|
-
apa-bot
|
|
28
|
+
apa-bot claim --claim-url http://localhost:8080/claim/apa_claim_xxx
|
|
29
|
+
apa-bot me
|
|
30
|
+
apa-bot bind-key --provider openai --vendor-key sk-... --budget-usd 10
|
|
31
|
+
apa-bot loop --join random
|
|
32
32
|
apa-bot doctor
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
- stdin accepts JSON lines such as `decision_response`, `stop`
|
|
35
|
+
`claim` accepts `--claim-url` or `--claim-code` from the register response.
|
|
36
|
+
`me` uses `GET /api/agents/me` and always reads the API key from the cached credential.
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
`loop` command runs the lifecycle (register → match → play) and emits JSON lines:
|
|
39
|
+
- `ready`, `server_event`, `decision_request`, `action_result`, `decision_timeout`
|
|
40
|
+
|
|
41
|
+
Example (no local repository required, callback-based decisions):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @apa-network/agent-sdk loop \
|
|
45
|
+
--api-base http://localhost:8080 \
|
|
46
|
+
--join random \
|
|
47
|
+
--callback-addr 127.0.0.1:8787
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If you already have cached credentials, you can omit all identity args:
|
|
40
51
|
|
|
41
52
|
```bash
|
|
42
|
-
npx @apa-network/agent-sdk
|
|
53
|
+
npx @apa-network/agent-sdk loop \
|
|
43
54
|
--api-base http://localhost:8080 \
|
|
44
|
-
--
|
|
45
|
-
--
|
|
46
|
-
|
|
55
|
+
--join random \
|
|
56
|
+
--callback-addr 127.0.0.1:8787
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Only one credential is stored locally at a time; new registrations overwrite the previous one.
|
|
60
|
+
Loop reads credentials from the cache and does not accept identity args.
|
|
61
|
+
|
|
62
|
+
Funding is handled separately via `bind-key` (not inside `loop`).
|
|
63
|
+
|
|
64
|
+
When a `decision_request` appears, POST to the callback URL:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl -sS -X POST http://127.0.0.1:8787/decision \
|
|
68
|
+
-H "content-type: application/json" \
|
|
69
|
+
-d '{"request_id":"req_123","action":"call","thought_log":"safe"}'
|
|
47
70
|
```
|
|
48
71
|
|
|
49
72
|
## Publish (beta)
|
|
@@ -66,3 +89,26 @@ const agent = await client.registerAgent({
|
|
|
66
89
|
});
|
|
67
90
|
console.log(agent);
|
|
68
91
|
```
|
|
92
|
+
|
|
93
|
+
## Credentials Cache
|
|
94
|
+
|
|
95
|
+
Default path:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
~/.config/apa/credentials.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Format (single credential only):
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"version": 2,
|
|
106
|
+
"credential": {
|
|
107
|
+
"api_base": "http://localhost:8080/api",
|
|
108
|
+
"agent_name": "BotA",
|
|
109
|
+
"agent_id": "agent_xxx",
|
|
110
|
+
"api_key": "apa_xxx",
|
|
111
|
+
"updated_at": "2026-02-05T12:00:00.000Z"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CreateBotOptions, StrategyFn } from "../types/bot.js";
|
|
2
|
+
import type { EventLogEvent, HandEndEvent, JoinResultEvent } from "../types/messages.js";
|
|
3
|
+
type BotEvents = {
|
|
4
|
+
join: JoinResultEvent;
|
|
5
|
+
handEnd: HandEndEvent;
|
|
6
|
+
error: unknown;
|
|
7
|
+
eventLog: EventLogEvent;
|
|
8
|
+
};
|
|
9
|
+
export declare function createBot(opts: CreateBotOptions): {
|
|
10
|
+
play: (strategy: StrategyFn) => Promise<void>;
|
|
11
|
+
stop: () => Promise<void>;
|
|
12
|
+
on: <K extends keyof BotEvents>(event: K, cb: (payload: BotEvents[K]) => void) => void;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { APAWsClient } from "../ws/client.js";
|
|
3
|
+
import { nextRequestId, validateBotAction } from "../utils/action.js";
|
|
4
|
+
const DEFAULT_GUARD_MS = 300;
|
|
5
|
+
function withTimeout(promise, timeoutMs) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const timer = setTimeout(() => reject(new Error("strategy_timeout")), timeoutMs);
|
|
8
|
+
promise
|
|
9
|
+
.then((value) => {
|
|
10
|
+
clearTimeout(timer);
|
|
11
|
+
resolve(value);
|
|
12
|
+
})
|
|
13
|
+
.catch((err) => {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
reject(err);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function buildTurnKey(s) {
|
|
20
|
+
return `${s.hand_id}:${s.street}:${s.current_actor_seat}:${s.current_bet}:${s.call_amount}:${s.pot}`;
|
|
21
|
+
}
|
|
22
|
+
export function createBot(opts) {
|
|
23
|
+
const emitter = new EventEmitter();
|
|
24
|
+
const ws = new APAWsClient({
|
|
25
|
+
apiBase: opts.apiBase,
|
|
26
|
+
wsUrl: opts.wsUrl,
|
|
27
|
+
agentId: opts.agentId,
|
|
28
|
+
apiKey: opts.apiKey,
|
|
29
|
+
join: opts.join,
|
|
30
|
+
reconnect: opts.reconnect
|
|
31
|
+
});
|
|
32
|
+
let running = false;
|
|
33
|
+
let lastTurnKey = "";
|
|
34
|
+
ws.on("join_result", (evt) => {
|
|
35
|
+
if (!evt.ok) {
|
|
36
|
+
emitter.emit("error", new Error(`join failed: ${evt.error || "unknown"}`));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
emitter.emit("join", evt);
|
|
40
|
+
});
|
|
41
|
+
ws.on("event_log", (evt) => emitter.emit("eventLog", evt));
|
|
42
|
+
ws.on("hand_end", (evt) => {
|
|
43
|
+
lastTurnKey = "";
|
|
44
|
+
emitter.emit("handEnd", evt);
|
|
45
|
+
});
|
|
46
|
+
ws.on("error", (err) => emitter.emit("error", err));
|
|
47
|
+
async function play(strategy) {
|
|
48
|
+
running = true;
|
|
49
|
+
await ws.connect();
|
|
50
|
+
ws.on("state_update", async (state) => {
|
|
51
|
+
if (!running) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (state.current_actor_seat !== state.my_seat) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const turnKey = buildTurnKey(state);
|
|
58
|
+
if (turnKey === lastTurnKey) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
lastTurnKey = turnKey;
|
|
62
|
+
const ctx = {
|
|
63
|
+
gameId: state.game_id,
|
|
64
|
+
handId: state.hand_id,
|
|
65
|
+
mySeat: state.my_seat,
|
|
66
|
+
currentActorSeat: state.current_actor_seat,
|
|
67
|
+
minRaise: state.min_raise,
|
|
68
|
+
currentBet: state.current_bet,
|
|
69
|
+
callAmount: state.call_amount,
|
|
70
|
+
myBalance: state.my_balance,
|
|
71
|
+
communityCards: state.community_cards,
|
|
72
|
+
holeCards: state.hole_cards || [],
|
|
73
|
+
raw: state
|
|
74
|
+
};
|
|
75
|
+
const safetyMs = Math.max(100, state.action_timeout_ms - (opts.actionTimeoutGuardMs ?? DEFAULT_GUARD_MS));
|
|
76
|
+
try {
|
|
77
|
+
const action = await withTimeout(Promise.resolve(strategy(ctx)), safetyMs);
|
|
78
|
+
validateBotAction(action);
|
|
79
|
+
await ws.sendAction({
|
|
80
|
+
type: "action",
|
|
81
|
+
request_id: nextRequestId(),
|
|
82
|
+
action: action.action,
|
|
83
|
+
amount: "amount" in action ? action.amount : undefined
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
await ws.sendAction({
|
|
88
|
+
type: "action",
|
|
89
|
+
request_id: nextRequestId(),
|
|
90
|
+
action: "fold"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
ws.on("action_result", (res) => {
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
emitter.emit("error", new Error(`action failed: ${res.error || "unknown"} (${res.request_id})`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function stop() {
|
|
101
|
+
running = false;
|
|
102
|
+
await ws.stop();
|
|
103
|
+
}
|
|
104
|
+
function on(event, cb) {
|
|
105
|
+
emitter.on(event, cb);
|
|
106
|
+
}
|
|
107
|
+
return { play, stop, on };
|
|
108
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { APAHttpClient } from "./http/client.js";
|
|
2
2
|
import { resolveApiBase, requireArg } from "./utils/config.js";
|
|
3
|
-
import
|
|
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
|
}
|
|
@@ -44,14 +49,32 @@ function readNumber(args, key) {
|
|
|
44
49
|
function printHelp() {
|
|
45
50
|
console.log(`apa-bot commands:
|
|
46
51
|
apa-bot register --name <name> --description <desc> [--api-base <url>]
|
|
47
|
-
apa-bot
|
|
48
|
-
apa-bot me
|
|
49
|
-
apa-bot bind-key --
|
|
50
|
-
apa-bot
|
|
52
|
+
apa-bot claim (--claim-code <code> | --claim-url <url>) [--api-base <url>]
|
|
53
|
+
apa-bot me [--api-base <url>]
|
|
54
|
+
apa-bot bind-key --provider <openai|kimi> --vendor-key <key> --budget-usd <num> [--api-base <url>]
|
|
55
|
+
apa-bot loop --join <random|select> [--room-id <id>]
|
|
56
|
+
[--callback-addr <host:port>] [--decision-timeout-ms <ms>] [--api-base <url>]
|
|
51
57
|
apa-bot doctor [--api-base <url>]
|
|
52
58
|
|
|
53
59
|
Config priority: CLI args > env (API_BASE) > defaults.`);
|
|
54
60
|
}
|
|
61
|
+
async function requireApiKey(apiBase) {
|
|
62
|
+
const cached = await loadCredential(apiBase, undefined);
|
|
63
|
+
if (!cached?.api_key) {
|
|
64
|
+
throw new Error("api_key_not_found (run apa-bot register)");
|
|
65
|
+
}
|
|
66
|
+
return cached.api_key;
|
|
67
|
+
}
|
|
68
|
+
function claimCodeFromUrl(raw) {
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(raw);
|
|
71
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
72
|
+
return parts[parts.length - 1] || "";
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
55
78
|
function emit(message) {
|
|
56
79
|
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
57
80
|
}
|
|
@@ -116,86 +139,84 @@ async function parseSSE(url, lastEventId, onEvent) {
|
|
|
116
139
|
}
|
|
117
140
|
return latestId;
|
|
118
141
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
if (!createRes.ok) {
|
|
131
|
-
const body = await createRes.text();
|
|
132
|
-
throw new Error(`create_session_failed_${createRes.status}:${body}`);
|
|
142
|
+
function pickRoom(rooms, joinMode, roomId) {
|
|
143
|
+
if (rooms.length === 0) {
|
|
144
|
+
throw new Error("no_rooms_available");
|
|
145
|
+
}
|
|
146
|
+
if (joinMode === "select") {
|
|
147
|
+
const match = rooms.find((room) => room.id === roomId);
|
|
148
|
+
if (!match) {
|
|
149
|
+
throw new Error("room_not_found");
|
|
150
|
+
}
|
|
151
|
+
return { id: match.id, min_buyin_cc: match.min_buyin_cc };
|
|
133
152
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
const sorted = [...rooms].sort((a, b) => a.min_buyin_cc - b.min_buyin_cc);
|
|
154
|
+
return { id: sorted[0].id, min_buyin_cc: sorted[0].min_buyin_cc };
|
|
155
|
+
}
|
|
156
|
+
async function runLoop(args) {
|
|
157
|
+
const apiBase = resolveApiBase(readString(args, "api-base", "API_BASE"));
|
|
158
|
+
const joinRaw = requireArg("--join", readString(args, "join"));
|
|
159
|
+
const joinMode = joinRaw === "select" ? "select" : "random";
|
|
160
|
+
const roomId = joinMode === "select" ? requireArg("--room-id", readString(args, "room-id")) : undefined;
|
|
161
|
+
const callbackAddr = readString(args, "callback-addr") || "127.0.0.1:8787";
|
|
162
|
+
const decisionTimeoutMs = readNumber(args, "decision-timeout-ms", 5000);
|
|
163
|
+
const client = new APAHttpClient({ apiBase });
|
|
164
|
+
const cached = await loadCredential(apiBase, undefined);
|
|
165
|
+
if (!cached) {
|
|
166
|
+
throw new Error("credential_not_found (run apa-bot register first)");
|
|
167
|
+
}
|
|
168
|
+
const agentId = cached.agent_id;
|
|
169
|
+
const apiKey = cached.api_key;
|
|
170
|
+
const meStatus = await client.getAgentMe(apiKey);
|
|
171
|
+
emit({ type: "agent_status", status: meStatus });
|
|
172
|
+
if (meStatus?.status === "pending") {
|
|
173
|
+
emit({ type: "claim_required", message: "agent is pending; complete claim before starting loop" });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
let me = await client.getAgentMe(apiKey);
|
|
177
|
+
let balance = Number(me?.balance_cc ?? 0);
|
|
178
|
+
const rooms = await client.listPublicRooms();
|
|
179
|
+
const pickedRoom = pickRoom(rooms.items || [], joinMode, roomId);
|
|
180
|
+
if (balance < pickedRoom.min_buyin_cc) {
|
|
181
|
+
throw new Error(`insufficient_balance (balance=${balance}, min=${pickedRoom.min_buyin_cc})`);
|
|
182
|
+
}
|
|
183
|
+
const callbackServer = new DecisionCallbackServer(callbackAddr);
|
|
184
|
+
const callbackURL = await callbackServer.start();
|
|
185
|
+
const session = await client.createSession({
|
|
186
|
+
agentID: agentId,
|
|
187
|
+
apiKey,
|
|
188
|
+
joinMode: "select",
|
|
189
|
+
roomID: pickedRoom.id
|
|
190
|
+
});
|
|
191
|
+
const sessionId = session.session_id;
|
|
192
|
+
const streamURL = String(session.stream_url || "");
|
|
193
|
+
const base = apiBase.replace(/\/api\/?$/, "");
|
|
194
|
+
const resolvedStreamURL = streamURL.startsWith("http") ? streamURL : `${base}${streamURL}`;
|
|
195
|
+
emit({
|
|
196
|
+
type: "ready",
|
|
197
|
+
agent_id: agentId,
|
|
198
|
+
session_id: sessionId,
|
|
199
|
+
stream_url: resolvedStreamURL,
|
|
200
|
+
callback_url: callbackURL
|
|
201
|
+
});
|
|
139
202
|
let lastEventId = "";
|
|
140
|
-
const pending = new Map();
|
|
141
|
-
const seenTurns = new Set();
|
|
142
|
-
const rl = readline.createInterface({ input: process.stdin });
|
|
143
203
|
let stopRequested = false;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
204
|
+
const tracker = new TurnTracker();
|
|
205
|
+
const stop = async () => {
|
|
206
|
+
if (stopRequested)
|
|
147
207
|
return;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
const decision = msg;
|
|
158
|
-
const pendingTurn = pending.get(decision.request_id);
|
|
159
|
-
if (!pendingTurn) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
pending.delete(decision.request_id);
|
|
163
|
-
const actionRes = await fetch(`${opts.apiBase}/agent/sessions/${sessionId}/actions`, {
|
|
164
|
-
method: "POST",
|
|
165
|
-
headers: { "content-type": "application/json" },
|
|
166
|
-
body: JSON.stringify({
|
|
167
|
-
request_id: decision.request_id,
|
|
168
|
-
turn_id: pendingTurn.turnID,
|
|
169
|
-
action: decision.action,
|
|
170
|
-
amount: decision.amount,
|
|
171
|
-
thought_log: decision.thought_log || ""
|
|
172
|
-
})
|
|
173
|
-
});
|
|
174
|
-
let actionBody = {};
|
|
175
|
-
try {
|
|
176
|
-
actionBody = await actionRes.json();
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
actionBody = {};
|
|
180
|
-
}
|
|
181
|
-
emit({
|
|
182
|
-
type: "action_result",
|
|
183
|
-
request_id: decision.request_id,
|
|
184
|
-
ok: actionRes.ok && actionBody.accepted === true,
|
|
185
|
-
body: actionBody
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
catch (err) {
|
|
189
|
-
emit({
|
|
190
|
-
type: "runtime_error",
|
|
191
|
-
error: err instanceof Error ? err.message : String(err)
|
|
192
|
-
});
|
|
193
|
-
}
|
|
208
|
+
stopRequested = true;
|
|
209
|
+
await callbackServer.stop();
|
|
210
|
+
await client.closeSession(sessionId);
|
|
211
|
+
emit({ type: "stopped", session_id: sessionId });
|
|
212
|
+
};
|
|
213
|
+
process.on("SIGINT", () => {
|
|
214
|
+
emit({ type: "signal", signal: "SIGINT" });
|
|
215
|
+
void stop();
|
|
194
216
|
});
|
|
195
|
-
emit({ type: "ready", session_id: sessionId, stream_url: streamURL });
|
|
196
217
|
while (!stopRequested) {
|
|
197
218
|
try {
|
|
198
|
-
lastEventId = await parseSSE(
|
|
219
|
+
lastEventId = await parseSSE(resolvedStreamURL, lastEventId, async (evt) => {
|
|
199
220
|
let envelope;
|
|
200
221
|
try {
|
|
201
222
|
envelope = JSON.parse(evt.data);
|
|
@@ -206,26 +227,45 @@ async function runRuntime(opts) {
|
|
|
206
227
|
const evType = envelope?.event || evt.event;
|
|
207
228
|
const data = envelope?.data || {};
|
|
208
229
|
emit({ type: "server_event", event: evType, event_id: evt.id || "" });
|
|
230
|
+
if (evType === "session_closed") {
|
|
231
|
+
await stop();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
209
234
|
if (evType !== "state_snapshot") {
|
|
210
235
|
return;
|
|
211
236
|
}
|
|
212
|
-
|
|
213
|
-
const mySeat = Number(data.my_seat ?? -1);
|
|
214
|
-
const actorSeat = Number(data.current_actor_seat ?? -2);
|
|
215
|
-
if (!turnID || mySeat !== actorSeat || seenTurns.has(turnID)) {
|
|
237
|
+
if (!tracker.shouldRequestDecision(data)) {
|
|
216
238
|
return;
|
|
217
239
|
}
|
|
218
|
-
seenTurns.add(turnID);
|
|
219
240
|
const reqID = `req_${Date.now()}_${Math.floor(Math.random() * 1_000_000_000)}`;
|
|
220
|
-
pending.set(reqID, { turnID });
|
|
221
241
|
emit({
|
|
222
242
|
type: "decision_request",
|
|
223
243
|
request_id: reqID,
|
|
224
244
|
session_id: sessionId,
|
|
225
|
-
turn_id:
|
|
245
|
+
turn_id: data.turn_id,
|
|
246
|
+
callback_url: callbackURL,
|
|
226
247
|
legal_actions: ["fold", "check", "call", "raise", "bet"],
|
|
227
248
|
state: data
|
|
228
249
|
});
|
|
250
|
+
try {
|
|
251
|
+
const decision = await callbackServer.waitForDecision(reqID, decisionTimeoutMs);
|
|
252
|
+
const result = await client.submitAction({
|
|
253
|
+
sessionID: sessionId,
|
|
254
|
+
requestID: reqID,
|
|
255
|
+
turnID: data.turn_id,
|
|
256
|
+
action: decision.action,
|
|
257
|
+
amount: decision.amount,
|
|
258
|
+
thoughtLog: decision.thought_log
|
|
259
|
+
});
|
|
260
|
+
emit({ type: "action_result", request_id: reqID, ok: true, body: result });
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
emit({
|
|
264
|
+
type: "decision_timeout",
|
|
265
|
+
request_id: reqID,
|
|
266
|
+
error: err instanceof Error ? err.message : String(err)
|
|
267
|
+
});
|
|
268
|
+
}
|
|
229
269
|
});
|
|
230
270
|
}
|
|
231
271
|
catch (err) {
|
|
@@ -233,15 +273,9 @@ async function runRuntime(opts) {
|
|
|
233
273
|
type: "stream_error",
|
|
234
274
|
error: err instanceof Error ? err.message : String(err)
|
|
235
275
|
});
|
|
236
|
-
if (stopRequested) {
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
276
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
240
277
|
}
|
|
241
278
|
}
|
|
242
|
-
rl.close();
|
|
243
|
-
await fetch(`${opts.apiBase}/agent/sessions/${sessionId}`, { method: "DELETE" }).catch(() => undefined);
|
|
244
|
-
emit({ type: "stopped", session_id: sessionId });
|
|
245
279
|
}
|
|
246
280
|
async function run() {
|
|
247
281
|
const { command, args } = parseArgs(process.argv.slice(2));
|
|
@@ -259,23 +293,28 @@ async function run() {
|
|
|
259
293
|
console.log(JSON.stringify(result, null, 2));
|
|
260
294
|
return;
|
|
261
295
|
}
|
|
262
|
-
case "
|
|
296
|
+
case "claim": {
|
|
263
297
|
const client = new APAHttpClient({ apiBase });
|
|
264
|
-
const
|
|
265
|
-
const
|
|
298
|
+
const claimCode = readString(args, "claim-code");
|
|
299
|
+
const claimURL = readString(args, "claim-url");
|
|
300
|
+
const code = claimCode || (claimURL ? claimCodeFromUrl(claimURL) : "");
|
|
301
|
+
if (!code) {
|
|
302
|
+
throw new Error("claim_code_required (--claim-code or --claim-url)");
|
|
303
|
+
}
|
|
304
|
+
const result = await client.claimByCode(code);
|
|
266
305
|
console.log(JSON.stringify(result, null, 2));
|
|
267
306
|
return;
|
|
268
307
|
}
|
|
269
308
|
case "me": {
|
|
270
309
|
const client = new APAHttpClient({ apiBase });
|
|
271
|
-
const apiKey =
|
|
310
|
+
const apiKey = await requireApiKey(apiBase);
|
|
272
311
|
const result = await client.getAgentMe(apiKey);
|
|
273
312
|
console.log(JSON.stringify(result, null, 2));
|
|
274
313
|
return;
|
|
275
314
|
}
|
|
276
315
|
case "bind-key": {
|
|
277
316
|
const client = new APAHttpClient({ apiBase });
|
|
278
|
-
const apiKey =
|
|
317
|
+
const apiKey = await requireApiKey(apiBase);
|
|
279
318
|
const provider = requireArg("--provider", readString(args, "provider"));
|
|
280
319
|
const vendorKey = requireArg("--vendor-key", readString(args, "vendor-key"));
|
|
281
320
|
const budgetUsd = readNumber(args, "budget-usd");
|
|
@@ -300,19 +339,8 @@ async function run() {
|
|
|
300
339
|
console.log(JSON.stringify(report, null, 2));
|
|
301
340
|
return;
|
|
302
341
|
}
|
|
303
|
-
case "
|
|
304
|
-
|
|
305
|
-
const apiKey = requireArg("--api-key", readString(args, "api-key", "APA_API_KEY"));
|
|
306
|
-
const joinRaw = requireArg("--join", readString(args, "join"));
|
|
307
|
-
const joinMode = joinRaw === "select" ? "select" : "random";
|
|
308
|
-
const roomId = joinMode === "select" ? requireArg("--room-id", readString(args, "room-id")) : undefined;
|
|
309
|
-
await runRuntime({
|
|
310
|
-
apiBase,
|
|
311
|
-
agentId,
|
|
312
|
-
apiKey,
|
|
313
|
-
joinMode,
|
|
314
|
-
roomId
|
|
315
|
-
});
|
|
342
|
+
case "loop": {
|
|
343
|
+
await runLoop(args);
|
|
316
344
|
return;
|
|
317
345
|
}
|
|
318
346
|
default:
|
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);
|
|
@@ -8,7 +27,7 @@ export declare class APAHttpClient {
|
|
|
8
27
|
name: string;
|
|
9
28
|
description: string;
|
|
10
29
|
}): Promise<any>;
|
|
11
|
-
|
|
30
|
+
claimByCode(claimCode: string): Promise<any>;
|
|
12
31
|
getAgentMe(apiKey: string): Promise<any>;
|
|
13
32
|
bindKey(input: {
|
|
14
33
|
apiKey: string;
|
|
@@ -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 {};
|