@rkat/sdk 0.6.33 → 0.7.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.
Files changed (42) hide show
  1. package/README.md +8 -8
  2. package/dist/client.d.ts +87 -46
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +473 -551
  5. package/dist/client.js.map +1 -1
  6. package/dist/events.d.ts +7 -10
  7. package/dist/events.d.ts.map +1 -1
  8. package/dist/events.js +35 -17
  9. package/dist/events.js.map +1 -1
  10. package/dist/generated/events.d.ts +5 -0
  11. package/dist/generated/events.d.ts.map +1 -0
  12. package/dist/generated/events.js +48 -0
  13. package/dist/generated/events.js.map +1 -0
  14. package/dist/generated/index.d.ts +2 -0
  15. package/dist/generated/index.d.ts.map +1 -1
  16. package/dist/generated/index.js +2 -0
  17. package/dist/generated/index.js.map +1 -1
  18. package/dist/generated/types.d.ts +328 -55
  19. package/dist/generated/types.d.ts.map +1 -1
  20. package/dist/generated/types.js +354 -2
  21. package/dist/generated/types.js.map +1 -1
  22. package/dist/generated/version_compat.d.ts +6 -0
  23. package/dist/generated/version_compat.d.ts.map +1 -0
  24. package/dist/generated/version_compat.js +24 -0
  25. package/dist/generated/version_compat.js.map +1 -0
  26. package/dist/index.d.ts +3 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/live.d.ts +3 -3
  31. package/dist/live.d.ts.map +1 -1
  32. package/dist/live.js +2 -4
  33. package/dist/live.js.map +1 -1
  34. package/dist/subscription.d.ts +1 -0
  35. package/dist/subscription.d.ts.map +1 -1
  36. package/dist/subscription.js +19 -3
  37. package/dist/subscription.js.map +1 -1
  38. package/dist/types.d.ts +34 -171
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js +1 -1
  41. package/dist/types.js.map +1 -1
  42. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -33,12 +33,14 @@ import { setTimeout as delay } from "node:timers/promises";
33
33
  import { createInterface } from "node:readline";
34
34
  import { Buffer } from "node:buffer";
35
35
  import { MeerkatError, CapabilityUnavailableError } from "./generated/errors.js";
36
+ import { isCompatibleWith } from "./generated/version_compat.js";
36
37
  import { CONTRACT_VERSION, } from "./generated/types.js";
37
38
  import { DeferredSession, Session } from "./session.js";
38
39
  import { Mob, } from "./mob.js";
39
40
  import { parseCoreEvent } from "./events.js";
40
41
  import { EventStream, AsyncQueue } from "./streaming.js";
41
42
  import { EventSubscription } from "./subscription.js";
43
+ import { parseAttentionListResult, parseGoalStatusResult, parseWorkGraphEvent, parseWorkGraphSnapshot, parseWorkItem, } from "./generated/types.js";
42
44
  const MEERKAT_REPO = "lukacf/meerkat";
43
45
  const MEERKAT_RELEASE_BINARY = "rkat-rpc";
44
46
  const MEERKAT_BINARY_CACHE_ROOT = path.join(os.homedir(), ".cache", "meerkat", "bin", MEERKAT_RELEASE_BINARY);
@@ -78,26 +80,6 @@ const MOB_SPAWN_MANY_FAILURE_CAUSES = new Set([
78
80
  "work_not_found",
79
81
  "internal",
80
82
  ]);
81
- const WORK_ATTENTION_DELEGATED_AUTHORITIES = new Set([
82
- "add_evidence",
83
- "close_own_review_item",
84
- "request_closure",
85
- "close_if_policy_allows",
86
- ]);
87
- const WORK_ATTENTION_MODES = new Set([
88
- "pursue",
89
- "coordinate",
90
- "review",
91
- "falsify",
92
- "judge",
93
- "observe",
94
- ]);
95
- const WORK_ATTENTION_STATES = new Set([
96
- "active",
97
- "paused",
98
- "superseded",
99
- "stopped",
100
- ]);
101
83
  function isMobSpawnManyFailureCause(value) {
102
84
  return typeof value === "string" && MOB_SPAWN_MANY_FAILURE_CAUSES.has(value);
103
85
  }
@@ -191,9 +173,7 @@ function mobTurnStartPayload(mobId, agentIdentity, prompt, options) {
191
173
  setIfDefined(payload, "output_schema", options?.outputSchema);
192
174
  setIfDefined(payload, "structured_output_retries", options?.structuredOutputRetries);
193
175
  setIfDefined(payload, "provider_params", options?.providerParams);
194
- setIfDefined(payload, "clear_provider_params", options?.clearProviderParams);
195
176
  setIfDefined(payload, "auth_binding", options?.authBinding);
196
- setIfDefined(payload, "clear_auth_binding", options?.clearAuthBinding);
197
177
  return payload;
198
178
  }
