@apa-network/agent-sdk 0.1.0 → 0.2.0-beta.10

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.
Files changed (48) hide show
  1. package/README.md +109 -14
  2. package/bin/apa-bot.js +6 -4
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.js +491 -74
  5. package/dist/cli.next_decision.e2e.test.js +468 -0
  6. package/dist/commands/register.d.ts +2 -0
  7. package/dist/commands/register.js +18 -0
  8. package/dist/commands/register.test.js +26 -0
  9. package/dist/commands/session_recovery.d.ts +6 -0
  10. package/dist/commands/session_recovery.js +25 -0
  11. package/dist/commands/session_recovery.test.js +27 -0
  12. package/dist/http/client.d.ts +30 -1
  13. package/dist/http/client.js +50 -7
  14. package/dist/index.d.ts +0 -3
  15. package/dist/index.js +0 -1
  16. package/dist/loop/callback.d.ts +20 -0
  17. package/dist/loop/callback.js +105 -0
  18. package/dist/loop/callback.test.js +33 -0
  19. package/dist/loop/credentials.d.ts +10 -0
  20. package/dist/loop/credentials.js +60 -0
  21. package/dist/loop/credentials.test.d.ts +1 -0
  22. package/dist/loop/credentials.test.js +33 -0
  23. package/dist/loop/decision_state.d.ts +30 -0
  24. package/dist/loop/decision_state.js +43 -0
  25. package/dist/loop/state.d.ts +9 -0
  26. package/dist/loop/state.js +16 -0
  27. package/dist/loop/state.test.d.ts +1 -0
  28. package/dist/loop/state.test.js +14 -0
  29. package/dist/utils/config.d.ts +1 -2
  30. package/dist/utils/config.js +8 -5
  31. package/dist/utils/config.test.js +7 -1
  32. package/package.json +7 -2
  33. package/dist/bot/createBot.d.ts +0 -14
  34. package/dist/bot/createBot.js +0 -107
  35. package/dist/types/bot.d.ts +0 -42
  36. package/dist/types/messages.d.ts +0 -85
  37. package/dist/utils/action.d.ts +0 -3
  38. package/dist/utils/action.js +0 -10
  39. package/dist/utils/action.test.js +0 -10
  40. package/dist/utils/backoff.d.ts +0 -2
  41. package/dist/utils/backoff.js +0 -11
  42. package/dist/utils/backoff.test.js +0 -8
  43. package/dist/ws/client.d.ts +0 -32
  44. package/dist/ws/client.js +0 -116
  45. /package/dist/{types/bot.js → cli.next_decision.e2e.test.d.ts} +0 -0
  46. /package/dist/{types/messages.js → commands/register.test.d.ts} +0 -0
  47. /package/dist/{utils/action.test.d.ts → commands/session_recovery.test.d.ts} +0 -0
  48. /package/dist/{utils/backoff.test.d.ts → loop/callback.test.d.ts} +0 -0
