castle-web-sdk 0.4.41 → 0.4.43

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 CHANGED
@@ -250,20 +250,48 @@ A deck's session server can submit any player's score, which is how a
250
250
  leaderboard becomes something a player cannot fake. See the multiplayer
251
251
  guide.
252
252
 
253
+ ### `Store.usage(): Promise<StoreUsage>`
254
+
255
+ How much the current player and the deck have stored, against the
256
+ limits below, so a deck can warn a player or clear out old saves before a
257
+ write is refused instead of after.
258
+
259
+ ```js
260
+ const { player, shared } = await Store.usage();
261
+ if (player.byteCount > player.byteLimit * 0.9) {
262
+ // Nearly full: offer to delete old saves.
263
+ }
264
+ ```
265
+
266
+ - `player` is the current player's own allowance in this deck: their
267
+ `Store.me` and `Store.private` keys, their board scores, and what the
268
+ deck's `Characters` keep for them in their playthrough.
269
+ - `shared` is `Store.deck`, one allowance for every player together.
270
+
271
+ Each has `byteCount`, `byteLimit`, `keyCount` and `keyLimit`. A board
272
+ score counts as a key. The answer includes the `set` and `remove` calls
273
+ the page made before calling it, even ones still being batched; await an
274
+ `increment` or a board `submit` first if the answer should include it. A player can only ever see their own usage, not
275
+ another player's.
276
+
277
+ An app too old to know `usage` rejects with a `CastleError` whose `code`
278
+ is `UNKNOWN_COMMAND`.
279
+
253
280
  ### Limits
254
281
 
255
282
  The platform caps key length, value size, how many keys one read may
256
- name, and how much a deck may store in total, and it limits how fast a
283
+ name, and how much each player may store, and it limits how fast a
257
284
  player may read and write. Going over one of these rejects with a
258
285
  `CastleError` whose `code` says which, so a deck can tell a player it is
259
- saving too fast apart from a deck that is out of space:
286
+ saving too fast apart from a player who is out of space:
260
287
 
261
288
  | limit | value |
262
289
  |---|---|
263
290
  | key | 128 bytes |
264
291
  | value | 256 KB |
265
292
  | keys in one `get` | 32 |
266
- | whole deck | 50 MB, 500,000 keys |
293
+ | one player's data in a deck | 50 MB, 50,000 keys |
294
+ | the deck's shared data (`Store.deck`) | 500 MB, 1,000,000 keys |
267
295
  | one player's calls | 20 a second, 512 KB a second |
268
296
  | every player of a deck together | 500 calls a second, 8 MB a second |
269
297
 
@@ -273,6 +301,14 @@ A `list` page holds at most 1,000 entries (a larger `limit` is lowered to
273
301
  it) and ends early once it reaches 1 MB; take the next page with its
274
302
  `cursor`.
275
303
 
304
+ A player's data is everything stored for them: their `Store.private`
305
+ keys, their `Store.user(id)` keys, their scores on every board, and
306
+ their `Characters` playthrough.
307
+ Each player has their own allowance, so a deck with more
308
+ players can hold more, and one player filling theirs does not stop
309
+ anyone else. A write that shrinks what a player stores always succeeds,
310
+ so a player who is full can always free space.
311
+
276
312
  A value is JSON, so binary data such as a picture or a video clip costs
277
313
  about a third more once encoded, and a clip of more than a few seconds
278
314
  does not fit in one. Ship media with the deck instead.
@@ -747,9 +783,10 @@ limited to Castle admins for now; every other player gets `'refused'`.
747
783
  - `tools` — optional function tools, in the OpenAI function-tool schema.
748
784
  - `toolChoice` — optional: `'auto'` (the default), `'none'`, `'required'`,
749
785
  or `{ type: 'function', function: { name } }` to force one tool.
