@rkat/sdk 0.7.31 → 0.8.1

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/dist/client.js CHANGED
@@ -32,12 +32,12 @@ import path from "node:path";
32
32
  import { setTimeout as delay } from "node:timers/promises";
33
33
  import { createInterface } from "node:readline";
34
34
  import { Buffer } from "node:buffer";
35
- import { MeerkatError, CapabilityUnavailableError } from "./generated/errors.js";
35
+ import { MeerkatError, CapabilityUnavailableError, meerkatErrorFromJsonRpcCode, } from "./generated/errors.js";
36
36
  import { isCompatibleWith } from "./generated/version_compat.js";
37
37
  import { CONTRACT_VERSION, } from "./generated/types.js";
38
38
  import { DeferredSession, Session } from "./session.js";
39
39
  import { Mob, } from "./mob.js";
40
- import { parseCoreEvent } from "./events.js";
40
+ import { parseAgentEventEnvelope } from "./event-envelope.js";
41
41
  import { EventStream, AsyncQueue } from "./streaming.js";
42
42
  import { EventSubscription } from "./subscription.js";
43
43
  import { parseAttentionListResult, parseGoalStatusResult, parseWorkGraphEvent, parseWorkGraphSnapshot, parseWorkItem, } from "./generated/types.js";
@@ -53,6 +53,7 @@ const MOB_SPAWN_MANY_FAILURE_CAUSES = new Set([
53
53
  "wiring_error",
54
54
  "bridge_command_rejected",
55
55
  "member_restore_failed",
56
+ "missing_member_capability",
56
57
  "kickoff_wait_timed_out",
57
58
  "ready_wait_timed_out",
58
59
  "definition_error",
@@ -104,6 +105,18 @@ function skillRefsToWire(refs) {
104
105
  const keys = skillKeysToWire(refs);
105
106
  return keys?.map((key) => ({ kind: "structured", ...key }));
106
107
  }
108
+ const MOB_CONTROL_SCOPES = new Set([
109
+ "list",
110
+ "read_history",
111
+ "subscribe_events",
112
+ "send_command",
113
+ "cancel",
114
+ "retire",
115
+ "wire_topology",
116
+ "live",
117
+ "admin_host",
118
+ "admin_grants",
119
+ ]);
107
120
  function setIfDefined(payload, key, value) {
108
121
  if (value !== undefined) {
109
122
  payload[key] = value;
@@ -121,12 +134,12 @@ function mobSpawnPayload(mobId, spec) {
121
134
  setIfDefined(payload, "labels", spec.labels);
122
135
  setIfDefined(payload, "context", spec.context);
123
136
  setIfDefined(payload, "additional_instructions", spec.additionalInstructions);
137
+ setIfDefined(payload, "placement", spec.placement);
124
138
  setIfDefined(payload, "binding", spec.binding);
125
139
  setIfDefined(payload, "shell_env", spec.shellEnv);
126
140
  setIfDefined(payload, "auto_wire_parent", spec.autoWireParent);
127
141
  setIfDefined(payload, "launch_mode", spec.launchMode);
128
142
  setIfDefined(payload, "tool_access_policy", spec.toolAccessPolicy);
129
- setIfDefined(payload, "budget_split_policy", spec.budgetSplitPolicy);
130
143
  setIfDefined(payload, "inherited_tool_filter", spec.inheritedToolFilter);
131
144
  setIfDefined(payload, "override_profile", spec.overrideProfile);
132
145
  setIfDefined(payload, "model_override", spec.modelOverride);
@@ -144,6 +157,7 @@ function mobSpawnManySpecPayload(spec) {
144
157
  setIfDefined(payload, "labels", spec.labels);
145
158
  setIfDefined(payload, "context", spec.context);
146
159
  setIfDefined(payload, "additional_instructions", spec.additionalInstructions);
160
+ setIfDefined(payload, "placement", spec.placement);
147
161
  setIfDefined(payload, "auth_binding", spec.authBinding);
148
162
  setIfDefined(payload, "model_override", spec.modelOverride);
149
163
  return payload;
@@ -171,6 +185,7 @@ function mobTurnStartPayload(mobId, agentIdentity, prompt, options) {
171
185
  setIfDefined(payload, "keep_alive", options?.keepAlive);
172
186
  setIfDefined(payload, "model", options?.model);
173
187
  setIfDefined(payload, "provider", options?.provider);
188
+ setIfDefined(payload, "self_hosted_server_id", options?.selfHostedServerId);
174
189
  setIfDefined(payload, "max_tokens", options?.maxTokens);
175
190
  setIfDefined(payload, "system_prompt", options?.systemPrompt);
176
191
  setIfDefined(payload, "output_schema", options?.outputSchema);
@@ -620,7 +635,8 @@ export class MeerkatClient {
620
635
  params.idempotency_key = options.idempotencyKey;
621
636
  }
622
637
  const result = await this.request("session/inject_context", params);
623
- return { status: String(result.status ?? "") };
638
+ const status = MeerkatClient.requireClosedStringField(result, "status", ["applied", "staged", "duplicate"], "Invalid session/inject_context response");
639
+ return { status };
624
640
  }
625
641
  /**
626
642
  * Read an input's stored runtime state (terminal outcome, run
@@ -682,6 +698,9 @@ export class MeerkatClient {
682
698
  if (options?.runningBehavior !== undefined) {
683
699
  params.running_behavior = options.runningBehavior;
684
700
  }
701
+ if (options?.toolAccessPolicy !== undefined) {
702
+ params.tool_access_policy = options.toolAccessPolicy;
703
+ }
685
704
  const raw = await this.request("session/fork_at", params);
686
705
  return MeerkatClient.parseSessionForkResult(raw);
687
706
  }
@@ -694,6 +713,9 @@ export class MeerkatClient {
694
713
  if (options?.runningBehavior !== undefined) {
695
714
  params.running_behavior = options.runningBehavior;
696
715
  }
716
+ if (options?.toolAccessPolicy !== undefined) {
717
+ params.tool_access_policy = options.toolAccessPolicy;
718
+ }
697
719
  const raw = await this.request("session/fork_replace", params);
698
720
  return MeerkatClient.parseSessionForkResult(raw);
699
721
  }
@@ -1133,6 +1155,232 @@ export class MeerkatClient {
1133
1155
  agent_identity: agentIdentity,
1134
1156
  });
1135
1157
  }
1158
+ /**
1159
+ * Record (full-replace) a principal's control-scope grant
1160
+ * (`mob/grant_scopes`).
1161
+ */
1162
+ async grantMobScopes(mobId, principal, scopes, expiresAtMs) {
1163
+ const result = await this.request("mob/grant_scopes", {
1164
+ mob_id: mobId,
1165
+ principal,
1166
+ scopes,
1167
+ ...(expiresAtMs !== undefined ? { expires_at_ms: expiresAtMs } : {}),
1168
+ });
1169
+ const context = "Invalid mob/grant_scopes response";
1170
+ const record = MeerkatClient.requireRecord(result.record, "record", context);
1171
+ return MeerkatClient.decodeMobGrantRecord(record, context);
1172
+ }
1173
+ /**
1174
+ * Revoke scopes from a principal's grant (`mob/revoke_scopes`); omit
1175
+ * `scopes` to revoke the entire grant. Returns whether the grant record
1176
+ * was removed entirely — revoking never-granted scopes is an idempotent
1177
+ * no-op (`false`).
1178
+ */
1179
+ async revokeMobScopes(mobId, principal, scopes) {
1180
+ const result = await this.request("mob/revoke_scopes", {
1181
+ mob_id: mobId,
1182
+ principal,
1183
+ ...(scopes !== undefined ? { scopes } : {}),
1184
+ });
1185
+ if (typeof result.removed !== "boolean") {
1186
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/revoke_scopes response: missing removed");
1187
+ }
1188
+ return result.removed;
1189
+ }
1190
+ /**
1191
+ * List raw control-scope grant records (`mob/grants`). Expired rows
1192
+ * preserve the generated `expires_at_ms` wire field verbatim.
1193
+ */
1194
+ async listMobGrants(mobId) {
1195
+ const result = await this.request("mob/grants", { mob_id: mobId });
1196
+ const context = "Invalid mob/grants response";
1197
+ const grants = result.grants;
1198
+ if (!Array.isArray(grants)) {
1199
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing grants`);
1200
+ }
1201
+ return grants.map((entry) => MeerkatClient.decodeMobGrantRecord(MeerkatClient.requireObject(entry, context), context));
1202
+ }
1203
+ static requireObject(value, context) {
1204
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1205
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: expected an object entry`);
1206
+ }
1207
+ return value;
1208
+ }
1209
+ static decodeMobGrantRecord(record, context) {
1210
+ const principal = MeerkatClient.requireStringField(record, "principal", context);
1211
+ const scopes = record.scopes;
1212
+ if (!Array.isArray(scopes) ||
1213
+ scopes.some((scope) => typeof scope !== "string" ||
1214
+ !MOB_CONTROL_SCOPES.has(scope))) {
1215
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: record scopes must use the closed control-scope vocabulary`);
1216
+ }
1217
+ let expiresAtMs;
1218
+ if (Object.prototype.hasOwnProperty.call(record, "expires_at_ms")) {
1219
+ expiresAtMs = MeerkatClient.requireNonNegativeIntegerField(record, "expires_at_ms", context);
1220
+ }
1221
+ return {
1222
+ principal,
1223
+ scopes: scopes,
1224
+ ...(expiresAtMs !== undefined ? { expires_at_ms: expiresAtMs } : {}),
1225
+ };
1226
+ }
1227
+ // ─── Multi-host console verbs (phase 7, DEC-P7A-9) ────────────────────
1228
+ /**
1229
+ * Read a mob member transcript page by identity (`mob/member_history`).
1230
+ * One shape local and remote; the envelope carries typed placement +
1231
+ * provenance facts.
1232
+ */
1233
+ async mobMemberHistory(mobId, agentIdentity, opts) {
1234
+ const result = await this.request("mob/member_history", {
1235
+ mob_id: mobId,
1236
+ agent_identity: agentIdentity,
1237
+ ...(opts?.fromIndex !== undefined ? { from_index: opts.fromIndex } : {}),
1238
+ ...(opts?.limit !== undefined ? { limit: opts.limit } : {}),
1239
+ });
1240
+ const context = "Invalid mob/member_history response";
1241
+ const page = MeerkatClient.requireRecord(result.page, "page", context);
1242
+ const messages = MeerkatClient.requireRecordArray(page.messages, `${context}: page.messages`);
1243
+ messages.forEach((message, index) => MeerkatClient.validateWireHistoryRow(message, `${context}: page.messages[${index}]`));
1244
+ MeerkatClient.requireBooleanField(page, "complete", `${context}: page`);
1245
+ const fromIndex = MeerkatClient.requireNonNegativeIntegerField(page, "from_index", `${context}: page`);
1246
+ const messageCount = MeerkatClient.requireNonNegativeIntegerField(page, "message_count", `${context}: page`);
1247
+ const hasNextIndex = Object.prototype.hasOwnProperty.call(page, "next_index");
1248
+ if (hasNextIndex) {
1249
+ MeerkatClient.requireNonNegativeIntegerField(page, "next_index", `${context}: page`);
1250
+ }
1251
+ const pageEnd = fromIndex + messages.length;
1252
+ if (pageEnd > messageCount) {
1253
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: page ends beyond message_count`);
1254
+ }
1255
+ const complete = page.complete;
1256
+ if (complete !== (pageEnd === messageCount)) {
1257
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: complete must match whether the page reaches message_count`);
1258
+ }
1259
+ if (complete && hasNextIndex) {
1260
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: complete page must not carry next_index`);
1261
+ }
1262
+ if (!complete) {
1263
+ if (messages.length === 0) {
1264
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: incomplete page must make progress`);
1265
+ }
1266
+ if (!hasNextIndex || page.next_index !== pageEnd) {
1267
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: incomplete page next_index must equal served end`);
1268
+ }
1269
+ }
1270
+ MeerkatClient.requireNonNegativeIntegerField(result, "generation", context);
1271
+ const provenance = MeerkatClient.requireStringField(result, "provenance", context);
1272
+ if (provenance !== "host_claimed" && provenance !== "controlling_host_verified") {
1273
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported provenance ${JSON.stringify(provenance)}`);
1274
+ }
1275
+ const placement = MeerkatClient.optionalStringField(result, "placement", context);
1276
+ if (placement === "") {
1277
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: placement must be non-empty`);
1278
+ }
1279
+ return result;
1280
+ }
1281
+ /** List tracked member hosts with bind phase and declared capabilities. */
1282
+ async mobHosts(mobId) {
1283
+ const result = await this.request("mob/hosts", { mob_id: mobId });
1284
+ const hosts = result.hosts;
1285
+ if (!Array.isArray(hosts)) {
1286
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/hosts response: missing hosts");
1287
+ }
1288
+ return hosts.map((row, index) => MeerkatClient.parseMobHostStatus(row, `Invalid mob/hosts response: hosts[${index}]`));
1289
+ }
1290
+ /** Outstanding cross-host route-install obligations. */
1291
+ async mobRouteInstalls(mobId) {
1292
+ const result = await this.request("mob/route_installs", { mob_id: mobId });
1293
+ const context = "Invalid mob/route_installs response";
1294
+ const outstanding = MeerkatClient.requireRecordArray(result.outstanding, `${context}: outstanding`);
1295
+ outstanding.forEach((row, index) => MeerkatClient.parseRouteInstallObligation(row, `${context}: outstanding[${index}]`));
1296
+ MeerkatClient.requireBooleanField(result, "complete", context);
1297
+ return result;
1298
+ }
1299
+ /** Bind a member-host daemon from its binding descriptor. */
1300
+ async bindMobHost(mobId, descriptor) {
1301
+ const result = await this.request("mob/bind_host", {
1302
+ mob_id: mobId,
1303
+ descriptor,
1304
+ });
1305
+ const context = "Invalid mob/bind_host response";
1306
+ MeerkatClient.requireStringField(result, "host_id", context);
1307
+ MeerkatClient.requireNonNegativeIntegerField(result, "authority_epoch", context);
1308
+ const capabilities = MeerkatClient.parseMobHostCapabilities(result.capabilities, `${context}: capabilities`);
1309
+ return { ...result, capabilities };
1310
+ }
1311
+ /** Revoke a bound (or bind-requested) member host. */
1312
+ async revokeMobHost(mobId, hostId) {
1313
+ const result = await this.request("mob/revoke_host", {
1314
+ mob_id: mobId,
1315
+ host_id: hostId,
1316
+ });
1317
+ const context = "Invalid mob/revoke_host response";
1318
+ MeerkatClient.requireStringField(result, "host_id", context);
1319
+ MeerkatClient.requireStringArray(result.released_members, `${context}: released_members`);
1320
+ return result;
1321
+ }
1322
+ /** Hard-cancel a mob member; `reason` is required. */
1323
+ async hardCancelMobMember(mobId, agentIdentity, reason) {
1324
+ const result = await this.request("mob/hard_cancel_member", {
1325
+ mob_id: mobId,
1326
+ agent_identity: agentIdentity,
1327
+ reason,
1328
+ });
1329
+ if (typeof result.cancelled !== "boolean") {
1330
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/hard_cancel_member response: missing cancelled");
1331
+ }
1332
+ return result.cancelled;
1333
+ }
1334
+ /** Open a live realtime channel on a mob member. */
1335
+ async openMobMemberLive(mobId, agentIdentity, opts) {
1336
+ const result = await this.request("mob/member_live_open", {
1337
+ mob_id: mobId,
1338
+ agent_identity: agentIdentity,
1339
+ ...(opts?.turningMode !== undefined ? { turning_mode: opts.turningMode } : {}),
1340
+ ...(opts?.transport !== undefined ? { transport: opts.transport } : {}),
1341
+ });
1342
+ return MeerkatClient.parseLiveOpenResult(result, "Invalid mob/member_live_open response");
1343
+ }
1344
+ /** Close one named live channel on a mob member. */
1345
+ async closeMobMemberLive(mobId, agentIdentity, channelId) {
1346
+ const result = await this.request("mob/member_live_close", {
1347
+ mob_id: mobId,
1348
+ agent_identity: agentIdentity,
1349
+ channel_id: channelId,
1350
+ });
1351
+ return MeerkatClient.parseLiveCloseResult(result);
1352
+ }
1353
+ /** Read live channel status for a mob member. */
1354
+ async mobMemberLiveStatus(mobId, agentIdentity, channelId) {
1355
+ const result = await this.request("mob/member_live_status", {
1356
+ mob_id: mobId,
1357
+ agent_identity: agentIdentity,
1358
+ ...(channelId !== undefined ? { channel_id: channelId } : {}),
1359
+ });
1360
+ return MeerkatClient.parseLiveStatusResult(result, "Invalid mob/member_live_status response");
1361
+ }
1362
+ /** Drive one turn-level live control verb on a member channel. */
1363
+ async controlMobMemberLive(mobId, agentIdentity, channelId, verb) {
1364
+ const result = await this.request("mob/member_live_control", {
1365
+ mob_id: mobId,
1366
+ agent_identity: agentIdentity,
1367
+ channel_id: channelId,
1368
+ verb,
1369
+ });
1370
+ const context = "Invalid mob/member_live_control response";
1371
+ const resultVerb = MeerkatClient.requireStringField(result, "verb", context);
1372
+ const status = MeerkatClient.requireStringField(result, "status", context);
1373
+ const expectedStatus = {
1374
+ commit_input: "committed",
1375
+ interrupt: "interrupted",
1376
+ truncate: "truncated",
1377
+ refresh: "queued",
1378
+ };
1379
+ if (expectedStatus[resultVerb] !== status || resultVerb !== verb.verb) {
1380
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: mismatched verb/status outcome`);
1381
+ }
1382
+ return result;
1383
+ }
1136
1384
  async respawnMobMember(mobId, agentIdentity, initialMessage) {
1137
1385
  const result = await this.request("mob/respawn", {
1138
1386
  mob_id: mobId,
@@ -1206,6 +1454,15 @@ export class MeerkatClient {
1206
1454
  if (result.progress !== undefined && result.progress !== null) {
1207
1455
  snapshot.progress = MeerkatClient.parseMemberProgressSnapshot(result.progress, "Invalid mob/member_status response");
1208
1456
  }
1457
+ snapshot.placement = MeerkatClient.optionalStringField(result, "placement", "Invalid mob/member_status response");
1458
+ snapshot.controlReachability = MeerkatClient.parseWireReachability(result.control_reachability, "control_reachability");
1459
+ snapshot.commsReachability = MeerkatClient.parseWireReachability(result.comms_reachability, "comms_reachability");
1460
+ if (result.last_seen_ms != null) {
1461
+ snapshot.lastSeenMs = MeerkatClient.requireNonNegativeIntegerField(result, "last_seen_ms", "Invalid mob/member_status response");
1462
+ }
1463
+ snapshot.freshnessReason = MeerkatClient.optionalStringField(result, "freshness_reason", "Invalid mob/member_status response");
1464
+ snapshot.lifecycleCapabilities = MeerkatClient.parseMemberLifecycleCapabilities(result.lifecycle_capabilities);
1465
+ snapshot.nonPortableDisabled = MeerkatClient.parseNonPortableDisabled(result.non_portable_disabled);
1209
1466
  return snapshot;
1210
1467
  }
1211
1468
  /**
@@ -1488,9 +1745,14 @@ export class MeerkatClient {
1488
1745
  params.limit = options.limit;
1489
1746
  }
1490
1747
  const result = await this.request("mob/events", params);
1491
- const events = Array.isArray(result.events)
1492
- ? result.events
1493
- : [];
1748
+ if (typeof result !== "object" ||
1749
+ result === null ||
1750
+ Array.isArray(result) ||
1751
+ !Object.hasOwn(result, "events") ||
1752
+ !Array.isArray(result.events)) {
1753
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/events response: events must be an array", result);
1754
+ }
1755
+ const events = result.events;
1494
1756
  return { events };
1495
1757
  }
1496
1758
  async mobIngressInteraction(params) {
@@ -1616,53 +1878,7 @@ export class MeerkatClient {
1616
1878
  });
1617
1879
  }
1618
1880
  static parseAgentEventEnvelope(raw) {
1619
- const eventId = MeerkatClient.parseOptionalString(raw.event_id ?? raw.eventId);
1620
- const source = MeerkatClient.parseEventSourceIdentity(raw.source);
1621
- const seq = MeerkatClient.parseOptionalNumber(raw.seq);
1622
- const timestampMs = MeerkatClient.parseOptionalNumber(raw.timestamp_ms ?? raw.timestampMs);
1623
- const payloadRaw = raw.payload;
1624
- const payload = payloadRaw && typeof payloadRaw === "object"
1625
- ? parseCoreEvent(payloadRaw)
1626
- : undefined;
1627
- return {
1628
- ...(eventId != null ? { eventId } : {}),
1629
- ...(source != null ? { source } : {}),
1630
- ...(seq != null ? { seq } : {}),
1631
- ...(timestampMs != null ? { timestampMs } : {}),
1632
- ...(payload ? { payload } : {}),
1633
- };
1634
- }
1635
- static parseEventSourceIdentity(raw) {
1636
- if (!raw || typeof raw !== "object") {
1637
- return undefined;
1638
- }
1639
- const record = raw;
1640
- const type = MeerkatClient.parseOptionalString(record.type);
1641
- switch (type) {
1642
- case "session": {
1643
- const sessionId = MeerkatClient.parseOptionalString(record.session_id ?? record.sessionId);
1644
- return sessionId != null ? { type: "session", sessionId } : undefined;
1645
- }
1646
- case "runtime": {
1647
- const runtimeId = MeerkatClient.parseOptionalString(record.runtime_id ?? record.runtimeId);
1648
- return runtimeId != null ? { type: "runtime", runtimeId } : undefined;
1649
- }
1650
- case "interaction": {
1651
- const interactionId = MeerkatClient.parseOptionalString(record.interaction_id ?? record.interactionId);
1652
- return interactionId != null ? { type: "interaction", interactionId } : undefined;
1653
- }
1654
- case "callback":
1655
- return { type: "callback" };
1656
- case "external": {
1657
- const sourceId = MeerkatClient.parseOptionalString(record.source_id ?? record.sourceId);
1658
- return sourceId != null ? { type: "external", sourceId } : undefined;
1659
- }
1660
- default:
1661
- return undefined;
1662
- }
1663
- }
1664
- static parseOptionalString(raw) {
1665
- return typeof raw === "string" ? raw : undefined;
1881
+ return parseAgentEventEnvelope(raw);
1666
1882
  }
1667
1883
  static parseRequiredString(raw, context) {
1668
1884
  if (typeof raw === "string" && raw.length > 0) {
@@ -1670,9 +1886,6 @@ export class MeerkatClient {
1670
1886
  }
1671
1887
  throw new MeerkatError("INVALID_RESPONSE", context);
1672
1888
  }
1673
- static parseOptionalNumber(raw) {
1674
- return typeof raw === "number" && Number.isFinite(raw) ? raw : undefined;
1675
- }
1676
1889
  static parseAttributedMobEvent(raw) {
1677
1890
  const context = "Invalid attributed mob event";
1678
1891
  // The runtime wire shape (meerkat-mob AttributedEvent) carries a typed
@@ -1688,7 +1901,7 @@ export class MeerkatClient {
1688
1901
  const source = MeerkatClient.requireRecord(raw.source, "source", context);
1689
1902
  const identity = MeerkatClient.requireStringField(source, "identity", context);
1690
1903
  const generation = MeerkatClient.requireNumberField(source, "generation", context);
1691
- if (!Number.isInteger(generation) || generation < 0) {
1904
+ if (!Number.isSafeInteger(generation) || generation < 0) {
1692
1905
  throw new MeerkatError("INVALID_RESPONSE", `${context}: source generation must be a non-negative integer`);
1693
1906
  }
1694
1907
  const role = MeerkatClient.requireStringField(raw, "role", context);
@@ -1724,10 +1937,14 @@ export class MeerkatClient {
1724
1937
  params.model = options.model;
1725
1938
  if (options?.provider)
1726
1939
  params.provider = options.provider;
1940
+ if (options?.selfHostedServerId != null) {
1941
+ params.self_hosted_server_id = options.selfHostedServerId;
1942
+ }
1727
1943
  if (options?.maxTokens)
1728
1944
  params.max_tokens = options.maxTokens;
1729
- if (options?.systemPrompt)
1945
+ if (options?.systemPrompt !== undefined) {
1730
1946
  params.system_prompt = options.systemPrompt;
1947
+ }
1731
1948
  if (options?.outputSchema)
1732
1949
  params.output_schema = options.outputSchema;
1733
1950
  if (options?.structuredOutputRetries != null) {
@@ -1771,10 +1988,14 @@ export class MeerkatClient {
1771
1988
  params.model = options.model;
1772
1989
  if (options?.provider)
1773
1990
  params.provider = options.provider;
1991
+ if (options?.selfHostedServerId != null) {
1992
+ params.self_hosted_server_id = options.selfHostedServerId;
1993
+ }
1774
1994
  if (options?.maxTokens)
1775
1995
  params.max_tokens = options.maxTokens;
1776
- if (options?.systemPrompt)
1996
+ if (options?.systemPrompt !== undefined) {
1777
1997
  params.system_prompt = options.systemPrompt;
1998
+ }
1778
1999
  if (options?.outputSchema)
1779
2000
  params.output_schema = options.outputSchema;
1780
2001
  if (options?.structuredOutputRetries != null) {
@@ -1865,7 +2086,11 @@ export class MeerkatClient {
1865
2086
  return MeerkatClient.parseCommsSendReceipt(result);
1866
2087
  }
1867
2088
  async peers(sessionId) {
1868
- return this.request("comms/peers", { session_id: sessionId });
2089
+ const result = await this.request("comms/peers", { session_id: sessionId });
2090
+ if (!Array.isArray(result.peers)) {
2091
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid comms/peers response: missing peers");
2092
+ }
2093
+ return { peers: result.peers };
1869
2094
  }
1870
2095
  /** Idempotent spawn: spawns or returns the existing member entry. */
1871
2096
  async mobEnsureMember(mobId, spec) {
@@ -1895,7 +2120,7 @@ export class MeerkatClient {
1895
2120
  // schemas regenerated into `./generated/types.ts`. See I52/I53.
1896
2121
  async liveOpen(params) {
1897
2122
  const result = await this.request("live/open", params);
1898
- return result;
2123
+ return MeerkatClient.parseLiveOpenResult(result, "Invalid live/open response");
1899
2124
  }
1900
2125
  async liveWebrtcAnswer(params) {
1901
2126
  const result = await this.request("live/webrtc/answer", params);
@@ -1903,7 +2128,7 @@ export class MeerkatClient {
1903
2128
  }
1904
2129
  async liveStatus(params) {
1905
2130
  const result = await this.request("live/status", params);
1906
- return result;
2131
+ return MeerkatClient.parseLiveStatusResult(result, "Invalid live/status response");
1907
2132
  }
1908
2133
  async liveClose(params) {
1909
2134
  const result = await this.request("live/close", params);
@@ -2132,7 +2357,18 @@ export class MeerkatClient {
2132
2357
  const error = data.error;
2133
2358
  if (error) {
2134
2359
  const normalized = MeerkatClient.parseRpcErrorPayload(error);
2135
- pending.reject(new MeerkatError(normalized.code, normalized.message, normalized.details));
2360
+ const errorData = typeof error.data === "object" &&
2361
+ error.data !== null &&
2362
+ !Array.isArray(error.data)
2363
+ ? error.data
2364
+ : undefined;
2365
+ const semanticCode = typeof errorData?.code === "string" && errorData.code.length > 0
2366
+ ? errorData.code
2367
+ : undefined;
2368
+ const rpcCode = typeof error.code === "number" || typeof error.code === "string"
2369
+ ? error.code
2370
+ : "UNKNOWN";
2371
+ pending.reject(meerkatErrorFromJsonRpcCode(rpcCode, semanticCode, normalized.message, normalized.details));
2136
2372
  }
2137
2373
  else {
2138
2374
  pending.resolve((data.result ?? {}));
@@ -2177,20 +2413,21 @@ export class MeerkatClient {
2177
2413
  return;
2178
2414
  }
2179
2415
  const sessionId = String(params.session_id ?? "");
2180
- const event = params.event;
2181
- if (event) {
2182
- const queue = this.eventQueues.get(sessionId);
2183
- if (queue) {
2184
- queue.put(event);
2185
- }
2186
- else if (this.pendingStreamQueues.size > 0) {
2187
- // A stream is pending but its session_id is not yet bound. Buffer by
2188
- // session_id; the create response that binds this session_id drains
2189
- // exactly this buffer into the matching request's queue.
2190
- const buffered = this.unmatchedStreamBuffer.get(sessionId) ?? [];
2191
- buffered.push(event);
2192
- this.unmatchedStreamBuffer.set(sessionId, buffered);
2193
- }
2416
+ const rawEvent = params.event;
2417
+ const event = typeof rawEvent === "object" && rawEvent !== null && !Array.isArray(rawEvent)
2418
+ ? rawEvent
2419
+ : { invalid_event_envelope: rawEvent };
2420
+ const queue = this.eventQueues.get(sessionId);
2421
+ if (queue) {
2422
+ queue.put(event);
2423
+ }
2424
+ else if (this.pendingStreamQueues.size > 0) {
2425
+ // A stream is pending but its session_id is not yet bound. Buffer by
2426
+ // session_id; the create response that binds this session_id drains
2427
+ // exactly this buffer into the matching request's queue.
2428
+ const buffered = this.unmatchedStreamBuffer.get(sessionId) ?? [];
2429
+ buffered.push(event);
2430
+ this.unmatchedStreamBuffer.set(sessionId, buffered);
2194
2431
  }
2195
2432
  }
2196
2433
  }
@@ -2266,6 +2503,31 @@ export class MeerkatClient {
2266
2503
  }
2267
2504
  return value;
2268
2505
  }
2506
+ static requireOwnField(raw, field, context) {
2507
+ if (!Object.prototype.hasOwnProperty.call(raw, field)) {
2508
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${field}`);
2509
+ }
2510
+ return raw[field];
2511
+ }
2512
+ static requireClosedStringField(raw, field, allowed, context) {
2513
+ const value = MeerkatClient.requirePresentStringField(raw, field, context);
2514
+ if (!allowed.includes(value)) {
2515
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported ${field} ${JSON.stringify(value)}`);
2516
+ }
2517
+ return value;
2518
+ }
2519
+ static validateOptionalStringField(raw, field, context) {
2520
+ if (Object.prototype.hasOwnProperty.call(raw, field) &&
2521
+ typeof raw[field] !== "string") {
2522
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be string`);
2523
+ }
2524
+ }
2525
+ static validateOptionalBooleanField(raw, field, context) {
2526
+ if (Object.prototype.hasOwnProperty.call(raw, field) &&
2527
+ typeof raw[field] !== "boolean") {
2528
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be boolean`);
2529
+ }
2530
+ }
2269
2531
  static requireNumberField(raw, field, context, displayField = field) {
2270
2532
  if (!(field in raw)) {
2271
2533
  throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${displayField}`);
@@ -2278,14 +2540,15 @@ export class MeerkatClient {
2278
2540
  }
2279
2541
  /**
2280
2542
  * Require a wire count/total field (Rust `usize`/`u64`). A present value
2281
- * that is fractional or negative can never be a valid unsigned integer, so
2282
- * it is a contract violation and fails closed instead of being accepted as
2283
- * an any-finite-number. Mirrors the web SDK `requireNonNegativeIntegerField`
2284
- * and Python `_require_non_negative_integer_field`.
2543
+ * that is fractional, negative, or outside JavaScript's lossless integer
2544
+ * range can never be represented faithfully, so it is a contract violation
2545
+ * and fails closed instead of being accepted as an any-finite-number.
2546
+ * Mirrors the web SDK `requireNonNegativeIntegerField` and Python
2547
+ * `_require_non_negative_integer_field`.
2285
2548
  */
2286
2549
  static requireNonNegativeIntegerField(raw, field, context, displayField = field) {
2287
2550
  const value = MeerkatClient.requireNumberField(raw, field, context, displayField);
2288
- if (!Number.isInteger(value) || value < 0) {
2551
+ if (!Number.isSafeInteger(value) || value < 0) {
2289
2552
  throw new MeerkatError("INVALID_RESPONSE", `${context}: ${displayField} must be a non-negative integer`);
2290
2553
  }
2291
2554
  return value;
@@ -2307,6 +2570,367 @@ export class MeerkatClient {
2307
2570
  }
2308
2571
  return value;
2309
2572
  }
2573
+ static validateGeneratedContentBlock(raw, context, systemNoticeContent) {
2574
+ const allowed = systemNoticeContent
2575
+ ? ["text", "image", "video", "structured", "skill_context"]
2576
+ : ["text", "image", "video", "structured", "unknown"];
2577
+ const type = MeerkatClient.requireClosedStringField(raw, "type", allowed, context);
2578
+ if (type === "text") {
2579
+ MeerkatClient.requirePresentStringField(raw, "text", context);
2580
+ }
2581
+ else if (type === "image") {
2582
+ MeerkatClient.requirePresentStringField(raw, "media_type", context);
2583
+ }
2584
+ else if (type === "video") {
2585
+ MeerkatClient.requirePresentStringField(raw, "media_type", context);
2586
+ MeerkatClient.requireNonNegativeIntegerField(raw, "duration_ms", context);
2587
+ }
2588
+ else if (type === "structured") {
2589
+ MeerkatClient.requireOwnField(raw, "data", context);
2590
+ }
2591
+ else if (type === "skill_context") {
2592
+ const skillKey = MeerkatClient.requireRecord(raw.skill_key, "skill_key", context);
2593
+ MeerkatClient.requireStringField(skillKey, "source_uuid", `${context}: skill_key`);
2594
+ MeerkatClient.requireStringField(skillKey, "skill_name", `${context}: skill_key`);
2595
+ MeerkatClient.requirePresentStringField(raw, "text", context);
2596
+ }
2597
+ }
2598
+ static validateOptionalMetaRecord(raw, context) {
2599
+ if (Object.prototype.hasOwnProperty.call(raw, "meta")) {
2600
+ MeerkatClient.requireRecord(raw.meta, "meta", context);
2601
+ }
2602
+ }
2603
+ static validateToolConfigStatus(raw, context) {
2604
+ const status = MeerkatClient.requireRecord(raw, "status_info", context);
2605
+ const kind = MeerkatClient.requireClosedStringField(status, "kind", [
2606
+ "boundary_applied",
2607
+ "deferred_catalog_delta",
2608
+ "warning_failed_closed",
2609
+ "external_tool_delta",
2610
+ ], context);
2611
+ if (kind === "boundary_applied") {
2612
+ MeerkatClient.requireBooleanField(status, "base_changed", context);
2613
+ MeerkatClient.requireNonNegativeIntegerField(status, "revision", context);
2614
+ MeerkatClient.requireBooleanField(status, "visible_changed", context);
2615
+ }
2616
+ else if (kind === "deferred_catalog_delta") {
2617
+ MeerkatClient.requireNonNegativeIntegerField(status, "added_hidden_count", context);
2618
+ MeerkatClient.requireNonNegativeIntegerField(status, "pending_source_count", context);
2619
+ MeerkatClient.requireNonNegativeIntegerField(status, "removed_hidden_count", context);
2620
+ }
2621
+ else if (kind === "warning_failed_closed") {
2622
+ MeerkatClient.requirePresentStringField(status, "error", context);
2623
+ }
2624
+ else {
2625
+ MeerkatClient.requireClosedStringField(status, "phase", ["pending", "applied", "draining", "forced", "failed"], context);
2626
+ MeerkatClient.validateOptionalStringField(status, "detail", context);
2627
+ }
2628
+ }
2629
+ static validateToolConfigPayload(raw, context) {
2630
+ const payload = MeerkatClient.requireRecord(raw, "payload", context);
2631
+ MeerkatClient.requireClosedStringField(payload, "operation", ["add", "remove", "reload"], context);
2632
+ MeerkatClient.requireBooleanField(payload, "persisted", context);
2633
+ MeerkatClient.requirePresentStringField(payload, "target", context);
2634
+ MeerkatClient.validateToolConfigStatus(payload.status_info, `${context}: status_info`);
2635
+ if (Object.prototype.hasOwnProperty.call(payload, "applied_at_turn")) {
2636
+ MeerkatClient.requireNonNegativeIntegerField(payload, "applied_at_turn", context);
2637
+ }
2638
+ if (Object.prototype.hasOwnProperty.call(payload, "domain")) {
2639
+ MeerkatClient.requireClosedStringField(payload, "domain", ["tool_scope", "deferred_catalog"], context);
2640
+ }
2641
+ if (Object.prototype.hasOwnProperty.call(payload, "deferred_catalog_delta")) {
2642
+ const delta = MeerkatClient.requireRecord(payload.deferred_catalog_delta, "deferred_catalog_delta", context);
2643
+ for (const field of [
2644
+ "added_hidden_names",
2645
+ "pending_sources",
2646
+ "removed_hidden_names",
2647
+ ]) {
2648
+ if (Object.prototype.hasOwnProperty.call(delta, field)) {
2649
+ MeerkatClient.requireStringArray(delta[field], `${context}: ${field}`);
2650
+ }
2651
+ }
2652
+ }
2653
+ }
2654
+ static validateSystemNoticeBlock(raw, context) {
2655
+ const type = MeerkatClient.requireClosedStringField(raw, "type", [
2656
+ "comms",
2657
+ "external_event",
2658
+ "tool_config",
2659
+ "mcp",
2660
+ "background_job",
2661
+ "auth",
2662
+ "runtime_notice",
2663
+ "unknown",
2664
+ ], context);
2665
+ const validateContent = () => {
2666
+ if (!Object.prototype.hasOwnProperty.call(raw, "content"))
2667
+ return;
2668
+ MeerkatClient.requireRecordArray(raw.content, `${context}: content`).forEach((block, index) => MeerkatClient.validateGeneratedContentBlock(block, `${context}: content[${index}]`, true));
2669
+ };
2670
+ if (type === "comms") {
2671
+ MeerkatClient.requireClosedStringField(raw, "direction", ["incoming", "outgoing", "internal"], context);
2672
+ MeerkatClient.requirePresentStringField(raw, "kind", context);
2673
+ for (const field of ["intent", "request_id", "status", "summary"]) {
2674
+ MeerkatClient.validateOptionalStringField(raw, field, context);
2675
+ }
2676
+ if (Object.prototype.hasOwnProperty.call(raw, "peer")) {
2677
+ const peer = MeerkatClient.requireRecord(raw.peer, "peer", context);
2678
+ MeerkatClient.requireStringField(peer, "id", `${context}: peer`);
2679
+ MeerkatClient.validateOptionalStringField(peer, "display_name", `${context}: peer`);
2680
+ }
2681
+ if (Object.prototype.hasOwnProperty.call(raw, "sender_taint")) {
2682
+ MeerkatClient.requireClosedStringField(raw, "sender_taint", ["clean", "tainted"], context);
2683
+ }
2684
+ validateContent();
2685
+ }
2686
+ else if (type === "external_event") {
2687
+ MeerkatClient.requirePresentStringField(raw, "event_type", context);
2688
+ MeerkatClient.requirePresentStringField(raw, "source", context);
2689
+ MeerkatClient.validateOptionalStringField(raw, "body", context);
2690
+ MeerkatClient.validateOptionalStringField(raw, "summary", context);
2691
+ validateContent();
2692
+ }
2693
+ else if (type === "tool_config") {
2694
+ MeerkatClient.validateToolConfigPayload(raw.payload, `${context}: payload`);
2695
+ }
2696
+ else if (type === "mcp") {
2697
+ for (const field of ["detail", "server_id"]) {
2698
+ MeerkatClient.validateOptionalStringField(raw, field, context);
2699
+ }
2700
+ if (Object.prototype.hasOwnProperty.call(raw, "operation")) {
2701
+ MeerkatClient.requireClosedStringField(raw, "operation", ["add", "remove", "reload"], context);
2702
+ }
2703
+ if (Object.prototype.hasOwnProperty.call(raw, "pending_sources")) {
2704
+ MeerkatClient.requireStringArray(raw.pending_sources, `${context}: pending_sources`);
2705
+ }
2706
+ MeerkatClient.validateOptionalBooleanField(raw, "persisted", context);
2707
+ if (Object.prototype.hasOwnProperty.call(raw, "phase")) {
2708
+ MeerkatClient.requireClosedStringField(raw, "phase", ["pending", "applied", "draining", "forced", "failed"], context);
2709
+ }
2710
+ }
2711
+ else if (type === "background_job") {
2712
+ MeerkatClient.requireStringField(raw, "job_id", context);
2713
+ MeerkatClient.requireClosedStringField(raw, "status", ["completed", "failed", "aborted", "cancelled", "retired", "terminated"], context);
2714
+ MeerkatClient.validateOptionalStringField(raw, "detail", context);
2715
+ MeerkatClient.validateOptionalStringField(raw, "display_name", context);
2716
+ }
2717
+ else if (type === "auth") {
2718
+ MeerkatClient.requirePresentStringField(raw, "state", context);
2719
+ MeerkatClient.validateOptionalStringField(raw, "binding", context);
2720
+ MeerkatClient.validateOptionalStringField(raw, "detail", context);
2721
+ }
2722
+ else if (type === "runtime_notice") {
2723
+ MeerkatClient.requirePresentStringField(raw, "category", context);
2724
+ MeerkatClient.validateOptionalStringField(raw, "detail", context);
2725
+ }
2726
+ else {
2727
+ MeerkatClient.validateOptionalStringField(raw, "summary", context);
2728
+ }
2729
+ }
2730
+ static validateWireAssistantBlock(raw, context) {
2731
+ const blockType = MeerkatClient.requireClosedStringField(raw, "block_type", [
2732
+ "text",
2733
+ "transcript",
2734
+ "reasoning",
2735
+ "tool_use",
2736
+ "server_tool_content",
2737
+ "image",
2738
+ "unknown",
2739
+ ], context);
2740
+ if (blockType === "unknown")
2741
+ return;
2742
+ const data = MeerkatClient.requireRecord(raw.data, "data", context);
2743
+ MeerkatClient.validateOptionalMetaRecord(data, `${context}: data`);
2744
+ if (blockType === "text") {
2745
+ MeerkatClient.requirePresentStringField(data, "text", `${context}: data`);
2746
+ }
2747
+ else if (blockType === "transcript") {
2748
+ MeerkatClient.requirePresentStringField(data, "text", `${context}: data`);
2749
+ const source = MeerkatClient.requireRecord(data.source, "source", `${context}: data`);
2750
+ const sourceKind = MeerkatClient.requireClosedStringField(source, "kind", ["spoken", "unknown"], `${context}: data.source`);
2751
+ if (sourceKind === "unknown") {
2752
+ MeerkatClient.requirePresentStringField(source, "debug", `${context}: data.source`);
2753
+ }
2754
+ }
2755
+ else if (blockType === "reasoning") {
2756
+ MeerkatClient.validateOptionalStringField(data, "text", `${context}: data`);
2757
+ }
2758
+ else if (blockType === "tool_use") {
2759
+ MeerkatClient.requireOwnField(data, "args", `${context}: data`);
2760
+ MeerkatClient.requireStringField(data, "id", `${context}: data`);
2761
+ MeerkatClient.requireStringField(data, "name", `${context}: data`);
2762
+ }
2763
+ else if (blockType === "server_tool_content") {
2764
+ MeerkatClient.requireOwnField(data, "content", `${context}: data`);
2765
+ MeerkatClient.requireRecord(data.kind, "kind", `${context}: data`);
2766
+ MeerkatClient.validateOptionalStringField(data, "id", `${context}: data`);
2767
+ }
2768
+ else {
2769
+ MeerkatClient.requireRecord(data.blob_ref, "blob_ref", `${context}: data`);
2770
+ MeerkatClient.requireNonNegativeIntegerField(data, "height", `${context}: data`);
2771
+ MeerkatClient.requireStringField(data, "image_id", `${context}: data`);
2772
+ MeerkatClient.requirePresentStringField(data, "media_type", `${context}: data`);
2773
+ MeerkatClient.requireRecord(data.meta, "meta", `${context}: data`);
2774
+ MeerkatClient.requireRecord(data.revised_prompt, "revised_prompt", `${context}: data`);
2775
+ MeerkatClient.requireNonNegativeIntegerField(data, "width", `${context}: data`);
2776
+ }
2777
+ }
2778
+ static validateWireToolResult(raw, context) {
2779
+ MeerkatClient.requireStringField(raw, "tool_use_id", context);
2780
+ const content = MeerkatClient.requireOwnField(raw, "content", context);
2781
+ if (typeof content !== "string" && !Array.isArray(content)) {
2782
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: invalid content`);
2783
+ }
2784
+ if (Array.isArray(content)) {
2785
+ MeerkatClient.requireRecordArray(content, `${context}: content`).forEach((block, index) => MeerkatClient.validateGeneratedContentBlock(block, `${context}: content[${index}]`, false));
2786
+ }
2787
+ MeerkatClient.validateOptionalBooleanField(raw, "is_error", context);
2788
+ }
2789
+ static validateWireHistoryRow(raw, context) {
2790
+ const role = MeerkatClient.requireStringField(raw, "role", context);
2791
+ MeerkatClient.requireStringField(raw, "created_at", context);
2792
+ for (const field of ["interaction_id", "run_id"]) {
2793
+ if (Object.prototype.hasOwnProperty.call(raw, field) &&
2794
+ (typeof raw[field] !== "string" || raw[field].length === 0)) {
2795
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be non-empty string`);
2796
+ }
2797
+ }
2798
+ if (role === "system") {
2799
+ MeerkatClient.requirePresentStringField(raw, "content", context);
2800
+ return;
2801
+ }
2802
+ if (role === "system_notice") {
2803
+ const kind = MeerkatClient.requireStringField(raw, "kind", context);
2804
+ if (![
2805
+ "generic",
2806
+ "comms",
2807
+ "external_event",
2808
+ "mcp_pending",
2809
+ "mcp",
2810
+ "background_job",
2811
+ "tool_scope",
2812
+ "tool_scope_warning",
2813
+ "auth_reauth_required",
2814
+ ].includes(kind)) {
2815
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported kind ${JSON.stringify(kind)}`);
2816
+ }
2817
+ MeerkatClient.validateOptionalStringField(raw, "body", context);
2818
+ if (Object.prototype.hasOwnProperty.call(raw, "blocks")) {
2819
+ MeerkatClient.requireRecordArray(raw.blocks, `${context}: blocks`).forEach((block, index) => MeerkatClient.validateSystemNoticeBlock(block, `${context}: blocks[${index}]`));
2820
+ }
2821
+ return;
2822
+ }
2823
+ if (role === "user") {
2824
+ if (raw.content == null || (!Array.isArray(raw.content) && typeof raw.content !== "string")) {
2825
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing content`);
2826
+ }
2827
+ if (Array.isArray(raw.content)) {
2828
+ MeerkatClient.requireRecordArray(raw.content, `${context}: content`).forEach((block, index) => MeerkatClient.validateGeneratedContentBlock(block, `${context}: content[${index}]`, false));
2829
+ }
2830
+ if (Object.prototype.hasOwnProperty.call(raw, "transcript_role")) {
2831
+ MeerkatClient.requireClosedStringField(raw, "transcript_role", ["conversational", "compaction_summary", "injected_context"], context);
2832
+ }
2833
+ return;
2834
+ }
2835
+ if (role === "block_assistant") {
2836
+ MeerkatClient.requireRecordArray(raw.blocks, `${context}: blocks`).forEach((block, index) => MeerkatClient.validateWireAssistantBlock(block, `${context}: blocks[${index}]`));
2837
+ const stopReason = MeerkatClient.requireStringField(raw, "stop_reason", context);
2838
+ if (![
2839
+ "end_turn",
2840
+ "tool_use",
2841
+ "max_tokens",
2842
+ "stop_sequence",
2843
+ "content_filter",
2844
+ "cancelled",
2845
+ ].includes(stopReason)) {
2846
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported stop_reason ${JSON.stringify(stopReason)}`);
2847
+ }
2848
+ return;
2849
+ }
2850
+ if (role === "tool_results") {
2851
+ MeerkatClient.requireRecordArray(raw.results, `${context}: results`).forEach((result, index) => MeerkatClient.validateWireToolResult(result, `${context}: results[${index}]`));
2852
+ return;
2853
+ }
2854
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported role ${JSON.stringify(role)}`);
2855
+ }
2856
+ static parseMobHostCapabilities(raw, context) {
2857
+ const capabilities = MeerkatClient.requireRecord(raw, "capabilities", context);
2858
+ for (const field of [
2859
+ "approval_forwarding",
2860
+ "autonomous_members",
2861
+ "durable_sessions",
2862
+ "hard_cancel_member",
2863
+ "mcp",
2864
+ "memory_store",
2865
+ ]) {
2866
+ MeerkatClient.requireBooleanField(capabilities, field, context);
2867
+ }
2868
+ const trackedInputCancel = Object.prototype.hasOwnProperty.call(capabilities, "tracked_input_cancel")
2869
+ ? MeerkatClient.requireBooleanField(capabilities, "tracked_input_cancel", context)
2870
+ : false;
2871
+ MeerkatClient.requireStringField(capabilities, "engine_version", context);
2872
+ const protocolMin = MeerkatClient.requireNonNegativeIntegerField(capabilities, "protocol_min", context);
2873
+ const protocolMax = MeerkatClient.requireNonNegativeIntegerField(capabilities, "protocol_max", context);
2874
+ if (protocolMin > protocolMax) {
2875
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: protocol_min exceeds protocol_max`);
2876
+ }
2877
+ const liveEndpoint = MeerkatClient.optionalStringField(capabilities, "live_endpoint", context);
2878
+ if (liveEndpoint === "") {
2879
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: live_endpoint must be non-empty`);
2880
+ }
2881
+ const resolvableProviders = Object.prototype.hasOwnProperty.call(capabilities, "resolvable_providers")
2882
+ ? MeerkatClient.requireStringArray(capabilities.resolvable_providers, `${context}: resolvable_providers`)
2883
+ : undefined;
2884
+ return {
2885
+ ...capabilities,
2886
+ tracked_input_cancel: trackedInputCancel,
2887
+ ...(resolvableProviders !== undefined
2888
+ ? { resolvable_providers: resolvableProviders }
2889
+ : {}),
2890
+ };
2891
+ }
2892
+ static parseMobHostStatus(raw, context) {
2893
+ const host = MeerkatClient.requireRecord(raw, "host", context);
2894
+ const bindPhase = MeerkatClient.requireStringField(host, "bind_phase", context);
2895
+ if (bindPhase !== "requested" && bindPhase !== "bound") {
2896
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported bind_phase ${JSON.stringify(bindPhase)}`);
2897
+ }
2898
+ MeerkatClient.requireStringField(host, "host_id", context);
2899
+ MeerkatClient.requireNonNegativeIntegerField(host, "materialized_member_count", context);
2900
+ const authorityEpoch = host.authority_epoch == null
2901
+ ? undefined
2902
+ : MeerkatClient.requireNonNegativeIntegerField(host, "authority_epoch", context);
2903
+ const endpoint = MeerkatClient.optionalStringField(host, "endpoint", context);
2904
+ const capabilities = host.capabilities == null
2905
+ ? undefined
2906
+ : MeerkatClient.parseMobHostCapabilities(host.capabilities, `${context}: capabilities`);
2907
+ if (bindPhase === "bound" &&
2908
+ (authorityEpoch === undefined || !endpoint || capabilities === undefined)) {
2909
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: bound host missing committed authority facts`);
2910
+ }
2911
+ if (bindPhase === "requested" &&
2912
+ (authorityEpoch !== undefined || endpoint !== undefined || capabilities !== undefined)) {
2913
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: requested host must not claim committed authority facts`);
2914
+ }
2915
+ if (host.control_reachability != null &&
2916
+ !["reachable", "stale", "unreachable", "unknown"].includes(String(host.control_reachability))) {
2917
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported control_reachability`);
2918
+ }
2919
+ if (host.last_seen_ms != null) {
2920
+ MeerkatClient.requireNonNegativeIntegerField(host, "last_seen_ms", context);
2921
+ }
2922
+ MeerkatClient.optionalStringField(host, "freshness_reason", context);
2923
+ return (capabilities === undefined
2924
+ ? host
2925
+ : { ...host, capabilities });
2926
+ }
2927
+ static parseRouteInstallObligation(raw, context) {
2928
+ const obligation = MeerkatClient.requireRecord(raw, "obligation", context);
2929
+ MeerkatClient.requireStringField(obligation, "edge_a", context);
2930
+ MeerkatClient.requireStringField(obligation, "edge_b", context);
2931
+ MeerkatClient.requireStringField(obligation, "host", context);
2932
+ return obligation;
2933
+ }
2310
2934
  static requireBooleanField(raw, field, context) {
2311
2935
  const value = raw[field];
2312
2936
  if (typeof value !== "boolean") {
@@ -2333,6 +2957,21 @@ export class MeerkatClient {
2333
2957
  }
2334
2958
  throw new MeerkatError("INVALID_RESPONSE", message);
2335
2959
  }
2960
+ static parseWireReachability(raw, field) {
2961
+ if (raw == null) {
2962
+ return undefined;
2963
+ }
2964
+ const values = [
2965
+ "reachable",
2966
+ "stale",
2967
+ "unreachable",
2968
+ "unknown",
2969
+ ];
2970
+ if (typeof raw === "string" && values.includes(raw)) {
2971
+ return raw;
2972
+ }
2973
+ throw new MeerkatError("INVALID_RESPONSE", `Invalid mob/member_status response: ${field} must be a valid reachability`);
2974
+ }
2336
2975
  static parseMobPeerConnectivitySnapshot(raw, context) {
2337
2976
  const record = MeerkatClient.requireRecord(raw, "peer_connectivity.snapshot", context);
2338
2977
  const unreachableRaw = record.unreachable_peers;
@@ -2427,6 +3066,85 @@ export class MeerkatClient {
2427
3066
  status: MeerkatClient.parseWireCapabilityStatus(record.status, context),
2428
3067
  };
2429
3068
  }
3069
+ /**
3070
+ * Validate the generated `LiveOpenResult` contract without projecting or
3071
+ * normalizing its transport bootstrap. In particular, single-use tokens are
3072
+ * returned byte-for-byte as received and are never logged.
3073
+ */
3074
+ static parseLiveOpenResult(raw, context) {
3075
+ const result = MeerkatClient.requireRecord(raw, "result", context);
3076
+ MeerkatClient.requireStringField(result, "channel_id", context);
3077
+ const capabilities = MeerkatClient.requireRecord(result.capabilities, "capabilities", context);
3078
+ for (const field of [
3079
+ "audio_in",
3080
+ "audio_out",
3081
+ "barge_in_supported",
3082
+ "image_in",
3083
+ "provider_native_resume",
3084
+ "text_in",
3085
+ "text_out",
3086
+ "transcript_supported",
3087
+ "video_in",
3088
+ ]) {
3089
+ MeerkatClient.requireBooleanField(capabilities, field, `${context}: capabilities`);
3090
+ }
3091
+ const continuity = MeerkatClient.requireRecord(result.continuity, "continuity", context);
3092
+ const continuityMode = MeerkatClient.requireClosedStringField(continuity, "mode", [
3093
+ "fresh",
3094
+ "transcript_only",
3095
+ "degraded",
3096
+ "provider_native_resume",
3097
+ "unknown",
3098
+ ], `${context}: continuity`);
3099
+ if (continuityMode === "provider_native_resume") {
3100
+ MeerkatClient.requireStringField(continuity, "provider_session_id", `${context}: continuity`);
3101
+ }
3102
+ else if (continuityMode === "unknown") {
3103
+ MeerkatClient.requirePresentStringField(continuity, "debug", `${context}: continuity`);
3104
+ }
3105
+ const transport = MeerkatClient.requireRecord(result.transport, "transport", context);
3106
+ const transportKind = MeerkatClient.requireClosedStringField(transport, "transport", ["websocket", "webrtc", "unknown"], `${context}: transport`);
3107
+ if (transportKind === "websocket") {
3108
+ MeerkatClient.requireStringField(transport, "url", `${context}: transport`);
3109
+ MeerkatClient.requireStringField(transport, "token", `${context}: transport`);
3110
+ }
3111
+ else if (transportKind === "webrtc") {
3112
+ MeerkatClient.requireStringField(transport, "answer_method", `${context}: transport`);
3113
+ MeerkatClient.requireStringField(transport, "token", `${context}: transport`);
3114
+ MeerkatClient.validateOptionalStringField(transport, "http_url", `${context}: transport`);
3115
+ }
3116
+ else {
3117
+ MeerkatClient.requirePresentStringField(transport, "debug", `${context}: transport`);
3118
+ }
3119
+ return result;
3120
+ }
3121
+ /** Validate the generated `LiveStatusResult` closed status vocabulary. */
3122
+ static parseLiveStatusResult(raw, context) {
3123
+ const result = MeerkatClient.requireRecord(raw, "result", context);
3124
+ MeerkatClient.requireStringField(result, "channel_id", context);
3125
+ const status = MeerkatClient.requireRecord(result.status, "status", context);
3126
+ const statusKind = MeerkatClient.requireClosedStringField(status, "status", ["idle", "opening", "ready", "degraded", "closing", "closed", "unknown"], `${context}: status`);
3127
+ if (statusKind === "degraded") {
3128
+ const reason = MeerkatClient.requireRecord(status.reason, "reason", `${context}: status`);
3129
+ const reasonKind = MeerkatClient.requireClosedStringField(reason, "kind", [
3130
+ "rate_limited",
3131
+ "provider_throttled",
3132
+ "network_unstable",
3133
+ "other",
3134
+ "unknown",
3135
+ ], `${context}: status.reason`);
3136
+ if (reasonKind === "other") {
3137
+ MeerkatClient.requirePresentStringField(reason, "detail", `${context}: status.reason`);
3138
+ }
3139
+ else if (reasonKind === "unknown") {
3140
+ MeerkatClient.requirePresentStringField(reason, "debug", `${context}: status.reason`);
3141
+ }
3142
+ }
3143
+ else if (statusKind === "unknown") {
3144
+ MeerkatClient.requirePresentStringField(status, "debug", `${context}: status`);
3145
+ }
3146
+ return result;
3147
+ }
2430
3148
  static parseLiveRefreshResult(raw) {
2431
3149
  const context = "Invalid live/refresh response";
2432
3150
  const record = MeerkatClient.requireRecord(raw, "result", context);
@@ -2700,6 +3418,37 @@ export class MeerkatClient {
2700
3418
  }
2701
3419
  return progress;
2702
3420
  }
3421
+ static parseMemberLifecycleCapabilities(raw) {
3422
+ if (raw == null) {
3423
+ return undefined;
3424
+ }
3425
+ const context = "Invalid mob/member_status response";
3426
+ const record = MeerkatClient.requireRecord(raw, "lifecycle_capabilities", context);
3427
+ return {
3428
+ transcript_edits: MeerkatClient.requireBooleanField(record, "transcript_edits", context),
3429
+ revisions: MeerkatClient.requireBooleanField(record, "revisions", context),
3430
+ resume_after_restart: MeerkatClient.requireBooleanField(record, "resume_after_restart", context),
3431
+ };
3432
+ }
3433
+ static parseNonPortableDisabled(raw) {
3434
+ if (raw == null) {
3435
+ return undefined;
3436
+ }
3437
+ const values = [
3438
+ "rust_bundles",
3439
+ "per_spawn_external_tools",
3440
+ "mob_default_external_tools",
3441
+ "default_llm_client_override",
3442
+ "host_surface_mcp_allowlist",
3443
+ "workgraph_tools",
3444
+ ];
3445
+ if (!Array.isArray(raw) ||
3446
+ raw.some((entry) => typeof entry !== "string" ||
3447
+ !values.includes(entry))) {
3448
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/member_status response: non_portable_disabled must contain valid resource kinds");
3449
+ }
3450
+ return raw;
3451
+ }
2703
3452
  static parseModelsCatalog(data) {
2704
3453
  const context = "Invalid models/catalog response";
2705
3454
  const contractVersion = MeerkatClient.parseContractVersion(data.contract_version, context);
@@ -2773,11 +3522,13 @@ export class MeerkatClient {
2773
3522
  }
2774
3523
  static parseContractVersion(raw, context) {
2775
3524
  const parseComponent = (value, field) => {
2776
- if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
2777
- return value;
2778
- }
2779
- if (typeof value === "string" && /^\d+$/.test(value)) {
2780
- return Number(value);
3525
+ const parsed = typeof value === "number"
3526
+ ? value
3527
+ : typeof value === "string" && /^\d+$/.test(value)
3528
+ ? Number(value)
3529
+ : undefined;
3530
+ if (typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed >= 0) {
3531
+ return parsed;
2781
3532
  }
2782
3533
  throw new MeerkatError("INVALID_RESPONSE", `${context}: contract_version.${field} must be non-negative integer`);
2783
3534
  };
@@ -3006,10 +3757,8 @@ export class MeerkatClient {
3006
3757
  }
3007
3758
  static parseSessionHistory(data) {
3008
3759
  const context = "Invalid session history response";
3009
- if (!Array.isArray(data.messages)) {
3010
- throw new MeerkatError("INVALID_RESPONSE", `${context}: messages must be a list`);
3011
- }
3012
- const rawMessages = data.messages;
3760
+ const rawMessages = MeerkatClient.requireRecordArray(data.messages, `${context}: messages`);
3761
+ rawMessages.forEach((message, index) => MeerkatClient.validateWireHistoryRow(message, `${context}: messages[${index}]`));
3013
3762
  return {
3014
3763
  sessionId: MeerkatClient.requireStringField(data, "session_id", context),
3015
3764
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
@@ -3022,10 +3771,8 @@ export class MeerkatClient {
3022
3771
  }
3023
3772
  static parseSessionTranscriptRevision(data) {
3024
3773
  const context = "Invalid session transcript revision response";
3025
- if (!Array.isArray(data.messages)) {
3026
- throw new MeerkatError("INVALID_RESPONSE", `${context}: messages must be a list`);
3027
- }
3028
- const rawMessages = data.messages;
3774
+ const rawMessages = MeerkatClient.requireRecordArray(data.messages, `${context}: messages`);
3775
+ rawMessages.forEach((message, index) => MeerkatClient.validateWireHistoryRow(message, `${context}: messages[${index}]`));
3029
3776
  return {
3030
3777
  sessionId: MeerkatClient.requireStringField(data, "session_id", context),
3031
3778
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
@@ -3135,7 +3882,12 @@ export class MeerkatClient {
3135
3882
  stopReason: data.stop_reason != null ? String(data.stop_reason) : undefined,
3136
3883
  interactionId: data.interaction_id != null ? String(data.interaction_id) : undefined,
3137
3884
  runId: data.run_id != null ? String(data.run_id) : undefined,
3138
- blocks: rawBlocks.map((block) => MeerkatClient.parseSessionAssistantBlock(block)),
3885
+ // System-notice blocks have their own generated union and remain
3886
+ // available in `raw`; only block-assistant rows project through the
3887
+ // public assistant-block view.
3888
+ blocks: role === "block_assistant"
3889
+ ? rawBlocks.map((block) => MeerkatClient.parseSessionAssistantBlock(block))
3890
+ : [],
3139
3891
  results: rawResults.map((result) => {
3140
3892
  if (typeof result !== "object" || result === null) {
3141
3893
  throw new MeerkatError("INVALID_RESPONSE", `${context}: tool result must be an object`);
@@ -3230,60 +3982,22 @@ export class MeerkatClient {
3230
3982
  }
3231
3983
  static parseContentInput(value) {
3232
3984
  if (Array.isArray(value)) {
3233
- return value
3234
- .filter((item) => typeof item === "object" && item !== null)
3235
- .map((block) => MeerkatClient.parseContentBlock(block));
3985
+ return value.map((item, index) => MeerkatClient.parseContentBlock(MeerkatClient.requireRecord(item, `content[${index}]`, "Invalid session content")));
3236
3986
  }
3237
- return String(value ?? "");
3987
+ if (typeof value === "string")
3988
+ return value;
3989
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid session content: expected string or content-block array");
3238
3990
  }
3239
3991
  static parseContentBlock(data) {
3240
- const type = String(data.type ?? "");
3241
- if (type === "text") {
3242
- return { type: "text", text: String(data.text ?? "") };
3243
- }
3244
- if (type === "image") {
3245
- const source = String(data.source ?? "inline");
3246
- if (source === "blob") {
3247
- return {
3248
- type: "image",
3249
- media_type: String(data.media_type ?? ""),
3250
- source: "blob",
3251
- blob_id: String(data.blob_id ?? ""),
3252
- };
3253
- }
3254
- return {
3255
- type: "image",
3256
- media_type: String(data.media_type ?? ""),
3257
- source: "inline",
3258
- data: String(data.data ?? ""),
3259
- };
3260
- }
3261
- if (type === "video") {
3262
- const source = String(data.source ?? "inline");
3263
- if (source === "uri") {
3264
- return {
3265
- type: "video",
3266
- media_type: String(data.media_type ?? ""),
3267
- duration_ms: Number(data.duration_ms ?? 0),
3268
- source: "uri",
3269
- uri: String(data.uri ?? ""),
3270
- };
3271
- }
3272
- return {
3273
- type: "video",
3274
- media_type: String(data.media_type ?? ""),
3275
- duration_ms: Number(data.duration_ms ?? 0),
3276
- source: "inline",
3277
- data: String(data.data ?? ""),
3278
- };
3279
- }
3280
- return { type: "text", text: "" };
3992
+ MeerkatClient.validateGeneratedContentBlock(data, "Invalid session content block", false);
3993
+ // Preserve the validated wire object exactly. In particular, do not
3994
+ // invent inline bytes for history-only image/video projections or collapse
3995
+ // structured/unknown variants into empty text.
3996
+ return { ...data };
3281
3997
  }
3282
3998
  static parseSessionAssistantBlock(data) {
3283
3999
  const context = "Invalid session assistant block";
3284
- if (data.data != null && (typeof data.data !== "object" || Array.isArray(data.data))) {
3285
- throw new MeerkatError("INVALID_RESPONSE", `${context}: data must be an object`);
3286
- }
4000
+ MeerkatClient.validateWireAssistantBlock(data, context);
3287
4001
  const blockData = data.data ?? {};
3288
4002
  const blobRef = blockData.blob_ref;
3289
4003
  const revisedPrompt = blockData.revised_prompt != null && typeof blockData.revised_prompt === "object"
@@ -3302,9 +4016,9 @@ export class MeerkatClient {
3302
4016
  height: blockData.height != null ? Number(blockData.height) : undefined,
3303
4017
  revisedPrompt,
3304
4018
  meta: blockData.meta,
3305
- // Lane provenance for transcript blocks (typed enum on the wire,
3306
- // serialized as a snake_case string — currently only "spoken").
3307
- source: typeof blockData.source === "string" ? blockData.source : undefined,
4019
+ source: data.block_type === "transcript"
4020
+ ? { ...blockData.source }
4021
+ : undefined,
3308
4022
  raw: { ...data },
3309
4023
  };
3310
4024
  }
@@ -3379,7 +4093,7 @@ export class MeerkatClient {
3379
4093
  params.model = options.model;
3380
4094
  if (options.provider)
3381
4095
  params.provider = options.provider;
3382
- if (options.systemPrompt)
4096
+ if (options.systemPrompt !== undefined)
3383
4097
  params.system_prompt = options.systemPrompt;
3384
4098
  if (options.maxTokens)
3385
4099
  params.max_tokens = options.maxTokens;