@pouchy_ai/world-sdk 0.28.0 → 0.29.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 +38 -0
- package/README.md +28 -2
- package/dist/index.d.ts +34 -1
- package/dist/index.js +33 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @pouchy_ai/world-sdk
|
|
2
2
|
|
|
3
|
+
## 0.29.0
|
|
4
|
+
|
|
5
|
+
- **`skippedRoles[].code`** — the machine half of "why this role did not
|
|
6
|
+
speak", as a closed `WorldSkipCode` union, plus `skipSpentNoModelCall(code)`
|
|
7
|
+
for the one derived question every consumer asks of it.
|
|
8
|
+
|
|
9
|
+
`reason` has always been a human sentence and stays one; nothing should key
|
|
10
|
+
off it. This exists because an integrator, given the reason prefixes in a
|
|
11
|
+
letter (the spec published none), built a cost model on
|
|
12
|
+
`reason.startsWith('turn error:')`. Their own words for the exposure: rename
|
|
13
|
+
that prefix and "we would not error — we would quietly file that class as
|
|
14
|
+
costing nothing." The same argument the rejection taxonomy made for
|
|
15
|
+
`rejectedEffects[].code`, applied to the sibling array that missed it.
|
|
16
|
+
|
|
17
|
+
Twelve codes. Eight of them mean no model call was made — `not_focused`,
|
|
18
|
+
`role_cap_reached`, `no_actor_bound`, `deadline_exhausted`, `not_admitted`,
|
|
19
|
+
`actor_unavailable`, `session_busy`, `content_policy`. Two mean a model ran or
|
|
20
|
+
may have: `turn_error` (deliberately does NOT promise a call happened —
|
|
21
|
+
context assembly failing and a provider dying mid-stream both land there) and
|
|
22
|
+
`no_message`. Two are beats where the role DID speak and the line was
|
|
23
|
+
withheld: `effect_refused`, `conflict_undelivered`.
|
|
24
|
+
|
|
25
|
+
Additive and optional: absent from a server predating the taxonomy, which
|
|
26
|
+
reads as unknown, never as a value. `skipSpentNoModelCall` returns false for
|
|
27
|
+
an unknown code and for `turn_error`, on the principle that a cost model
|
|
28
|
+
which under-counts silently is worse than one that over-counts visibly.
|
|
29
|
+
|
|
30
|
+
## 0.28.1
|
|
31
|
+
|
|
32
|
+
- **`WorldApiError.errorId`** — the server's lookup reference (`err_…`) for a
|
|
33
|
+
persisted 5xx, when the envelope carried one. An uncaught world-plane fault is
|
|
34
|
+
sanitized to "An unexpected error occurred." before it leaves the server; the
|
|
35
|
+
`errorId` on that envelope is the one thing an operator can resolve to the
|
|
36
|
+
real cause, and the client read `error` / `code` / `retryAfterSec` and dropped
|
|
37
|
+
it — so a 5xx gave you `500 server_error` and nothing to quote. Additive;
|
|
38
|
+
absent on every 4xx and on a 5xx that was not persisted; string-only, like
|
|
39
|
+
`serverCode`. Quote it in a support request.
|
|
40
|
+
|
|
3
41
|
## 0.28.0
|
|
4
42
|
|
|
5
43
|
- **`WorldApiError.serverCode`** — the server's own machine-readable `code`
|
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ answers 422 rather than dropping them quietly.
|
|
|
121
121
|
**Focusing a beat on some of the cast.** By default every bound role answers a
|
|
122
122
|
beat (up to the server cap). For an interview or a one-on-one scene, pass
|
|
123
123
|
`focusRoles` and only those roles run — the rest are not billed and are
|
|
124
|
-
recorded in `skippedRoles`
|
|
124
|
+
recorded in `skippedRoles` with `code: 'not_focused'`:
|
|
125
125
|
|
|
126
126
|
```ts
|
|
127
127
|
const beat = await world.runTurn({
|
|
@@ -135,6 +135,31 @@ Shrink-only: the server intersects the focus with the bound story cast, so it
|
|
|
135
135
|
can never widen a beat, and a focus matching no bound role is refused as
|
|
136
136
|
`no selectable role` without spending anything.
|
|
137
137
|
|
|
138
|
+
**Why a role was passed over.** Each `skippedRoles` entry carries a `reason`
|
|
139
|
+
(the human line, which carries the sub-cause and is free to be reworded) and a
|
|
140
|
+
`code` — a closed `WorldSkipCode`. **Key off the code, never the reason.**
|
|
141
|
+
Reading an error string to decide what happened is how a message edit silently
|
|
142
|
+
reclassifies a whole class; this SDK ships the enum precisely so you do not
|
|
143
|
+
have to pattern-match prose.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { skipSpentNoModelCall } from '@pouchy_ai/world-sdk';
|
|
147
|
+
|
|
148
|
+
for (const s of beat.skippedRoles) {
|
|
149
|
+
if (s.code === undefined) audit.unknown(s.roleId, s.reason); // old server
|
|
150
|
+
else if (skipSpentNoModelCall(s.code)) cost.free(s.roleId, s.code);
|
|
151
|
+
else cost.mayHaveSpent(s.roleId, s.code);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`skipSpentNoModelCall` answers false for `turn_error` even though it often is
|
|
156
|
+
free: context assembly failing and a provider dying mid-stream both land there
|
|
157
|
+
and cannot be told apart afterwards, so a cost model that counted it free would
|
|
158
|
+
under-count silently. It answers false for an unknown code for the same reason.
|
|
159
|
+
|
|
160
|
+
To read a beat, subtract: a role in `selectedRoles` and absent from
|
|
161
|
+
`skippedRoles` chose silence; a role listed here never ran.
|
|
162
|
+
|
|
138
163
|
**Resuming after a crash.** `getTurn` returns the same fields the live result
|
|
139
164
|
did — `nextOptions` included — so a recovered session can offer the audience
|
|
140
165
|
the choices it was about to. To catch up on beats you missed entirely, store
|
|
@@ -251,7 +276,8 @@ an `approved` editorial can be exported.
|
|
|
251
276
|
`newTurnId` / `isReservedTurnId` (idempotency), `describeTurn` (read a result
|
|
252
277
|
without guessing), `WorldApiError` with typed codes, `.retryable`,
|
|
253
278
|
`serverCode` (the server's own `code` on a refusal — which 409, since `code`
|
|
254
|
-
maps the status),
|
|
279
|
+
maps the status), `errorId` (the server's `err_…` lookup reference on a
|
|
280
|
+
persisted 5xx — quote it in a support request), and `rejectedEffects` (which of YOUR proposed ops the world
|
|
255
281
|
refused, and why — validation is all-or-nothing, so one bad op moves nothing).
|
|
256
282
|
|
|
257
283
|
## When a signed door refuses you
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* a project is running — which is exactly the field you reach for when a
|
|
5
5
|
* customer's integration behaves like an older SDK than they say they have.
|
|
6
6
|
* It sat at '0.1.0' for eight releases before anything compared the two. */
|
|
7
|
-
export declare const WORLD_SDK_VERSION = "0.
|
|
7
|
+
export declare const WORLD_SDK_VERSION = "0.29.0";
|
|
8
8
|
export declare const DEFAULT_BASE_URL = "https://pouchy.ai/v1";
|
|
9
9
|
/** One direction a beat could take. The WHOLE of what a deliberation shows a
|
|
10
10
|
* player: no reasoning, no role secrets, no simulated effects, no scores. */
|
|
@@ -61,6 +61,13 @@ export declare class WorldApiError extends Error {
|
|
|
61
61
|
* dropped `code` on the floor, so the one carrier an integrator could
|
|
62
62
|
* branch on never arrived. */
|
|
63
63
|
readonly serverCode?: string;
|
|
64
|
+
/** The server's lookup reference for a persisted 5xx (`err_…`), when the
|
|
65
|
+
* envelope carried one. A world-plane fault is sanitized to "An unexpected
|
|
66
|
+
* error occurred." before it leaves the server; the `errorId` is the ONE
|
|
67
|
+
* greppable thing the operator can resolve to the real cause — quote it in
|
|
68
|
+
* a support request. Absent on every 4xx and on a 5xx that was not
|
|
69
|
+
* persisted (0.28.1; until then the client dropped it). */
|
|
70
|
+
readonly errorId?: string;
|
|
64
71
|
readonly retryAfterSec?: number;
|
|
65
72
|
/** Why the world refused the effects you proposed — per op, with the
|
|
66
73
|
* server's own reason and code.
|
|
@@ -90,6 +97,7 @@ export declare class WorldApiError extends Error {
|
|
|
90
97
|
message: string;
|
|
91
98
|
detail?: string;
|
|
92
99
|
serverCode?: string;
|
|
100
|
+
errorId?: string;
|
|
93
101
|
retryAfterSec?: number;
|
|
94
102
|
rejectedEffects?: readonly {
|
|
95
103
|
readonly index: number;
|
|
@@ -168,6 +176,7 @@ export interface WorldTurnResult {
|
|
|
168
176
|
skippedRoles: Array<{
|
|
169
177
|
roleId: string;
|
|
170
178
|
reason: string;
|
|
179
|
+
code?: WorldSkipCode;
|
|
171
180
|
}>;
|
|
172
181
|
nextOptions: Array<{
|
|
173
182
|
branchId: string;
|
|
@@ -373,6 +382,7 @@ export interface WorldTurnReadback {
|
|
|
373
382
|
skippedRoles?: Array<{
|
|
374
383
|
roleId: string;
|
|
375
384
|
reason: string;
|
|
385
|
+
code?: WorldSkipCode;
|
|
376
386
|
}>;
|
|
377
387
|
repairs?: Array<{
|
|
378
388
|
roleId: string;
|
|
@@ -517,6 +527,29 @@ export interface EditorialDraftRow {
|
|
|
517
527
|
* content problem. */
|
|
518
528
|
/** The shared refusal taxonomy (Batch 7). Only `narrative_conflict` is content. */
|
|
519
529
|
export type WorldRejectionCode = 'narrative_conflict' | 'policy_rejection' | 'schema_rejection' | 'canon_rejection' | 'concurrency_rejection' | 'repair_failure';
|
|
530
|
+
/** WHY a role that was in the cast did not speak — the machine half of
|
|
531
|
+
* `skippedRoles[].reason`, added 2026-09-09.
|
|
532
|
+
*
|
|
533
|
+
* `reason` remains the human line and carries the sub-cause (which admission
|
|
534
|
+
* rule, which runtime error); it is free to be reworded, so do not key off it.
|
|
535
|
+
* This exists because an integrator built a cost model on
|
|
536
|
+
* `reason.startsWith('turn error:')` — a rename on the platform side would not
|
|
537
|
+
* have errored, it would have silently reclassified that whole class.
|
|
538
|
+
*
|
|
539
|
+
* No model call was made for the first eight; a model ran, or may have, for
|
|
540
|
+
* `turn_error` and `no_message`; the last two are beats where the role DID
|
|
541
|
+
* speak and the line was withheld. `skipSpentNoModelCall` below is that split,
|
|
542
|
+
* so a consumer does not re-derive it. Absent on turns from a server predating
|
|
543
|
+
* the taxonomy — read that as unknown, never as a value. */
|
|
544
|
+
export type WorldSkipCode = 'not_focused' | 'role_cap_reached' | 'no_actor_bound' | 'deadline_exhausted' | 'not_admitted' | 'actor_unavailable' | 'session_busy' | 'content_policy' | 'turn_error' | 'no_message' | 'effect_refused' | 'conflict_undelivered';
|
|
545
|
+
/** Did this skip happen before any model call — is the role's silence free?
|
|
546
|
+
*
|
|
547
|
+
* `turn_error` is deliberately NOT in the free set even though it often is:
|
|
548
|
+
* context assembly failing and a provider dying mid-stream both land there and
|
|
549
|
+
* cannot be told apart after the fact, so a cost model that counted it free
|
|
550
|
+
* would under-count silently. Returns false for an unknown code, for the same
|
|
551
|
+
* reason. */
|
|
552
|
+
export declare function skipSpentNoModelCall(code: WorldSkipCode | undefined): boolean;
|
|
520
553
|
/** True for every code except `narrative_conflict`. */
|
|
521
554
|
export declare function isDefectRejection(code: WorldRejectionCode | undefined): boolean;
|
|
522
555
|
export interface WorldInstanceMetrics {
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { createHash, createHmac, randomUUID } from 'node:crypto';
|
|
|
23
23
|
* a project is running — which is exactly the field you reach for when a
|
|
24
24
|
* customer's integration behaves like an older SDK than they say they have.
|
|
25
25
|
* It sat at '0.1.0' for eight releases before anything compared the two. */
|
|
26
|
-
export const WORLD_SDK_VERSION = '0.
|
|
26
|
+
export const WORLD_SDK_VERSION = '0.29.0';
|
|
27
27
|
export const DEFAULT_BASE_URL = 'https://pouchy.ai/v1';
|
|
28
28
|
/** Read the commit turn id out of an envelope, so the signature covers the id
|
|
29
29
|
* the server will commit under. The payload half is base64url JSON; the
|
|
@@ -71,6 +71,13 @@ export class WorldApiError extends Error {
|
|
|
71
71
|
* dropped `code` on the floor, so the one carrier an integrator could
|
|
72
72
|
* branch on never arrived. */
|
|
73
73
|
serverCode;
|
|
74
|
+
/** The server's lookup reference for a persisted 5xx (`err_…`), when the
|
|
75
|
+
* envelope carried one. A world-plane fault is sanitized to "An unexpected
|
|
76
|
+
* error occurred." before it leaves the server; the `errorId` is the ONE
|
|
77
|
+
* greppable thing the operator can resolve to the real cause — quote it in
|
|
78
|
+
* a support request. Absent on every 4xx and on a 5xx that was not
|
|
79
|
+
* persisted (0.28.1; until then the client dropped it). */
|
|
80
|
+
errorId;
|
|
74
81
|
retryAfterSec;
|
|
75
82
|
/** Why the world refused the effects you proposed — per op, with the
|
|
76
83
|
* server's own reason and code.
|
|
@@ -97,6 +104,8 @@ export class WorldApiError extends Error {
|
|
|
97
104
|
this.detail = input.detail;
|
|
98
105
|
if (input.serverCode !== undefined)
|
|
99
106
|
this.serverCode = input.serverCode;
|
|
107
|
+
if (input.errorId !== undefined)
|
|
108
|
+
this.errorId = input.errorId;
|
|
100
109
|
if (input.retryAfterSec !== undefined)
|
|
101
110
|
this.retryAfterSec = input.retryAfterSec;
|
|
102
111
|
if (input.rejectedEffects !== undefined)
|
|
@@ -297,6 +306,23 @@ export function describeTurn(result) {
|
|
|
297
306
|
` delivery ${result.deliveryStatus}`
|
|
298
307
|
};
|
|
299
308
|
}
|
|
309
|
+
/** Did this skip happen before any model call — is the role's silence free?
|
|
310
|
+
*
|
|
311
|
+
* `turn_error` is deliberately NOT in the free set even though it often is:
|
|
312
|
+
* context assembly failing and a provider dying mid-stream both land there and
|
|
313
|
+
* cannot be told apart after the fact, so a cost model that counted it free
|
|
314
|
+
* would under-count silently. Returns false for an unknown code, for the same
|
|
315
|
+
* reason. */
|
|
316
|
+
export function skipSpentNoModelCall(code) {
|
|
317
|
+
return (code === 'not_focused' ||
|
|
318
|
+
code === 'role_cap_reached' ||
|
|
319
|
+
code === 'no_actor_bound' ||
|
|
320
|
+
code === 'deadline_exhausted' ||
|
|
321
|
+
code === 'not_admitted' ||
|
|
322
|
+
code === 'actor_unavailable' ||
|
|
323
|
+
code === 'session_busy' ||
|
|
324
|
+
code === 'content_policy');
|
|
325
|
+
}
|
|
300
326
|
/** True for every code except `narrative_conflict`. */
|
|
301
327
|
export function isDefectRejection(code) {
|
|
302
328
|
return code !== undefined && code !== 'narrative_conflict';
|
|
@@ -940,12 +966,18 @@ export class PouchyWorldClient {
|
|
|
940
966
|
const serverCode = parsed && typeof parsed === 'object' && typeof parsed.code === 'string'
|
|
941
967
|
? parsed.code
|
|
942
968
|
: undefined;
|
|
969
|
+
// Same string-only rule as `serverCode`: the envelope's lookup ref, or
|
|
970
|
+
// nothing — never a coerced non-string.
|
|
971
|
+
const errorId = parsed && typeof parsed === 'object' && typeof parsed.errorId === 'string'
|
|
972
|
+
? parsed.errorId || undefined
|
|
973
|
+
: undefined;
|
|
943
974
|
throw new WorldApiError({
|
|
944
975
|
code: codeForStatus(response.status),
|
|
945
976
|
status: response.status,
|
|
946
977
|
message: `${method} ${path} → ${response.status}`,
|
|
947
978
|
...(detail !== undefined ? { detail } : {}),
|
|
948
979
|
...(serverCode !== undefined ? { serverCode } : {}),
|
|
980
|
+
...(errorId !== undefined ? { errorId } : {}),
|
|
949
981
|
...(retryAfterSec !== undefined ? { retryAfterSec } : {}),
|
|
950
982
|
...(rejectedEffects !== undefined ? { rejectedEffects } : {})
|
|
951
983
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/world-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Server-side TypeScript client for Pouchy World \u2014 story packages, world definitions, world sessions, coordinated turns, trusted events, replay verification and script drafts. Node only: it holds a project Secret Key and a source signing key, which never belong in a browser or a mobile app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|