750
- - `model` — optional OpenRouter model id: `'meta/muse-spark-1.3-contributor'`
751
- (the default), `'meta/muse-spark-1.3'` or `'openai/gpt-4.1-mini'`. Any
752
- other model resolves `'error'`.
786
+ - `model` — optional OpenRouter model id, `'meta/muse-spark-1.3-contributor'`
787
+ by default. Any model OpenRouter lists with a per-token price works; one it
788
+ does not list, or one with a variable price (`'openrouter/auto'`), resolves
789
+ `'error'`.
753
790
  - `maxTokens` — optional cap on the reply's length. Defaults to 4096; the
754
791
  service caps it at 16384.
755
792
  - `reasoning` — optional `{ effort: 'minimal' | 'low' | 'medium' | 'high' }`,
@@ -817,7 +854,10 @@ for (let round = 0; round < 8; round++) {
817
854
  `AgentChatResult` has a `status`:
818
855
 
819
856
  - `'ok'` — `message` is the assistant's reply: `{ role, content,
820
- tool_calls? }`, with `content` null when the reply is only tool calls.
857
+ tool_calls?, reasoning_details? }`, with `content` null when the reply is
858
+ only tool calls. `reasoning_details` holds the model's reasoning blocks
859
+ (some encrypted) when it returned any; append the message unchanged so the
860
+ model can pick its reasoning back up on the next call.
821
861
  `reasoning` is the model's reasoning summary when it gave one, for
822
862
  display; it is not part of `message`. `finishReason` is `'stop'`,
823
863
  `'tool_calls'` or `'length'`. `usage` carries `prompt_tokens`,
package/dist/castle.d.ts CHANGED
@@ -33,7 +33,7 @@ export { flushSaves, hasPendingSave, onSaveState, writeFile, } from "./saveQueue
33
33
  export type { SaveState } from "./saveQueue";
34
34
  export { SharedStorage, Storage } from "./storage";
35
35
  export { Store } from "./store";
36
- export type { CastleStoreApi, StoreBoardApi, StoreBoardScore, StoreListEntry, StoreListOptions, StoreListPage, StoreScopeApi, } from "./store";
36
+ export type { CastleStoreApi, StoreBoardApi, StoreBoardScore, StoreListEntry, StoreListOptions, StoreListPage, StoreAllowance, StoreScopeApi, StoreUsage, } from "./store";
37
37
  export { Time } from "./time";
38
38
  export type { CastleClockZone, CastleDateParts, CastleTimeApi } from "./time";
39
39
  export type { Json } from "./types";
@@ -11,6 +11,17 @@ export interface StoreUsage {
11
11
  rowCount: number;
12
12
  byteCount: number;
13
13
  }