199
179
  function normalizeMobWireMembersBatchEdge(edge) {
@@ -206,6 +186,11 @@ function normalizeMobWireMembersBatchEdge(edge) {
206
186
  }
207
187
  export class MeerkatClient {
208
188
  process = null;
189
+ // Permanent transport-failed state: once the JSONL framing is untrustworthy
190
+ // (corrupted frame), every subsequent call must reject with this recorded
191
+ // typed fault instead of writing into the condemned stream. Cleared only by
192
+ // a fresh connect() (new process, new stream).
193
+ transportFault = null;
209
194
  processStderr = "";
210
195
  requestId = 0;
211
196
  _capabilities = [];
@@ -214,14 +199,28 @@ export class MeerkatClient {
214
199
  pendingRequests = new Map();
215
200
  eventQueues = new Map();
216
201
  streamQueues = new Map();
217
- pendingStreamQueue = null;
218
- pendingStreamRequestId = null;
202
+ // Per-request_id stream subscriptions for createSessionStreaming calls whose
203
+ // session_id is not yet bound. Keyed by the JSON-RPC request id so concurrent
204
+ // creates are admitted and correlated independently instead of colliding on a
205
+ // single client-local slot (which previously threw INVALID_STATE before the
206
+ // server had any chance to admit or reject the second create).
207
+ pendingStreamQueues = new Map();
208
+ // Pre-binding events keyed by session_id. session_id is globally unique per
209
+ // session, so buffering by session_id correlates correctly even when several
210
+ // streams are pending: each event lands under the session it names, and the
211
+ // response that binds that session_id drains exactly its own buffer.
219
212
  unmatchedStreamBuffer = new Map();
220
213
  unmatchedStandaloneStreamBuffer = new Map();
221
214
  unmatchedStandaloneStreamEnd = new Map();
222
215
  streamTerminalOutcomes = new Map();
223
216
  rl = null;
224
217
  toolHandlers = new Map();
218
+ // Typed faults from post-connect tool registration. `registerTool` is sync and
219
+ // the server round-trip is detached, so a genuine server rejection cannot be
220
+ // surfaced to the synchronous caller. Recording it here makes the
221
+ // silent-never-registers failure programmatically distinguishable instead of
222
+ // being swallowed by a best-effort `.catch(() => {})`.
223
+ toolRegistrationErrors = new Map();
225
224
  constructor(rkatPath = "rkat-rpc") {
226
225
  this.rkatPath = rkatPath;
227
226
  }
@@ -234,17 +233,77 @@ export class MeerkatClient {
234
233
  * return `Results for ${args.q}`;
235
234
  * });
236
235
  * ```
236
+ *
237
+ * Handlers may return a plain string or a `ContentBlock[]` for multimodal
238
+ * tool results. Block arrays are forwarded faithfully to the runtime as
239
+ * `WireToolResultContent` (no string coercion).
237
240
  */
238
241
  registerTool(name, description, inputSchema, handler) {
239
242
  this.toolHandlers.set(name, { description, inputSchema, handler });
243
+ // A fresh declaration supersedes any stale fault from a prior failed attempt.
244
+ this.toolRegistrationErrors.delete(name);
240
245
  // If already connected, register the new tool with the server immediately.
241
246
  if (this.process?.stdin) {
242
- this.request("tools/register", {
247
+ void this.registerToolWithServer(name, description, inputSchema);
248
+ }
249
+ }
250
+ /**
251
+ * Send a single post-connect tool registration and resolve its outcome
252
+ * without swallowing genuine faults.
253
+ *
254
+ * Benign disconnect/shutdown faults are tolerated: the local
255
+ * `toolHandlers` registry still holds the definition and the next
256
+ * `connect()` re-sends it, so the tool is not lost. A genuine server
257
+ * rejection or protocol fault is a real failure that cannot reach the
258
+ * synchronous `registerTool` caller, so we record it as an inspectable
259
+ * typed fault (queryable via {@link toolRegistrationError}), marking the
260
+ * tool unconfirmed. This replaces the previous `.catch(() => {})` that
261
+ * laundered every failure into silence. The recorded fault is the
262
+ * propagation channel: re-throwing on this detached promise would surface
263
+ * only as an unhandled rejection (the caller is sync), so the typed record
264
+ * is the observable signal instead.
265
+ */
266
+ async registerToolWithServer(name, description, inputSchema) {
267
+ try {
268
+ await this.request("tools/register", {
243
269
  tools: [{ name, description, input_schema: inputSchema }],
244
- }).catch(() => {
245
- // Best-effort: tool registration may fail if connection is closing.
246
270
  });
247
271
  }
272
+ catch (error) {
273
+ const fault = error instanceof MeerkatError
274
+ ? error
275
+ : new MeerkatError("TOOL_REGISTRATION_FAILED", `Tool ${name} registration failed: ${String(error)}`);
276
+ // Clean shutdown / disconnect is genuinely benign — the registry already
277
+ // holds the definition and a future connect() will re-send it.
278
+ const BENIGN_CODES = new Set([
279
+ "CLIENT_CLOSED",
280
+ "CONNECTION_CLOSED",
281
+ "NOT_CONNECTED",
282
+ "PROCESS_EXITED",
283
+ "PROCESS_ERROR",
284
+ ]);
285
+ if (BENIGN_CODES.has(fault.code)) {
286
+ return;
287
+ }
288
+ // Genuine server rejection / protocol fault: record the typed fault as
289
+ // caller-inspectable state so the tool is programmatically known to be
290
+ // unconfirmed. The synchronous `registerTool` caller cannot observe a
291
+ // rejection on this detached promise, so the recorded fault — not a
292
+ // re-throw — is the surfaced signal.
293
+ this.toolRegistrationErrors.set(name, fault);
294
+ return;
295
+ }
296
+ // Success: a prior failed attempt's fault no longer applies.
297
+ this.toolRegistrationErrors.delete(name);
298
+ }
299
+ /**
300
+ * Return the typed fault recorded for a tool whose post-connect registration
301
+ * was rejected by the server, or `undefined` if the tool's most recent
302
+ * registration attempt succeeded (or has not completed yet). Lets callers
303
+ * distinguish a tool that silently never registered from one that is live.
304
+ */
305
+ toolRegistrationError(name) {
306
+ return this.toolRegistrationErrors.get(name);
248
307
  }
249
308
  // -- Connection ---------------------------------------------------------
250
309
  async connect(options) {
@@ -269,6 +328,8 @@ export class MeerkatClient {
269
328
  this.process = spawn(this.rkatPath, args, {
270
329
  stdio: ["pipe", "pipe", "pipe"],
271
330
  });
331
+ // Fresh process, fresh stream: clear any recorded transport fault.
332
+ this.transportFault = null;
272
333
  const child = this.process;
273
334
  this.processStderr = "";
274
335
  child.stderr?.on("data", (chunk) => {
@@ -296,8 +357,9 @@ export class MeerkatClient {
296
357
  });
297
358
  this.rl = createInterface({ input: this.process.stdout });
298
359
  this.rl.on("line", (line) => this.handleLine(line));
299
- // Handshake
300
- const initResult = await this.request("initialize", {});
360
+ // Handshake — `initialize` returns the generated `ServerCapabilities`
361
+ // contract; fields are validated below.
362
+ const initResult = (await this.request("initialize", {}));
301
363
  const serverVersion = String(initResult.contract_version ?? "");
302
364
  if (!MeerkatClient.checkVersionCompatible(serverVersion, CONTRACT_VERSION)) {
303
365
  throw new MeerkatError("VERSION_MISMATCH", `Server version ${serverVersion} incompatible with SDK ${CONTRACT_VERSION}`);
@@ -305,14 +367,12 @@ export class MeerkatClient {
305
367
  this._methods = new Set(Array.isArray(initResult.methods)
306
368
  ? initResult.methods.map((method) => String(method))
307
369
  : []);
308
- // Fetch capabilities
309
- const capsResult = await this.request("capabilities/get", {});
310
- const rawCaps = capsResult.capabilities ?? [];
311
- this._capabilities = rawCaps.map((cap) => ({
312
- id: String(cap.id ?? ""),
313
- description: String(cap.description ?? ""),
314
- status: MeerkatClient.normalizeStatus(cap.status),
315
- }));
370
+ // Fetch capabilities — generated `CapabilitiesResponse` contract.
371
+ const capsResult = (await this.request("capabilities/get", {}));
372
+ if (!Array.isArray(capsResult.capabilities)) {
373
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid capabilities/get response: capabilities must be a list");
374
+ }
375
+ this._capabilities = capsResult.capabilities.map((cap) => MeerkatClient.parseWireCapabilityEntry(cap));
316
376
  // Register callback tools if any were declared.
317
377
  if (this.toolHandlers.size > 0) {
318
378
  const tools = Array.from(this.toolHandlers.entries()).map(([name, { description, inputSchema }]) => ({
@@ -321,6 +381,8 @@ export class MeerkatClient {
321
381
  input_schema: inputSchema,
322
382
  }));
323
383
  await this.request("tools/register", { tools });
384
+ // The bulk re-send confirmed every declared tool; drop stale per-tool faults.
385
+ this.toolRegistrationErrors.clear();
324
386
  }
325
387
  return this;
326
388
  }
@@ -354,6 +416,35 @@ export class MeerkatClient {
354
416
  }
355
417
  this.pendingRequests.clear();
356
418
  }
419
+ /**
420
+ * Surface a typed transport/protocol fault to every pending caller and
421
+ * stream consumer. Used when the read loop encounters a corrupted frame and
422
+ * the stream can no longer be trusted, so callers fail closed instead of
423
+ * hanging on a dropped response.
424
+ */
425
+ failTransport(reason) {
426
+ if (!this.transportFault) {
427
+ this.transportFault =
428
+ reason instanceof MeerkatError
429
+ ? reason
430
+ : new MeerkatError("PROTOCOL_ERROR", String(reason));
431
+ }
432
+ this.rejectPendingRequests(reason);
433
+ this.closeQueues();
434
+ // Fail closed: the stream is condemned, so the process goes with it —
435
+ // leaving it alive would let later calls write into framing we just
436
+ // classified as untrustworthy.
437
+ if (this.rl) {
438
+ this.rl.close();
439
+ this.rl = null;
440
+ }
441
+ const process = this.process;
442
+ this.process = null;
443
+ if (process) {
444
+ process.stdin?.destroy();
445
+ process.kill();
446
+ }
447
+ }
357
448
  closeQueues() {
358
449
  for (const [, queue] of this.eventQueues) {
359
450
  queue.put(null);
@@ -364,12 +455,11 @@ export class MeerkatClient {
364
455
  }
365
456
  this.streamQueues.clear();
366
457
  this.streamTerminalOutcomes.clear();
367
- if (this.pendingStreamQueue) {
368
- this.pendingStreamQueue.put(null);
369
- this.pendingStreamQueue = null;
370
- this.pendingStreamRequestId = null;
371
- this.unmatchedStreamBuffer.clear();
458
+ for (const [, queue] of this.pendingStreamQueues) {
459
+ queue.put(null);
372
460
  }
461
+ this.pendingStreamQueues.clear();
462
+ this.unmatchedStreamBuffer.clear();
373
463
  }
374
464
  // -- Session lifecycle --------------------------------------------------
375
465
  /**
@@ -401,17 +491,19 @@ export class MeerkatClient {
401
491
  const params = MeerkatClient.buildCreateParams(prompt, options);
402
492
  this.requestId++;
403
493
  const requestId = this.requestId;
404
- if (this.pendingStreamQueue) {
405
- throw new MeerkatError("INVALID_STATE", "Only one createSessionStreaming request can be pending at a time");
406
- }
494
+ // Per-request_id subscription: concurrent createSessionStreaming calls each
495
+ // get their own pending queue keyed by request id, so a second create is no
496
+ // longer rejected by a client-local singleton invariant. Admission/rejection
497
+ // is left to the server.
407
498
  const queue = new AsyncQueue();
408
- this.pendingStreamQueue = queue;
409
- this.pendingStreamRequestId = requestId;
499
+ this.pendingStreamQueues.set(requestId, queue);
410
500
  const responsePromise = this.registerRequest(requestId);
411
- // When response arrives, bind the queue to the session_id
501
+ // When response arrives, bind this request's queue to its session_id.
412
502
  const wrappedPromise = responsePromise.then((result) => {
413
503
  const sid = String(result.session_id ?? "");
414
504
  if (sid) {
505
+ // Drain only this session's pre-binding buffer — events are buffered by
506
+ // session_id, so concurrent pending streams do not cross-deliver.
415
507
  const buffered = this.unmatchedStreamBuffer.get(sid) ?? [];
416
508
  for (const evt of buffered) {
417
509
  queue.put(evt);
@@ -419,10 +511,15 @@ export class MeerkatClient {
419
511
  this.unmatchedStreamBuffer.delete(sid);
420
512
  this.eventQueues.set(sid, queue);
421
513
  }
422
- this.pendingStreamQueue = null;
423
- this.pendingStreamRequestId = null;
424
- this.unmatchedStreamBuffer.clear();
514
+ this.pendingStreamQueues.delete(requestId);
425
515
  return result;
516
+ }, (error) => {
517
+ // The server rejected this create: tear down only this request's pending
518
+ // subscription so its consumer fails closed, and leave any concurrent
519
+ // pending streams untouched.
520
+ this.pendingStreamQueues.delete(requestId);
521
+ queue.put(null);
522
+ throw error;
426
523
  });
427
524
  const rpcRequest = { jsonrpc: "2.0", id: requestId, method: "session/create", params };
428
525
  this.process.stdin.write(JSON.stringify(rpcRequest) + "\n");
@@ -504,7 +601,10 @@ export class MeerkatClient {
504
601
  return this.request("session/peer_response_terminal", params);
505
602
  }
506
603
  async injectContext(sessionId, text, options) {
507
- const params = { session_id: sessionId, text };
604
+ const params = {
605
+ session_id: sessionId,
606
+ content: { type: "text", text },
607
+ };
508
608
  if (options?.source !== undefined) {
509
609
  params.source = options.source;
510
610
  }
@@ -615,24 +715,24 @@ export class MeerkatClient {
615
715
  }
616
716
  // -- Config -------------------------------------------------------------
617
717
  async getConfig() {
618
- const raw = await this.request("config/get", {});
619
- return MeerkatClient.parseConfigEnvelope(raw);
718
+ // The wire body IS the generated `ConfigEnvelope` contract.
719
+ return (await this.request("config/get", {}));
620
720
  }
621
721
  async setConfig(config, options) {
622
722
  const params = { config };
623
723
  if (options?.expectedGeneration !== undefined) {
624
724
  params.expected_generation = options.expectedGeneration;
625
725
  }
626
- const raw = await this.request("config/set", params);
627
- return MeerkatClient.parseConfigEnvelope(raw);
726
+ // The wire body IS the generated `ConfigWriteResult` contract (envelope
727
+ // plus the live-propagation report).
728
+ return (await this.request("config/set", params));
628
729
  }
629
730
  async patchConfig(patch, options) {
630
731
  const params = { patch };
631
732
  if (options?.expectedGeneration !== undefined) {
632
733
  params.expected_generation = options.expectedGeneration;
633
734
  }
634
- const raw = await this.request("config/patch", params);
635
- return MeerkatClient.parseConfigEnvelope(raw);
735
+ return (await this.request("config/patch", params));
636
736
  }
637
737
  async mcpAdd(params) {
638
738
  const raw = await this.request("mcp/add", params);
@@ -659,7 +759,9 @@ export class MeerkatClient {
659
759
  // -- Skills ---------------------------------------------------------------
660
760
  async listSkills() {
661
761
  const result = await this.request("skills/list", {});
662
- return result.skills ?? [];
762
+ MeerkatClient.requireRecordArray(result.skills, "Invalid skills/list response");
763
+ const envelope = result;
764
+ return envelope.skills;
663
765
  }
664
766
  async getBlob(blobId) {
665
767
  const result = await this.request("blob/get", { blob_id: blobId });
@@ -779,43 +881,42 @@ export class MeerkatClient {
779
881
  });
780
882
  }
781
883
  async getWorkGraphItem(itemId, options) {
782
- const result = await this.request("workgraph/get", {
783
- id: itemId,
784
- ...MeerkatClient.toWireWorkGraphScope(options),
785
- });
786
- return MeerkatClient.parseWorkItem(result);
884
+ const params = { id: itemId };
885
+ if (options?.realmId !== undefined) {
886
+ params.realm_id = options.realmId;
887
+ }
888
+ if (options?.namespace !== undefined) {
889
+ params.namespace = options.namespace;
890
+ }
891
+ const result = await this.request("workgraph/get", params);
892
+ return parseWorkItem(result);
787
893
  }
788
894
  async listWorkGraphItems(filter = {}) {
789
- const result = await this.request("workgraph/list", MeerkatClient.toWireWorkGraphItemFilter(filter));
790
- return {
791
- items: MeerkatClient.parseWorkItemArray(result.items),
792
- };
895
+ const result = await this.request("workgraph/list", filter);
896
+ const items = MeerkatClient.requireRecordArray(result.items, "Invalid workgraph item list").map((entry) => parseWorkItem(entry));
897
+ return { items };
793
898
  }
794
899
  async listReadyWorkGraphItems(filter = {}) {
795
- const result = await this.request("workgraph/ready", MeerkatClient.toWireWorkGraphReadyFilter(filter));
796
- return {
797
- items: MeerkatClient.parseWorkItemArray(result.items),
798
- };
900
+ const result = await this.request("workgraph/ready", filter);
901
+ const items = MeerkatClient.requireRecordArray(result.items, "Invalid workgraph item list").map((entry) => parseWorkItem(entry));
902
+ return { items };
799
903
  }
800
904
  async getWorkGraphSnapshot(filter = {}) {
801
- const result = await this.request("workgraph/snapshot", MeerkatClient.toWireWorkGraphItemFilter(filter));
802
- return MeerkatClient.parseWorkGraphSnapshot(result);
905
+ const result = await this.request("workgraph/snapshot", filter);
906
+ return parseWorkGraphSnapshot(result);
803
907
  }
804
908
  async listWorkGraphEvents(filter = {}) {
805
- const result = await this.request("workgraph/events", MeerkatClient.toWireWorkGraphEventFilter(filter));
806
- return {
807
- events: MeerkatClient.parseWorkGraphEventArray(result.events),
808
- };
909
+ const result = await this.request("workgraph/events", filter);
910
+ const events = MeerkatClient.requireRecordArray(result.events, "Invalid workgraph event list").map((entry) => parseWorkGraphEvent(entry));
911
+ return { events };
809
912
  }
810
913
  async getWorkGraphGoalStatus(params) {
811
- const result = await this.request("workgraph/goal/status", MeerkatClient.toWireWorkGraphGoalStatusRequest(params));
812
- return MeerkatClient.parseWorkGraphGoalResult(result);
914
+ const result = await this.request("workgraph/goal/status", params);
915
+ return parseGoalStatusResult(result);
813
916
  }
814
917
  async listWorkGraphAttention(params = {}) {
815
- const result = await this.request("workgraph/attention/list", MeerkatClient.toWireWorkGraphAttentionListRequest(params));
816
- return {
817
- attention: MeerkatClient.parseWorkAttentionBindingArray(result.attention),
818
- };
918
+ const result = await this.request("workgraph/attention/list", params);
919
+ return parseAttentionListResult(result);
819
920
  }
820
921
  async subscribeSessionEvents(sessionId) {
821
922
  return this.openEventSubscription("session/stream_open", { session_id: sessionId }, "session/stream_close", MeerkatClient.parseAgentEventEnvelope);
@@ -831,28 +932,23 @@ export class MeerkatClient {
831
932
  async listMobs() {
832
933
  this.requireCapability("mob");
833
934
  const result = await this.request("mob/list", {});
834
- const mobs = result.mobs ?? [];
935
+ const mobs = MeerkatClient.requireRecordArray(result.mobs, "Invalid mob/list response");
835
936
  return mobs.map((mob) => ({
836
937
  mobId: String(mob.mob_id ?? mob.mobId ?? ""),
837
- status: String(mob.status ?? ""),
938
+ status: MeerkatClient.requireStringField(mob, "status", "Invalid mob/list response entry: missing status"),
838
939
  }));
839
940
  }
840
941
  async mobStatus(mobId) {
841
942
  const result = await this.request("mob/status", { mob_id: mobId });
842
- const rawStatus = result.status;
843
- const status = typeof rawStatus === "string"
844
- ? rawStatus
845
- : (typeof rawStatus === "object" && rawStatus !== null
846
- ? Object.keys(rawStatus)[0]
847
- : undefined);
848
- if (!status) {
849
- throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/status response: missing status");
850
- }
851
- return { mobId: String(result.mob_id ?? mobId), status };
943
+ const context = "Invalid mob/status response";
944
+ const record = MeerkatClient.requireRecord(result, "result", context);
945
+ const status = MeerkatClient.requireStringField(record, "status", context);
946
+ const responseMobId = MeerkatClient.requireStringField(record, "mob_id", context);
947
+ return { mobId: responseMobId, status };
852
948
  }
853
949
  async listMobMembers(mobId) {
854
950
  const result = await this.request("mob/members", { mob_id: mobId });
855
- const members = result.members ?? [];
951
+ const members = MeerkatClient.requireRecordArray(result.members, "Invalid mob/members response");
856
952
  return members.map((member) => {
857
953
  const agentIdentity = String(member.agent_identity ?? "");
858
954
  const memberRef = typeof member.member_ref === "string" && member.member_ref.length > 0
@@ -864,20 +960,21 @@ export class MeerkatClient {
864
960
  return {
865
961
  agentIdentity,
866
962
  memberRef,
867
- profile: String(member.profile_name ?? member.profile ?? member.role ?? ""),
963
+ profile: MeerkatClient.requireStringField(member, "role", "Invalid mob/members response: missing role"),
868
964
  peerId: member.peer_id != null ? String(member.peer_id) : undefined,
869
965
  externalPeerSpecs: member.external_peer_specs && typeof member.external_peer_specs === "object"
870
966
  ? Object.fromEntries(Object.entries(member.external_peer_specs).map(([key, value]) => [key, (value ?? {})]))
871
967
  : undefined,
872
968
  runtimeMode: member.runtime_mode != null ? String(member.runtime_mode) : undefined,
873
- state: member.state != null ? String(member.state) : undefined,
874
969
  wiredTo: Array.isArray(member.wired_to)
875
970
  ? member.wired_to.map((peer) => String(peer))
876
971
  : undefined,
877
972
  labels: member.labels && typeof member.labels === "object"
878
973
  ? Object.fromEntries(Object.entries(member.labels).map(([key, value]) => [key, String(value)]))
879
974
  : undefined,
880
- status: member.status != null ? String(member.status) : undefined,
975
+ status: member.status != null
976
+ ? MeerkatClient.parseWireMobMemberStatus(member.status, "Invalid mob/members response: invalid member status")
977
+ : undefined,
881
978
  error: member.error != null ? String(member.error) : undefined,
882
979
  isFinal: member.is_final != null ? Boolean(member.is_final) : undefined,
883
980
  };
@@ -902,9 +999,7 @@ export class MeerkatClient {
902
999
  ? result.agent_identity
903
1000
  : agentIdentity,
904
1001
  memberRef,
905
- handlingMode: result.handling_mode === "steer" || result.handling_mode === "queue"
906
- ? result.handling_mode
907
- : (options?.handlingMode ?? "queue"),
1002
+ handlingMode: MeerkatClient.parseWireHandlingMode(result.handling_mode, "Invalid mob/member_send response: missing or unknown handling_mode"),
908
1003
  };
909
1004
  }
910
1005
  async spawnMobMember(mobId, options) {
@@ -1006,7 +1101,12 @@ export class MeerkatClient {
1006
1101
  agent_identity: agentIdentity,
1007
1102
  initial_message: initialMessage,
1008
1103
  });
1009
- const status = String(result.status ?? "completed");
1104
+ const respawnContext = "Invalid mob/respawn response";
1105
+ const respawnRecord = MeerkatClient.requireRecord(result, "result", respawnContext);
1106
+ const status = MeerkatClient.requireStringField(respawnRecord, "status", respawnContext);
1107
+ if (status !== "completed" && status !== "topology_restore_failed") {
1108
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/respawn response: invalid status");
1109
+ }
1010
1110
  const rawFailed = Array.isArray(result.failed_peer_ids)
1011
1111
  ? result.failed_peer_ids
1012
1112
  : [];
@@ -1021,7 +1121,7 @@ export class MeerkatClient {
1021
1121
  throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/respawn response: receipt missing member_ref");
1022
1122
  }
1023
1123
  return {
1024
- status: status === "topology_restore_failed" ? "topology_restore_failed" : "completed",
1124
+ status,
1025
1125
  receipt: {
1026
1126
  agentIdentity: receipt.identity != null ? String(receipt.identity) : agentIdentity,
1027
1127
  memberRef,
@@ -1047,7 +1147,7 @@ export class MeerkatClient {
1047
1147
  ? result.peer_connectivity
1048
1148
  : undefined;
1049
1149
  return {
1050
- status: String(result.status ?? "unknown"),
1150
+ status: MeerkatClient.parseWireMobMemberStatus(result.status, "Invalid mob/member_status response: missing or invalid status"),
1051
1151
  outputPreview: result.output_preview != null ? String(result.output_preview) : undefined,
1052
1152
  error: result.error != null ? String(result.error) : undefined,
1053
1153
  tokensUsed: Number(result.tokens_used ?? 0),
@@ -1079,9 +1179,10 @@ export class MeerkatClient {
1079
1179
  */
1080
1180
  async mobSnapshot(mobId) {
1081
1181
  const result = await this.request("mob/snapshot", { mob_id: mobId });
1182
+ const snapshotRecord = MeerkatClient.requireRecord(result, "result", "Invalid mob/snapshot response");
1082
1183
  return {
1083
1184
  mobId: String(result.mob_id ?? mobId),
1084
- status: String(result.status ?? "unknown"),
1185
+ status: MeerkatClient.requireStringField(snapshotRecord, "status", "Invalid mob/snapshot response: missing status"),
1085
1186
  members: Array.isArray(result.members) ? result.members : [],
1086
1187
  };
1087
1188
  }
@@ -1165,9 +1266,8 @@ export class MeerkatClient {
1165
1266
  params.timeout_ms = options.timeoutMs;
1166
1267
  }
1167
1268
  const result = await this.request("mob/wait_kickoff", params);
1168
- const members = Array.isArray(result.members) ? result.members : [];
1169
- return members.map((entry) => {
1170
- const member = entry && typeof entry === "object" ? entry : {};
1269
+ const members = MeerkatClient.requireRecordArray(result.members, "Invalid mob/wait_kickoff response");
1270
+ return members.map((member) => {
1171
1271
  const agentIdentity = String(member.agent_identity ?? "");
1172
1272
  if (!agentIdentity) {
1173
1273
  throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/wait_kickoff response: member missing agent_identity");
@@ -1212,9 +1312,8 @@ export class MeerkatClient {
1212
1312
  params.timeout_ms = options.timeoutMs;
1213
1313
  }
1214
1314
  const result = await this.request("mob/wait_ready", params);
1215
- const members = Array.isArray(result.members) ? result.members : [];
1216
- return members.map((entry) => {
1217
- const member = entry && typeof entry === "object" ? entry : {};
1315
+ const members = MeerkatClient.requireRecordArray(result.members, "Invalid mob/wait_ready response");
1316
+ return members.map((member) => {
1218
1317
  const agentIdentity = String(member.agent_identity ?? "");
1219
1318
  if (!agentIdentity) {
1220
1319
  throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/wait_ready response: member missing agent_identity");
@@ -1450,8 +1549,10 @@ export class MeerkatClient {
1450
1549
  streamId,
1451
1550
  queue,
1452
1551
  closeRemote: async (id) => {
1453
- this.streamQueues.delete(id);
1552
+ // Await stream-close authority before tearing down local dispatch:
1553
+ // a rejected close must leave the subscription delivering events.
1454
1554
  await this.request(closeMethod, { stream_id: id });
1555
+ this.streamQueues.delete(id);
1455
1556
  },
1456
1557
  parseEvent: parse,
1457
1558
  getTerminalOutcome: () => this.streamTerminalOutcomes.get(streamId),
@@ -1460,7 +1561,6 @@ export class MeerkatClient {
1460
1561
  static parseAgentEventEnvelope(raw) {
1461
1562
  const eventId = MeerkatClient.parseOptionalString(raw.event_id ?? raw.eventId);
1462
1563
  const source = MeerkatClient.parseEventSourceIdentity(raw.source);
1463
- const sourceId = MeerkatClient.parseOptionalString(raw.source_id ?? raw.sourceId);
1464
1564
  const seq = MeerkatClient.parseOptionalNumber(raw.seq);
1465
1565
  const timestampMs = MeerkatClient.parseOptionalNumber(raw.timestamp_ms ?? raw.timestampMs);
1466
1566
  const payloadRaw = raw.payload;
@@ -1470,7 +1570,6 @@ export class MeerkatClient {
1470
1570
  return {
1471
1571
  ...(eventId != null ? { eventId } : {}),
1472
1572
  ...(source != null ? { source } : {}),
1473
- ...(sourceId != null ? { sourceId } : {}),
1474
1573
  ...(seq != null ? { seq } : {}),
1475
1574
  ...(timestampMs != null ? { timestampMs } : {}),
1476
1575
  ...(payload ? { payload } : {}),
@@ -1512,10 +1611,28 @@ export class MeerkatClient {
1512
1611
  return typeof raw === "number" && Number.isFinite(raw) ? raw : undefined;
1513
1612
  }
1514
1613
  static parseAttributedMobEvent(raw) {
1614
+ const context = "Invalid attributed mob event";
1615
+ // The runtime wire shape (meerkat-mob AttributedEvent) carries a typed
1616
+ // source `{ identity, generation }` (AgentRuntimeId) and a `role`
1617
+ // (ProfileName) string — NOT free-form `source`/`profile` strings. Mirror
1618
+ // the web SDK `parseAttributedEventItem`/`parseAttributedSource`: validate
1619
+ // the source record (require non-empty `identity` + non-negative integer
1620
+ // `generation`) and the `role` field, throwing on absence/malformation.
1621
+ // The public `AttributedMobEvent` TS type exposes `source: string` /
1622
+ // `profile: string`, so the validated `identity` is projected into
1623
+ // `source` and the validated `role` into `profile`. Nothing is coalesced
1624
+ // to "" — absent/unknown fields fail closed.
1625
+ const source = MeerkatClient.requireRecord(raw.source, "source", context);
1626
+ const identity = MeerkatClient.requireStringField(source, "identity", context);
1627
+ const generation = MeerkatClient.requireNumberField(source, "generation", context);
1628
+ if (!Number.isInteger(generation) || generation < 0) {
1629
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: source generation must be a non-negative integer`);
1630
+ }
1631
+ const role = MeerkatClient.requireStringField(raw, "role", context);
1515
1632
  return {
1516
- source: String(raw.source ?? ""),
1517
- profile: String(raw.profile ?? ""),
1518
- envelope: MeerkatClient.parseAgentEventEnvelope((raw.envelope ?? {})),
1633
+ source: identity,
1634
+ profile: role,
1635
+ envelope: MeerkatClient.parseAgentEventEnvelope(MeerkatClient.requireRecord(raw.envelope, "envelope", context)),
1519
1636
  };
