@band-ai/band-sdk-core 1.2.1 → 2.1.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
@@ -148,6 +148,51 @@ native `Error` with the same `.issues`/`.traceContext` contract as
148
148
  `type` are independent issues, both reported when both strings are
149
149
  invalid.
150
150
 
151
+ ### One-shot delivery lifecycle
152
+
153
+ `evaluateDeliveryEvent`, `evaluateNextMessage`, `evaluateDrainCandidate`, and
154
+ `evaluateAdapterResult` are the stateless one-shot delivery lifecycle
155
+ decisions — design decisions live as rustdoc in
156
+ `crates/core/src/runtime/delivery.rs`. Each returns a plain tagged object;
157
+ there is no cross-invocation state, so there is no class here to construct.
158
+ Decision fields stay snake_case (message-wire vocabulary: `room_id`,
159
+ `sender_id`, `sender_type`, …), unlike this module's own camelCase
160
+ function/argument names, so the same shape returns identically from both
161
+ bindings.
162
+
163
+ `evaluateDeliveryEvent(eventType, roomId, payload, agentId, traceContext?)`
164
+ routes one inbound event: `"ignored"` for an unrecognized or out-of-scope
165
+ `eventType`; `"cleanup"` for `room_removed`/`room_deleted`; `"skip_self"` or
166
+ `"invocation"` for `message_created`, depending on whether the sender is
167
+ this agent (`sender_type === "Agent"` and `sender_id === agentId`).
168
+ `message_created` delegates to `validateEventPayload` for payload-shape
169
+ validation, so a malformed `payload` throws the same `Error`, and
170
+ additionally rejects an empty `payload.id` — a message with no identity
171
+ cannot be claimed or acknowledged. `room_removed`/`room_deleted` need no
172
+ payload shape and read `id` straight off the raw payload.
173
+
174
+ `roomId` resolves from the caller-supplied `roomId` first, else the payload's
175
+ own `chat_room_id` (message events) or `id` (room events). An empty string
176
+ counts as absent at both steps. Unresolvable throws an `Error` with one issue
177
+ on path `room_id`: code `missing` when the fallback field is absent or empty,
178
+ `wrong_type` when it is present but not a string.
179
+
180
+ `evaluateNextMessage(triggeringMessageId, nextMessageId)` compares the
181
+ triggering message against the platform's authoritative "next open message"
182
+ for a room (already fetched by the caller): `"no_pending"`,
183
+ `"already_processed"`, or `"ready_to_claim"`.
184
+
185
+ `evaluateDrainCandidate(candidate, seenIds, agentId)` classifies one
186
+ already-fetched drain candidate — `candidate` is `{id, sender_id,
187
+ sender_type}`, or `null`/`undefined` when the fetch returned nothing:
188
+ `"no_candidate"`, `"self_echo"` (checked before the snapshot, so an echo
189
+ never halts a drain), `"out_of_snapshot"` (stop), or `"drain"` (continue).
190
+ The caller's own bounded or unbounded loop owns the cap; this function
191
+ classifies one candidate per call.
192
+
193
+ `evaluateAdapterResult(roomId, messageId, succeeded)` maps an adapter
194
+ outcome to `"processed"` or `"failed"`.
195
+
151
196
  The public declaration is
