@whitesands/agent-player 0.0.0-stage → 1.0.0
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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +313 -2
- package/dist/index.cjs +1072 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +1030 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.cjs +51 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +113 -0
- package/dist/protocol.d.ts +113 -0
- package/dist/protocol.js +22 -0
- package/dist/protocol.js.map +1 -0
- package/dist/testing.cjs +319 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +92 -0
- package/dist/testing.d.ts +92 -0
- package/dist/testing.js +289 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-B4iGtwwk.d.cts +124 -0
- package/dist/types-BgUxWDKm.d.ts +124 -0
- package/package.json +78 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Remember each character's actions in a local SQLite file and include them on the next view.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 46faef6: Publish the agent player SDK: typed actions, turn results, reconnect, and an offline testing entry.
|
|
14
|
+
|
|
15
|
+
First release published to npm.
|
|
16
|
+
|
|
17
|
+
- `AgentServer` reads `WSI_PLATFORM_KEY` and `WSI_PLATFORM_URL`.
|
|
18
|
+
- Player classes can declare zod argument schemas and descriptions.
|
|
19
|
+
- `turn()` returns the action, arguments, result, attempt count, and token usage.
|
|
20
|
+
- Typed errors, event listeners, turn timeouts, and `await using`.
|
|
21
|
+
- Reconnect with session resume, heartbeat, and a protocol version handshake.
|
|
22
|
+
- `@whitesands/agent-player/testing` runs the production loop offline.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 White Sands Interactive
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,314 @@
|
|
|
1
|
-
#
|
|
1
|
+
# WSI Agent Player
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@whitesands/agent-player)
|
|
4
|
+
[](https://github.com/White-Sands-Interactive/WSI-Node-Framework/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
The library starts a remote game agent from a player class. The game calls `start` and `turn`. The SDK publishes that class as tools. The model runs on the [platform](https://github.com/White-Sands-Interactive/Platform), so the game process never holds the model key.
|
|
7
|
+
|
|
8
|
+
The library also provides:
|
|
9
|
+
|
|
10
|
+
- A player class turned into tools, with optional Zod argument schemas.
|
|
11
|
+
- One action per turn, and one retry when that action throws.
|
|
12
|
+
- An offline loop for tests, with no socket and no platform key.
|
|
13
|
+
- A local SQLite log of what each character has already called.
|
|
14
|
+
|
|
15
|
+
## Documentation
|
|
16
|
+
|
|
17
|
+
This page is the guide. Generate the API reference with:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm run docs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
TypeDoc writes HTML to `docs`.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
You don't need this source code unless you want to modify the SDK. To use the package:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install @whitesands/agent-player
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Requirements
|
|
34
|
+
|
|
35
|
+
Node.js 20 or newer.
|
|
36
|
+
|
|
37
|
+
Installing the package builds `better-sqlite3`. npm uses a prebuilt binary on current macOS, Linux, and Windows. That module is the action log described below.
|
|
38
|
+
|
|
39
|
+
Use the same Zod 3 the SDK depends on when you declare argument schemas. Zod 4 objects are not read as schemas.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
Set the platform key, then start one agent per character. `turn()` grants that character one chance to act and resolves with what they did.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
export WSI_PLATFORM_KEY=pk_...
|
|
47
|
+
export WSI_PLATFORM_URL=ws://127.0.0.1:8787
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`WSI_PLATFORM_URL` defaults to `ws://127.0.0.1:8787` when it is unset.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { AgentServer } from "@whitesands/agent-player";
|
|
54
|
+
|
|
55
|
+
const agents = new AgentServer();
|
|
56
|
+
const ada = await agents.start(new Villager(world, "Ada"), {
|
|
57
|
+
name: "Ada",
|
|
58
|
+
goal: "Stand on the tree and chop it",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await ada.turn();
|
|
62
|
+
// { action: "move", args: { dx: 1, dy: 0 }, result, attempts: 1, usage }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`new AgentServer({ platformKey, platformUrl })` overrides the environment variables.
|
|
66
|
+
|
|
67
|
+
### The player class
|
|
68
|
+
|
|
69
|
+
`see()` is required. It returns the JSON the model reads at the start of the turn. Every other public method is an action the model may call. Names that start with `_`, and the constructor, are left off the tool list.
|
|
70
|
+
|
|
71
|
+
Throw to refuse an action. The platform asks the model once more. A second failure rejects `turn()` with `TurnFailedError`.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
class Villager {
|
|
75
|
+
x = 0;
|
|
76
|
+
y = 0;
|
|
77
|
+
wood = 0;
|
|
78
|
+
|
|
79
|
+
see() {
|
|
80
|
+
return { x: this.x, y: this.y, wood: this.wood, tile: world.get(this.x, this.y) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
move(dx: number, dy: number) {
|
|
84
|
+
const nextX = this.x + dx;
|
|
85
|
+
const nextY = this.y + dy;
|
|
86
|
+
if (!world.inBounds(nextX, nextY)) {
|
|
87
|
+
throw new Error(`Cannot move to ${nextX},${nextY}`);
|
|
88
|
+
}
|
|
89
|
+
this.x = nextX;
|
|
90
|
+
this.y = nextY;
|
|
91
|
+
return { x: this.x, y: this.y };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
chop() {
|
|
95
|
+
if (world.get(this.x, this.y) !== "T") {
|
|
96
|
+
throw new Error(`No tree at ${this.x},${this.y}`);
|
|
97
|
+
}
|
|
98
|
+
this.wood += 1;
|
|
99
|
+
return { wood: this.wood };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A method with no `static agent` entry is still a tool. Its arguments accept any JSON value.
|
|
105
|
+
|
|
106
|
+
### Declaring arguments
|
|
107
|
+
|
|
108
|
+
`static agent` adds the description the model sees and checks arguments with Zod before your method runs. A schema failure counts as a failed action and uses the one retry.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { z } from "zod";
|
|
112
|
+
|
|
113
|
+
class Villager {
|
|
114
|
+
static agent = {
|
|
115
|
+
description: "A villager who gathers wood",
|
|
116
|
+
actions: {
|
|
117
|
+
move: {
|
|
118
|
+
description: "Step by dx and dy. Stay inside the map.",
|
|
119
|
+
args: { dx: z.number().int(), dy: z.number().int() },
|
|
120
|
+
},
|
|
121
|
+
chop: { description: "Chop the tree on your tile" },
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
see() {
|
|
126
|
+
return { x: this.x, y: this.y };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Turns
|
|
132
|
+
|
|
133
|
+
`turn()` does not choose the action. It unblocks that session's `wait`. The platform reads `see()`, calls one action, and reports back.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const result = await ada.turn({ timeoutMs: 30_000, signal });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Field | Meaning |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `action` | Tool the model called |
|
|
142
|
+
| `args` | Arguments it passed |
|
|
143
|
+
| `result` | What your method returned |
|
|
144
|
+
| `attempts` | `1`, or `2` when the first call failed and the retry succeeded |
|
|
145
|
+
| `usage` | `promptTokens` and `completionTokens` for that turn |
|
|
146
|
+
|
|
147
|
+
The default timeout is 60 seconds. Pass `signal` to abort the wait. `handle.stop()` ends that session. `agents.close()`, also run by `await using`, stops every session on the server. The platform process keeps running.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
await using agents = new AgentServer();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Errors
|
|
154
|
+
|
|
155
|
+
| Error | When |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `PlatformAuthError` | The platform rejected the key |
|
|
158
|
+
| `ProtocolVersionError` | This SDK and the platform disagree on the protocol |
|
|
159
|
+
| `TurnFailedError` | The action and the retry both failed. `error.attempts` lists both calls |
|
|
160
|
+
| `TurnTimeoutError` | The platform did not finish within `timeoutMs` |
|
|
161
|
+
| `ConnectionLostError` | The socket dropped and the session could not be resumed |
|
|
162
|
+
|
|
163
|
+
The SDK reconnects with backoff. A disconnected session can be resumed for 30 seconds.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { TurnFailedError } from "@whitesands/agent-player";
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await ada.turn();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (error instanceof TurnFailedError) {
|
|
172
|
+
console.log(error.attempts);
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Several characters
|
|
179
|
+
|
|
180
|
+
One `AgentServer` hosts every character. Each `start()` is its own session.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const ada = await agents.start(new Villager(world, "Ada"), { name: "Ada", goal: "Chop the tree" });
|
|
184
|
+
const bea = await agents.start(new Villager(world, "Bea"), { name: "Bea", goal: "Carry wood home" });
|
|
185
|
+
|
|
186
|
+
await ada.turn();
|
|
187
|
+
await bea.turn();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The game decides whose turn it is by calling `turn()` on that handle.
|
|
191
|
+
|
|
192
|
+
### Events
|
|
193
|
+
|
|
194
|
+
`agents.on` hears every session. `handle.on` hears one session. Both return an unsubscribe function.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
const stop = ada.on((event) => {
|
|
198
|
+
if (event.type === "action") console.log(event.action, event.args);
|
|
199
|
+
if (event.type === "retry") console.log(event.action, event.error);
|
|
200
|
+
if (event.type === "turnFailed") console.log(event.error);
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Connection events are `disconnected` and `reconnected`. Those have no `sessionId`.
|
|
205
|
+
|
|
206
|
+
### Action history
|
|
207
|
+
|
|
208
|
+
Every `start()` records that character's calls in one SQLite file, `.wsi/agent-memory.sqlite` in the process's current directory. The directory is created on the first `start()`. Deleting it is safe. The next start creates it again.
|
|
209
|
+
|
|
210
|
+
The next `see()` view includes `done`, oldest first, up to the last 50 calls for that session:
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"x": 1,
|
|
215
|
+
"y": 0,
|
|
216
|
+
"done": [
|
|
217
|
+
{ "action": "move", "args": { "dx": 1, "dy": 0 }, "ok": true }
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
A refused call is `{ "ok": false, "error": "..." }`. A new `start()` has an empty `done` list. Older rows stay in the file. Two characters on one server share the file and see only their own rows. The name on each row is the `name` passed to `start()`, or the class name.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const agents = new AgentServer({
|
|
226
|
+
memoryPath: "saves/agents.sqlite",
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`memoryPath: false` turns the log off. The view then has no `done` field.
|
|
231
|
+
|
|
232
|
+
### Goal and instructions
|
|
233
|
+
|
|
234
|
+
`goal` is added to the model prompt for that character. `instructions` on `start()` is appended to the default prompt. `instructions` on `AgentServer` replaces the default prompt for every character on that server.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
await agents.start(ferry, {
|
|
238
|
+
name: "Ferryman",
|
|
239
|
+
goal: "Get the wolf, the goat, and the cabbage onto the far bank.",
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Testing without a platform
|
|
244
|
+
|
|
245
|
+
`@whitesands/agent-player/testing` runs the same wait, act, and retry loop in memory. The script is the list of tool calls, in order. No key and no socket.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { AgentServer } from "@whitesands/agent-player";
|
|
249
|
+
import { createTestPlatform } from "@whitesands/agent-player/testing";
|
|
250
|
+
|
|
251
|
+
const agents = new AgentServer({
|
|
252
|
+
inline: createTestPlatform([
|
|
253
|
+
{ name: "move", arguments: { dx: 1, dy: 0 } },
|
|
254
|
+
{ name: "chop", arguments: {} },
|
|
255
|
+
]),
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Where each piece runs
|
|
260
|
+
|
|
261
|
+
| | Game process | Platform |
|
|
262
|
+
| --- | --- | --- |
|
|
263
|
+
| World and player methods | yes | |
|
|
264
|
+
| Tools (`see`, actions, `wait`) | yes | calls them |
|
|
265
|
+
| Whose turn it is | `turn()` | |
|
|
266
|
+
| Which action to take | | the model |
|
|
267
|
+
| Model API key | | yes |
|
|
268
|
+
|
|
269
|
+
### Logging
|
|
270
|
+
|
|
271
|
+
Pass a `logger` to print SDK warnings. Omit it and nothing is written.
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
const agents = new AgentServer({
|
|
275
|
+
logger: {
|
|
276
|
+
warn: (message) => console.warn(message),
|
|
277
|
+
error: (message) => console.error(message),
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`debug`, `info`, `warn`, and `error` are all optional.
|
|
283
|
+
|
|
284
|
+
### Protocol
|
|
285
|
+
|
|
286
|
+
`@whitesands/agent-player/protocol` exports the wire types and `PROTOCOL_VERSION`. The platform's copy is `internal/protocol` in [White-Sands-Interactive/Platform](https://github.com/White-Sands-Interactive/Platform). A change to the wire format has to land in both places. Game code uses the main entry and does not import the protocol.
|
|
287
|
+
|
|
288
|
+
## This repository
|
|
289
|
+
|
|
290
|
+
| Path | Role |
|
|
291
|
+
| --- | --- |
|
|
292
|
+
| `src` | The published SDK |
|
|
293
|
+
| `examples/forest` | Two villagers, reflected methods |
|
|
294
|
+
| `examples/courier` | Zod arguments and a goal |
|
|
295
|
+
| `examples/ferry` | One passenger chosen each turn |
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
npm install
|
|
299
|
+
npm test
|
|
300
|
+
npm run build
|
|
301
|
+
npm run forest
|
|
302
|
+
npm run courier
|
|
303
|
+
npm run ferry
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Each example replays a script. Add `-- --remote` to use a platform that is already serving:
|
|
307
|
+
|
|
308
|
+
```sh
|
|
309
|
+
./platform create-key demo
|
|
310
|
+
MODEL_API_KEY=... MODEL_BASE_URL=... MODEL_NAME=... ./platform serve
|
|
311
|
+
PLATFORM_KEY=pk_... npm run forest -- --remote
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`./platform` is the Go server from the Platform repository. Releases are recorded in [CHANGELOG.md](CHANGELOG.md).
|