1520
1637
  }
1521
1638
  // -- Internal methods used by Session -----------------------------------
@@ -1608,7 +1725,9 @@ export class MeerkatClient {
1608
1725
  }
1609
1726
  /** @internal */
1610
1727
  async _interrupt(sessionId) {
1611
- await this.request("turn/interrupt", { session_id: sessionId });
1728
+ return (await this.request("turn/interrupt", {
1729
+ session_id: sessionId,
1730
+ }));
1612
1731
  }
1613
1732
  /** @internal */
1614
1733
  async _archive(sessionId) {
@@ -1718,7 +1837,8 @@ export class MeerkatClient {
1718
1837
  return result;
1719
1838
  }
1720
1839
  async liveClose(params) {
1721
- await this.request("live/close", params);
1840
+ const result = await this.request("live/close", params);
1841
+ return MeerkatClient.parseLiveCloseResult(result);
1722
1842
  }
1723
1843
  async liveSendInput(params) {
1724
1844
  await this.request("live/send_input", params);
@@ -1782,19 +1902,18 @@ export class MeerkatClient {
1782
1902
  * `model_id` and `provider_id` match the channel's currently-open
1783
1903
  * identity and rejects swaps with a typed adapter-level error.
1784
1904
  *
1785
- * R4-5 (P3): the result is a typed `LiveRefreshResult` carrying both the
1786
- * typed `status` discriminator (today: always `"queued"`; the wire enum
1787
- * is open so future variants like `applied_sync` can land without breaking
1788
- * the shape) and the legacy `refresh_enqueued: true` back-compat boolean.
1789
- * The host's `send_command` returns when the command has been queued on
1790
- * the adapter's mpsc channel; the adapter pump applies the
1791
- * `session.update` asynchronously, and the realtime stream is the source
1792
- * of truth for the actual outcome (failures surface as
1793
- * `WireLiveAdapterObservation::Error`).
1905
+ * R4-5 (P3): the result is a typed `LiveRefreshResult` carrying the
1906
+ * generated-authority `status` discriminator (today: `"queued"`). This SDK
1907
+ * build accepts the generated status set it was built with and rejects
1908
+ * missing or unknown status values until regenerated for a newer contract.
1909
+ * The host queue acceptance happens before this result is projected; the
1910
+ * adapter pump applies the `session.update` asynchronously, and the
1911
+ * realtime stream is the source of truth for the actual outcome (failures
1912
+ * surface as `WireLiveAdapterObservation::Error`).
1794
1913
  */
1795
1914
  async liveRefresh(params) {
1796
1915
  const result = await this.request("live/refresh", params);
1797
- return result;
1916
+ return MeerkatClient.parseLiveRefreshResult(result);
1798
1917
  }
1799
1918
  /**
1800
1919
  * Type-narrow an inbound live-adapter observation against
@@ -1912,6 +2031,11 @@ export class MeerkatClient {
1912
2031
  data = JSON.parse(line);
1913
2032
  }
1914
2033
  catch {
2034
+ // A corrupted (non-JSON) frame means the JSONL stream framing can no
2035
+ // longer be trusted. Surface a typed protocol fault to every pending
2036
+ // caller and stream consumer rather than silently dropping the line,
2037
+ // which would launder a corrupted stream into a hang / missing response.
2038
+ this.failTransport(new MeerkatError("PROTOCOL_ERROR", "Corrupted JSON-RPC frame: stream framing is no longer trustworthy"));
1915
2039
  return;
1916
2040
  }
1917
2041
  // Server→client callback request (has both id and method).
@@ -1977,7 +2101,10 @@ export class MeerkatClient {
1977
2101
  if (queue) {
1978
2102
  queue.put(event);
1979
2103
  }
1980
- else if (this.pendingStreamQueue) {
2104
+ else if (this.pendingStreamQueues.size > 0) {
2105
+ // A stream is pending but its session_id is not yet bound. Buffer by
2106
+ // session_id; the create response that binds this session_id drains
2107
+ // exactly this buffer into the matching request's queue.
1981
2108
  const buffered = this.unmatchedStreamBuffer.get(sessionId) ?? [];
1982
2109
  buffered.push(event);
1983
2110
  this.unmatchedStreamBuffer.set(sessionId, buffered);
@@ -1995,29 +2122,20 @@ export class MeerkatClient {
1995
2122
  details: parsed.details ?? parsed.reason ?? rawData,
1996
2123
  };
1997
2124
  }
1998
- const rawMessage = error.message;
1999
- if (typeof rawMessage === "string") {
2000
- try {
2001
- const parsed = JSON.parse(rawMessage);
2002
- if (parsed && typeof parsed === "object") {
2003
- return {
2004
- code: String(parsed.code ?? error.code ?? "UNKNOWN"),
2005
- message: String(parsed.message ?? rawMessage),
2006
- details: parsed.details ?? parsed.reason ?? error.data,
2007
- };
2008
- }
2009
- }
2010
- catch {
2011
- // Fall back to the outer JSON-RPC error payload.
2012
- }
2013
- }
2125
+ // The server's typed error projection is `error.data` ({code, message,
2126
+ // details}). `error.message` is presentation text only — it is never
2127
+ // parsed as JSON to recover typed fields, so SDK error semantics cannot
2128
+ // depend on message-string folklore.
2014
2129
  return {
2015
2130
  code: String(error.code ?? "UNKNOWN"),
2016
- message: String(rawMessage ?? "Unknown error"),
2131
+ message: String(error.message ?? "Unknown error"),
2017
2132
  details: error.data,
2018
2133
  };
2019
2134
  }
2020
2135
  request(method, params) {
2136
+ if (this.transportFault) {
2137
+ throw this.transportFault;
2138
+ }
2021
2139
  if (!this.process?.stdin) {
2022
2140
  throw new MeerkatError("NOT_CONNECTED", "Client not connected");
2023
2141
  }
@@ -2037,16 +2155,6 @@ export class MeerkatClient {
2037
2155
  });
2038
2156
  }
2039
2157
  // -- Static helpers -----------------------------------------------------
2040
- static normalizeStatus(raw) {
2041
- if (typeof raw === "string")
2042
- return raw;
2043
- if (typeof raw === "object" && raw !== null) {
2044
- // Rust can emit externally-tagged enums for status:
2045
- // { DisabledByPolicy: { description: "..." } }
2046
- return Object.keys(raw)[0] ?? "Unknown";
2047
- }
2048
- return String(raw);
2049
- }
2050
2158
  static requireRecord(raw, field, context) {
2051
2159
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2052
2160
  throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${field}`);
@@ -2083,6 +2191,81 @@ export class MeerkatClient {
2083
2191
  }
2084
2192
  return value;
2085
2193
  }
2194
+ static parseWireMobMemberStatus(raw, message) {
2195
+ if (typeof raw === "string" &&
2196
+ ["active", "retiring", "broken", "completed", "unknown"].includes(raw)) {
2197
+ return raw;
2198
+ }
2199
+ throw new MeerkatError("INVALID_RESPONSE", message);
2200
+ }
2201
+ /**
2202
+ * Parse a runtime-emitted handling mode against the closed WireHandlingMode
2203
+ * union. Fails closed on an absent or unrecognized variant — the SDK never
2204
+ * substitutes the client-requested mode for the runtime receipt. Mirrors the
2205
+ * web SDK `parseWireHandlingMode` and Python `_parse_wire_handling_mode`.
2206
+ */
2207
+ static parseWireHandlingMode(raw, message) {
2208
+ if (raw === "queue" || raw === "steer") {
2209
+ return raw;
2210
+ }
2211
+ throw new MeerkatError("INVALID_RESPONSE", message);
2212
+ }
2213
+ /**
2214
+ * Parse a capability status against the externally-tagged Rust
2215
+ * `CapabilityStatus` enum: either the bare string variant (e.g. "Available")
2216
+ * or a single-key object like `{ DisabledByPolicy: {...} }`. The capability
2217
+ * vocabulary evolves, so we do not whitelist variants, but we fail closed on
2218
+ * an absent or otherwise unparseable status rather than fabricating a
2219
+ * permissive default. Mirrors Python `_parse_wire_capability_status`.
2220
+ */
2221
+ static parseWireCapabilityStatus(raw, context) {
2222
+ if (typeof raw === "string" && raw.length > 0) {
2223
+ return raw;
2224
+ }
2225
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
2226
+ const firstKey = Object.keys(raw)[0];
2227
+ if (typeof firstKey === "string" && firstKey.length > 0) {
2228
+ return firstKey;
2229
+ }
2230
+ }
2231
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing or invalid capability status`);
2232
+ }
2233
+ /**
2234
+ * Parse a single capabilities/get entry, failing closed on absent/malformed
2235
+ * required fields rather than coercing to empty strings or a permissive
2236
+ * status default. Mirrors the Python capability parser.
2237
+ */
2238
+ static parseWireCapabilityEntry(raw) {
2239
+ const context = "Invalid capabilities/get response";
2240
+ const record = MeerkatClient.requireRecord(raw, "capability", context);
2241
+ return {
2242
+ id: MeerkatClient.requireStringField(record, "id", context),
2243
+ description: MeerkatClient.requireStringField(record, "description", context),
2244
+ status: MeerkatClient.parseWireCapabilityStatus(record.status, context),
2245
+ };
2246
+ }
2247
+ static parseLiveRefreshResult(raw) {
2248
+ const context = "Invalid live/refresh response";
2249
+ const record = MeerkatClient.requireRecord(raw, "result", context);
2250
+ const status = MeerkatClient.requireStringField(record, "status", context);
2251
+ if (status !== "queued") {
2252
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported status ${JSON.stringify(status)}`);
2253
+ }
2254
+ return {
2255
+ status,
2256
+ };
2257
+ }
2258
+ static parseLiveCloseResult(raw) {
2259
+ const context = "Invalid live/close response";
2260
+ const record = MeerkatClient.requireRecord(raw, "result", context);
2261
+ const status = MeerkatClient.requireStringField(record, "status", context);
2262
+ if (status !== "closed") {
2263
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: unsupported status ${JSON.stringify(status)}`);
2264
+ }
2265
+ return {
2266
+ status,
2267
+ };
2268
+ }
2086
2269
  static parseSkillDiagnostics(raw) {
2087
2270
  if (!raw || typeof raw !== "object")
2088
2271
  return undefined;
@@ -2116,16 +2299,10 @@ export class MeerkatClient {
2116
2299
  };
2117
2300
  }
2118
2301
  static checkVersionCompatible(server, client) {
2119
- try {
2120
- const s = server.split(".").map(Number);
2121
- const c = client.split(".").map(Number);
2122
- if (s[0] === 0 && c[0] === 0)
2123
- return s[1] === c[1];
2124
- return s[0] === c[0];
2125
- }
2126
- catch {
2127
- return false;
2128
- }
2302
+ // Drive contract-version compatibility off the generated helper
2303
+ // (mirrors `ContractVersion::is_compatible_with`) instead of a hand-rolled
2304
+ // copy of the rule (dogma row #193).
2305
+ return isCompatibleWith(server, client);
2129
2306
  }
2130
2307
  static parseRunResult(data) {
2131
2308
  const context = "Invalid run result";
@@ -2195,33 +2372,30 @@ export class MeerkatClient {
2195
2372
  return undefined;
2196
2373
  }
2197
2374
  const raw = data;
2375
+ const vision = raw.vision;
2376
+ const imageInput = raw.image_input;
2377
+ const imageToolResults = raw.image_tool_results;
2378
+ const inlineVideo = raw.inline_video;
2379
+ const realtime = raw.realtime;
2380
+ const webSearch = raw.web_search;
2381
+ const imageGeneration = raw.image_generation;
2382
+ if (typeof vision !== "boolean" ||
2383
+ typeof imageInput !== "boolean" ||
2384
+ typeof imageToolResults !== "boolean" ||
2385
+ typeof inlineVideo !== "boolean" ||
2386
+ typeof realtime !== "boolean" ||
2387
+ typeof webSearch !== "boolean" ||
2388
+ typeof imageGeneration !== "boolean") {
2389
+ return undefined;
2390
+ }
2198
2391
  return {
2199
- vision: Boolean(raw.vision),
2200
- imageInput: Boolean(raw.image_input),
2201
- imageToolResults: Boolean(raw.image_tool_results),
2202
- inlineVideo: Boolean(raw.inline_video),
2203
- realtime: Boolean(raw.realtime),
2204
- webSearch: Boolean(raw.web_search),
2205
- imageGeneration: Boolean(raw.image_generation),
2206
- };
2207
- }
2208
- static parseConfigEnvelope(data) {
2209
- const rawConfig = data.config && typeof data.config === "object"
2210
- ? data.config
2211
- : {};
2212
- const rawResolvedPaths = data.resolved_paths && typeof data.resolved_paths === "object"
2213
- ? data.resolved_paths
2214
- : undefined;
2215
- const resolvedPaths = rawResolvedPaths
2216
- ? Object.fromEntries(Object.entries(rawResolvedPaths).map(([key, value]) => [key, String(value)]))
2217
- : undefined;
2218
- return {
2219
- config: rawConfig,
2220
- generation: Number(data.generation ?? 0),
2221
- realmId: data.realm_id != null ? String(data.realm_id) : undefined,
2222
- instanceId: data.instance_id != null ? String(data.instance_id) : undefined,
2223
- backend: data.backend != null ? String(data.backend) : undefined,
2224
- resolvedPaths,
2392
+ vision,
2393
+ imageInput,
2394
+ imageToolResults,
2395
+ inlineVideo,
2396
+ realtime,
2397
+ webSearch,
2398
+ imageGeneration,
2225
2399
  };
2226
2400
  }
2227
2401
  static parseCommsSendReceipt(data) {
@@ -2236,25 +2410,7 @@ export class MeerkatClient {
2236
2410
  const providersRaw = Array.isArray(data.providers)
2237
2411
  ? data.providers
2238
2412
  : [];
2239
- let contractVersion = { major: 0, minor: 0, patch: 0 };
2240
- if (data.contract_version && typeof data.contract_version === "object") {
2241
- const contractVersionRaw = data.contract_version;
2242
- contractVersion = {
2243
- major: Number(contractVersionRaw.major ?? 0),
2244
- minor: Number(contractVersionRaw.minor ?? 0),
2245
- patch: Number(contractVersionRaw.patch ?? 0),
2246
- };
2247
- }
2248
- else if (typeof data.contract_version === "string") {
2249
- const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(data.contract_version);
2250
- if (match) {
2251
- contractVersion = {
2252
- major: Number(match[1]),
2253
- minor: Number(match[2]),
2254
- patch: Number(match[3]),
2255
- };
2256
- }
2257
- }
2413
+ const contractVersion = MeerkatClient.parseContractVersion(data.contract_version, "Invalid models/catalog response");
2258
2414
  return {
2259
2415
  contractVersion,
2260
2416
  providers: providersRaw.map((provider) => ({
@@ -2291,6 +2447,36 @@ export class MeerkatClient {
2291
2447
  })),
2292
2448
  };
2293
2449
  }
2450
+ static parseContractVersion(raw, context) {
2451
+ const parseComponent = (value, field) => {
2452
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
2453
+ return value;
2454
+ }
2455
+ if (typeof value === "string" && /^\d+$/.test(value)) {
2456
+ return Number(value);
2457
+ }
2458
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: contract_version.${field} must be non-negative integer`);
2459
+ };
2460
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
2461
+ const record = raw;
2462
+ return {
2463
+ major: parseComponent(record.major, "major"),
2464
+ minor: parseComponent(record.minor, "minor"),
2465
+ patch: parseComponent(record.patch, "patch"),
2466
+ };
2467
+ }
2468
+ if (typeof raw === "string") {
2469
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(raw);
2470
+ if (match) {
2471
+ return {
2472
+ major: parseComponent(match[1], "major"),
2473
+ minor: parseComponent(match[2], "minor"),
2474
+ patch: parseComponent(match[3], "patch"),
2475
+ };
2476
+ }
2477
+ }
2478
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing contract_version`);
2479
+ }
2294
2480
  static parseSchedule(data) {
2295
2481
  const labelsRaw = data.labels && typeof data.labels === "object"
2296
2482
  ? data.labels
@@ -2373,53 +2559,6 @@ export class MeerkatClient {
2373
2559
  : undefined,
2374
2560
  };
2375
2561
  }
2376
- static toWireWorkGraphScope(options) {
2377
- const params = {};
2378
- setIfDefined(params, "realm_id", options?.realmId);
2379
- setIfDefined(params, "namespace", options?.namespace);
2380
- return params;
2381
- }
2382
- static toWireWorkGraphItemFilter(filter) {
2383
- const params = MeerkatClient.toWireWorkGraphScope(filter);
2384
- setIfDefined(params, "all_namespaces", filter.allNamespaces);
2385
- setIfDefined(params, "statuses", filter.statuses);
2386
- setIfDefined(params, "labels", filter.labels);
2387
- setIfDefined(params, "include_terminal", filter.includeTerminal);
2388
- setIfDefined(params, "limit", filter.limit);
2389
- return params;
2390
- }
2391
- static toWireWorkGraphReadyFilter(filter) {
2392
- const params = MeerkatClient.toWireWorkGraphScope(filter);
2393
- setIfDefined(params, "labels", filter.labels);
2394
- setIfDefined(params, "limit", filter.limit);
2395
- return params;
2396
- }
2397
- static toWireWorkGraphEventFilter(filter) {
2398
- const params = MeerkatClient.toWireWorkGraphScope(filter);
2399
- setIfDefined(params, "all_namespaces", filter.allNamespaces);
2400
- setIfDefined(params, "after_seq", filter.afterSeq);
2401
- setIfDefined(params, "limit", filter.limit);
2402
- return params;
2403
- }
2404
- static toWireWorkGraphGoalStatusRequest(request) {
2405
- const params = MeerkatClient.toWireWorkGraphScope(request);
2406
- params.binding_id = request.bindingId;
2407
- return params;
2408
- }
2409
- static toWireWorkGraphAttentionTarget(target) {
2410
- if (target.kind === "session") {
2411
- return { kind: "session", session_id: target.sessionId };
2412
- }
2413
- return { kind: "lowered_owner", owner_key: target.ownerKey };
2414
- }
2415
- static toWireWorkGraphAttentionListRequest(request) {
2416
- const params = MeerkatClient.toWireWorkGraphScope(request);
2417
- setIfDefined(params, "status", request.status);
2418
- if (request.target !== undefined) {
2419
- params.target = MeerkatClient.toWireWorkGraphAttentionTarget(request.target);
2420
- }
2421
- return params;
2422
- }
2423
2562
  static parseStringArray(value, context) {
2424
2563
  if (value == null) {
2425
2564
  return [];
@@ -2449,35 +2588,6 @@ export class MeerkatClient {
2449
2588
  }
2450
2589
  return value.map((item, index) => MeerkatClient.requireRecord(item, `entry ${index}`, context));
2451
2590
  }
2452
- static parseWorkGraphOwner(raw, context) {
2453
- if (raw == null) {
2454
- return undefined;
2455
- }
2456
- const data = MeerkatClient.requireRecord(raw, "owner", context);
2457
- const key = MeerkatClient.requireRecord(data.key, "key", context);
2458
- const kind = MeerkatClient.requireStringField(key, "kind", context);
2459
- if (!["principal", "agent", "session", "mob", "label"].includes(kind)) {
2460
- throw new MeerkatError("INVALID_RESPONSE", `${context}: invalid owner key kind`);
2461
- }
2462
- return {
2463
- key: {
2464
- kind: kind,
2465
- id: MeerkatClient.requireStringField(key, "id", context),
2466
- },
2467
- displayName: MeerkatClient.parseOptionalString(data.display_name),
2468
- };
2469
- }
2470
- static parseWorkOwnerKey(raw, context) {
2471
- const key = MeerkatClient.requireRecord(raw, "owner_key", context);
2472
- const kind = MeerkatClient.requireStringField(key, "kind", context);
2473
- if (!["principal", "agent", "session", "mob", "label"].includes(kind)) {
2474
- throw new MeerkatError("INVALID_RESPONSE", `${context}: invalid owner key kind`);
2475
- }
2476
- return {
2477
- kind: kind,
2478
- id: MeerkatClient.requireStringField(key, "id", context),
2479
- };
2480
- }
2481
2591
  static parseMobWireMembersBatchEdge(raw, context) {
2482
2592
  const data = MeerkatClient.requireRecord(raw, "edge", context);
2483
2593
  return {
@@ -2496,214 +2606,6 @@ export class MeerkatClient {
2496
2606
  alreadyWired: MeerkatClient.parseMobWireMembersBatchEdges(data.already_wired, `${context} already_wired`),
2497
2607
  };
2498
2608
  }
2499
- static parseWorkGraphClaim(raw) {
2500
- if (raw == null) {
2501
- return undefined;
2502
- }
2503
- const data = MeerkatClient.requireRecord(raw, "claim", "Invalid workgraph item");
2504
- const owner = MeerkatClient.parseWorkGraphOwner(data.owner, "Invalid workgraph item claim");
2505
- if (!owner) {
2506
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph item claim: missing owner");
2507
- }
2508
- return {
2509
- owner,
2510
- claimedAt: MeerkatClient.requireStringField(data, "claimed_at", "Invalid workgraph item claim"),
2511
- leaseExpiresAt: MeerkatClient.parseOptionalString(data.lease_expires_at),
2512
- };
2513
- }
2514
- static parseWorkCompletionPolicy(raw) {
2515
- if (raw === undefined || raw === null) {
2516
- return { kind: "self_attest" };
2517
- }
2518
- const policy = MeerkatClient.requireRecord(raw, "completion_policy", "Invalid workgraph item");
2519
- const kind = MeerkatClient.requireStringField(policy, "kind", "Invalid workgraph completion policy");
2520
- switch (kind) {
2521
- case "self_attest":
2522
- case "host_confirmed":
2523
- case "principal_confirmed":
2524
- return { kind };
2525
- case "supervisor":
2526
- return {
2527
- kind,
2528
- owner_key: MeerkatClient.parseWorkOwnerKey(policy.owner_key, "Invalid workgraph completion policy"),
2529
- };
2530
- case "reviewer_quorum":
2531
- return {
2532
- kind,
2533
- threshold: MeerkatClient.requireNumberField(policy, "threshold", "Invalid workgraph completion policy"),
2534
- };
2535
- default:
2536
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph completion policy: invalid kind");
2537
- }
2538
- }
2539
- static parseWorkItem(data) {
2540
- const status = MeerkatClient.requireStringField(data, "status", "Invalid workgraph item");
2541
- if (!["open", "in_progress", "blocked", "completed", "cancelled", "failed"].includes(status)) {
2542
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph item: invalid status");
2543
- }
2544
- const priority = MeerkatClient.requireStringField(data, "priority", "Invalid workgraph item");
2545
- if (!["low", "medium", "high"].includes(priority)) {
2546
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph item: invalid priority");
2547
- }
2548
- return {
2549
- id: MeerkatClient.requireStringField(data, "id", "Invalid workgraph item"),
2550
- realmId: MeerkatClient.requireStringField(data, "realm_id", "Invalid workgraph item"),
2551
- namespace: MeerkatClient.requireStringField(data, "namespace", "Invalid workgraph item"),
2552
- title: MeerkatClient.requireStringField(data, "title", "Invalid workgraph item"),
2553
- description: MeerkatClient.parseOptionalString(data.description),
2554
- status: status,
2555
- priority: priority,
2556
- completionPolicy: MeerkatClient.parseWorkCompletionPolicy(data.completion_policy),
2557
- labels: MeerkatClient.parseStringArray(data.labels, "Invalid workgraph item labels"),
2558
- owner: MeerkatClient.parseWorkGraphOwner(data.owner, "Invalid workgraph item"),
2559
- claim: MeerkatClient.parseWorkGraphClaim(data.claim),
2560
- machineState: MeerkatClient.requireRecord(data.machine_state, "machine_state", "Invalid workgraph item"),
2561
- revision: MeerkatClient.requireNumberField(data, "revision", "Invalid workgraph item"),
2562
- dueAt: MeerkatClient.parseOptionalString(data.due_at),
2563
- notBefore: MeerkatClient.parseOptionalString(data.not_before),
2564
- snoozedUntil: MeerkatClient.parseOptionalString(data.snoozed_until),
2565
- createdAt: MeerkatClient.requireStringField(data, "created_at", "Invalid workgraph item"),
2566
- updatedAt: MeerkatClient.requireStringField(data, "updated_at", "Invalid workgraph item"),
2567
- terminalAt: MeerkatClient.parseOptionalString(data.terminal_at),
2568
- externalRefs: MeerkatClient.parseRecordArray(data.external_refs, "Invalid workgraph external refs").map((ref) => ({
2569
- kind: MeerkatClient.requireStringField(ref, "kind", "Invalid workgraph external ref"),
2570
- id: MeerkatClient.requireStringField(ref, "id", "Invalid workgraph external ref"),
2571
- url: MeerkatClient.parseOptionalString(ref.url),
2572
- })),
2573
- evidenceRefs: MeerkatClient.parseRecordArray(data.evidence_refs, "Invalid workgraph evidence refs").map((ref) => ({
2574
- kind: MeerkatClient.requireStringField(ref, "kind", "Invalid workgraph evidence ref"),
2575
- id: MeerkatClient.requireStringField(ref, "id", "Invalid workgraph evidence ref"),
2576
- label: MeerkatClient.parseOptionalString(ref.label),
2577
- summary: MeerkatClient.parseOptionalString(ref.summary),
2578
- })),
2579
- };
2580
- }
2581
- static parseWorkItemArray(value, context = "Invalid workgraph item list") {
2582
- return MeerkatClient.requireRecordArray(value, context).map((item) => MeerkatClient.parseWorkItem(item));
2583
- }
2584
- static parseWorkGraphGoalResult(data) {
2585
- return {
2586
- item: MeerkatClient.parseWorkItem(MeerkatClient.requireRecord(data.item, "item", "Invalid workgraph goal result")),
2587
- attention: MeerkatClient.parseWorkAttentionBinding(MeerkatClient.requireRecord(data.attention, "attention", "Invalid workgraph goal result")),
2588
- };
2589
- }
2590
- static parseWorkAttentionBinding(data) {
2591
- const bindingId = MeerkatClient.requireStringField(data, "binding_id", "Invalid workgraph attention binding");
2592
- const createdAt = MeerkatClient.requireStringField(data, "created_at", "Invalid workgraph attention binding");
2593
- const delegatedAuthority = MeerkatClient.requireStringField(data, "delegated_authority", "Invalid workgraph attention binding");
2594
- if (!WORK_ATTENTION_DELEGATED_AUTHORITIES.has(delegatedAuthority)) {
2595
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph attention binding: invalid delegated_authority");
2596
- }
2597
- const mode = MeerkatClient.requireStringField(data, "mode", "Invalid workgraph attention binding");
2598
- if (!WORK_ATTENTION_MODES.has(mode)) {
2599
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph attention binding: invalid mode");
2600
- }
2601
- const updatedAt = MeerkatClient.requireStringField(data, "updated_at", "Invalid workgraph attention binding");
2602
- const status = MeerkatClient.requireRecord(data.status, "status", "Invalid workgraph attention binding");
2603
- const statusState = MeerkatClient.requireStringField(status, "state", "Invalid workgraph attention status");
2604
- if (!WORK_ATTENTION_STATES.has(statusState)) {
2605
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph attention status: invalid state");
2606
- }
2607
- const target = MeerkatClient.requireRecord(data.target, "target", "Invalid workgraph attention binding");
2608
- const targetKind = MeerkatClient.requireStringField(target, "kind", "Invalid workgraph attention target");
2609
- if (targetKind === "session") {
2610
- MeerkatClient.requireStringField(target, "session_id", "Invalid workgraph attention target");
2611
- }
2612
- else if (targetKind === "lowered_owner") {
2613
- MeerkatClient.requireRecord(target.owner_key, "owner_key", "Invalid workgraph attention target");
2614
- }
2615
- else {
2616
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph attention target: invalid kind");
2617
- }
2618
- const workRef = MeerkatClient.requireRecord(data.work_ref, "work_ref", "Invalid workgraph attention binding");
2619
- MeerkatClient.requireStringField(workRef, "realm_id", "Invalid workgraph attention work ref");
2620
- MeerkatClient.requireStringField(workRef, "namespace", "Invalid workgraph attention work ref");
2621
- MeerkatClient.requireStringField(workRef, "item_id", "Invalid workgraph attention work ref");
2622
- const attention = {
2623
- bindingId,
2624
- createdAt,
2625
- delegatedAuthority: delegatedAuthority,
2626
- machineState: MeerkatClient.optionalRecord(data.machine_state),
2627
- mode: mode,
2628
- projectionPolicy: MeerkatClient.optionalRecord(data.projection_policy),
2629
- status: status,
2630
- target: targetKind === "session"
2631
- ? {
2632
- kind: "session",
2633
- sessionId: MeerkatClient.requireStringField(target, "session_id", "Invalid workgraph attention target"),
2634
- }
2635
- : {
2636
- kind: "loweredOwner",
2637
- ownerKey: MeerkatClient.parseWorkOwnerKey(target.owner_key, "Invalid workgraph attention target"),
2638
- },
2639
- updatedAt,
2640
- workRef: {
2641
- realmId: MeerkatClient.requireStringField(workRef, "realm_id", "Invalid workgraph attention work ref"),
2642
- namespace: MeerkatClient.requireStringField(workRef, "namespace", "Invalid workgraph attention work ref"),
2643
- itemId: MeerkatClient.requireStringField(workRef, "item_id", "Invalid workgraph attention work ref"),
2644
- },
2645
- };
2646
- return attention;
2647
- }
2648
- static parseWorkAttentionBindingArray(value) {
2649
- return MeerkatClient.requireRecordArray(value, "Invalid workgraph attention list").map((attention) => MeerkatClient.parseWorkAttentionBinding(attention));
2650
- }
2651
- static parseWorkGraphEdge(data) {
2652
- const kind = MeerkatClient.requireStringField(data, "kind", "Invalid workgraph edge");
2653
- if (!["blocks", "parent", "related", "supersedes", "derived_from"].includes(kind)) {
2654
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph edge: invalid kind");
2655
- }
2656
- return {
2657
- realmId: MeerkatClient.requireStringField(data, "realm_id", "Invalid workgraph edge"),
2658
- namespace: MeerkatClient.requireStringField(data, "namespace", "Invalid workgraph edge"),
2659
- kind: kind,
2660
- fromId: MeerkatClient.requireStringField(data, "from_id", "Invalid workgraph edge"),
2661
- toId: MeerkatClient.requireStringField(data, "to_id", "Invalid workgraph edge"),
2662
- createdAt: MeerkatClient.requireStringField(data, "created_at", "Invalid workgraph edge"),
2663
- };
2664
- }
2665
- static parseWorkGraphEvent(data) {
2666
- const kind = MeerkatClient.requireStringField(data, "kind", "Invalid workgraph event");
2667
- if (![
2668
- "created",
2669
- "updated",
2670
- "claimed",
2671
- "released",
2672
- "blocked",
2673
- "closed",
2674
- "linked",
2675
- "evidence_added",
2676
- "attention_created",
2677
- "attention_updated",
2678
- ].includes(kind)) {
2679
- throw new MeerkatError("INVALID_RESPONSE", "Invalid workgraph event: invalid kind");
2680
- }
2681
- return {
2682
- seq: MeerkatClient.parseOptionalNumber(data.seq),
2683
- realmId: MeerkatClient.requireStringField(data, "realm_id", "Invalid workgraph event"),
2684
- namespace: MeerkatClient.requireStringField(data, "namespace", "Invalid workgraph event"),
2685
- itemId: MeerkatClient.parseOptionalString(data.item_id),
2686
- kind: kind,
2687
- at: MeerkatClient.requireStringField(data, "at", "Invalid workgraph event"),
2688
- payload: data.payload,
2689
- };
2690
- }
2691
- static parseWorkGraphEventArray(value) {
2692
- return MeerkatClient.requireRecordArray(value, "Invalid workgraph event list").map((event) => MeerkatClient.parseWorkGraphEvent(event));
2693
- }
2694
- static parseWorkGraphSnapshot(data) {
2695
- return {
2696
- realmId: MeerkatClient.requireStringField(data, "realm_id", "Invalid workgraph snapshot"),
2697
- namespace: MeerkatClient.parseOptionalString(data.namespace),
2698
- allNamespaces: MeerkatClient.requireBooleanField(data, "all_namespaces", "Invalid workgraph snapshot"),
2699
- capturedAt: MeerkatClient.requireStringField(data, "captured_at", "Invalid workgraph snapshot"),
2700
- eventHighWaterMark: MeerkatClient.parseOptionalNumber(data.event_high_water_mark),
2701
- items: MeerkatClient.parseWorkItemArray(data.items, "Invalid workgraph snapshot items"),
2702
- edges: MeerkatClient.requireRecordArray(data.edges, "Invalid workgraph snapshot edges").map((edge) => MeerkatClient.parseWorkGraphEdge(edge)),
2703
- attention: MeerkatClient.parseWorkAttentionBindingArray(data.attention ?? []),
2704
- readyItemIds: MeerkatClient.requireStringArray(data.ready_item_ids, "Invalid workgraph snapshot ready item ids"),
2705
- };
2706
- }
2707
2609
  static parseMobProfileLookup(data) {
2708
2610
  if (Boolean(data.not_found)) {
2709
2611
  return {
@@ -2752,50 +2654,60 @@ export class MeerkatClient {
2752
2654
  };
2753
2655
  }
2754
2656
  static parseSessionHistory(data) {
2755
- const rawMessages = Array.isArray(data.messages)
2756
- ? data.messages
2757
- : [];
2657
+ const context = "Invalid session history response";
2658
+ if (!Array.isArray(data.messages)) {
2659
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: messages must be a list`);
2660
+ }
2661
+ const rawMessages = data.messages;
2758
2662
  return {
2759
- sessionId: String(data.session_id ?? ""),
2663
+ sessionId: MeerkatClient.requireStringField(data, "session_id", context),
2760
2664
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
2761
- messageCount: Number(data.message_count ?? 0),
2762
- offset: Number(data.offset ?? 0),
2665
+ messageCount: MeerkatClient.requireNumberField(data, "message_count", context),
2666
+ offset: MeerkatClient.requireNumberField(data, "offset", context),
2763
2667
  limit: data.limit != null ? Number(data.limit) : undefined,
2764
- hasMore: Boolean(data.has_more ?? false),
2668
+ hasMore: MeerkatClient.requireBooleanField(data, "has_more", context),
2765
2669
  messages: rawMessages.map((message) => MeerkatClient.parseSessionMessage(message)),
2766
2670
  };