14
+ /** One storage allowance: what is stored against it, and its limits. */
15
+ export interface StoreAllowance {
16
+ keyCount: number;
17
+ keyLimit: number;
18
+ byteCount: number;
19
+ byteLimit: number;
20
+ }
21
+ export interface StoreAllowances {
22
+ player: StoreAllowance;
23
+ shared: StoreAllowance;
24
+ }
14
25
  export interface StoreBoardEntry {
15
26
  subject: string;
16
27
  score: number;
@@ -201,6 +212,7 @@ export interface AgentMessage {
201
212
  tool_calls?: AgentToolCall[];
202
213
  tool_call_id?: string;
203
214
  name?: string;
215
+ reasoning_details?: Json[];
204
216
  }
205
217
  export interface AgentTool {
206
218
  type: "function";
@@ -428,6 +440,7 @@ export interface CommandParams {
428
440
  board: string;
429
441
  subject?: string | null;
430
442
  };
443
+ "cauldronStorage.usage": Record<string, never>;
431
444
  "lifecycle.restoreState": Record<string, never>;
432
445
  }
433
446
  export interface CommandResult {
@@ -525,6 +538,9 @@ export interface CommandResult {
525
538
  "cauldronStorage.boardGet": {
526
539
  entry: StoreBoardEntry | null;
527
540
  };
541
+ "cauldronStorage.usage": {
542
+ usage: StoreAllowances;
543
+ };
528
544
  "lifecycle.restoreState": LifecycleRestoreStateResult;
529
545
  }
530
546
  export interface UserPayload {
package/dist/runtime.js CHANGED
@@ -8,6 +8,8 @@ const HOST_CAPTURE_REQUEST = "castle-capture-cover";
8
8
  const HOST_CAPTURE_RESULT = "castle-capture-cover-result";
9
9
  let ws = null;
10
10
  let logBuffer = [];
11
+ // The shell's play log keeps the same number of lines.
12
+ const MAX_BUFFERED_MESSAGES = 500;
11
13
  let nextRequestId = 1;
12
14
  const pendingRequests = new Map();
13
15
  // Local-dev command channel: the `castle-web serve` dev server is the host, so
@@ -306,6 +308,11 @@ function sendMsg(msg) {
306
308
  }
307
309
  else {
308
310
  logBuffer.push(msg);
311
+ // A deck logging every frame fills this while no socket is open, so the
312
+ // oldest messages are dropped.
313
+ if (logBuffer.length > MAX_BUFFERED_MESSAGES) {
314
+ logBuffer.splice(0, logBuffer.length - MAX_BUFFERED_MESSAGES);
315
+ }
309
316
  }
310
317
  }
311
318
  function sendLocalRequest(msg) {
package/dist/store.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { StoreAllowance } from "./commands";
1
2
  import type { Json } from "./types";
2
3
  export interface StoreListOptions {
3
4
  /** Only keys beginning with this. Omit for every key in the scope. */
@@ -52,6 +53,16 @@ export interface StoreBoardApi {
52
53
  /** One player's score, defaulting to the current player. Null if unset. */
53
54
  get(userId?: string): Promise<StoreBoardScore | null>;
54
55
  }
56
+ export type { StoreAllowance };
57
+ export interface StoreUsage {
58
+ /**
59
+ * The current player's own data in this deck: their `Store.me` and
60
+ * `Store.private` keys, their board scores and their `Characters` playthrough.
61
+ */
62
+ player: StoreAllowance;
63
+ /** `Store.deck`: one allowance shared by every player of the deck. */
64
+ shared: StoreAllowance;
65
+ }
55
66
  export interface CastleStoreApi {
56
67
  /** Shared by everyone playing the deck. */
57
68
  readonly deck: StoreScopeApi;
@@ -63,5 +74,11 @@ export interface CastleStoreApi {
63
74
  readonly private: StoreScopeApi;
64
75
  /** A leaderboard. */
65
76
  board(name: string): StoreBoardApi;
77
+ /**
78
+ * How much of the current player's allowance, and of the deck's shared one, is
79
+ * used. Includes the `set` and `remove` calls made before it, even ones still
80
+ * being batched; an `increment` or board `submit` is included once awaited.
81
+ */
82
+ usage(): Promise<StoreUsage>;
66
83
  }
67
84
  export declare const Store: CastleStoreApi;
package/dist/store.js CHANGED
@@ -300,6 +300,15 @@ function createBoard(name) {
300
300
  },
301
301
  };
302
302
  }
303
+ async function usage() {
304
+ // Writes still waiting out the coalescing window would otherwise be missing from
305
+ // the answer, so a deck that saves and then asks would see its old usage. Send
306
+ // them now; flushBucket never rejects, and a write that fails reports to its own
307
+ // caller.
308
+ await Promise.all(Array.from(buckets.values(), (bucket) => flushBucket(bucket, false)));
309
+ const { usage: allowances } = await hostRequest("cauldronStorage.usage", {});
310
+ return { player: allowances.player, shared: allowances.shared };
311
+ }
303
312
  export const Store = {
304
313
  deck: createScope("deck", null),
305
314
  me: createScope("user", null),
@@ -310,4 +319,5 @@ export const Store = {
310
319
  board(name) {
311
320
  return createBoard(name);
312
321
  },
322
+ usage,
313
323
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-sdk",
3
- "version": "0.4.41",
3
+ "version": "0.4.43",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/castle-xyz/castle-experimental-web.git"