@rkat/sdk 0.7.30 → 0.8.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/dist/client.js CHANGED
@@ -32,7 +32,7 @@ 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";
@@ -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
  /**
@@ -1688,7 +1945,7 @@ export class MeerkatClient {
1688
1945
  const source = MeerkatClient.requireRecord(raw.source, "source", context);
1689
1946
  const identity = MeerkatClient.requireStringField(source, "identity", context);
1690
1947
  const generation = MeerkatClient.requireNumberField(source, "generation", context);
1691
- if (!Number.isInteger(generation) || generation < 0) {
1948
+ if (!Number.isSafeInteger(generation) || generation < 0) {
1692
1949
  throw new MeerkatError("INVALID_RESPONSE", `${context}: source generation must be a non-negative integer`);
1693
1950
  }
1694
1951
  const role = MeerkatClient.requireStringField(raw, "role", context);
@@ -1724,10 +1981,14 @@ export class MeerkatClient {
1724
1981
  params.model = options.model;
1725
1982
  if (options?.provider)
1726
1983
  params.provider = options.provider;
1984
+ if (options?.selfHostedServerId != null) {
1985
+ params.self_hosted_server_id = options.selfHostedServerId;
1986
+ }
1727
1987
  if (options?.maxTokens)
1728
1988
  params.max_tokens = options.maxTokens;
1729
- if (options?.systemPrompt)
1989
+ if (options?.systemPrompt !== undefined) {
1730
1990
  params.system_prompt = options.systemPrompt;
1991
+ }
1731
1992
  if (options?.outputSchema)
1732
1993
  params.output_schema = options.outputSchema;
1733
1994
  if (options?.structuredOutputRetries != null) {
@@ -1771,10 +2032,14 @@ export class MeerkatClient {
1771
2032
  params.model = options.model;
1772
2033
  if (options?.provider)
1773
2034
  params.provider = options.provider;
2035
+ if (options?.selfHostedServerId != null) {
2036
+ params.self_hosted_server_id = options.selfHostedServerId;
2037
+ }
1774
2038
  if (options?.maxTokens)
1775
2039
  params.max_tokens = options.maxTokens;
1776
- if (options?.systemPrompt)
2040
+ if (options?.systemPrompt !== undefined) {
1777
2041
  params.system_prompt = options.systemPrompt;
2042
+ }
1778
2043
  if (options?.outputSchema)
1779
2044
  params.output_schema = options.outputSchema;
1780
2045
  if (options?.structuredOutputRetries != null) {
@@ -1865,7 +2130,11 @@ export class MeerkatClient {
1865
2130
  return MeerkatClient.parseCommsSendReceipt(result);
1866
2131
  }
1867
2132
  async peers(sessionId) {
1868
- return this.request("comms/peers", { session_id: sessionId });
2133
+ const result = await this.request("comms/peers", { session_id: sessionId });
2134
+ if (!Array.isArray(result.peers)) {
2135
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid comms/peers response: missing peers");
2136
+ }
2137
+ return { peers: result.peers };
1869
2138
  }
1870
2139
  /** Idempotent spawn: spawns or returns the existing member entry. */
