@band-ai/band-sdk-core 0.5.0 → 0.6.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/README.md CHANGED
@@ -107,6 +107,36 @@ switch (tracker.recordRoomParticipantsJoinFailed("room-1", ticket, false)) {
107
107
  }
108
108
  ```
109
109
 
110
+ ### `Session` — WebSocket reconnect state machine
111
+
112
+ `Session`/`SessionPolicy` are a sans-io session state machine plus
113
+ reconnect backoff/jitter policy; `classifyClose`/`classifyUpgrade`
114
+ classify a WebSocket close code or HTTP upgrade-rejection status.
115
+ `Session` never sleeps, connects, or closes a socket itself — the caller
116
+ drives its own transport and reports what happened through `onConnected`/
117
+ `onSocketClose`/`onUpgradeRejected`/`onSupersede`. Its epoch tickets are
118
+ JavaScript `bigint` values, matching `SubscriptionTracker`'s. Confirmed
119
+ decisions:
120
+ [`runtime-state-policy.md`](https://github.com/band-ai/band-sdk-core/blob/main/crates/core/docs/runtime-state-policy.md)'s
121
+ `## Session` section.
122
+
123
+ ### `Session` lifecycle
124
+
125
+ ```ts
126
+ import { Session, SessionPolicy } from "@band-ai/band-sdk-core";
127
+
128
+ const session = new Session(SessionPolicy.default());
129
+ const epoch = session.beginAttempt(0.0);
130
+ if (epoch === undefined) throw new Error("fresh session must yield an epoch");
131
+
132
+ const connected = session.onConnected(epoch, 0.0);
133
+ if (connected.state !== "up") throw new Error("expected up");
134
+
135
+ const disconnected = session.onSocketClose(epoch, 5.0, 1006, 0.5);
136
+ if (disconnected.state !== "reconnecting") throw new Error("expected reconnecting");
137
+ if (disconnected.retryAfterS === undefined) throw new Error("expected a retry delay");
138
+ ```
139
+
110
140
  ### `validateMemoryTypeForSystem(system, type, traceContext?)`
111
141
 
112
142
  Validates the canonical memory taxonomy — design decisions:
package/band_sdk_core.js CHANGED
@@ -348,6 +348,246 @@ class RetryTracker {
348
348
  if (Symbol.dispose) RetryTracker.prototype[Symbol.dispose] = RetryTracker.prototype.free;
349
349
  exports.RetryTracker = RetryTracker;
350
350
 
351
+ /**
352
+ * One WebSocket session's state machine. Behavior contract:
353
+ * `docs/runtime-state-policy.md`'s `## Session` section.
354
+ */
355
+ class Session {
356
+ __destroy_into_raw() {
357
+ const ptr = this.__wbg_ptr;
358
+ this.__wbg_ptr = 0;
359
+ SessionFinalization.unregister(this);
360
+ return ptr;
361
+ }
362
+ free() {
363
+ const ptr = this.__destroy_into_raw();
364
+ wasm.__wbg_session_free(ptr, 0);
365
+ }
366
+ /**
367
+ * @param {number} nowS
368
+ * @returns {bigint | undefined}
369
+ */
370
+ beginAttempt(nowS) {
371
+ const ret = wasm.session_beginAttempt(this.__wbg_ptr, nowS);
372
+ if (ret[3]) {
373
+ throw takeFromExternrefTable0(ret[2]);
374
+ }
375
+ return ret[0] === 0 ? undefined : BigInt.asUintN(64, ret[1]);
376
+ }
377
+ /**
378
+ * @returns {string}
379
+ */
380
+ end() {
381
+ let deferred1_0;
382
+ let deferred1_1;
383
+ try {
384
+ const ret = wasm.session_end(this.__wbg_ptr);
385
+ deferred1_0 = ret[0];
386
+ deferred1_1 = ret[1];
387
+ return getStringFromWasm0(ret[0], ret[1]);
388
+ } finally {
389
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
390
+ }
391
+ }
392
+ /**
393
+ * @param {SessionPolicy} policy
394
+ */
395
+ constructor(policy) {
396
+ _assertClass(policy, SessionPolicy);
397
+ const ret = wasm.session_new(policy.__wbg_ptr);
398
+ this.__wbg_ptr = ret;
399
+ SessionFinalization.register(this, this.__wbg_ptr, this);
400
+ return this;
401
+ }
402
+ /**
403
+ * @param {bigint} epoch
404
+ * @param {number} nowS
405
+ * @returns {SessionOutcome}
406
+ */
407
+ onConnected(epoch, nowS) {
408
+ const ret = wasm.session_onConnected(this.__wbg_ptr, epoch, nowS);
409
+ if (ret[2]) {
410
+ throw takeFromExternrefTable0(ret[1]);
411
+ }
412
+ return SessionOutcome.__wrap(ret[0]);
413
+ }
414
+ /**
415
+ * @param {bigint} epoch
416
+ * @param {number} nowS
417
+ * @param {number | null | undefined} closeCode
418
+ * @param {number} jitterSample
419
+ * @returns {SessionOutcome}
420
+ */
421
+ onSocketClose(epoch, nowS, closeCode, jitterSample) {
422
+ const ret = wasm.session_onSocketClose(this.__wbg_ptr, epoch, nowS, !isLikeNone(closeCode), isLikeNone(closeCode) ? 0 : closeCode, jitterSample);
423
+ if (ret[2]) {
424
+ throw takeFromExternrefTable0(ret[1]);
425
+ }
426
+ return SessionOutcome.__wrap(ret[0]);
427
+ }
428
+ /**
429
+ * @param {number} nowS
430
+ * @param {boolean} retryable
431
+ * @param {number | null | undefined} retryAfterS
432
+ * @param {number} jitterSample
433
+ * @returns {SessionOutcome}
434
+ */
435
+ onSupersede(nowS, retryable, retryAfterS, jitterSample) {
436
+ const ret = wasm.session_onSupersede(this.__wbg_ptr, nowS, retryable, !isLikeNone(retryAfterS), isLikeNone(retryAfterS) ? 0 : retryAfterS, jitterSample);
437
+ if (ret[2]) {
438
+ throw takeFromExternrefTable0(ret[1]);
439
+ }
440
+ return SessionOutcome.__wrap(ret[0]);
441
+ }
442
+ /**
443
+ * @param {bigint} epoch
444
+ * @param {number} nowS
445
+ * @param {number} status
446
+ * @param {number | null | undefined} retryAfterS
447
+ * @param {number} jitterSample
448
+ * @returns {SessionOutcome}
449
+ */
450
+ onUpgradeRejected(epoch, nowS, status, retryAfterS, jitterSample) {
451
+ const ret = wasm.session_onUpgradeRejected(this.__wbg_ptr, epoch, nowS, status, !isLikeNone(retryAfterS), isLikeNone(retryAfterS) ? 0 : retryAfterS, jitterSample);
452
+ if (ret[2]) {
453
+ throw takeFromExternrefTable0(ret[1]);
454
+ }
455
+ return SessionOutcome.__wrap(ret[0]);
456
+ }
457
+ /**
458
+ * @returns {string}
459
+ */
460
+ get state() {
461
+ let deferred1_0;
462
+ let deferred1_1;
463
+ try {
464
+ const ret = wasm.session_state(this.__wbg_ptr);
465
+ deferred1_0 = ret[0];
466
+ deferred1_1 = ret[1];
467
+ return getStringFromWasm0(ret[0], ret[1]);
468
+ } finally {
469
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
470
+ }
471
+ }
472
+ }
473
+ if (Symbol.dispose) Session.prototype[Symbol.dispose] = Session.prototype.free;
474
+ exports.Session = Session;
475
+
476
+ /**
477
+ * The settled outcome of one `Session` call.
478
+ */
479
+ class SessionOutcome {
480
+ static __wrap(ptr) {
481
+ const obj = Object.create(SessionOutcome.prototype);
482
+ obj.__wbg_ptr = ptr;
483
+ SessionOutcomeFinalization.register(obj, obj.__wbg_ptr, obj);
484
+ return obj;
485
+ }
486
+ __destroy_into_raw() {
487
+ const ptr = this.__wbg_ptr;
488
+ this.__wbg_ptr = 0;
489
+ SessionOutcomeFinalization.unregister(this);
490
+ return ptr;
491
+ }
492
+ free() {
493
+ const ptr = this.__destroy_into_raw();
494
+ wasm.__wbg_sessionoutcome_free(ptr, 0);
495
+ }
496
+ /**
497
+ * @returns {string | undefined}
498
+ */
499
+ get deadReason() {
500
+ const ret = wasm.sessionoutcome_deadReason(this.__wbg_ptr);
501
+ let v1;
502
+ if (ret[0] !== 0) {
503
+ v1 = getStringFromWasm0(ret[0], ret[1]);
504
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
505
+ }
506
+ return v1;
507
+ }
508
+ /**
509
+ * @returns {number | undefined}
510
+ */
511
+ get retryAfterS() {
512
+ const ret = wasm.sessionoutcome_retryAfterS(this.__wbg_ptr);
513
+ return ret[0] === 0 ? undefined : ret[1];
514
+ }
515
+ /**
516
+ * @returns {boolean}
517
+ */
518
+ get stale() {
519
+ const ret = wasm.sessionoutcome_stale(this.__wbg_ptr);
520
+ return ret !== 0;
521
+ }
522
+ /**
523
+ * @returns {string}
524
+ */
525
+ get state() {
526
+ let deferred1_0;
527
+ let deferred1_1;
528
+ try {
529
+ const ret = wasm.sessionoutcome_state(this.__wbg_ptr);
530
+ deferred1_0 = ret[0];
531
+ deferred1_1 = ret[1];
532
+ return getStringFromWasm0(ret[0], ret[1]);
533
+ } finally {
534
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
535
+ }
536
+ }
537
+ }
538
+ if (Symbol.dispose) SessionOutcome.prototype[Symbol.dispose] = SessionOutcome.prototype.free;
539
+ exports.SessionOutcome = SessionOutcome;
540
+
541
+ /**
542
+ * Reconnect backoff/jitter policy, plus the graduated rapid-disconnect
543
+ * cooldown ladder. Behavior contract: `docs/runtime-state-policy.md`'s
544
+ * `## Session` section.
545
+ */
546
+ class SessionPolicy {
547
+ static __wrap(ptr) {
548
+ const obj = Object.create(SessionPolicy.prototype);
549
+ obj.__wbg_ptr = ptr;
550
+ SessionPolicyFinalization.register(obj, obj.__wbg_ptr, obj);
551
+ return obj;
552
+ }
553
+ __destroy_into_raw() {
554
+ const ptr = this.__wbg_ptr;
555
+ this.__wbg_ptr = 0;
556
+ SessionPolicyFinalization.unregister(this);
557
+ return ptr;
558
+ }
559
+ free() {
560
+ const ptr = this.__destroy_into_raw();
561
+ wasm.__wbg_sessionpolicy_free(ptr, 0);
562
+ }
563
+ /**
564
+ * A recommended, not final, default.
565
+ * @returns {SessionPolicy}
566
+ */
567
+ static default() {
568
+ const ret = wasm.sessionpolicy_default();
569
+ return SessionPolicy.__wrap(ret);
570
+ }
571
+ /**
572
+ * `config` is any plain object with the fields declared in
573
+ * `index.d.ts`'s `SessionPolicyConfig`; grouped into one value rather
574
+ * than a flat, many-argument constructor.
575
+ * @param {any} config
576
+ * @param {any} traceContext
577
+ */
578
+ constructor(config, traceContext) {
579
+ const ret = wasm.sessionpolicy_new(config, traceContext);
580
+ if (ret[2]) {
581
+ throw takeFromExternrefTable0(ret[1]);
582
+ }
583
+ this.__wbg_ptr = ret[0];
584
+ SessionPolicyFinalization.register(this, this.__wbg_ptr, this);
585
+ return this;
586
+ }
587
+ }
588
+ if (Symbol.dispose) SessionPolicy.prototype[Symbol.dispose] = SessionPolicy.prototype.free;
589
+ exports.SessionPolicy = SessionPolicy;
590
+
351
591
  /**
352
592
  * Transport-independent subscription decisions for one agent session.
353
593
  */
@@ -606,6 +846,35 @@ class SubscriptionTracker {
606
846
  if (Symbol.dispose) SubscriptionTracker.prototype[Symbol.dispose] = SubscriptionTracker.prototype.free;
607
847
  exports.SubscriptionTracker = SubscriptionTracker;
608
848
 
849
+ /**
850
+ * Classify a WebSocket close code. Returns `[terminal, delayRange]`.
851
+ * @param {number | null} [closeCode]
852
+ * @returns {Array<any>}
853
+ */
854
+ function classifyClose(closeCode) {
855
+ const ret = wasm.classifyClose(!isLikeNone(closeCode), isLikeNone(closeCode) ? 0 : closeCode);
856
+ if (ret[2]) {
857
+ throw takeFromExternrefTable0(ret[1]);
858
+ }
859
+ return takeFromExternrefTable0(ret[0]);
860
+ }
861
+ exports.classifyClose = classifyClose;
862
+
863
+ /**
864
+ * Classify an HTTP upgrade-rejection status code. Returns
865
+ * `[terminal, delayRange]`.
866
+ * @param {number} status
867
+ * @returns {Array<any>}
868
+ */
869
+ function classifyUpgrade(status) {
870
+ const ret = wasm.classifyUpgrade(status);
871
+ if (ret[2]) {
872
+ throw takeFromExternrefTable0(ret[1]);
873
+ }
874
+ return takeFromExternrefTable0(ret[0]);
875
+ }
876
+ exports.classifyUpgrade = classifyUpgrade;
877
+
609
878
  /**
610
879
  * Validate and normalize an inbound platform event payload.
611
880
  *
@@ -744,6 +1013,10 @@ function __wbg_get_imports() {
744
1013
  const ret = Object.entries(arg0);
745
1014
  return ret;
746
1015
  },
1016
+ __wbg_get_971a0c45d172643f: function() { return handleError(function (arg0, arg1) {
1017
+ const ret = Reflect.get(arg0, arg1);
1018
+ return ret;
1019
+ }, arguments); },
747
1020
  __wbg_get_c0c8f8d7da0c03dd: function(arg0, arg1) {
748
1021
  const ret = arg0[arg1 >>> 0];
749
1022
  return ret;
@@ -908,6 +1181,15 @@ const ParticipantRosterFinalization = (typeof FinalizationRegistry === 'undefine
908
1181
  const RetryTrackerFinalization = (typeof FinalizationRegistry === 'undefined')
909
1182
  ? { register: () => {}, unregister: () => {} }
910
1183
  : new FinalizationRegistry(ptr => wasm.__wbg_retrytracker_free(ptr, 1));
1184
+ const SessionFinalization = (typeof FinalizationRegistry === 'undefined')
1185
+ ? { register: () => {}, unregister: () => {} }
1186
+ : new FinalizationRegistry(ptr => wasm.__wbg_session_free(ptr, 1));
1187
+ const SessionOutcomeFinalization = (typeof FinalizationRegistry === 'undefined')
1188
+ ? { register: () => {}, unregister: () => {} }
1189
+ : new FinalizationRegistry(ptr => wasm.__wbg_sessionoutcome_free(ptr, 1));
1190
+ const SessionPolicyFinalization = (typeof FinalizationRegistry === 'undefined')
1191
+ ? { register: () => {}, unregister: () => {} }
1192
+ : new FinalizationRegistry(ptr => wasm.__wbg_sessionpolicy_free(ptr, 1));
911
1193
  const SubscriptionTrackerFinalization = (typeof FinalizationRegistry === 'undefined')
912
1194
  ? { register: () => {}, unregister: () => {} }
913
1195
  : new FinalizationRegistry(ptr => wasm.__wbg_subscriptiontracker_free(ptr, 1));
@@ -918,6 +1200,12 @@ function addToExternrefTable0(obj) {
918
1200
  return idx;
919
1201
  }
920
1202
 
1203
+ function _assertClass(instance, klass) {
1204
+ if (!(instance instanceof klass)) {
1205
+ throw new Error(`expected instance of ${klass.name}`);
1206
+ }
1207
+ }
1208
+
921
1209
  function debugString(val) {
922
1210
  // primitive types
923
1211
  const type = typeof val;
Binary file
package/index.d.ts CHANGED
@@ -169,6 +169,108 @@ export type AgentTopicStatus =
169
169
 
170
170
  export type LeaveOutcome = "left" | "failed" | "unknown";
171
171
 
172
+ export type SessionState = "connecting" | "up" | "reconnecting" | "dead";
173
+
174
+ export type DeadReason = "classified" | "rapid_disconnect";
175
+
176
+ /**
177
+ * Classify a WebSocket close code. Returns `[terminal, delayRange]`.
178
+ * Throws a plain `Error` if `closeCode` is not an integer in `0..=65535`
179
+ * (or `undefined`).
180
+ */
181
+ export function classifyClose(
182
+ closeCode: number | undefined,
183
+ ): [boolean, [number, number] | undefined];
184
+
185
+ /**
186
+ * Classify an HTTP upgrade-rejection status code. Returns
187
+ * `[terminal, delayRange]`. Throws a plain `Error` if `status` is not an
188
+ * integer in `0..=65535`.
189
+ */
190
+ export function classifyUpgrade(
191
+ status: number,
192
+ ): [boolean, [number, number] | undefined];
193
+
194
+ /** The settled outcome of one {@link Session} call. */
195
+ export class SessionOutcome {
196
+ private constructor();
197
+ readonly state: SessionState;
198
+ readonly retryAfterS: number | undefined;
199
+ readonly deadReason: DeadReason | undefined;
200
+ readonly stale: boolean;
201
+ }
202
+
203
+ /** {@link SessionPolicy}'s constructor fields. */
204
+ export interface SessionPolicyConfig {
205
+ baseDelayS: number;
206
+ factor: number;
207
+ maxDelayS: number;
208
+ stableResetS: number;
209
+ rapidDisconnectUptimeS: number;
210
+ rapidWindowS: number;
211
+ rapidFirstMinDelayS: number;
212
+ rapidSecondMinDelayS: number;
213
+ rapidCooldownBaseS: number;
214
+ rapidCooldownStepS: number;
215
+ rapidCooldownMaxS: number;
216
+ rapidThreshold: number;
217
+ }
218
+
219
+ /**
220
+ * Reconnect backoff/jitter policy, plus the graduated rapid-disconnect
221
+ * cooldown ladder. A recommended, not final, starting design -- see
222
+ * `docs/runtime-state-policy.md`'s `## Session` section.
223
+ *
224
+ * Throws a native `Error` with `.issues`/`.traceContext` if `factor < 1.0`
225
+ * or `baseDelayS > maxDelayS`. Throws a plain `Error` if `config` is
226
+ * missing a field or a field is not a finite, non-negative number
227
+ * (`rapidThreshold` must also be an integer).
228
+ */
229
+ export class SessionPolicy {
230
+ constructor(config: SessionPolicyConfig, traceContext?: string | null);
231
+ /** A recommended, not final, default. */
232
+ static default(): SessionPolicy;
233
+ }
234
+
235
+ /**
236
+ * One WebSocket session's state machine: which epoch (connection attempt)
237
+ * is live, the current lifecycle state, and the reconnect/rapid-disconnect
238
+ * bookkeeping. Single-writer -- exactly one owner drives a given `Session`
239
+ * at a time.
240
+ *
241
+ * `nowS`/`retryAfterS` each throw a plain `Error` if not a finite,
242
+ * non-negative number; `jitterSample` throws unless it's a finite number
243
+ * in `[0.0, 1.0]` (a fraction, not an open-ended time value);
244
+ * `closeCode`/`status` throw if not an integer in `0..=65535` (or
245
+ * `undefined` for `closeCode`).
246
+ */
247
+ export class Session {
248
+ constructor(policy: SessionPolicy);
249
+ readonly state: SessionState;
250
+ beginAttempt(nowS: number): bigint | undefined;
251
+ onConnected(epoch: bigint, nowS: number): SessionOutcome;
252
+ onSocketClose(
253
+ epoch: bigint,
254
+ nowS: number,
255
+ closeCode: number | undefined,
256
+ jitterSample: number,
257
+ ): SessionOutcome;
258
+ onUpgradeRejected(
259
+ epoch: bigint,
260
+ nowS: number,
261
+ status: number,
262
+ retryAfterS: number | undefined,
263
+ jitterSample: number,
264
+ ): SessionOutcome;
265
+ onSupersede(
266
+ nowS: number,
267
+ retryable: boolean,
268
+ retryAfterS: number | undefined,
269
+ jitterSample: number,
270
+ ): SessionOutcome;
271
+ end(): SessionState;
272
+ }
273
+
172
274
  /** Transport-independent subscription decisions for one agent session. */
173
275
  export class SubscriptionTracker {
174
276
  constructor();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@band-ai/band-sdk-core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared Band event-payload validation",
5
5
  "license": "MIT",
6
6
  "repository": {