2767
2671
  }
2768
2672
  static parseSessionTranscriptRevision(data) {
2769
- const rawMessages = Array.isArray(data.messages)
2770
- ? data.messages
2771
- : [];
2673
+ const context = "Invalid session transcript revision response";
2674
+ if (!Array.isArray(data.messages)) {
2675
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: messages must be a list`);
2676
+ }
2677
+ const rawMessages = data.messages;
2772
2678
  return {
2773
- sessionId: String(data.session_id ?? ""),
2679
+ sessionId: MeerkatClient.requireStringField(data, "session_id", context),
2774
2680
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
2775
- revision: String(data.revision ?? ""),
2776
- headRevision: String(data.head_revision ?? ""),
2777
- messageCount: Number(data.message_count ?? 0),
2778
- offset: Number(data.offset ?? 0),
2681
+ revision: MeerkatClient.requireStringField(data, "revision", context),
2682
+ headRevision: MeerkatClient.requireStringField(data, "head_revision", context),
2683
+ messageCount: MeerkatClient.requireNumberField(data, "message_count", context),
2684
+ offset: MeerkatClient.requireNumberField(data, "offset", context),
2779
2685
  limit: data.limit != null ? Number(data.limit) : undefined,
2780
- hasMore: Boolean(data.has_more ?? false),
2686
+ hasMore: MeerkatClient.requireBooleanField(data, "has_more", context),
2781
2687
  messages: rawMessages.map((message) => MeerkatClient.parseSessionMessage(message)),
2782
2688
  };
2783
2689
  }
2784
2690
  static parseSessionForkResult(data) {
2691
+ const context = "Invalid session fork response";
2785
2692
  return {
2786
- sourceSessionId: String(data.source_session_id ?? ""),
2787
- sessionId: String(data.session_id ?? ""),
2693
+ sourceSessionId: MeerkatClient.requireStringField(data, "source_session_id", context),
2694
+ sessionId: MeerkatClient.requireStringField(data, "session_id", context),
2788
2695
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
2789
- messageCount: Number(data.message_count ?? 0),
2696
+ messageCount: MeerkatClient.requireNumberField(data, "message_count", context),
2790
2697
  };
2791
2698
  }
2792
2699
  static parseSessionTranscriptRewriteResult(data) {
2700
+ const context = "Invalid session transcript rewrite response";
2701
+ const commit = data.commit;
2702
+ if (typeof commit !== "object" || commit === null || Array.isArray(commit)) {
2703
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: commit must be an object`);
2704
+ }
2793
2705
  return {
2794
- sessionId: String(data.session_id ?? ""),
2795
- parentRevision: String(data.parent_revision ?? ""),
2796
- revision: String(data.revision ?? ""),
2797
- messageCount: Number(data.message_count ?? 0),
2798
- commit: (data.commit ?? {}),
2706
+ sessionId: MeerkatClient.requireStringField(data, "session_id", context),
2707
+ parentRevision: MeerkatClient.requireStringField(data, "parent_revision", context),
2708
+ revision: MeerkatClient.requireStringField(data, "revision", context),
2709
+ messageCount: MeerkatClient.requireNumberField(data, "message_count", context),
2710
+ commit: commit,
2799
2711
  };