152
197
  [`index.d.ts`](https://github.com/band-ai/band-sdk-core/blob/main/crates/wasm/index.d.ts).
153
198
 
package/band_sdk_core.js CHANGED
@@ -727,6 +727,13 @@ class SessionPolicy {
727
727
  const ptr = this.__destroy_into_raw();
728
728
  wasm.__wbg_sessionpolicy_free(ptr, 0);
729
729
  }
730
+ /**
731
+ * @returns {number}
732
+ */
733
+ get deadThresholdS() {
734
+ const ret = wasm.sessionpolicy_deadThresholdS(this.__wbg_ptr);
735
+ return ret;
736
+ }
730
737
  /**
731
738
  * A recommended, not final, default.
732
739
  * @returns {SessionPolicy}
@@ -735,6 +742,13 @@ class SessionPolicy {
735
742
  const ret = wasm.sessionpolicy_default();
736
743
  return SessionPolicy.__wrap(ret);
737
744
  }
745
+ /**
746
+ * @returns {number}
747
+ */
748
+ get heartbeatIntervalS() {
749
+ const ret = wasm.sessionpolicy_heartbeatIntervalS(this.__wbg_ptr);
750
+ return ret;
751
+ }
738
752
  /**
739
753
  * `config` is any plain object with the fields declared in
740
754
  * `index.d.ts`'s `SessionPolicyConfig`; grouped into one value rather
@@ -1192,6 +1206,82 @@ function classifyUpgrade(status) {
1192
1206
  }
1193
1207
  exports.classifyUpgrade = classifyUpgrade;
1194
1208
 
1209
+ /**
1210
+ * Adapter outcome -> ack decision. Throws a plain `Error` if `roomId`/
1211
+ * `messageId` is not a string.
1212
+ * @param {any} roomId
1213
+ * @param {any} messageId
1214
+ * @param {boolean} succeeded
1215
+ * @returns {any}
1216
+ */
1217
+ function evaluateAdapterResult(roomId, messageId, succeeded) {
1218
+ const ret = wasm.evaluateAdapterResult(roomId, messageId, succeeded);
1219
+ if (ret[2]) {
1220
+ throw takeFromExternrefTable0(ret[1]);
1221
+ }
1222
+ return takeFromExternrefTable0(ret[0]);
1223
+ }
1224
+ exports.evaluateAdapterResult = evaluateAdapterResult;
1225
+
1226
+ /**
1227
+ * Evaluate the routing decision for one inbound platform event.
1228
+ *
1229
+ * On failure throws a native `Error` with `.issues` (an array of
1230
+ * `{path, code, message}` objects) and `.traceContext`. A non-string
1231
+ * `eventType`/`roomId`/`agentId`/`traceContext`, or a `payload` that
1232
+ * cannot be converted, throws a plain `Error` without `.issues`.
1233
+ * @param {any} eventType
1234
+ * @param {any} roomId
1235
+ * @param {any} payload
1236
+ * @param {any} agentId
1237
+ * @param {any} traceContext
1238
+ * @returns {any}
1239
+ */
1240
+ function evaluateDeliveryEvent(eventType, roomId, payload, agentId, traceContext) {
1241
+ const ret = wasm.evaluateDeliveryEvent(eventType, roomId, payload, agentId, traceContext);
1242
+ if (ret[2]) {
1243
+ throw takeFromExternrefTable0(ret[1]);
1244
+ }
1245
+ return takeFromExternrefTable0(ret[0]);
1246
+ }
1247
+ exports.evaluateDeliveryEvent = evaluateDeliveryEvent;
1248
+
1249
+ /**
1250
+ * One step of the drain loop. `candidate` is `{id, sender_id,
1251
+ * sender_type}` or `null`/`undefined` when the host's fetch returned no
1252
+ * candidate; a malformed object throws a plain `Error`.
1253
+ * @param {any} candidate
1254
+ * @param {any} seenIds
1255
+ * @param {any} agentId
1256
+ * @returns {any}
1257
+ */
1258
+ function evaluateDrainCandidate(candidate, seenIds, agentId) {
1259
+ const ret = wasm.evaluateDrainCandidate(candidate, seenIds, agentId);
1260
+ if (ret[2]) {
1261
+ throw takeFromExternrefTable0(ret[1]);
1262
+ }
1263
+ return takeFromExternrefTable0(ret[0]);
1264
+ }
1265
+ exports.evaluateDrainCandidate = evaluateDrainCandidate;
1266
+
1267
+ /**
1268
+ * Compares the triggering message against the platform's authoritative
1269
+ * "next open message" for a room. Throws a plain `Error` if
1270
+ * `triggeringMessageId`/`nextMessageId` is not a string (or `null`/
1271
+ * `undefined` for `nextMessageId`).
1272
+ * @param {any} triggeringMessageId
1273
+ * @param {any} nextMessageId
1274
+ * @returns {any}
1275
+ */
1276
+ function evaluateNextMessage(triggeringMessageId, nextMessageId) {
1277
+ const ret = wasm.evaluateNextMessage(triggeringMessageId, nextMessageId);
1278
+ if (ret[2]) {
1279
+ throw takeFromExternrefTable0(ret[1]);
1280
+ }
1281
+ return takeFromExternrefTable0(ret[0]);
1282
+ }
1283
+ exports.evaluateNextMessage = evaluateNextMessage;
1284
+
1195
1285
  /**
1196
1286
  * This platform's Phoenix Channels topic for a room's `room_participants`
1197
1287
  * channel.
@@ -1372,6 +1462,10 @@ function __wbg_get_imports() {
1372
1462
  const ret = arg0[arg1 >>> 0];
1373
1463
  return ret;
1374
1464
  },
1465
+ __wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) {
1466
+ const ret = arg0[arg1];
1467
+ return ret;
1468
+ },
1375
1469
  __wbg_instanceof_ArrayBuffer_993d02d2d254cad1: function(arg0) {
1376
1470
  let result;
1377
1471
  try {
Binary file
package/index.d.ts CHANGED
@@ -240,21 +240,26 @@ export interface SessionPolicyConfig {
240
240
  rapidCooldownStepS: number;
241
241
  rapidCooldownMaxS: number;
242
242
  rapidThreshold: number;
243
+ heartbeatIntervalS: number;
244
+ deadThresholdS: number;
243
245
  }
244
246
 
245
247
  /**
246
248
  * Reconnect backoff/jitter policy, plus the graduated rapid-disconnect
247
249
  * cooldown ladder. A recommended, not final, starting design.
248
250
  *
249
- * Throws a native `Error` with `.issues`/`.traceContext` if `factor < 1.0`
250
- * or `baseDelayS > maxDelayS`. Throws a plain `Error` if `config` is
251
- * missing a field or a field is not a finite, non-negative number
252
- * (`rapidThreshold` must also be an integer).
251
+ * Throws a native `Error` with `.issues`/`.traceContext` if `factor < 1.0`,
252
+ * `baseDelayS > maxDelayS`, `heartbeatIntervalS == 0`, or
253
+ * `deadThresholdS <= heartbeatIntervalS`. Throws a plain `Error` if
254
+ * `config` is missing a field or a field is not a finite, non-negative
255
+ * number (`rapidThreshold` must also be an integer).
253
256
  */
254
257
  export class SessionPolicy {
255
258
  constructor(config: SessionPolicyConfig, traceContext?: string | null);
256
259
  /** A recommended, not final, default. */
257
260
  static default(): SessionPolicy;
261
+ readonly heartbeatIntervalS: number;
262
+ readonly deadThresholdS: number;
258
263
  }
259
264
 
260
265
  /**
@@ -359,3 +364,139 @@ export class RoomRoster {
359
364
  reconcile(currentRoomIds: string[]): RoomReconciliation;
360
365
  clear(): string[];
361
366
  }
367
+
368
+ // The one-shot delivery lifecycle decisions: evaluateDeliveryEvent,
369
+ // evaluateNextMessage, evaluateDrainCandidate, evaluateAdapterResult.
370
+ // Each returns a plain tagged object -- these functions carry no
371
+ // cross-invocation state, so there is no class here to construct. Decision
372
+ // fields stay snake_case (message-wire vocabulary: `room_id`, `sender_id`,
373
+ // `sender_type`, ...), unlike this module's own camelCase function/argument
374
+ // names, so the same shape is returned identically from both bindings.
375
+
376
+ export interface EventIgnored {
377
+ decision: "ignored";
378
+ event_type: string;
379
+ }
380
+ export interface EventCleanup {
381
+ decision: "cleanup";
382
+ room_id: string;
383
+ }
384
+ export interface EventSkipSelf {
385
+ decision: "skip_self";
386
+ room_id: string;
387
+ message_id: string;
388
+ }
389
+ export interface EventInvocation {
390
+ decision: "invocation";
391
+ room_id: string;
392
+ message_id: string;
393
+ sender_id: string;
394
+ sender_type: string;
395
+ }
396
+ export type EventDecision = EventIgnored | EventCleanup | EventSkipSelf | EventInvocation;
397
+
398
+ /**
399
+ * Evaluate the routing decision for one inbound platform event.
400
+ *
401
+ * `message_created` delegates to {@link validateEventPayload}'s own
402
+ * validation; a rejection there throws the same native `Error` with
403
+ * `.issues`/`.traceContext`, as does an empty `payload.id`.
404
+ * `room_removed`/`room_deleted` need no payload shape and read `id` straight
405
+ * off the raw payload. Both event kinds throw that same `Error` shape when
406
+ * `roomId` resolves from neither the envelope nor the payload — an empty
407
+ * string counting as absent — with issue code `missing` when the fallback
408
+ * field is absent or empty and `wrong_type` when it is present but not a
409
+ * string. Throws a plain `Error` without `.issues` if
410
+ * `eventType`/`roomId`/`agentId`/`traceContext` is not a string (or not
411
+ * `null`/`undefined` for `roomId`/`traceContext`), or if `payload` cannot be
412
+ * converted.
413
+ */
414
+ export function evaluateDeliveryEvent(
415
+ eventType: string,
416
+ roomId: string | null | undefined,
417
+ payload: unknown,
418
+ agentId: string,
419
+ traceContext?: string | null,
420
+ ): EventDecision;
421
+
422
+ export interface NextMessageNoPending {
423
+ decision: "no_pending";
424
+ }
425
+ export interface NextMessageAlreadyProcessed {
426
+ decision: "already_processed";
427
+ next_open_id: string;
428
+ }
429
+ export interface NextMessageReadyToClaim {
430
+ decision: "ready_to_claim";
431
+ }
432
+ export type NextMessageDecision =
433
+ | NextMessageNoPending
434
+ | NextMessageAlreadyProcessed
435
+ | NextMessageReadyToClaim;
436
+
437
+ /**
438
+ * Compares the triggering message against the platform's authoritative
439
+ * "next open message" for a room. Throws a plain `Error` if
440
+ * `triggeringMessageId` is not a string, or `nextMessageId` is not a
441
+ * string, `null`, or `undefined`.
442
+ */
443
+ export function evaluateNextMessage(
444
+ triggeringMessageId: string,
445
+ nextMessageId: string | null | undefined,
446
+ ): NextMessageDecision;
447
+
448
+ /** One already-fetched drain candidate, matching `evaluateDeliveryEvent`'s own field names. */
449
+ export interface DrainCandidate {
450
+ id: string;
451
+ sender_id: string;
452
+ sender_type: string;
453
+ }
454
+ export interface DrainNoCandidate {
455
+ decision: "no_candidate";
456
+ }
457
+ export interface DrainOutOfSnapshot {
458
+ decision: "out_of_snapshot";
459
+ message_id: string;
460
+ }
461
+ export interface DrainSelfEcho {
462
+ decision: "self_echo";
463
+ message_id: string;
464
+ }
465
+ export interface DrainMessage {
466
+ decision: "drain";
467
+ message_id: string;
468
+ }
469
+ export type DrainStep = DrainNoCandidate | DrainOutOfSnapshot | DrainSelfEcho | DrainMessage;
470
+
471
+ /**
472
+ * One step of the drain loop. `candidate` is `null`/`undefined` when the
473
+ * host's fetch returned no candidate. Throws a plain `Error` if
474
+ * `candidate` is present but malformed, or if `agentId` is not a string.
475
+ */
476
+ export function evaluateDrainCandidate(
477
+ candidate: DrainCandidate | null | undefined,
478
+ seenIds: string[],
479
+ agentId: string,
480
+ ): DrainStep;
481
+
482
+ export interface AckProcessed {
483
+ decision: "processed";
484
+ room_id: string;
485
+ message_id: string;
486
+ }
487
+ export interface AckFailed {
488
+ decision: "failed";
489
+ room_id: string;
490
+ message_id: string;
491
+ }
492
+ export type AckDecision = AckProcessed | AckFailed;
493
+
494
+ /**
495
+ * Adapter outcome -> ack decision. Throws a plain `Error` if `roomId`/
496
+ * `messageId` is not a string.
497
+ */
498
+ export function evaluateAdapterResult(
499
+ roomId: string,
500
+ messageId: string,
501
+ succeeded: boolean,
502
+ ): AckDecision;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@band-ai/band-sdk-core",
3
- "version": "1.2.1",
3
+ "version": "2.1.0",
4
4
  "description": "Shared Band event-payload validation",
5
5
  "license": "MIT",
6
6
  "repository": {