@@ -1,107 +0,0 @@
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
- wsUrl: opts.wsUrl,
26
- agentId: opts.agentId,
27
- apiKey: opts.apiKey,
28
- join: opts.join,
29
- reconnect: opts.reconnect
30
- });
31
- let running = false;
32
- let lastTurnKey = "";
33
- ws.on("join_result", (evt) => {
34
- if (!evt.ok) {
35
- emitter.emit("error", new Error(`join failed: ${evt.error || "unknown"}`));
36
- return;
37
- }
38
- emitter.emit("join", evt);
39
- });
40
- ws.on("event_log", (evt) => emitter.emit("eventLog", evt));
41
- ws.on("hand_end", (evt) => {
42
- lastTurnKey = "";
43
- emitter.emit("handEnd", evt);
44
- });
45
- ws.on("error", (err) => emitter.emit("error", err));
46
- async function play(strategy) {
47
- running = true;
48
- await ws.connect();
49
- ws.on("state_update", async (state) => {
50
- if (!running) {
51
- return;
52
- }
53
- if (state.current_actor_seat !== state.my_seat) {
54
- return;
55
- }
56
- const turnKey = buildTurnKey(state);
57
- if (turnKey === lastTurnKey) {
58
- return;
59
- }
60
- lastTurnKey = turnKey;
61
- const ctx = {
62
- gameId: state.game_id,
63
- handId: state.hand_id,
64
- mySeat: state.my_seat,
65
- currentActorSeat: state.current_actor_seat,
66
- minRaise: state.min_raise,
67
- currentBet: state.current_bet,
68
- callAmount: state.call_amount,
69
- myBalance: state.my_balance,
70
- communityCards: state.community_cards,
71
- holeCards: state.hole_cards || [],
72
- raw: state
73
- };
74
- const safetyMs = Math.max(100, state.action_timeout_ms - (opts.actionTimeoutGuardMs ?? DEFAULT_GUARD_MS));
75
- try {
76
- const action = await withTimeout(Promise.resolve(strategy(ctx)), safetyMs);
77
- validateBotAction(action);
78
- await ws.sendAction({
79
- type: "action",
80
- request_id: nextRequestId(),
81
- action: action.action,
82
- amount: "amount" in action ? action.amount : undefined
83
- });
84
- }
85
- catch {
86
- await ws.sendAction({
87
- type: "action",
88
- request_id: nextRequestId(),
89
- action: "fold"
90
- });
91
- }
92
- });
93
- ws.on("action_result", (res) => {
94
- if (!res.ok) {
95
- emitter.emit("error", new Error(`action failed: ${res.error || "unknown"} (${res.request_id})`));
96
- }
97
- });
98
- }
99
- async function stop() {
100
- running = false;
101
- await ws.stop();
102
- }
103
- function on(event, cb) {
104
- emitter.on(event, cb);
105
- }
106
- return { play, stop, on };
107
- }
@@ -1,42 +0,0 @@
1
- import type { JoinMode, StateUpdateEvent } from "./messages.js";
2
- export type BotAction = {
3
- action: "fold";
4
- } | {
5
- action: "check";
6
- } | {
7
- action: "call";
8
- } | {
9
- action: "raise";
10
- amount: number;
11
- } | {
12
- action: "bet";
13
- amount: number;
14
- };
15
- export type PlayContext = {
16
- gameId: string;
17
- handId: string;
18
- mySeat: number;
19
- currentActorSeat: number;
20
- minRaise: number;
21
- currentBet: number;
22
- callAmount: number;
23
- myBalance: number;
24
- communityCards: string[];
25
- holeCards: string[];
26
- raw: StateUpdateEvent;
27
- };
28
- export type StrategyFn = (ctx: PlayContext) => BotAction | Promise<BotAction>;
29
- export type ReconnectOptions = {
30
- enabled?: boolean;
31
- baseMs?: number;
32
- maxMs?: number;
33
- jitter?: boolean;
34
- };
35
- export type CreateBotOptions = {
36
- wsUrl?: string;
37
- agentId: string;
38
- apiKey: string;
39
- join: JoinMode;
40
- reconnect?: ReconnectOptions;
41
- actionTimeoutGuardMs?: number;
42
- };
@@ -1,85 +0,0 @@
1
- export type JoinMode = {
2
- mode: "random";
3
- } | {
4
- mode: "select";
5
- roomId: string;
6
- };
7
- export type JoinMessage = {
8
- type: "join";
9
- agent_id: string;
10
- api_key: string;
11
- join_mode: "random" | "select";
12
- room_id?: string;
13
- };
14
- export type ActionMessage = {
15
- type: "action";
16
- request_id: string;
17
- action: "fold" | "check" | "call" | "raise" | "bet";
18
- amount?: number;
19
- thought_log?: string;
20
- };
21
- export type JoinResultEvent = {
22
- type: "join_result";
23
- protocol_version: string;
24
- ok: boolean;
25
- error?: string;
26
- room_id?: string;
27
- };
28
- export type StateUpdateEvent = {
29
- type: "state_update";
30
- protocol_version: string;
31
- game_id: string;
32
- hand_id: string;
33
- my_seat: number;
34
- current_actor_seat: number;
35
- min_raise: number;
36
- current_bet: number;
37
- call_amount: number;
38
- my_balance: number;
39
- action_timeout_ms: number;
40
- street: string;
41
- hole_cards?: string[];
42
- community_cards: string[];
43
- pot: number;
44
- opponents: Array<{
45
- seat: number;
46
- name: string;
47
- stack: number;
48
- action: string;
49
- }>;
50
- };
51
- export type ActionResultEvent = {
52
- type: "action_result";
53
- protocol_version: string;
54
- request_id: string;
55
- ok: boolean;
56
- error?: string;
57
- };
58
- export type EventLogEvent = {
59
- type: "event_log";
60
- protocol_version: string;
61
- timestamp_ms: number;
62
- player_seat: number;
63
- action: string;
64
- amount?: number;
65
- thought_log?: string;
66
- event: string;
67
- };
68
- export type HandEndEvent = {
69
- type: "hand_end";
70
- protocol_version: string;
71
- winner: string;
72
- pot: number;
73
- balances: Array<{
74
- agent_id: string;
75
- balance: number;
76
- }>;
77
- showdown?: Array<{
78
- agent_id: string;
79
- hole_cards: string[];
80
- }>;
81
- };
82
- export type ServerEvent = JoinResultEvent | StateUpdateEvent | ActionResultEvent | EventLogEvent | HandEndEvent | {
83
- type: string;
84
- [k: string]: unknown;
85
- };
@@ -1,3 +0,0 @@
1
- import type { BotAction } from "../types/bot.js";
2
- export declare function validateBotAction(action: BotAction): void;
3
- export declare function nextRequestId(): string;
@@ -1,10 +0,0 @@
1
- export function validateBotAction(action) {
2
- if (action.action === "raise" || action.action === "bet") {
3
- if (!Number.isFinite(action.amount) || action.amount <= 0) {
4
- throw new Error(`${action.action} requires positive amount`);
5
- }
6
- }
7
- }
8
- export function nextRequestId() {
9
- return `req_${Date.now()}_${Math.floor(Math.random() * 1_000_000_000)}`;
10
- }
@@ -1,10 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { validateBotAction } from "./action.js";
4
- test("validateBotAction accepts non-amount actions", () => {
5
- validateBotAction({ action: "check" });
6
- validateBotAction({ action: "fold" });
7
- });
8
- test("validateBotAction rejects invalid raise amount", () => {
9
- assert.throws(() => validateBotAction({ action: "raise", amount: 0 }), /positive amount/);
10
- });
@@ -1,2 +0,0 @@
1
- export declare function computeBackoffMs(attempt: number, baseMs: number, maxMs: number, jitter: boolean): number;
2
- export declare function sleep(ms: number): Promise<void>;
@@ -1,11 +0,0 @@
1
- export function computeBackoffMs(attempt, baseMs, maxMs, jitter) {
2
- const exponential = Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt - 1));
3
- if (!jitter) {
4
- return exponential;
5
- }
6
- const spread = Math.max(1, Math.floor(exponential * 0.2));
7
- return exponential - spread + Math.floor(Math.random() * spread * 2);
8
- }
9
- export function sleep(ms) {
10
- return new Promise((resolve) => setTimeout(resolve, ms));
11
- }
@@ -1,8 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { computeBackoffMs } from "./backoff.js";
4
- test("computeBackoffMs grows exponentially without jitter", () => {
5
- assert.equal(computeBackoffMs(1, 500, 8000, false), 500);
6
- assert.equal(computeBackoffMs(2, 500, 8000, false), 1000);
7
- assert.equal(computeBackoffMs(5, 500, 8000, false), 8000);
8
- });
@@ -1,32 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import type { ActionMessage, JoinMode } from "../types/messages.js";
3
- type WsOptions = {
4
- wsUrl?: string;
5
- agentId: string;
6
- apiKey: string;
7
- join: JoinMode;
8
- reconnect?: {
9
- enabled?: boolean;
10
- baseMs?: number;
11
- maxMs?: number;
12
- jitter?: boolean;
13
- };
14
- };
15
- export declare class APAWsClient extends EventEmitter {
16
- private readonly wsUrl;
17
- private readonly agentId;
18
- private readonly apiKey;
19
- private readonly join;
20
- private readonly reconnect;
21
- private socket;
22
- private shouldRun;
23
- private connectAttempt;
24
- constructor(opts: WsOptions);
25
- connect(): Promise<void>;
26
- stop(): Promise<void>;
27
- sendAction(action: ActionMessage): Promise<void>;
28
- private openSocket;
29
- private reconnectLoop;
30
- private sendJoin;
31
- }
32
- export {};
package/dist/ws/client.js DELETED
@@ -1,116 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import { computeBackoffMs, sleep } from "../utils/backoff.js";
3
- import { resolveWsUrl } from "../utils/config.js";
4
- const DEFAULT_RECONNECT = {
5
- enabled: true,
6
- baseMs: 500,
7
- maxMs: 8000,
8
- jitter: true
9
- };
10
- export class APAWsClient extends EventEmitter {
11
- wsUrl;
12
- agentId;
13
- apiKey;
14
- join;
15
- reconnect;
16
- socket = null;
17
- shouldRun = true;
18
- connectAttempt = 0;
19
- constructor(opts) {
20
- super();
21
- this.wsUrl = resolveWsUrl(opts.wsUrl);
22
- this.agentId = opts.agentId;
23
- this.apiKey = opts.apiKey;
24
- this.join = opts.join;
25
- this.reconnect = {
26
- enabled: opts.reconnect?.enabled ?? DEFAULT_RECONNECT.enabled,
27
- baseMs: opts.reconnect?.baseMs ?? DEFAULT_RECONNECT.baseMs,
28
- maxMs: opts.reconnect?.maxMs ?? DEFAULT_RECONNECT.maxMs,
29
- jitter: opts.reconnect?.jitter ?? DEFAULT_RECONNECT.jitter
30
- };
31
- }
32
- async connect() {
33
- this.shouldRun = true;
34
- await this.openSocket();
35
- await this.sendJoin();
36
- }
37
- async stop() {
38
- this.shouldRun = false;
39
- if (this.socket) {
40
- this.socket.close();
41
- this.socket = null;
42
- }
43
- }
44
- async sendAction(action) {
45
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
46
- throw new Error("ws not connected");
47
- }
48
- this.socket.send(JSON.stringify(action));
49
- }
50
- async openSocket() {
51
- const Impl = globalThis.WebSocket;
52
- if (!Impl) {
53
- throw new Error("WebSocket is unavailable in current Node runtime (requires Node 20+)");
54
- }
55
- await new Promise((resolve, reject) => {
56
- const ws = new Impl(this.wsUrl);
57
- this.socket = ws;
58
- ws.onopen = () => {
59
- this.connectAttempt = 0;
60
- this.emit("connected");
61
- resolve();
62
- };
63
- ws.onmessage = (evt) => {
64
- try {
65
- const data = JSON.parse(String(evt.data));
66
- this.emit("event", data);
67
- this.emit(data.type, data);
68
- }
69
- catch (err) {
70
- this.emit("error", err);
71
- }
72
- };
73
- ws.onerror = () => {
74
- reject(new Error("ws connection failed"));
75
- };
76
- ws.onclose = async () => {
77
- this.emit("disconnected");
78
- if (!this.shouldRun || !this.reconnect.enabled) {
79
- return;
80
- }
81
- await this.reconnectLoop();
82
- };
83
- });
84
- }
85
- async reconnectLoop() {
86
- while (this.shouldRun) {
87
- this.connectAttempt += 1;
88
- const waitMs = computeBackoffMs(this.connectAttempt, this.reconnect.baseMs, this.reconnect.maxMs, this.reconnect.jitter);
89
- this.emit("reconnect", { attempt: this.connectAttempt, waitMs });
90
- await sleep(waitMs);
91
- try {
92
- await this.openSocket();
93
- await this.sendJoin();
94
- return;
95
- }
96
- catch (err) {
97
- this.emit("error", err);
98
- }
99
- }
100
- }
101
- async sendJoin() {
102
- const msg = {
103
- type: "join",
104
- agent_id: this.agentId,
105
- api_key: this.apiKey,
106
- join_mode: this.join.mode
107
- };
108
- if (this.join.mode === "select") {
109
- msg.room_id = this.join.roomId;
110
- }
111
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
112
- throw new Error("ws not connected for join");
113
- }
114
- this.socket.send(JSON.stringify(msg));
115
- }
116
- }