2800
2712
  }
2801
2713
  static serializeTranscriptReplacement(replacement) {
@@ -2828,37 +2740,50 @@ export class MeerkatClient {
2828
2740
  throw new Error(`Unsupported transcript replacement type: ${replacement.type}`);
2829
2741
  }
2830
2742
  static parseSessionMessage(data) {
2831
- const role = String(data.role ?? "");
2743
+ // Transcript truth fails closed: a message without its identity facts
2744
+ // (role, created_at) or with malformed collections is a wire-contract
2745
+ // violation, never coerced to ""/[] placeholder truth.
2746
+ const context = "Invalid session message";
2747
+ const role = MeerkatClient.requireStringField(data, "role", context);
2748
+ const createdAt = MeerkatClient.requireStringField(data, "created_at", context);
2832
2749
  const contentValue = role === "system_notice" && data.content == null && data.body != null
2833
2750
  ? String(data.body)
2834
2751
  : data.content;
2835
- const rawToolCalls = Array.isArray(data.tool_calls)
2836
- ? data.tool_calls
2837
- : [];
2838
- const rawBlocks = Array.isArray(data.blocks)
2839
- ? data.blocks
2840
- : [];
2841
- const rawResults = Array.isArray(data.results)
2842
- ? data.results
2843
- : [];
2752
+ if (data.blocks != null && !Array.isArray(data.blocks)) {
2753
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: blocks must be a list`);
2754
+ }
2755
+ if (data.results != null && !Array.isArray(data.results)) {
2756
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: results must be a list`);
2757
+ }
2758
+ const rawBlocks = data.blocks ?? [];
2759
+ const rawResults = data.results ?? [];
2844
2760
  return {
2845
2761
  role,
2846
- createdAt: String(data.created_at ?? ""),
2762
+ createdAt,
2847
2763
  kind: data.kind != null ? String(data.kind) : undefined,
2848
2764
  body: data.body != null ? String(data.body) : undefined,
2849
2765
  content: contentValue != null ? MeerkatClient.parseContentInput(contentValue) : undefined,
2850
- toolCalls: rawToolCalls.map((toolCall) => ({
2851
- id: String(toolCall.id ?? ""),
2852
- name: String(toolCall.name ?? ""),
2853
- args: toolCall.args,
2854
- })),
2855
2766
  stopReason: data.stop_reason != null ? String(data.stop_reason) : undefined,
2856
2767
  blocks: rawBlocks.map((block) => MeerkatClient.parseSessionAssistantBlock(block)),
2857
- results: rawResults.map((result) => ({
2858
- toolUseId: String(result.tool_use_id ?? ""),
2859
- content: MeerkatClient.parseContentInput(result.content),
2860
- isError: Boolean(result.is_error ?? false),
2861
- })),
2768
+ results: rawResults.map((result) => {
2769
+ if (typeof result !== "object" || result === null) {
2770
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: tool result must be an object`);
2771
+ }
2772
+ const isError = result.is_error ?? false;
2773
+ if (typeof isError !== "boolean") {
2774
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: is_error must be a boolean`);
2775
+ }
2776
+ // `WireToolResult.content` is mandatory on the wire; a frame without
2777
+ // it is malformed and must not be coalesced into an empty transcript.
2778
+ if (result.content === undefined || result.content === null) {
2779
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: tool result missing content`);
2780
+ }
2781
+ return {
2782
+ toolUseId: MeerkatClient.requireStringField(result, "tool_use_id", context),
2783
+ content: MeerkatClient.parseContentInput(result.content),
2784
+ isError,
2785
+ };
2786
+ }),
2862
2787
  raw: { ...data },
2863
2788
  };
2864
2789
  }
@@ -2891,13 +2816,6 @@ export class MeerkatClient {
2891
2816
  payload.content = camel.content;
2892
2817
  }
2893
2818
  }
2894
- if (camel.toolCalls?.length > 0) {
2895
- payload.tool_calls = camel.toolCalls.map((toolCall) => ({
2896
- id: toolCall.id,
2897
- name: toolCall.name,
2898
- args: toolCall.args,
2899
- }));
2900
- }
2901
2819
  if (camel.stopReason !== undefined) {
2902
2820
  payload.stop_reason = camel.stopReason;
2903
2821
  }
@@ -2981,13 +2899,17 @@ export class MeerkatClient {
2981
2899
  return { type: "text", text: "" };
2982
2900
  }
2983
2901
  static parseSessionAssistantBlock(data) {
2902
+ const context = "Invalid session assistant block";
2903
+ if (data.data != null && (typeof data.data !== "object" || Array.isArray(data.data))) {
2904
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: data must be an object`);
2905
+ }
2984
2906
  const blockData = data.data ?? {};
2985
2907
  const blobRef = blockData.blob_ref;
2986
2908
  const revisedPrompt = blockData.revised_prompt != null && typeof blockData.revised_prompt === "object"
2987
2909
  ? blockData.revised_prompt
2988
2910
  : undefined;
2989
2911
  return {
2990
- blockType: String(data.block_type ?? ""),
2912
+ blockType: MeerkatClient.requireStringField(data, "block_type", context),
2991
2913
  text: blockData.text != null ? String(blockData.text) : undefined,
2992
2914
  id: blockData.id != null ? String(blockData.id) : undefined,
2993
2915
  name: blockData.name != null ? String(blockData.name) : undefined,