1871
2140
  async mobEnsureMember(mobId, spec) {
@@ -1895,7 +2164,7 @@ export class MeerkatClient {
1895
2164
  // schemas regenerated into `./generated/types.ts`. See I52/I53.
1896
2165
  async liveOpen(params) {
1897
2166
  const result = await this.request("live/open", params);
1898
- return result;
2167
+ return MeerkatClient.parseLiveOpenResult(result, "Invalid live/open response");
1899
2168
  }
1900
2169
  async liveWebrtcAnswer(params) {
1901
2170
  const result = await this.request("live/webrtc/answer", params);
@@ -1903,7 +2172,7 @@ export class MeerkatClient {
1903
2172
  }
1904
2173
  async liveStatus(params) {
1905
2174
  const result = await this.request("live/status", params);
1906
- return result;
2175
+ return MeerkatClient.parseLiveStatusResult(result, "Invalid live/status response");
1907
2176
  }
1908
2177
  async liveClose(params) {
1909
2178
  const result = await this.request("live/close", params);
@@ -2132,7 +2401,18 @@ export class MeerkatClient {
2132
2401
  const error = data.error;
2133
2402
  if (error) {
2134
2403
  const normalized = MeerkatClient.parseRpcErrorPayload(error);
2135
- pending.reject(new MeerkatError(normalized.code, normalized.message, normalized.details));
2404
+ const errorData = typeof error.data === "object" &&
2405
+ error.data !== null &&
2406
+ !Array.isArray(error.data)
2407
+ ? error.data
2408
+ : undefined;
2409
+ const semanticCode = typeof errorData?.code === "string" && errorData.code.length > 0
2410
+ ? errorData.code
2411
+ : undefined;
2412
+ const rpcCode = typeof error.code === "number" || typeof error.code === "string"
2413
+ ? error.code
2414
+ : "UNKNOWN";
2415
+ pending.reject(meerkatErrorFromJsonRpcCode(rpcCode, semanticCode, normalized.message, normalized.details));
2136
2416
  }
2137
2417
  else {
2138
2418
  pending.resolve((data.result ?? {}));
@@ -2266,6 +2546,31 @@ export class MeerkatClient {
2266
2546
  }
2267
2547
  return value;
2268
2548
  }
2549
+ static requireOwnField(raw, field, context) {
2550
+ if (!Object.prototype.hasOwnProperty.call(raw, field)) {
2551
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${field}`);
2552
+ }
2553
+ return raw[field];
2554
+ }
2555
+ static requireClosedStringField(raw, field, allowed, context) {
2556
+ const value = MeerkatClient.requirePresentStringField(raw, field, context);
2557
+ if (!allowed.includes(value)) {
2558
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported ${field} ${JSON.stringify(value)}`);
2559
+ }
2560
+ return value;
2561
+ }
2562
+ static validateOptionalStringField(raw, field, context) {
2563
+ if (Object.prototype.hasOwnProperty.call(raw, field) &&
2564
+ typeof raw[field] !== "string") {
2565
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be string`);
2566
+ }
2567
+ }
2568
+ static validateOptionalBooleanField(raw, field, context) {
2569
+ if (Object.prototype.hasOwnProperty.call(raw, field) &&
2570
+ typeof raw[field] !== "boolean") {
2571
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be boolean`);
2572
+ }
2573
+ }
2269
2574
  static requireNumberField(raw, field, context, displayField = field) {
2270
2575
  if (!(field in raw)) {
2271
2576
  throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${displayField}`);
@@ -2278,14 +2583,15 @@ export class MeerkatClient {
2278
2583
  }
2279
2584
  /**
2280
2585
  * 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`.
2586
+ * that is fractional, negative, or outside JavaScript's lossless integer
2587
+ * range can never be represented faithfully, so it is a contract violation
2588
+ * and fails closed instead of being accepted as an any-finite-number.
2589
+ * Mirrors the web SDK `requireNonNegativeIntegerField` and Python
2590
+ * `_require_non_negative_integer_field`.
2285
2591
  */
2286
2592
  static requireNonNegativeIntegerField(raw, field, context, displayField = field) {
2287
2593
  const value = MeerkatClient.requireNumberField(raw, field, context, displayField);
2288
- if (!Number.isInteger(value) || value < 0) {
2594
+ if (!Number.isSafeInteger(value) || value < 0) {
2289
2595
  throw new MeerkatError("INVALID_RESPONSE", `${context}: ${displayField} must be a non-negative integer`);
2290
2596
  }
2291
2597
  return value;
@@ -2307,6 +2613,367 @@ export class MeerkatClient {
2307
2613
  }
2308
2614
  return value;
2309
2615
  }
2616
+ static validateGeneratedContentBlock(raw, context, systemNoticeContent) {
2617
+ const allowed = systemNoticeContent
2618
+ ? ["text", "image", "video", "structured", "skill_context"]
2619
+ : ["text", "image", "video", "structured", "unknown"];
2620
+ const type = MeerkatClient.requireClosedStringField(raw, "type", allowed, context);
2621
+ if (type === "text") {
2622
+ MeerkatClient.requirePresentStringField(raw, "text", context);
2623
+ }
2624
+ else if (type === "image") {
2625
+ MeerkatClient.requirePresentStringField(raw, "media_type", context);
2626
+ }
2627
+ else if (type === "video") {
2628
+ MeerkatClient.requirePresentStringField(raw, "media_type", context);
2629
+ MeerkatClient.requireNonNegativeIntegerField(raw, "duration_ms", context);
2630
+ }
2631
+ else if (type === "structured") {
2632
+ MeerkatClient.requireOwnField(raw, "data", context);
2633
+ }
2634
+ else if (type === "skill_context") {
2635
+ const skillKey = MeerkatClient.requireRecord(raw.skill_key, "skill_key", context);
2636
+ MeerkatClient.requireStringField(skillKey, "source_uuid", `${context}: skill_key`);
2637
+ MeerkatClient.requireStringField(skillKey, "skill_name", `${context}: skill_key`);
2638
+ MeerkatClient.requirePresentStringField(raw, "text", context);
2639
+ }
2640
+ }
2641
+ static validateOptionalMetaRecord(raw, context) {
2642
+ if (Object.prototype.hasOwnProperty.call(raw, "meta")) {
2643
+ MeerkatClient.requireRecord(raw.meta, "meta", context);
2644
+ }
2645
+ }
2646
+ static validateToolConfigStatus(raw, context) {
2647
+ const status = MeerkatClient.requireRecord(raw, "status_info", context);
2648
+ const kind = MeerkatClient.requireClosedStringField(status, "kind", [
2649
+ "boundary_applied",
2650
+ "deferred_catalog_delta",
2651
+ "warning_failed_closed",
2652
+ "external_tool_delta",
2653
+ ], context);
2654
+ if (kind === "boundary_applied") {
2655
+ MeerkatClient.requireBooleanField(status, "base_changed", context);
2656
+ MeerkatClient.requireNonNegativeIntegerField(status, "revision", context);
2657
+ MeerkatClient.requireBooleanField(status, "visible_changed", context);
2658
+ }
2659
+ else if (kind === "deferred_catalog_delta") {
2660
+ MeerkatClient.requireNonNegativeIntegerField(status, "added_hidden_count", context);
2661
+ MeerkatClient.requireNonNegativeIntegerField(status, "pending_source_count", context);
2662
+ MeerkatClient.requireNonNegativeIntegerField(status, "removed_hidden_count", context);
2663
+ }
2664
+ else if (kind === "warning_failed_closed") {
2665
+ MeerkatClient.requirePresentStringField(status, "error", context);
2666
+ }
2667
+ else {
2668
+ MeerkatClient.requireClosedStringField(status, "phase", ["pending", "applied", "draining", "forced", "failed"], context);
2669
+ MeerkatClient.validateOptionalStringField(status, "detail", context);
2670
+ }
2671
+ }
2672
+ static validateToolConfigPayload(raw, context) {
2673
+ const payload = MeerkatClient.requireRecord(raw, "payload", context);
2674
+ MeerkatClient.requireClosedStringField(payload, "operation", ["add", "remove", "reload"], context);
2675
+ MeerkatClient.requireBooleanField(payload, "persisted", context);
2676
+ MeerkatClient.requirePresentStringField(payload, "target", context);
2677
+ MeerkatClient.validateToolConfigStatus(payload.status_info, `${context}: status_info`);
2678
+ if (Object.prototype.hasOwnProperty.call(payload, "applied_at_turn")) {
2679
+ MeerkatClient.requireNonNegativeIntegerField(payload, "applied_at_turn", context);
2680
+ }
2681
+ if (Object.prototype.hasOwnProperty.call(payload, "domain")) {
2682
+ MeerkatClient.requireClosedStringField(payload, "domain", ["tool_scope", "deferred_catalog"], context);
2683
+ }
2684
+ if (Object.prototype.hasOwnProperty.call(payload, "deferred_catalog_delta")) {
2685
+ const delta = MeerkatClient.requireRecord(payload.deferred_catalog_delta, "deferred_catalog_delta", context);
2686
+ for (const field of [
2687
+ "added_hidden_names",
2688
+ "pending_sources",
2689
+ "removed_hidden_names",
2690
+ ]) {
2691
+ if (Object.prototype.hasOwnProperty.call(delta, field)) {
2692
+ MeerkatClient.requireStringArray(delta[field], `${context}: ${field}`);
2693
+ }
2694
+ }
2695
+ }
2696
+ }
2697
+ static validateSystemNoticeBlock(raw, context) {
2698
+ const type = MeerkatClient.requireClosedStringField(raw, "type", [
2699
+ "comms",
2700
+ "external_event",
2701
+ "tool_config",
2702
+ "mcp",
2703
+ "background_job",
2704
+ "auth",
2705
+ "runtime_notice",
2706
+ "unknown",
2707
+ ], context);
2708
+ const validateContent = () => {
2709
+ if (!Object.prototype.hasOwnProperty.call(raw, "content"))
2710
+ return;
2711
+ MeerkatClient.requireRecordArray(raw.content, `${context}: content`).forEach((block, index) => MeerkatClient.validateGeneratedContentBlock(block, `${context}: content[${index}]`, true));
2712
+ };
2713
+ if (type === "comms") {
2714
+ MeerkatClient.requireClosedStringField(raw, "direction", ["incoming", "outgoing", "internal"], context);
2715
+ MeerkatClient.requirePresentStringField(raw, "kind", context);
2716
+ for (const field of ["intent", "request_id", "status", "summary"]) {
2717
+ MeerkatClient.validateOptionalStringField(raw, field, context);
2718
+ }
2719
+ if (Object.prototype.hasOwnProperty.call(raw, "peer")) {
2720
+ const peer = MeerkatClient.requireRecord(raw.peer, "peer", context);
2721
+ MeerkatClient.requireStringField(peer, "id", `${context}: peer`);
2722
+ MeerkatClient.validateOptionalStringField(peer, "display_name", `${context}: peer`);
2723
+ }
2724
+ if (Object.prototype.hasOwnProperty.call(raw, "sender_taint")) {
2725
+ MeerkatClient.requireClosedStringField(raw, "sender_taint", ["clean", "tainted"], context);
2726
+ }
2727
+ validateContent();
2728
+ }
2729
+ else if (type === "external_event") {
2730
+ MeerkatClient.requirePresentStringField(raw, "event_type", context);
2731
+ MeerkatClient.requirePresentStringField(raw, "source", context);
2732
+ MeerkatClient.validateOptionalStringField(raw, "body", context);
2733
+ MeerkatClient.validateOptionalStringField(raw, "summary", context);
2734
+ validateContent();
2735
+ }
2736
+ else if (type === "tool_config") {
2737
+ MeerkatClient.validateToolConfigPayload(raw.payload, `${context}: payload`);
2738
+ }
2739
+ else if (type === "mcp") {
2740
+ for (const field of ["detail", "server_id"]) {
2741
+ MeerkatClient.validateOptionalStringField(raw, field, context);
2742
+ }
2743
+ if (Object.prototype.hasOwnProperty.call(raw, "operation")) {
2744
+ MeerkatClient.requireClosedStringField(raw, "operation", ["add", "remove", "reload"], context);
2745
+ }
2746
+ if (Object.prototype.hasOwnProperty.call(raw, "pending_sources")) {
2747
+ MeerkatClient.requireStringArray(raw.pending_sources, `${context}: pending_sources`);
2748
+ }
2749
+ MeerkatClient.validateOptionalBooleanField(raw, "persisted", context);
2750
+ if (Object.prototype.hasOwnProperty.call(raw, "phase")) {
2751
+ MeerkatClient.requireClosedStringField(raw, "phase", ["pending", "applied", "draining", "forced", "failed"], context);
2752
+ }
2753
+ }
2754
+ else if (type === "background_job") {
2755
+ MeerkatClient.requireStringField(raw, "job_id", context);
2756
+ MeerkatClient.requireClosedStringField(raw, "status", ["completed", "failed", "aborted", "cancelled", "retired", "terminated"], context);
2757
+ MeerkatClient.validateOptionalStringField(raw, "detail", context);
2758
+ MeerkatClient.validateOptionalStringField(raw, "display_name", context);
2759
+ }
2760
+ else if (type === "auth") {
2761
+ MeerkatClient.requirePresentStringField(raw, "state", context);
2762
+ MeerkatClient.validateOptionalStringField(raw, "binding", context);
2763
+ MeerkatClient.validateOptionalStringField(raw, "detail", context);
2764
+ }
2765
+ else if (type === "runtime_notice") {
2766
+ MeerkatClient.requirePresentStringField(raw, "category", context);
2767
+ MeerkatClient.validateOptionalStringField(raw, "detail", context);
2768
+ }
2769
+ else {
2770
+ MeerkatClient.validateOptionalStringField(raw, "summary", context);
2771
+ }
2772
+ }
2773
+ static validateWireAssistantBlock(raw, context) {
2774
+ const blockType = MeerkatClient.requireClosedStringField(raw, "block_type", [
2775
+ "text",
2776
+ "transcript",
2777
+ "reasoning",
2778
+ "tool_use",
2779
+ "server_tool_content",
2780
+ "image",
2781
+ "unknown",
2782
+ ], context);
2783
+ if (blockType === "unknown")
2784
+ return;
2785
+ const data = MeerkatClient.requireRecord(raw.data, "data", context);
2786
+ MeerkatClient.validateOptionalMetaRecord(data, `${context}: data`);
2787
+ if (blockType === "text") {
2788
+ MeerkatClient.requirePresentStringField(data, "text", `${context}: data`);
2789
+ }
2790
+ else if (blockType === "transcript") {
2791
+ MeerkatClient.requirePresentStringField(data, "text", `${context}: data`);
2792
+ const source = MeerkatClient.requireRecord(data.source, "source", `${context}: data`);
2793
+ const sourceKind = MeerkatClient.requireClosedStringField(source, "kind", ["spoken", "unknown"], `${context}: data.source`);
2794
+ if (sourceKind === "unknown") {
2795
+ MeerkatClient.requirePresentStringField(source, "debug", `${context}: data.source`);
2796
+ }
2797
+ }
2798
+ else if (blockType === "reasoning") {
2799
+ MeerkatClient.validateOptionalStringField(data, "text", `${context}: data`);
2800
+ }
2801
+ else if (blockType === "tool_use") {
2802
+ MeerkatClient.requireOwnField(data, "args", `${context}: data`);
2803
+ MeerkatClient.requireStringField(data, "id", `${context}: data`);
2804
+ MeerkatClient.requireStringField(data, "name", `${context}: data`);
2805
+ }
2806
+ else if (blockType === "server_tool_content") {
2807
+ MeerkatClient.requireOwnField(data, "content", `${context}: data`);
2808
+ MeerkatClient.requireRecord(data.kind, "kind", `${context}: data`);
2809
+ MeerkatClient.validateOptionalStringField(data, "id", `${context}: data`);
2810
+ }
2811
+ else {
2812
+ MeerkatClient.requireRecord(data.blob_ref, "blob_ref", `${context}: data`);
2813
+ MeerkatClient.requireNonNegativeIntegerField(data, "height", `${context}: data`);
2814
+ MeerkatClient.requireStringField(data, "image_id", `${context}: data`);
2815
+ MeerkatClient.requirePresentStringField(data, "media_type", `${context}: data`);
2816
+ MeerkatClient.requireRecord(data.meta, "meta", `${context}: data`);
2817
+ MeerkatClient.requireRecord(data.revised_prompt, "revised_prompt", `${context}: data`);
2818
+ MeerkatClient.requireNonNegativeIntegerField(data, "width", `${context}: data`);
2819
+ }
2820
+ }
2821
+ static validateWireToolResult(raw, context) {
2822
+ MeerkatClient.requireStringField(raw, "tool_use_id", context);
2823
+ const content = MeerkatClient.requireOwnField(raw, "content", context);
2824
+ if (typeof content !== "string" && !Array.isArray(content)) {
2825
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: invalid content`);
2826
+ }
2827
+ if (Array.isArray(content)) {
2828
+ MeerkatClient.requireRecordArray(content, `${context}: content`).forEach((block, index) => MeerkatClient.validateGeneratedContentBlock(block, `${context}: content[${index}]`, false));
2829
+ }
2830
+ MeerkatClient.validateOptionalBooleanField(raw, "is_error", context);
2831
+ }
2832
+ static validateWireHistoryRow(raw, context) {
2833
+ const role = MeerkatClient.requireStringField(raw, "role", context);
2834
+ MeerkatClient.requireStringField(raw, "created_at", context);
2835
+ for (const field of ["interaction_id", "run_id"]) {
2836
+ if (Object.prototype.hasOwnProperty.call(raw, field) &&
2837
+ (typeof raw[field] !== "string" || raw[field].length === 0)) {
2838
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be non-empty string`);
2839
+ }
2840
+ }
2841
+ if (role === "system") {
2842
+ MeerkatClient.requirePresentStringField(raw, "content", context);
2843
+ return;
2844
+ }
2845
+ if (role === "system_notice") {
2846
+ const kind = MeerkatClient.requireStringField(raw, "kind", context);
2847
+ if (![
2848
+ "generic",
2849
+ "comms",
2850
+ "external_event",
2851
+ "mcp_pending",
2852
+ "mcp",
2853
+ "background_job",
2854
+ "tool_scope",
2855
+ "tool_scope_warning",
2856
+ "auth_reauth_required",
2857
+ ].includes(kind)) {
2858
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported kind ${JSON.stringify(kind)}`);
2859
+ }
2860
+ MeerkatClient.validateOptionalStringField(raw, "body", context);
2861
+ if (Object.prototype.hasOwnProperty.call(raw, "blocks")) {
2862
+ MeerkatClient.requireRecordArray(raw.blocks, `${context}: blocks`).forEach((block, index) => MeerkatClient.validateSystemNoticeBlock(block, `${context}: blocks[${index}]`));
2863
+ }
2864
+ return;
2865
+ }
2866
+ if (role === "user") {
2867
+ if (raw.content == null || (!Array.isArray(raw.content) && typeof raw.content !== "string")) {
2868
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing content`);
2869
+ }
2870
+ if (Array.isArray(raw.content)) {
2871
+ MeerkatClient.requireRecordArray(raw.content, `${context}: content`).forEach((block, index) => MeerkatClient.validateGeneratedContentBlock(block, `${context}: content[${index}]`, false));
2872
+ }
2873
+ if (Object.prototype.hasOwnProperty.call(raw, "transcript_role")) {
2874
+ MeerkatClient.requireClosedStringField(raw, "transcript_role", ["conversational", "compaction_summary", "injected_context"], context);
2875
+ }
2876
+ return;
2877
+ }
2878
+ if (role === "block_assistant") {
2879
+ MeerkatClient.requireRecordArray(raw.blocks, `${context}: blocks`).forEach((block, index) => MeerkatClient.validateWireAssistantBlock(block, `${context}: blocks[${index}]`));
2880
+ const stopReason = MeerkatClient.requireStringField(raw, "stop_reason", context);
2881
+ if (![
2882
+ "end_turn",
2883
+ "tool_use",
2884
+ "max_tokens",
2885
+ "stop_sequence",
2886
+ "content_filter",
2887
+ "cancelled",
2888
+ ].includes(stopReason)) {
2889
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported stop_reason ${JSON.stringify(stopReason)}`);
2890
+ }
2891
+ return;
2892
+ }
2893
+ if (role === "tool_results") {
2894
+ MeerkatClient.requireRecordArray(raw.results, `${context}: results`).forEach((result, index) => MeerkatClient.validateWireToolResult(result, `${context}: results[${index}]`));
2895
+ return;
2896
+ }
2897
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported role ${JSON.stringify(role)}`);
2898
+ }
2899
+ static parseMobHostCapabilities(raw, context) {
2900
+ const capabilities = MeerkatClient.requireRecord(raw, "capabilities", context);
2901
+ for (const field of [
2902
+ "approval_forwarding",
2903
+ "autonomous_members",
2904
+ "durable_sessions",
2905
+ "hard_cancel_member",
2906
+ "mcp",
2907
+ "memory_store",
2908
+ ]) {
2909
+ MeerkatClient.requireBooleanField(capabilities, field, context);
2910
+ }
2911
+ const trackedInputCancel = Object.prototype.hasOwnProperty.call(capabilities, "tracked_input_cancel")
2912
+ ? MeerkatClient.requireBooleanField(capabilities, "tracked_input_cancel", context)
2913
+ : false;
2914
+ MeerkatClient.requireStringField(capabilities, "engine_version", context);
2915
+ const protocolMin = MeerkatClient.requireNonNegativeIntegerField(capabilities, "protocol_min", context);
2916
+ const protocolMax = MeerkatClient.requireNonNegativeIntegerField(capabilities, "protocol_max", context);
2917
+ if (protocolMin > protocolMax) {
2918
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: protocol_min exceeds protocol_max`);
2919
+ }
2920
+ const liveEndpoint = MeerkatClient.optionalStringField(capabilities, "live_endpoint", context);
2921
+ if (liveEndpoint === "") {
2922
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: live_endpoint must be non-empty`);
2923
+ }
2924
+ const resolvableProviders = Object.prototype.hasOwnProperty.call(capabilities, "resolvable_providers")
2925
+ ? MeerkatClient.requireStringArray(capabilities.resolvable_providers, `${context}: resolvable_providers`)
2926
+ : undefined;
2927
+ return {
2928
+ ...capabilities,
2929
+ tracked_input_cancel: trackedInputCancel,
2930
+ ...(resolvableProviders !== undefined
2931
+ ? { resolvable_providers: resolvableProviders }
2932
+ : {}),
2933
+ };
2934
+ }
2935
+ static parseMobHostStatus(raw, context) {
2936
+ const host = MeerkatClient.requireRecord(raw, "host", context);
2937
+ const bindPhase = MeerkatClient.requireStringField(host, "bind_phase", context);
2938
+ if (bindPhase !== "requested" && bindPhase !== "bound") {
2939
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported bind_phase ${JSON.stringify(bindPhase)}`);
2940
+ }
2941
+ MeerkatClient.requireStringField(host, "host_id", context);
2942
+ MeerkatClient.requireNonNegativeIntegerField(host, "materialized_member_count", context);
2943
+ const authorityEpoch = host.authority_epoch == null
2944
+ ? undefined
2945
+ : MeerkatClient.requireNonNegativeIntegerField(host, "authority_epoch", context);
2946
+ const endpoint = MeerkatClient.optionalStringField(host, "endpoint", context);
2947
+ const capabilities = host.capabilities == null
2948
+ ? undefined
2949
+ : MeerkatClient.parseMobHostCapabilities(host.capabilities, `${context}: capabilities`);
2950
+ if (bindPhase === "bound" &&
2951
+ (authorityEpoch === undefined || !endpoint || capabilities === undefined)) {
2952
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: bound host missing committed authority facts`);
2953
+ }
2954
+ if (bindPhase === "requested" &&
2955
+ (authorityEpoch !== undefined || endpoint !== undefined || capabilities !== undefined)) {
2956
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: requested host must not claim committed authority facts`);
2957
+ }
2958
+ if (host.control_reachability != null &&
2959
+ !["reachable", "stale", "unreachable", "unknown"].includes(String(host.control_reachability))) {
2960
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported control_reachability`);
2961
+ }
2962
+ if (host.last_seen_ms != null) {
2963
+ MeerkatClient.requireNonNegativeIntegerField(host, "last_seen_ms", context);
2964
+ }
2965
+ MeerkatClient.optionalStringField(host, "freshness_reason", context);
2966
+ return (capabilities === undefined
2967
+ ? host
2968
+ : { ...host, capabilities });
2969
+ }
2970
+ static parseRouteInstallObligation(raw, context) {
2971
+ const obligation = MeerkatClient.requireRecord(raw, "obligation", context);
2972
+ MeerkatClient.requireStringField(obligation, "edge_a", context);
2973
+ MeerkatClient.requireStringField(obligation, "edge_b", context);
2974
+ MeerkatClient.requireStringField(obligation, "host", context);
2975
+ return obligation;
2976
+ }
2310
2977
  static requireBooleanField(raw, field, context) {
2311
2978
  const value = raw[field];
2312
2979
  if (typeof value !== "boolean") {
@@ -2333,6 +3000,21 @@ export class MeerkatClient {
2333
3000
  }
2334
3001
  throw new MeerkatError("INVALID_RESPONSE", message);
2335
3002
  }
3003
+ static parseWireReachability(raw, field) {
3004
+ if (raw == null) {
3005
+ return undefined;
3006
+ }
3007
+ const values = [
3008
+ "reachable",
3009
+ "stale",
3010
+ "unreachable",
3011
+ "unknown",
3012
+ ];
3013
+ if (typeof raw === "string" && values.includes(raw)) {
3014
+ return raw;
3015
+ }
3016
+ throw new MeerkatError("INVALID_RESPONSE", `Invalid mob/member_status response: ${field} must be a valid reachability`);
3017
+ }
2336
3018
  static parseMobPeerConnectivitySnapshot(raw, context) {
2337
3019
  const record = MeerkatClient.requireRecord(raw, "peer_connectivity.snapshot", context);
2338
3020
  const unreachableRaw = record.unreachable_peers;
@@ -2427,6 +3109,85 @@ export class MeerkatClient {
2427
3109
  status: MeerkatClient.parseWireCapabilityStatus(record.status, context),
2428
3110
  };
2429
3111
  }
3112
+ /**
3113
+ * Validate the generated `LiveOpenResult` contract without projecting or
3114
+ * normalizing its transport bootstrap. In particular, single-use tokens are
3115
+ * returned byte-for-byte as received and are never logged.
3116
+ */
3117
+ static parseLiveOpenResult(raw, context) {
3118
+ const result = MeerkatClient.requireRecord(raw, "result", context);
3119
+ MeerkatClient.requireStringField(result, "channel_id", context);
3120
+ const capabilities = MeerkatClient.requireRecord(result.capabilities, "capabilities", context);
3121
+ for (const field of [
3122
+ "audio_in",
3123
+ "audio_out",
3124
+ "barge_in_supported",
3125
+ "image_in",
3126
+ "provider_native_resume",
3127
+ "text_in",
3128
+ "text_out",
3129
+ "transcript_supported",
3130
+ "video_in",
3131
+ ]) {
3132
+ MeerkatClient.requireBooleanField(capabilities, field, `${context}: capabilities`);
3133
+ }
3134
+ const continuity = MeerkatClient.requireRecord(result.continuity, "continuity", context);
3135
+ const continuityMode = MeerkatClient.requireClosedStringField(continuity, "mode", [
3136
+ "fresh",
3137
+ "transcript_only",
3138
+ "degraded",
3139
+ "provider_native_resume",
3140
+ "unknown",
3141
+ ], `${context}: continuity`);
3142
+ if (continuityMode === "provider_native_resume") {
3143
+ MeerkatClient.requireStringField(continuity, "provider_session_id", `${context}: continuity`);
3144
+ }
3145
+ else if (continuityMode === "unknown") {
3146
+ MeerkatClient.requirePresentStringField(continuity, "debug", `${context}: continuity`);
3147
+ }
3148
+ const transport = MeerkatClient.requireRecord(result.transport, "transport", context);
3149
+ const transportKind = MeerkatClient.requireClosedStringField(transport, "transport", ["websocket", "webrtc", "unknown"], `${context}: transport`);
3150
+ if (transportKind === "websocket") {
3151
+ MeerkatClient.requireStringField(transport, "url", `${context}: transport`);
3152
+ MeerkatClient.requireStringField(transport, "token", `${context}: transport`);
3153
+ }
3154
+ else if (transportKind === "webrtc") {
3155
+ MeerkatClient.requireStringField(transport, "answer_method", `${context}: transport`);
3156
+ MeerkatClient.requireStringField(transport, "token", `${context}: transport`);
3157
+ MeerkatClient.validateOptionalStringField(transport, "http_url", `${context}: transport`);
3158
+ }
3159
+ else {
3160
+ MeerkatClient.requirePresentStringField(transport, "debug", `${context}: transport`);
3161
+ }
3162
+ return result;
3163
+ }
3164
+ /** Validate the generated `LiveStatusResult` closed status vocabulary. */
3165
+ static parseLiveStatusResult(raw, context) {
3166
+ const result = MeerkatClient.requireRecord(raw, "result", context);
3167
+ MeerkatClient.requireStringField(result, "channel_id", context);
3168
+ const status = MeerkatClient.requireRecord(result.status, "status", context);
3169
+ const statusKind = MeerkatClient.requireClosedStringField(status, "status", ["idle", "opening", "ready", "degraded", "closing", "closed", "unknown"], `${context}: status`);
3170
+ if (statusKind === "degraded") {
3171
+ const reason = MeerkatClient.requireRecord(status.reason, "reason", `${context}: status`);
3172
+ const reasonKind = MeerkatClient.requireClosedStringField(reason, "kind", [
3173
+ "rate_limited",
3174
+ "provider_throttled",
3175
+ "network_unstable",
3176
+ "other",
3177
+ "unknown",
3178
+ ], `${context}: status.reason`);
3179
+ if (reasonKind === "other") {
3180
+ MeerkatClient.requirePresentStringField(reason, "detail", `${context}: status.reason`);
3181
+ }
3182
+ else if (reasonKind === "unknown") {
3183
+ MeerkatClient.requirePresentStringField(reason, "debug", `${context}: status.reason`);
3184
+ }
3185
+ }
3186
+ else if (statusKind === "unknown") {
3187
+ MeerkatClient.requirePresentStringField(status, "debug", `${context}: status`);
3188
+ }
3189
+ return result;
3190
+ }
2430
3191
  static parseLiveRefreshResult(raw) {
2431
3192
  const context = "Invalid live/refresh response";
2432
3193
  const record = MeerkatClient.requireRecord(raw, "result", context);
@@ -2700,6 +3461,37 @@ export class MeerkatClient {
2700
3461
  }
2701
3462
  return progress;
2702
3463
  }
3464
+ static parseMemberLifecycleCapabilities(raw) {
3465
+ if (raw == null) {
3466
+ return undefined;
3467
+ }
3468
+ const context = "Invalid mob/member_status response";
3469
+ const record = MeerkatClient.requireRecord(raw, "lifecycle_capabilities", context);
3470
+ return {
3471
+ transcript_edits: MeerkatClient.requireBooleanField(record, "transcript_edits", context),
3472
+ revisions: MeerkatClient.requireBooleanField(record, "revisions", context),
3473
+ resume_after_restart: MeerkatClient.requireBooleanField(record, "resume_after_restart", context),
3474
+ };
3475
+ }
3476
+ static parseNonPortableDisabled(raw) {
3477
+ if (raw == null) {
3478
+ return undefined;
3479
+ }
3480
+ const values = [
3481
+ "rust_bundles",
3482
+ "per_spawn_external_tools",
3483
+ "mob_default_external_tools",
3484
+ "default_llm_client_override",
3485
+ "host_surface_mcp_allowlist",
3486
+ "workgraph_tools",
3487
+ ];
3488
+ if (!Array.isArray(raw) ||
3489
+ raw.some((entry) => typeof entry !== "string" ||
3490
+ !values.includes(entry))) {
3491
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/member_status response: non_portable_disabled must contain valid resource kinds");
3492
+ }
3493
+ return raw;
3494
+ }
2703
3495
  static parseModelsCatalog(data) {
2704
3496
  const context = "Invalid models/catalog response";
2705
3497
  const contractVersion = MeerkatClient.parseContractVersion(data.contract_version, context);
@@ -2773,11 +3565,13 @@ export class MeerkatClient {
2773
3565
  }
2774
3566
  static parseContractVersion(raw, context) {
2775
3567
  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);
3568
+ const parsed = typeof value === "number"
3569
+ ? value
3570
+ : typeof value === "string" && /^\d+$/.test(value)
3571
+ ? Number(value)
3572
+ : undefined;
3573
+ if (typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed >= 0) {
3574
+ return parsed;
2781
3575
  }
2782
3576
  throw new MeerkatError("INVALID_RESPONSE", `${context}: contract_version.${field} must be non-negative integer`);
2783
3577
  };
@@ -3006,10 +3800,8 @@ export class MeerkatClient {
3006
3800
  }
3007
3801
  static parseSessionHistory(data) {
3008
3802
  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;
3803
+ const rawMessages = MeerkatClient.requireRecordArray(data.messages, `${context}: messages`);
3804
+ rawMessages.forEach((message, index) => MeerkatClient.validateWireHistoryRow(message, `${context}: messages[${index}]`));
3013
3805
  return {
3014
3806
  sessionId: MeerkatClient.requireStringField(data, "session_id", context),
3015
3807
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
@@ -3022,10 +3814,8 @@ export class MeerkatClient {
3022
3814
  }
3023
3815
  static parseSessionTranscriptRevision(data) {
3024
3816
  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;
3817
+ const rawMessages = MeerkatClient.requireRecordArray(data.messages, `${context}: messages`);
3818
+ rawMessages.forEach((message, index) => MeerkatClient.validateWireHistoryRow(message, `${context}: messages[${index}]`));
3029
3819
  return {
3030
3820
  sessionId: MeerkatClient.requireStringField(data, "session_id", context),
3031
3821
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
@@ -3135,7 +3925,12 @@ export class MeerkatClient {
3135
3925
  stopReason: data.stop_reason != null ? String(data.stop_reason) : undefined,
3136
3926
  interactionId: data.interaction_id != null ? String(data.interaction_id) : undefined,
3137
3927
  runId: data.run_id != null ? String(data.run_id) : undefined,
3138
- blocks: rawBlocks.map((block) => MeerkatClient.parseSessionAssistantBlock(block)),
3928
+ // System-notice blocks have their own generated union and remain
3929
+ // available in `raw`; only block-assistant rows project through the
3930
+ // public assistant-block view.
3931
+ blocks: role === "block_assistant"
3932
+ ? rawBlocks.map((block) => MeerkatClient.parseSessionAssistantBlock(block))
3933
+ : [],
3139
3934
  results: rawResults.map((result) => {
3140
3935
  if (typeof result !== "object" || result === null) {
3141
3936
  throw new MeerkatError("INVALID_RESPONSE", `${context}: tool result must be an object`);
@@ -3230,60 +4025,22 @@ export class MeerkatClient {
3230
4025
  }
3231
4026
  static parseContentInput(value) {
3232
4027
  if (Array.isArray(value)) {
3233
- return value
3234
- .filter((item) => typeof item === "object" && item !== null)
3235
- .map((block) => MeerkatClient.parseContentBlock(block));
4028
+ return value.map((item, index) => MeerkatClient.parseContentBlock(MeerkatClient.requireRecord(item, `content[${index}]`, "Invalid session content")));
3236
4029
  }
3237
- return String(value ?? "");
4030
+ if (typeof value === "string")
4031
+ return value;
4032
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid session content: expected string or content-block array");
3238
4033
  }
3239
4034
  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: "" };
4035
+ MeerkatClient.validateGeneratedContentBlock(data, "Invalid session content block", false);
4036
+ // Preserve the validated wire object exactly. In particular, do not
4037
+ // invent inline bytes for history-only image/video projections or collapse
4038
+ // structured/unknown variants into empty text.
4039
+ return { ...data };
3281
4040
  }
3282
4041
  static parseSessionAssistantBlock(data) {
3283
4042
  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
- }
4043
+ MeerkatClient.validateWireAssistantBlock(data, context);
3287
4044
  const blockData = data.data ?? {};
3288
4045
  const blobRef = blockData.blob_ref;
3289
4046
  const revisedPrompt = blockData.revised_prompt != null && typeof blockData.revised_prompt === "object"
@@ -3302,9 +4059,9 @@ export class MeerkatClient {
3302
4059
  height: blockData.height != null ? Number(blockData.height) : undefined,
3303
4060
  revisedPrompt,
3304
4061
  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,
4062
+ source: data.block_type === "transcript"
4063
+ ? { ...blockData.source }
4064
+ : undefined,
3308
4065
  raw: { ...data },
3309
4066
  };
3310
4067
  }
@@ -3379,7 +4136,7 @@ export class MeerkatClient {
3379
4136
  params.model = options.model;
3380
4137
  if (options.provider)
3381
4138
  params.provider = options.provider;
3382
- if (options.systemPrompt)
4139
+ if (options.systemPrompt !== undefined)
3383
4140
  params.system_prompt = options.systemPrompt;
3384
4141
  if (options.maxTokens)
3385
4142
  params.max_tokens = options.maxTokens;