@meshagent/meshagent-agents 0.46.3 → 0.47.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/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.47.0]
2
+ - Improved TypeScript messaging clients so room delivery order and startup/presence behavior stay consistent.
3
+ - Moved managed-agent client routing onto the Rust tunnel path and hardened agent chat-client event handling.
4
+ - Added a `test:rust-process-e2e` entry point for the agents-ts package and expanded live chat/process-agent coverage.
5
+ - Finished aligning CLI pagination behavior across feeds, rooms, mailboxes, scheduled tasks, and shared helpers.
6
+
1
7
  ## [0.46.3]
2
8
  - `LivekitProtocolChannel.sendData` now clones outgoing `Uint8Array` payloads before publishing, avoiding shared-buffer mutation issues.
3
9
 
package/README.md CHANGED
@@ -204,3 +204,27 @@ npm test
204
204
  ```
205
205
 
206
206
  The build emits ESM, Node CommonJS, and browser CommonJS outputs under `dist/`.
207
+
208
+ An external E2E harness can validate interoperability with a Rust process agent by
209
+ starting that agent in a room, then supplying its room participant credentials:
210
+
211
+ ```bash
212
+ MESHAGENT_TOKEN=... \
213
+ MESHAGENT_ROOM=... \
214
+ TEST_AGENT_NAME=... \
215
+ MESHAGENT_API_URL=... \
216
+ npm run test:rust-process-e2e
217
+ ```
218
+
219
+ The runner owns only the TypeScript client. The harness remains responsible for
220
+ starting and stopping the Rust process agent named by `TEST_AGENT_NAME`. It must
221
+ also supply either `MESHAGENT_API_URL` (preferred for the Rust harness) or
222
+ `MESHAGENT_ROOM_URL`, so the client does not silently connect to production.
223
+
224
+ From this repository, the preferred full interoperability check starts the
225
+ Rust server and process agent, runs both the Dart and TypeScript suites, and
226
+ stops the fixture automatically:
227
+
228
+ ```bash
229
+ rust/meshagent-server-e2e/run-cross-language-e2e.sh
230
+ ```
@@ -747,26 +747,16 @@ function makeMessageClass({ type, base = "message", fields = [], }) {
747
747
  params.model = stringOrNull(json.model);
748
748
  }
749
749
  for (const field of fields) {
750
- if (field.required) {
751
- if (field.from != null) {
752
- params[field.prop] = field.from(json[field.json], json);
750
+ if (!hasOwn(json, field.json)) {
751
+ if (field.required) {
752
+ if (field.from != null) {
753
+ field.from(undefined, json);
754
+ }
755
+ throw new TypeError(`missing required field '${field.json}'`);
753
756
  }
754
- else if (typeof json[field.json] === "string") {
755
- params[field.prop] = requiredString(json, field.json);
756
- }
757
- else if (typeof json[field.json] === "number") {
758
- params[field.prop] = json[field.json];
759
- }
760
- else {
761
- params[field.prop] = requiredMap(json, field.json);
762
- }
763
- }
764
- else if (hasOwn(json, field.json)) {
765
- params[field.prop] = field.from != null ? field.from(json[field.json], json) : json[field.json];
766
- }
767
- else if (field.default != null) {
768
- params[field.prop] = field.default(json);
757
+ continue;
769
758
  }
759
+ params[field.prop] = json[field.json];
770
760
  }
771
761
  return new this(params);
772
762
  }
@@ -990,10 +980,10 @@ exports.AgentToolCallLogDelta = makeMessageClass({ type: exports.agentToolCallLo
990
980
  exports.AgentToolCallEnded = makeMessageClass({ type: exports.agentToolCallEndedType, base: "llm", fields: [...contentPart, { prop: "namespace", json: "namespace", from: (value) => stringOrNull(value) ?? "meshagent", default: () => "meshagent", always: true }, s("callId"), s("toolkit"), s("tool"), { prop: "result", json: "result", from: contentOrNull, to: contentHeader }, obj("error", "error", AgentError.fromJson)] });
991
981
  exports.AgentToolCallApprovalRequested = makeMessageClass({ type: exports.agentToolCallApprovalRequestedType, base: "llm", fields: toolState.filter((field) => field.prop !== "argumentBytes") });
992
982
  exports.AgentSecretRequested = makeMessageClass({ type: exports.agentSecretRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("name", "name", true), s("scope")] });
993
- exports.AgentClientToolCallRequested = makeMessageClass({ type: exports.agentClientToolCallRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("toolkit", "toolkit", true), s("tool", "tool", true), m("arguments", "arguments", true)] });
994
- exports.AgentClientToolCallCancelled = makeMessageClass({ type: exports.agentClientToolCallCancelledType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("reason")] });
983
+ exports.AgentClientToolCallRequested = makeMessageClass({ type: exports.agentClientToolCallRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("targetParticipantId"), s("toolkit", "toolkit", true), s("tool", "tool", true), m("arguments", "arguments", true)] });
984
+ exports.AgentClientToolCallCancelled = makeMessageClass({ type: exports.agentClientToolCallCancelledType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("targetParticipantId"), s("reason")] });
995
985
  exports.AgentThreadStatus = makeMessageClass({ type: exports.agentThreadStatusType, base: "thread", fields: [s("status"), s("mode"), s("startedAt"), s("turnId"), s("pendingItemId"), i("totalBytes"), i("linesAdded"), i("linesRemoved")] });
996
- exports.AgentConnectionStatus = makeMessageClass({ type: exports.agentConnectionStatusType, fields: [s("status", "status", true), s("message"), s("reason"), n("retryInSeconds")] });
986
+ exports.AgentConnectionStatus = makeMessageClass({ type: exports.agentConnectionStatusType, fields: [s("status", "status", true), s("message"), s("reason"), n("retryInSeconds"), s("participantId")] });
997
987
  exports.AgentThreadEvent = makeMessageClass({ type: exports.agentThreadEventType, base: "llm", fields: [m("event", "event", true)] });
998
988
  exports.AgentImageGenerationStarted = makeMessageClass({ type: exports.agentImageGenerationStartedType, base: "llm", fields: imageGeneration });
999
989
  exports.AgentImageGenerationPartial = makeMessageClass({ type: exports.agentImageGenerationPartialType, base: "llm", fields: [...imageGeneration, obj("image", "image", AgentGeneratedImage.fromJson), i("partialIndex")] });
@@ -1,6 +1,6 @@
1
1
  import { RemoteParticipant } from "@meshagent/meshagent";
2
2
  import type { RoomClient, Content } from "@meshagent/meshagent";
3
- import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus } from "./agent-messages.js";
3
+ import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus, AgentClientToolCallRequested } from "./agent-messages.js";
4
4
  import type { AgentPayload, JsonMap, ClientToolkitDescription } from "./agent-messages.js";
5
5
  import { ChangeEmitter } from "./change-emitter.js";
6
6
  export declare class AgentMessageEvent {
@@ -90,6 +90,8 @@ export declare abstract class BaseChatClient extends ChangeEmitter {
90
90
  stop(): Promise<void>;
91
91
  agentParticipant(): RemoteParticipant | null;
92
92
  localParticipantName(): string | undefined;
93
+ localParticipantId(): string | undefined;
94
+ isClientToolCallForLocalParticipant(request: InstanceType<typeof AgentClientToolCallRequested>): boolean;
93
95
  protected cleanParticipantName(value: string | undefined): string | undefined;
94
96
  openThread(threadPath: string, { load, sinceTurn, reloadIfOpen, drainPendingEvents, }?: {
95
97
  load?: boolean;
@@ -146,6 +148,7 @@ export declare class MessagingChatClient extends BaseChatClient {
146
148
  agentName?: string;
147
149
  });
148
150
  localParticipantName(): string | undefined;
151
+ localParticipantId(): string | undefined;
149
152
  start(): Promise<void>;
150
153
  stop(): Promise<void>;
151
154
  private attachMessagingListeners;
@@ -171,6 +174,8 @@ export declare class ChatThreadSession extends ChangeEmitter {
171
174
  private readonly messageIndexes;
172
175
  private readonly localAgentMessageIds;
173
176
  private readonly pendingLocalInputMessageIds;
177
+ private readonly resolvedInputMessageIds;
178
+ private readonly completedTurnIds;
174
179
  private readonly mergedDeltaMessageIds;
175
180
  private readonly pendingInputsById;
176
181
  private openState;
@@ -175,6 +175,13 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
175
175
  localParticipantName() {
176
176
  return undefined;
177
177
  }
178
+ localParticipantId() {
179
+ return undefined;
180
+ }
181
+ isClientToolCallForLocalParticipant(request) {
182
+ const targetParticipantId = request.targetParticipantId?.trim();
183
+ return targetParticipantId == null || targetParticipantId === "" || targetParticipantId === this.localParticipantId();
184
+ }
178
185
  cleanParticipantName(value) {
179
186
  return nonEmptyTrimmed(value);
180
187
  }
@@ -280,6 +287,10 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
280
287
  }
281
288
  }
282
289
  handleAgentMessage(message, { createdAt, attachment } = {}) {
290
+ if (message instanceof agent_messages_js_1.AgentClientToolCallRequested &&
291
+ !this.isClientToolCallForLocalParticipant(message)) {
292
+ return;
293
+ }
283
294
  if (message.type === agent_messages_js_1.agentConnectionStatusType) {
284
295
  this._connectionStatus = message;
285
296
  }
@@ -420,6 +431,9 @@ class MessagingChatClient extends BaseChatClient {
420
431
  const name = this.room.localParticipant?.getAttribute("name");
421
432
  return typeof name === "string" ? this.cleanParticipantName(name) : undefined;
422
433
  }
434
+ localParticipantId() {
435
+ return this.room.localParticipant?.id;
436
+ }
423
437
  async start() {
424
438
  if (this.started) {
425
439
  return;
@@ -560,6 +574,8 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
560
574
  this.messageIndexes = new Map();
561
575
  this.localAgentMessageIds = new Set();
562
576
  this.pendingLocalInputMessageIds = new Set();
577
+ this.resolvedInputMessageIds = new Set();
578
+ this.completedTurnIds = new Set();
563
579
  this.mergedDeltaMessageIds = new Set();
564
580
  this.pendingInputsById = new Map();
565
581
  this.openState = false;
@@ -638,6 +654,8 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
638
654
  this.messageIndexes.clear();
639
655
  this.localAgentMessageIds.clear();
640
656
  this.pendingLocalInputMessageIds.clear();
657
+ this.resolvedInputMessageIds.clear();
658
+ this.completedTurnIds.clear();
641
659
  this.mergedDeltaMessageIds.clear();
642
660
  this.pendingInputsById.clear();
643
661
  this.lastCompletedTurnIdValue = undefined;
@@ -789,15 +807,19 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
789
807
  return;
790
808
  }
791
809
  if (message.type === agent_messages_js_1.agentThreadStatusType && message instanceof agent_messages_js_1.AgentThreadStatus) {
792
- this.threadStatusValue = message;
793
810
  const mode = normalizedString(message.mode);
794
811
  const status = normalizedString(message.status);
795
812
  const turnId = normalizedString(message.turnId);
796
- if (status != null && mode === "steerable" && turnId != null) {
797
- this.activeTurnIdValue = turnId;
813
+ if (status == null) {
814
+ this.threadStatusValue = undefined;
815
+ if (turnId != null && this.activeTurnIdValue === turnId) {
816
+ this.activeTurnIdValue = undefined;
817
+ }
818
+ return;
798
819
  }
799
- else if (status == null && turnId != null && this.activeTurnIdValue === turnId) {
800
- this.activeTurnIdValue = undefined;
820
+ this.threadStatusValue = message;
821
+ if (mode === "steerable" && turnId != null) {
822
+ this.activeTurnIdValue = turnId;
801
823
  }
802
824
  return;
803
825
  }
@@ -825,21 +847,29 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
825
847
  updatePendingInputsForMessage(message, createdAt) {
826
848
  const type = message.type;
827
849
  const messageId = message.messageId;
828
- const sourceMessageId = sourceMessageIdFor(message);
850
+ const sourceMessageId = normalizedString(sourceMessageIdFor(message));
829
851
  if (type === agent_messages_js_1.agentThreadStartType || type === agent_messages_js_1.agentTurnStartType || type === agent_messages_js_1.agentTurnSteerType) {
830
852
  const normalizedMessageId = messageId.trim();
831
853
  if (normalizedMessageId !== "") {
854
+ const wasPending = this.pendingInputsById.has(normalizedMessageId);
855
+ const turnId = type === agent_messages_js_1.agentTurnStartType || type === agent_messages_js_1.agentTurnSteerType
856
+ ? normalizedString(threadMessageValue(message, "turnId"))
857
+ : undefined;
858
+ const wasAlreadyResolved = !wasPending && (this.resolvedInputMessageIds.has(normalizedMessageId) ||
859
+ (turnId != null && this.completedTurnIds.has(turnId)));
832
860
  this.localAgentMessageIds.add(normalizedMessageId);
833
861
  this.pendingLocalInputMessageIds.add(normalizedMessageId);
834
- this.markPending(new PendingAgentInput({
835
- messageId: normalizedMessageId,
836
- messageType: type,
837
- threadPath: this.threadPath,
838
- payload: message,
839
- createdAt,
840
- awaitingAcceptance: true,
841
- awaitingApplication: true,
842
- }));
862
+ if (!wasAlreadyResolved) {
863
+ this.markPending(new PendingAgentInput({
864
+ messageId: normalizedMessageId,
865
+ messageType: type,
866
+ threadPath: this.threadPath,
867
+ payload: message,
868
+ createdAt,
869
+ awaitingAcceptance: true,
870
+ awaitingApplication: true,
871
+ }));
872
+ }
843
873
  }
844
874
  }
845
875
  else if (type === agent_messages_js_1.agentThreadStartRejectedType) {
@@ -852,12 +882,14 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
852
882
  }
853
883
  else if (type === agent_messages_js_1.agentTurnStartedType || type === agent_messages_js_1.agentTurnSteeredType) {
854
884
  if (sourceMessageId != null) {
885
+ this.resolvedInputMessageIds.add(sourceMessageId);
855
886
  this.pendingInputsById.delete(sourceMessageId);
856
887
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
857
888
  }
858
889
  }
859
890
  else if (type === agent_messages_js_1.agentTurnStartRejectedType || type === agent_messages_js_1.agentTurnSteerRejectedType) {
860
891
  if (sourceMessageId != null) {
892
+ this.resolvedInputMessageIds.add(sourceMessageId);
861
893
  this.pendingInputsById.delete(sourceMessageId);
862
894
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
863
895
  }
@@ -865,6 +897,15 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
865
897
  else if (type === agent_messages_js_1.agentTurnEndedType || type === agent_messages_js_1.agentThreadClearedType) {
866
898
  this.pendingInputsById.clear();
867
899
  this.pendingLocalInputMessageIds.clear();
900
+ if (type === agent_messages_js_1.agentTurnEndedType) {
901
+ const turnId = normalizedString(threadMessageValue(message, "turnId"));
902
+ if (turnId != null)
903
+ this.completedTurnIds.add(turnId);
904
+ }
905
+ else if (type === agent_messages_js_1.agentThreadClearedType) {
906
+ this.resolvedInputMessageIds.clear();
907
+ this.completedTurnIds.clear();
908
+ }
868
909
  }
869
910
  }
870
911
  appendRenderableMessage(event) {
@@ -721,26 +721,16 @@ function makeMessageClass({ type, base = "message", fields = [], }) {
721
721
  params.model = stringOrNull(json.model);
722
722
  }
723
723
  for (const field of fields) {
724
- if (field.required) {
725
- if (field.from != null) {
726
- params[field.prop] = field.from(json[field.json], json);
724
+ if (!hasOwn(json, field.json)) {
725
+ if (field.required) {
726
+ if (field.from != null) {
727
+ field.from(undefined, json);
728
+ }
729
+ throw new TypeError(`missing required field '${field.json}'`);
727
730
  }
728
- else if (typeof json[field.json] === "string") {
729
- params[field.prop] = requiredString(json, field.json);
730
- }
731
- else if (typeof json[field.json] === "number") {
732
- params[field.prop] = json[field.json];
733
- }
734
- else {
735
- params[field.prop] = requiredMap(json, field.json);
736
- }
737
- }
738
- else if (hasOwn(json, field.json)) {
739
- params[field.prop] = field.from != null ? field.from(json[field.json], json) : json[field.json];
740
- }
741
- else if (field.default != null) {
742
- params[field.prop] = field.default(json);
731
+ continue;
743
732
  }
733
+ params[field.prop] = json[field.json];
744
734
  }
745
735
  return new this(params);
746
736
  }
@@ -964,10 +954,10 @@ export const AgentToolCallLogDelta = makeMessageClass({ type: agentToolCallLogDe
964
954
  export const AgentToolCallEnded = makeMessageClass({ type: agentToolCallEndedType, base: "llm", fields: [...contentPart, { prop: "namespace", json: "namespace", from: (value) => stringOrNull(value) ?? "meshagent", default: () => "meshagent", always: true }, s("callId"), s("toolkit"), s("tool"), { prop: "result", json: "result", from: contentOrNull, to: contentHeader }, obj("error", "error", AgentError.fromJson)] });
965
955
  export const AgentToolCallApprovalRequested = makeMessageClass({ type: agentToolCallApprovalRequestedType, base: "llm", fields: toolState.filter((field) => field.prop !== "argumentBytes") });
966
956
  export const AgentSecretRequested = makeMessageClass({ type: agentSecretRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("name", "name", true), s("scope")] });
967
- export const AgentClientToolCallRequested = makeMessageClass({ type: agentClientToolCallRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("toolkit", "toolkit", true), s("tool", "tool", true), m("arguments", "arguments", true)] });
968
- export const AgentClientToolCallCancelled = makeMessageClass({ type: agentClientToolCallCancelledType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("reason")] });
957
+ export const AgentClientToolCallRequested = makeMessageClass({ type: agentClientToolCallRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("targetParticipantId"), s("toolkit", "toolkit", true), s("tool", "tool", true), m("arguments", "arguments", true)] });
958
+ export const AgentClientToolCallCancelled = makeMessageClass({ type: agentClientToolCallCancelledType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("targetParticipantId"), s("reason")] });
969
959
  export const AgentThreadStatus = makeMessageClass({ type: agentThreadStatusType, base: "thread", fields: [s("status"), s("mode"), s("startedAt"), s("turnId"), s("pendingItemId"), i("totalBytes"), i("linesAdded"), i("linesRemoved")] });
970
- export const AgentConnectionStatus = makeMessageClass({ type: agentConnectionStatusType, fields: [s("status", "status", true), s("message"), s("reason"), n("retryInSeconds")] });
960
+ export const AgentConnectionStatus = makeMessageClass({ type: agentConnectionStatusType, fields: [s("status", "status", true), s("message"), s("reason"), n("retryInSeconds"), s("participantId")] });
971
961
  export const AgentThreadEvent = makeMessageClass({ type: agentThreadEventType, base: "llm", fields: [m("event", "event", true)] });
972
962
  export const AgentImageGenerationStarted = makeMessageClass({ type: agentImageGenerationStartedType, base: "llm", fields: imageGeneration });
973
963
  export const AgentImageGenerationPartial = makeMessageClass({ type: agentImageGenerationPartialType, base: "llm", fields: [...imageGeneration, obj("image", "image", AgentGeneratedImage.fromJson), i("partialIndex")] });
@@ -1,6 +1,6 @@
1
1
  import { RemoteParticipant } from "@meshagent/meshagent";
2
2
  import type { RoomClient, Content } from "@meshagent/meshagent";
3
- import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus } from "./agent-messages.js";
3
+ import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus, AgentClientToolCallRequested } from "./agent-messages.js";
4
4
  import type { AgentPayload, JsonMap, ClientToolkitDescription } from "./agent-messages.js";
5
5
  import { ChangeEmitter } from "./change-emitter.js";
6
6
  export declare class AgentMessageEvent {
@@ -90,6 +90,8 @@ export declare abstract class BaseChatClient extends ChangeEmitter {
90
90
  stop(): Promise<void>;
91
91
  agentParticipant(): RemoteParticipant | null;
92
92
  localParticipantName(): string | undefined;
93
+ localParticipantId(): string | undefined;
94
+ isClientToolCallForLocalParticipant(request: InstanceType<typeof AgentClientToolCallRequested>): boolean;
93
95
  protected cleanParticipantName(value: string | undefined): string | undefined;
94
96
  openThread(threadPath: string, { load, sinceTurn, reloadIfOpen, drainPendingEvents, }?: {
95
97
  load?: boolean;
@@ -146,6 +148,7 @@ export declare class MessagingChatClient extends BaseChatClient {
146
148
  agentName?: string;
147
149
  });
148
150
  localParticipantName(): string | undefined;
151
+ localParticipantId(): string | undefined;
149
152
  start(): Promise<void>;
150
153
  stop(): Promise<void>;
151
154
  private attachMessagingListeners;
@@ -171,6 +174,8 @@ export declare class ChatThreadSession extends ChangeEmitter {
171
174
  private readonly messageIndexes;
172
175
  private readonly localAgentMessageIds;
173
176
  private readonly pendingLocalInputMessageIds;
177
+ private readonly resolvedInputMessageIds;
178
+ private readonly completedTurnIds;
174
179
  private readonly mergedDeltaMessageIds;
175
180
  private readonly pendingInputsById;
176
181
  private openState;
@@ -168,6 +168,13 @@ export class BaseChatClient extends ChangeEmitter {
168
168
  localParticipantName() {
169
169
  return undefined;
170
170
  }
171
+ localParticipantId() {
172
+ return undefined;
173
+ }
174
+ isClientToolCallForLocalParticipant(request) {
175
+ const targetParticipantId = request.targetParticipantId?.trim();
176
+ return targetParticipantId == null || targetParticipantId === "" || targetParticipantId === this.localParticipantId();
177
+ }
171
178
  cleanParticipantName(value) {
172
179
  return nonEmptyTrimmed(value);
173
180
  }
@@ -273,6 +280,10 @@ export class BaseChatClient extends ChangeEmitter {
273
280
  }
274
281
  }
275
282
  handleAgentMessage(message, { createdAt, attachment } = {}) {
283
+ if (message instanceof AgentClientToolCallRequested &&
284
+ !this.isClientToolCallForLocalParticipant(message)) {
285
+ return;
286
+ }
276
287
  if (message.type === agentConnectionStatusType) {
277
288
  this._connectionStatus = message;
278
289
  }
@@ -412,6 +423,9 @@ export class MessagingChatClient extends BaseChatClient {
412
423
  const name = this.room.localParticipant?.getAttribute("name");
413
424
  return typeof name === "string" ? this.cleanParticipantName(name) : undefined;
414
425
  }
426
+ localParticipantId() {
427
+ return this.room.localParticipant?.id;
428
+ }
415
429
  async start() {
416
430
  if (this.started) {
417
431
  return;
@@ -551,6 +565,8 @@ export class ChatThreadSession extends ChangeEmitter {
551
565
  this.messageIndexes = new Map();
552
566
  this.localAgentMessageIds = new Set();
553
567
  this.pendingLocalInputMessageIds = new Set();
568
+ this.resolvedInputMessageIds = new Set();
569
+ this.completedTurnIds = new Set();
554
570
  this.mergedDeltaMessageIds = new Set();
555
571
  this.pendingInputsById = new Map();
556
572
  this.openState = false;
@@ -629,6 +645,8 @@ export class ChatThreadSession extends ChangeEmitter {
629
645
  this.messageIndexes.clear();
630
646
  this.localAgentMessageIds.clear();
631
647
  this.pendingLocalInputMessageIds.clear();
648
+ this.resolvedInputMessageIds.clear();
649
+ this.completedTurnIds.clear();
632
650
  this.mergedDeltaMessageIds.clear();
633
651
  this.pendingInputsById.clear();
634
652
  this.lastCompletedTurnIdValue = undefined;
@@ -780,15 +798,19 @@ export class ChatThreadSession extends ChangeEmitter {
780
798
  return;
781
799
  }
782
800
  if (message.type === agentThreadStatusType && message instanceof AgentThreadStatus) {
783
- this.threadStatusValue = message;
784
801
  const mode = normalizedString(message.mode);
785
802
  const status = normalizedString(message.status);
786
803
  const turnId = normalizedString(message.turnId);
787
- if (status != null && mode === "steerable" && turnId != null) {
788
- this.activeTurnIdValue = turnId;
804
+ if (status == null) {
805
+ this.threadStatusValue = undefined;
806
+ if (turnId != null && this.activeTurnIdValue === turnId) {
807
+ this.activeTurnIdValue = undefined;
808
+ }
809
+ return;
789
810
  }
790
- else if (status == null && turnId != null && this.activeTurnIdValue === turnId) {
791
- this.activeTurnIdValue = undefined;
811
+ this.threadStatusValue = message;
812
+ if (mode === "steerable" && turnId != null) {
813
+ this.activeTurnIdValue = turnId;
792
814
  }
793
815
  return;
794
816
  }
@@ -816,21 +838,29 @@ export class ChatThreadSession extends ChangeEmitter {
816
838
  updatePendingInputsForMessage(message, createdAt) {
817
839
  const type = message.type;
818
840
  const messageId = message.messageId;
819
- const sourceMessageId = sourceMessageIdFor(message);
841
+ const sourceMessageId = normalizedString(sourceMessageIdFor(message));
820
842
  if (type === agentThreadStartType || type === agentTurnStartType || type === agentTurnSteerType) {
821
843
  const normalizedMessageId = messageId.trim();
822
844
  if (normalizedMessageId !== "") {
845
+ const wasPending = this.pendingInputsById.has(normalizedMessageId);
846
+ const turnId = type === agentTurnStartType || type === agentTurnSteerType
847
+ ? normalizedString(threadMessageValue(message, "turnId"))
848
+ : undefined;
849
+ const wasAlreadyResolved = !wasPending && (this.resolvedInputMessageIds.has(normalizedMessageId) ||
850
+ (turnId != null && this.completedTurnIds.has(turnId)));
823
851
  this.localAgentMessageIds.add(normalizedMessageId);
824
852
  this.pendingLocalInputMessageIds.add(normalizedMessageId);
825
- this.markPending(new PendingAgentInput({
826
- messageId: normalizedMessageId,
827
- messageType: type,
828
- threadPath: this.threadPath,
829
- payload: message,
830
- createdAt,
831
- awaitingAcceptance: true,
832
- awaitingApplication: true,
833
- }));
853
+ if (!wasAlreadyResolved) {
854
+ this.markPending(new PendingAgentInput({
855
+ messageId: normalizedMessageId,
856
+ messageType: type,
857
+ threadPath: this.threadPath,
858
+ payload: message,
859
+ createdAt,
860
+ awaitingAcceptance: true,
861
+ awaitingApplication: true,
862
+ }));
863
+ }
834
864
  }
835
865
  }
836
866
  else if (type === agentThreadStartRejectedType) {
@@ -843,12 +873,14 @@ export class ChatThreadSession extends ChangeEmitter {
843
873
  }
844
874
  else if (type === agentTurnStartedType || type === agentTurnSteeredType) {
845
875
  if (sourceMessageId != null) {
876
+ this.resolvedInputMessageIds.add(sourceMessageId);
846
877
  this.pendingInputsById.delete(sourceMessageId);
847
878
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
848
879
  }
849
880
  }
850
881
  else if (type === agentTurnStartRejectedType || type === agentTurnSteerRejectedType) {
851
882
  if (sourceMessageId != null) {
883
+ this.resolvedInputMessageIds.add(sourceMessageId);
852
884
  this.pendingInputsById.delete(sourceMessageId);
853
885
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
854
886
  }
@@ -856,6 +888,15 @@ export class ChatThreadSession extends ChangeEmitter {
856
888
  else if (type === agentTurnEndedType || type === agentThreadClearedType) {
857
889
  this.pendingInputsById.clear();
858
890
  this.pendingLocalInputMessageIds.clear();
891
+ if (type === agentTurnEndedType) {
892
+ const turnId = normalizedString(threadMessageValue(message, "turnId"));
893
+ if (turnId != null)
894
+ this.completedTurnIds.add(turnId);
895
+ }
896
+ else if (type === agentThreadClearedType) {
897
+ this.resolvedInputMessageIds.clear();
898
+ this.completedTurnIds.clear();
899
+ }
859
900
  }
860
901
  }
861
902
  appendRenderableMessage(event) {
@@ -747,26 +747,16 @@ function makeMessageClass({ type, base = "message", fields = [], }) {
747
747
  params.model = stringOrNull(json.model);
748
748
  }
749
749
  for (const field of fields) {
750
- if (field.required) {
751
- if (field.from != null) {
752
- params[field.prop] = field.from(json[field.json], json);
750
+ if (!hasOwn(json, field.json)) {
751
+ if (field.required) {
752
+ if (field.from != null) {
753
+ field.from(undefined, json);
754
+ }
755
+ throw new TypeError(`missing required field '${field.json}'`);
753
756
  }
754
- else if (typeof json[field.json] === "string") {
755
- params[field.prop] = requiredString(json, field.json);
756
- }
757
- else if (typeof json[field.json] === "number") {
758
- params[field.prop] = json[field.json];
759
- }
760
- else {
761
- params[field.prop] = requiredMap(json, field.json);
762
- }
763
- }
764
- else if (hasOwn(json, field.json)) {
765
- params[field.prop] = field.from != null ? field.from(json[field.json], json) : json[field.json];
766
- }
767
- else if (field.default != null) {
768
- params[field.prop] = field.default(json);
757
+ continue;
769
758
  }
759
+ params[field.prop] = json[field.json];
770
760
  }
771
761
  return new this(params);
772
762
  }
@@ -990,10 +980,10 @@ exports.AgentToolCallLogDelta = makeMessageClass({ type: exports.agentToolCallLo
990
980
  exports.AgentToolCallEnded = makeMessageClass({ type: exports.agentToolCallEndedType, base: "llm", fields: [...contentPart, { prop: "namespace", json: "namespace", from: (value) => stringOrNull(value) ?? "meshagent", default: () => "meshagent", always: true }, s("callId"), s("toolkit"), s("tool"), { prop: "result", json: "result", from: contentOrNull, to: contentHeader }, obj("error", "error", AgentError.fromJson)] });
991
981
  exports.AgentToolCallApprovalRequested = makeMessageClass({ type: exports.agentToolCallApprovalRequestedType, base: "llm", fields: toolState.filter((field) => field.prop !== "argumentBytes") });
992
982
  exports.AgentSecretRequested = makeMessageClass({ type: exports.agentSecretRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("name", "name", true), s("scope")] });
993
- exports.AgentClientToolCallRequested = makeMessageClass({ type: exports.agentClientToolCallRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("toolkit", "toolkit", true), s("tool", "tool", true), m("arguments", "arguments", true)] });
994
- exports.AgentClientToolCallCancelled = makeMessageClass({ type: exports.agentClientToolCallCancelledType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("reason")] });
983
+ exports.AgentClientToolCallRequested = makeMessageClass({ type: exports.agentClientToolCallRequestedType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("targetParticipantId"), s("toolkit", "toolkit", true), s("tool", "tool", true), m("arguments", "arguments", true)] });
984
+ exports.AgentClientToolCallCancelled = makeMessageClass({ type: exports.agentClientToolCallCancelledType, base: "llm", fields: [...contentPart, s("requestId", "request_id", true), s("targetParticipantId"), s("reason")] });
995
985
  exports.AgentThreadStatus = makeMessageClass({ type: exports.agentThreadStatusType, base: "thread", fields: [s("status"), s("mode"), s("startedAt"), s("turnId"), s("pendingItemId"), i("totalBytes"), i("linesAdded"), i("linesRemoved")] });
996
- exports.AgentConnectionStatus = makeMessageClass({ type: exports.agentConnectionStatusType, fields: [s("status", "status", true), s("message"), s("reason"), n("retryInSeconds")] });
986
+ exports.AgentConnectionStatus = makeMessageClass({ type: exports.agentConnectionStatusType, fields: [s("status", "status", true), s("message"), s("reason"), n("retryInSeconds"), s("participantId")] });
997
987
  exports.AgentThreadEvent = makeMessageClass({ type: exports.agentThreadEventType, base: "llm", fields: [m("event", "event", true)] });
998
988
  exports.AgentImageGenerationStarted = makeMessageClass({ type: exports.agentImageGenerationStartedType, base: "llm", fields: imageGeneration });
999
989
  exports.AgentImageGenerationPartial = makeMessageClass({ type: exports.agentImageGenerationPartialType, base: "llm", fields: [...imageGeneration, obj("image", "image", AgentGeneratedImage.fromJson), i("partialIndex")] });
@@ -1,6 +1,6 @@
1
1
  import { RemoteParticipant } from "@meshagent/meshagent";
2
2
  import type { RoomClient, Content } from "@meshagent/meshagent";
3
- import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus } from "./agent-messages.js";
3
+ import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus, AgentClientToolCallRequested } from "./agent-messages.js";
4
4
  import type { AgentPayload, JsonMap, ClientToolkitDescription } from "./agent-messages.js";
5
5
  import { ChangeEmitter } from "./change-emitter.js";
6
6
  export declare class AgentMessageEvent {
@@ -90,6 +90,8 @@ export declare abstract class BaseChatClient extends ChangeEmitter {
90
90
  stop(): Promise<void>;
91
91
  agentParticipant(): RemoteParticipant | null;
92
92
  localParticipantName(): string | undefined;
93
+ localParticipantId(): string | undefined;
94
+ isClientToolCallForLocalParticipant(request: InstanceType<typeof AgentClientToolCallRequested>): boolean;
93
95
  protected cleanParticipantName(value: string | undefined): string | undefined;
94
96
  openThread(threadPath: string, { load, sinceTurn, reloadIfOpen, drainPendingEvents, }?: {
95
97
  load?: boolean;
@@ -146,6 +148,7 @@ export declare class MessagingChatClient extends BaseChatClient {
146
148
  agentName?: string;
147
149
  });
148
150
  localParticipantName(): string | undefined;
151
+ localParticipantId(): string | undefined;
149
152
  start(): Promise<void>;
150
153
  stop(): Promise<void>;
151
154
  private attachMessagingListeners;
@@ -171,6 +174,8 @@ export declare class ChatThreadSession extends ChangeEmitter {
171
174
  private readonly messageIndexes;
172
175
  private readonly localAgentMessageIds;
173
176
  private readonly pendingLocalInputMessageIds;
177
+ private readonly resolvedInputMessageIds;
178
+ private readonly completedTurnIds;
174
179
  private readonly mergedDeltaMessageIds;
175
180
  private readonly pendingInputsById;
176
181
  private openState;
@@ -175,6 +175,13 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
175
175
  localParticipantName() {
176
176
  return undefined;
177
177
  }
178
+ localParticipantId() {
179
+ return undefined;
180
+ }
181
+ isClientToolCallForLocalParticipant(request) {
182
+ const targetParticipantId = request.targetParticipantId?.trim();
183
+ return targetParticipantId == null || targetParticipantId === "" || targetParticipantId === this.localParticipantId();
184
+ }
178
185
  cleanParticipantName(value) {
179
186
  return nonEmptyTrimmed(value);
180
187
  }
@@ -280,6 +287,10 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
280
287
  }
281
288
  }
282
289
  handleAgentMessage(message, { createdAt, attachment } = {}) {
290
+ if (message instanceof agent_messages_js_1.AgentClientToolCallRequested &&
291
+ !this.isClientToolCallForLocalParticipant(message)) {
292
+ return;
293
+ }
283
294
  if (message.type === agent_messages_js_1.agentConnectionStatusType) {
284
295
  this._connectionStatus = message;
285
296
  }
@@ -420,6 +431,9 @@ class MessagingChatClient extends BaseChatClient {
420
431
  const name = this.room.localParticipant?.getAttribute("name");
421
432
  return typeof name === "string" ? this.cleanParticipantName(name) : undefined;
422
433
  }
434
+ localParticipantId() {
435
+ return this.room.localParticipant?.id;
436
+ }
423
437
  async start() {
424
438
  if (this.started) {
425
439
  return;
@@ -560,6 +574,8 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
560
574
  this.messageIndexes = new Map();
561
575
  this.localAgentMessageIds = new Set();
562
576
  this.pendingLocalInputMessageIds = new Set();
577
+ this.resolvedInputMessageIds = new Set();
578
+ this.completedTurnIds = new Set();
563
579
  this.mergedDeltaMessageIds = new Set();
564
580
  this.pendingInputsById = new Map();
565
581
  this.openState = false;
@@ -638,6 +654,8 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
638
654
  this.messageIndexes.clear();
639
655
  this.localAgentMessageIds.clear();
640
656
  this.pendingLocalInputMessageIds.clear();
657
+ this.resolvedInputMessageIds.clear();
658
+ this.completedTurnIds.clear();
641
659
  this.mergedDeltaMessageIds.clear();
642
660
  this.pendingInputsById.clear();
643
661
  this.lastCompletedTurnIdValue = undefined;
@@ -789,15 +807,19 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
789
807
  return;
790
808
  }
791
809
  if (message.type === agent_messages_js_1.agentThreadStatusType && message instanceof agent_messages_js_1.AgentThreadStatus) {
792
- this.threadStatusValue = message;
793
810
  const mode = normalizedString(message.mode);
794
811
  const status = normalizedString(message.status);
795
812
  const turnId = normalizedString(message.turnId);
796
- if (status != null && mode === "steerable" && turnId != null) {
797
- this.activeTurnIdValue = turnId;
813
+ if (status == null) {
814
+ this.threadStatusValue = undefined;
815
+ if (turnId != null && this.activeTurnIdValue === turnId) {
816
+ this.activeTurnIdValue = undefined;
817
+ }
818
+ return;
798
819
  }
799
- else if (status == null && turnId != null && this.activeTurnIdValue === turnId) {
800
- this.activeTurnIdValue = undefined;
820
+ this.threadStatusValue = message;
821
+ if (mode === "steerable" && turnId != null) {
822
+ this.activeTurnIdValue = turnId;
801
823
  }
802
824
  return;
803
825
  }
@@ -825,21 +847,29 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
825
847
  updatePendingInputsForMessage(message, createdAt) {
826
848
  const type = message.type;
827
849
  const messageId = message.messageId;
828
- const sourceMessageId = sourceMessageIdFor(message);
850
+ const sourceMessageId = normalizedString(sourceMessageIdFor(message));
829
851
  if (type === agent_messages_js_1.agentThreadStartType || type === agent_messages_js_1.agentTurnStartType || type === agent_messages_js_1.agentTurnSteerType) {
830
852
  const normalizedMessageId = messageId.trim();
831
853
  if (normalizedMessageId !== "") {
854
+ const wasPending = this.pendingInputsById.has(normalizedMessageId);
855
+ const turnId = type === agent_messages_js_1.agentTurnStartType || type === agent_messages_js_1.agentTurnSteerType
856
+ ? normalizedString(threadMessageValue(message, "turnId"))
857
+ : undefined;
858
+ const wasAlreadyResolved = !wasPending && (this.resolvedInputMessageIds.has(normalizedMessageId) ||
859
+ (turnId != null && this.completedTurnIds.has(turnId)));
832
860
  this.localAgentMessageIds.add(normalizedMessageId);
833
861
  this.pendingLocalInputMessageIds.add(normalizedMessageId);
834
- this.markPending(new PendingAgentInput({
835
- messageId: normalizedMessageId,
836
- messageType: type,
837
- threadPath: this.threadPath,
838
- payload: message,
839
- createdAt,
840
- awaitingAcceptance: true,
841
- awaitingApplication: true,
842
- }));
862
+ if (!wasAlreadyResolved) {
863
+ this.markPending(new PendingAgentInput({
864
+ messageId: normalizedMessageId,
865
+ messageType: type,
866
+ threadPath: this.threadPath,
867
+ payload: message,
868
+ createdAt,
869
+ awaitingAcceptance: true,
870
+ awaitingApplication: true,
871
+ }));
872
+ }
843
873
  }
844
874
  }
845
875
  else if (type === agent_messages_js_1.agentThreadStartRejectedType) {
@@ -852,12 +882,14 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
852
882
  }
853
883
  else if (type === agent_messages_js_1.agentTurnStartedType || type === agent_messages_js_1.agentTurnSteeredType) {
854
884
  if (sourceMessageId != null) {
885
+ this.resolvedInputMessageIds.add(sourceMessageId);
855
886
  this.pendingInputsById.delete(sourceMessageId);
856
887
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
857
888
  }
858
889
  }
859
890
  else if (type === agent_messages_js_1.agentTurnStartRejectedType || type === agent_messages_js_1.agentTurnSteerRejectedType) {
860
891
  if (sourceMessageId != null) {
892
+ this.resolvedInputMessageIds.add(sourceMessageId);
861
893
  this.pendingInputsById.delete(sourceMessageId);
862
894
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
863
895
  }
@@ -865,6 +897,15 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
865
897
  else if (type === agent_messages_js_1.agentTurnEndedType || type === agent_messages_js_1.agentThreadClearedType) {
866
898
  this.pendingInputsById.clear();
867
899
  this.pendingLocalInputMessageIds.clear();
900
+ if (type === agent_messages_js_1.agentTurnEndedType) {
901
+ const turnId = normalizedString(threadMessageValue(message, "turnId"));
902
+ if (turnId != null)
903
+ this.completedTurnIds.add(turnId);
904
+ }
905
+ else if (type === agent_messages_js_1.agentThreadClearedType) {
906
+ this.resolvedInputMessageIds.clear();
907
+ this.completedTurnIds.clear();
908
+ }
868
909
  }
869
910
  }
870
911
  appendRenderableMessage(event) {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@meshagent/meshagent-agents",
3
- "version": "0.46.3",
3
+ "version": "0.47.0",
4
4
  "description": "Agent chat sessions and clients for Meshagent TypeScript applications.",
5
5
  "homepage": "https://github.com/meshagent/meshagent-agents-ts",
6
6
  "scripts": {
7
7
  "test": "./run-tests.sh",
8
+ "test:rust-process-e2e": "bash ./run-rust-process-agent-e2e.sh",
8
9
  "build": "mkdir -p dist && ./scripts/build.sh"
9
10
  },
10
11
  "author": "Timu, LLC",
@@ -47,7 +48,7 @@
47
48
  "typescript": "^5.8.2"
48
49
  },
49
50
  "dependencies": {
50
- "@meshagent/meshagent": "^0.46.3",
51
+ "@meshagent/meshagent": "^0.47.0",
51
52
  "apache-arrow": "^21.0.0",
52
53
  "uuid": "^14.0.0"
53
54
  },