@bpmsoftwaresolutions/ai-engine-client 1.1.57 → 1.1.59

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +518 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmsoftwaresolutions/ai-engine-client",
3
- "version": "1.1.57",
3
+ "version": "1.1.59",
4
4
  "description": "Thin npm client for the AI Engine operator and retrieval APIs",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const DEFAULT_TIMEOUT_MS = 30000;
2
- export const AI_ENGINE_CLIENT_VERSION = '1.1.57';
2
+ export const AI_ENGINE_CLIENT_VERSION = '1.1.59';
3
3
  export const GOVERNED_MUTATION_REQUIRED_CAPABILITIES = [
4
4
  'executeVerifiedMutation',
5
5
  'post_mutation_verification',
@@ -207,6 +207,19 @@ function cleanText(value) {
207
207
  return text || null;
208
208
  }
209
209
 
210
+ function compareSemanticVersions(left, right) {
211
+ const leftParts = String(left || '').split('.').map((part) => Number.parseInt(part, 10) || 0);
212
+ const rightParts = String(right || '').split('.').map((part) => Number.parseInt(part, 10) || 0);
213
+ const width = Math.max(leftParts.length, rightParts.length);
214
+ for (let index = 0; index < width; index += 1) {
215
+ const a = leftParts[index] || 0;
216
+ const b = rightParts[index] || 0;
217
+ if (a > b) return 1;
218
+ if (a < b) return -1;
219
+ }
220
+ return 0;
221
+ }
222
+
210
223
  function looksLikeUuid(value) {
211
224
  const text = cleanText(value);
212
225
  return Boolean(text && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(text));
@@ -454,6 +467,12 @@ export class AIEngineClient {
454
467
  verifyMessageSent: (request) => this.verifyMessageSent(request),
455
468
  verifyMessageReceived: (request) => this.verifyMessageReceived(request),
456
469
  getMessageDeliveryReceipt: (request) => this.getMessageDeliveryReceipt(request),
470
+ startCoordinationPingPong: (request) => this.startCoordinationPingPong(request),
471
+ sendCoordinationPing: (request) => this.sendCoordinationPing(request),
472
+ sendCoordinationPong: (request) => this.sendCoordinationPong(request),
473
+ getCoordinationPingPongStatus: (request) => this.getCoordinationPingPongStatus(request),
474
+ stopCoordinationPingPong: (request) => this.stopCoordinationPingPong(request),
475
+ checkCoordinationPingPongPreflight: (request) => this.checkCoordinationPingPongPreflight(request),
457
476
  connectToTransferChannel: (request) => this.connectToTransferChannel(request),
458
477
  respondToMessageWatch: (request) => this.respondToMessageWatch(request),
459
478
  };
@@ -482,6 +501,12 @@ export class AIEngineClient {
482
501
  verifyMessageSent: (request) => this.verifyMessageSent(request),
483
502
  verifyMessageReceived: (request) => this.verifyMessageReceived(request),
484
503
  getMessageDeliveryReceipt: (request) => this.getMessageDeliveryReceipt(request),
504
+ startCoordinationPingPong: (request) => this.startCoordinationPingPong(request),
505
+ sendCoordinationPing: (request) => this.sendCoordinationPing(request),
506
+ sendCoordinationPong: (request) => this.sendCoordinationPong(request),
507
+ getCoordinationPingPongStatus: (request) => this.getCoordinationPingPongStatus(request),
508
+ stopCoordinationPingPong: (request) => this.stopCoordinationPingPong(request),
509
+ checkCoordinationPingPongPreflight: (request) => this.checkCoordinationPingPongPreflight(request),
485
510
  };
486
511
  }
487
512
 
@@ -672,6 +697,79 @@ export class AIEngineClient {
672
697
  return this._request('/api/agent-communications/deployment-capabilities');
673
698
  }
674
699
 
700
+ async checkCoordinationPingPongPreflight({
701
+ packageVersion,
702
+ } = {}) {
703
+ const capabilities = await this.getCollaborationCapabilities();
704
+ const requiredMethods = [
705
+ 'startCoordinationPingPong',
706
+ 'sendCoordinationPing',
707
+ 'sendCoordinationPong',
708
+ 'getCoordinationPingPongStatus',
709
+ 'stopCoordinationPingPong',
710
+ ];
711
+ const resolvedPackageVersion = cleanText(packageVersion) || AI_ENGINE_CLIENT_VERSION;
712
+ const minimumClientVersion = cleanText(capabilities.minimum_client_version) || resolvedPackageVersion;
713
+ const supported = Boolean(capabilities.coordination_ping_pong_supported);
714
+ const reportedMethods = Array.isArray(capabilities.required_methods) && capabilities.required_methods.length > 0
715
+ ? capabilities.required_methods
716
+ : capabilities.collaboration_choreography_methods;
717
+ const missingMethods = requiredMethods.filter((method) => !Array.isArray(reportedMethods) || !reportedMethods.includes(method));
718
+ const issues = [];
719
+ if (!supported) {
720
+ issues.push({
721
+ drift_type: 'capability_drift',
722
+ rule: 'coordination_ping_pong_unsupported',
723
+ message: 'Coordination Ping Pong is not supported by the deployed collaboration capability surface.',
724
+ });
725
+ }
726
+ if (compareSemanticVersions(resolvedPackageVersion, minimumClientVersion) < 0) {
727
+ issues.push({
728
+ drift_type: 'capability_drift',
729
+ rule: 'package_version_below_minimum',
730
+ message: `Package version ${resolvedPackageVersion} is below required minimum ${minimumClientVersion}.`,
731
+ package_version: resolvedPackageVersion,
732
+ required_minimum_version: minimumClientVersion,
733
+ });
734
+ }
735
+ if (missingMethods.length > 0) {
736
+ issues.push({
737
+ drift_type: 'deployment_drift',
738
+ rule: 'missing_ping_pong_methods',
739
+ message: `Missing required coordination methods: ${missingMethods.join(', ')}.`,
740
+ missing_methods: missingMethods,
741
+ });
742
+ }
743
+ if (issues.length > 0) {
744
+ return {
745
+ ok: false,
746
+ supported: false,
747
+ minimum_client_version: minimumClientVersion,
748
+ package_version: resolvedPackageVersion,
749
+ coordination_ping_pong_supported: supported,
750
+ required_methods: requiredMethods,
751
+ capabilities,
752
+ issues,
753
+ stop_reason: {
754
+ code: 'coordination_ping_pong_preflight_failed',
755
+ message: 'Coordination Ping Pong is not ready to start.',
756
+ details: { issues },
757
+ },
758
+ };
759
+ }
760
+ return {
761
+ ok: true,
762
+ supported: true,
763
+ minimum_client_version: minimumClientVersion,
764
+ package_version: resolvedPackageVersion,
765
+ coordination_ping_pong_supported: supported,
766
+ required_methods: requiredMethods,
767
+ capabilities,
768
+ issues: [],
769
+ stop_reason: null,
770
+ };
771
+ }
772
+
675
773
  async listCommunicationChannels({
676
774
  workflowRunId,
677
775
  workflow_run_id,
@@ -771,6 +869,108 @@ export class AIEngineClient {
771
869
  });
772
870
  }
773
871
 
872
+ async _resolveCoordinationChannelContext({
873
+ transferChannelId,
874
+ transfer_channel_id,
875
+ channelId,
876
+ channel_id,
877
+ workTransferPacketId,
878
+ work_transfer_packet_id,
879
+ packetId,
880
+ packet_id,
881
+ workflowRunId,
882
+ workflow_run_id,
883
+ participantRole,
884
+ participant_role,
885
+ role,
886
+ } = {}) {
887
+ const normalizedTransferChannelId = cleanText(transfer_channel_id) || cleanText(transferChannelId) || cleanText(channel_id) || cleanText(channelId);
888
+ const normalizedParticipantRole = cleanText(participant_role) || cleanText(participantRole) || cleanText(role);
889
+ const normalizedWorkflowRunId = cleanText(workflow_run_id) || cleanText(workflowRunId);
890
+ if (normalizedTransferChannelId) {
891
+ let packetId = cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId);
892
+ if (!packetId) {
893
+ try {
894
+ const status = await this.getCommunicationChannelStatus({ transferChannelId: normalizedTransferChannelId });
895
+ packetId = cleanText(status?.packet_id || status?.packetId || status?.work_transfer_packet_id);
896
+ } catch (error) {
897
+ void error;
898
+ }
899
+ }
900
+ return {
901
+ channel_id: normalizedTransferChannelId,
902
+ packet_id: packetId,
903
+ workflow_run_id: normalizedWorkflowRunId,
904
+ stop_reason: null,
905
+ };
906
+ }
907
+ const presence = await this.whoIsOnline({
908
+ workflowRunId: normalizedWorkflowRunId,
909
+ participantRole: normalizedParticipantRole,
910
+ includeStale: true,
911
+ });
912
+ const directoryRows = Array.isArray(presence?.presence)
913
+ ? presence.presence.filter((row) => row && typeof row === 'object')
914
+ : Array.isArray(presence?.online_participants)
915
+ ? presence.online_participants.filter((row) => row && typeof row === 'object')
916
+ : Array.isArray(presence?.participants)
917
+ ? presence.participants.filter((row) => row && typeof row === 'object')
918
+ : [];
919
+ const filteredRows = normalizedParticipantRole
920
+ ? directoryRows.filter((row) => cleanText(row.participant_role) === normalizedParticipantRole)
921
+ : [];
922
+ const onlineRows = filteredRows.filter((row) => Boolean(row.is_online));
923
+ const chooseRows = onlineRows.length > 0 ? onlineRows : filteredRows;
924
+ if (chooseRows.length === 1) {
925
+ return {
926
+ channel_id: cleanText(chooseRows[0].transfer_channel_id),
927
+ packet_id: cleanText(chooseRows[0].work_transfer_packet_id) || cleanText(chooseRows[0].packet_id),
928
+ workflow_run_id: cleanText(chooseRows[0].workflow_run_id) || normalizedWorkflowRunId,
929
+ stop_reason: null,
930
+ };
931
+ }
932
+ const openChannels = await this.listOpenCommunicationChannels({ workflowRunId: normalizedWorkflowRunId });
933
+ const openChannelRows = Array.isArray(openChannels) ? openChannels.filter((row) => row && typeof row === 'object') : [];
934
+ if (openChannelRows.length === 1) {
935
+ return {
936
+ channel_id: cleanText(openChannelRows[0].channel_id || openChannelRows[0].transfer_channel_id),
937
+ packet_id: cleanText(openChannelRows[0].packet_id || openChannelRows[0].work_transfer_packet_id),
938
+ workflow_run_id: cleanText(openChannelRows[0].workflow_run_id) || normalizedWorkflowRunId,
939
+ stop_reason: null,
940
+ };
941
+ }
942
+ if (openChannelRows.length > 1) {
943
+ return {
944
+ channel_id: null,
945
+ packet_id: null,
946
+ workflow_run_id: normalizedWorkflowRunId,
947
+ stop_reason: {
948
+ code: 'multiple_open_channels_need_disambiguation',
949
+ message: 'Multiple open channels matched the requested coordination context.',
950
+ details: {
951
+ workflow_run_id: normalizedWorkflowRunId,
952
+ participant_role: normalizedParticipantRole,
953
+ candidate_count: openChannelRows.length,
954
+ candidate_channel_ids: openChannelRows.map((row) => cleanText(row.channel_id || row.transfer_channel_id)).filter(Boolean),
955
+ },
956
+ },
957
+ };
958
+ }
959
+ return {
960
+ channel_id: null,
961
+ packet_id: null,
962
+ workflow_run_id: normalizedWorkflowRunId,
963
+ stop_reason: {
964
+ code: 'active_channel_not_found',
965
+ message: 'Active transfer channel not found.',
966
+ details: {
967
+ workflow_run_id: normalizedWorkflowRunId,
968
+ participant_role: normalizedParticipantRole,
969
+ },
970
+ },
971
+ };
972
+ }
973
+
774
974
  async markParticipantOnline({
775
975
  transferChannelId,
776
976
  transfer_channel_id,
@@ -802,7 +1002,7 @@ export class AIEngineClient {
802
1002
  method: 'POST',
803
1003
  body: {
804
1004
  transfer_channel_id: normalizedTransferChannelId,
805
- work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId),
1005
+ work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId) || resolved.packet_id,
806
1006
  workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId),
807
1007
  participant_role: cleanText(participant_role) || cleanText(participantRole),
808
1008
  current_phase: cleanText(current_phase) || cleanText(currentPhase),
@@ -848,7 +1048,7 @@ export class AIEngineClient {
848
1048
  method: 'POST',
849
1049
  body: {
850
1050
  transfer_channel_id: normalizedTransferChannelId,
851
- work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId),
1051
+ work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId) || resolved.packet_id,
852
1052
  workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId),
853
1053
  participant_role: cleanText(participant_role) || cleanText(participantRole),
854
1054
  current_phase: cleanText(current_phase) || cleanText(currentPhase),
@@ -967,7 +1167,7 @@ export class AIEngineClient {
967
1167
  return this._request(`/api/agent-communications/transfer-channels/${encodeURIComponent(normalizedTransferChannelId)}/connect`, {
968
1168
  method: 'POST',
969
1169
  body: {
970
- work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId),
1170
+ work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId) || resolved.packet_id,
971
1171
  workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId),
972
1172
  participant_role: cleanText(participant_role) || cleanText(participantRole),
973
1173
  expected_peer_role: cleanText(expected_peer_role) || cleanText(expectedPeerRole),
@@ -1676,6 +1876,308 @@ export class AIEngineClient {
1676
1876
  });
1677
1877
  }
1678
1878
 
1879
+ async startCoordinationPingPong({
1880
+ transferChannelId,
1881
+ transfer_channel_id,
1882
+ channelId,
1883
+ channel_id,
1884
+ participantRole,
1885
+ participant_role,
1886
+ role,
1887
+ expectedPeerRole,
1888
+ expected_peer_role,
1889
+ expectedMessageKind,
1890
+ expected_message_kind,
1891
+ workTransferPacketId,
1892
+ work_transfer_packet_id,
1893
+ packetId,
1894
+ packet_id,
1895
+ workflowRunId,
1896
+ workflow_run_id,
1897
+ proposalId,
1898
+ proposal_id,
1899
+ mode,
1900
+ bodyMarkdown,
1901
+ body_markdown,
1902
+ payload = {},
1903
+ scope = {},
1904
+ metadata = {},
1905
+ staleAfterSeconds,
1906
+ stale_after_seconds,
1907
+ } = {}) {
1908
+ const resolved = await this._resolveCoordinationChannelContext({
1909
+ transferChannelId,
1910
+ transfer_channel_id,
1911
+ channelId,
1912
+ channel_id,
1913
+ workTransferPacketId,
1914
+ work_transfer_packet_id,
1915
+ packetId,
1916
+ packet_id,
1917
+ workflowRunId,
1918
+ workflow_run_id,
1919
+ participantRole,
1920
+ participant_role,
1921
+ role,
1922
+ });
1923
+ if (resolved.stop_reason) {
1924
+ return {
1925
+ started: false,
1926
+ sent: false,
1927
+ stop_reason: resolved.stop_reason,
1928
+ };
1929
+ }
1930
+ const preflight = await this.checkCoordinationPingPongPreflight({ packageVersion: this.clientVersion });
1931
+ if (!preflight.ok) {
1932
+ return {
1933
+ started: false,
1934
+ sent: false,
1935
+ stop_reason: preflight.stop_reason,
1936
+ coordination_ping_pong_preflight: preflight,
1937
+ };
1938
+ }
1939
+ return this._request(`/api/agent-communications/transfer-channels/${encodeURIComponent(resolved.channel_id)}/coordination/ping-pong/start`, {
1940
+ method: 'POST',
1941
+ body: {
1942
+ transfer_channel_id: resolved.channel_id,
1943
+ participant_role: cleanText(participant_role) || cleanText(participantRole) || cleanText(role),
1944
+ expected_peer_role: cleanText(expected_peer_role) || cleanText(expectedPeerRole),
1945
+ expected_message_kind: cleanText(expected_message_kind) || cleanText(expectedMessageKind),
1946
+ work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId),
1947
+ workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId) || resolved.workflow_run_id,
1948
+ proposal_id: cleanText(proposal_id) || cleanText(proposalId),
1949
+ mode: cleanText(mode) || 'communication_participant',
1950
+ body_markdown: cleanText(body_markdown) || cleanText(bodyMarkdown),
1951
+ payload: isPlainObject(payload) ? payload : {},
1952
+ scope: isPlainObject(scope) ? scope : {},
1953
+ metadata: isPlainObject(metadata) ? metadata : {},
1954
+ stale_after_seconds: stale_after_seconds ?? staleAfterSeconds,
1955
+ },
1956
+ });
1957
+ }
1958
+
1959
+ async sendCoordinationPing({
1960
+ transferChannelId,
1961
+ transfer_channel_id,
1962
+ channelId,
1963
+ channel_id,
1964
+ participantRole,
1965
+ participant_role,
1966
+ role,
1967
+ expectedPeerRole,
1968
+ expected_peer_role,
1969
+ bodyMarkdown,
1970
+ body_markdown,
1971
+ payload = {},
1972
+ scope = {},
1973
+ metadata = {},
1974
+ workflowRunId,
1975
+ workflow_run_id,
1976
+ workTransferPacketId,
1977
+ work_transfer_packet_id,
1978
+ packetId,
1979
+ packet_id,
1980
+ proposalId,
1981
+ proposal_id,
1982
+ expectedMessageKind,
1983
+ expected_message_kind,
1984
+ } = {}) {
1985
+ const resolved = await this._resolveCoordinationChannelContext({
1986
+ transferChannelId,
1987
+ transfer_channel_id,
1988
+ channelId,
1989
+ channel_id,
1990
+ workTransferPacketId,
1991
+ work_transfer_packet_id,
1992
+ packetId,
1993
+ packet_id,
1994
+ workflowRunId,
1995
+ workflow_run_id,
1996
+ participantRole,
1997
+ participant_role,
1998
+ role,
1999
+ });
2000
+ if (resolved.stop_reason) {
2001
+ return {
2002
+ sent: false,
2003
+ stop_reason: resolved.stop_reason,
2004
+ };
2005
+ }
2006
+ return this._request(`/api/agent-communications/transfer-channels/${encodeURIComponent(resolved.channel_id)}/coordination/ping-pong/ping`, {
2007
+ method: 'POST',
2008
+ body: {
2009
+ transfer_channel_id: resolved.channel_id,
2010
+ participant_role: cleanText(participant_role) || cleanText(participantRole) || cleanText(role),
2011
+ expected_peer_role: cleanText(expected_peer_role) || cleanText(expectedPeerRole),
2012
+ body_markdown: cleanText(body_markdown) || cleanText(bodyMarkdown),
2013
+ payload: isPlainObject(payload) ? payload : {},
2014
+ scope: isPlainObject(scope) ? scope : {},
2015
+ metadata: isPlainObject(metadata) ? metadata : {},
2016
+ workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId) || resolved.workflow_run_id,
2017
+ work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId),
2018
+ proposal_id: cleanText(proposal_id) || cleanText(proposalId),
2019
+ expected_message_kind: cleanText(expected_message_kind) || cleanText(expectedMessageKind),
2020
+ },
2021
+ });
2022
+ }
2023
+
2024
+ async sendCoordinationPong({
2025
+ transferChannelId,
2026
+ transfer_channel_id,
2027
+ channelId,
2028
+ channel_id,
2029
+ participantRole,
2030
+ participant_role,
2031
+ role,
2032
+ expectedPeerRole,
2033
+ expected_peer_role,
2034
+ bodyMarkdown,
2035
+ body_markdown,
2036
+ payload = {},
2037
+ scope = {},
2038
+ metadata = {},
2039
+ workflowRunId,
2040
+ workflow_run_id,
2041
+ workTransferPacketId,
2042
+ work_transfer_packet_id,
2043
+ packetId,
2044
+ packet_id,
2045
+ proposalId,
2046
+ proposal_id,
2047
+ expectedMessageKind,
2048
+ expected_message_kind,
2049
+ } = {}) {
2050
+ const resolved = await this._resolveCoordinationChannelContext({
2051
+ transferChannelId,
2052
+ transfer_channel_id,
2053
+ channelId,
2054
+ channel_id,
2055
+ workTransferPacketId,
2056
+ work_transfer_packet_id,
2057
+ packetId,
2058
+ packet_id,
2059
+ workflowRunId,
2060
+ workflow_run_id,
2061
+ participantRole,
2062
+ participant_role,
2063
+ role,
2064
+ });
2065
+ if (resolved.stop_reason) {
2066
+ return {
2067
+ sent: false,
2068
+ stop_reason: resolved.stop_reason,
2069
+ };
2070
+ }
2071
+ return this._request(`/api/agent-communications/transfer-channels/${encodeURIComponent(resolved.channel_id)}/coordination/ping-pong/pong`, {
2072
+ method: 'POST',
2073
+ body: {
2074
+ transfer_channel_id: resolved.channel_id,
2075
+ participant_role: cleanText(participant_role) || cleanText(participantRole) || cleanText(role),
2076
+ expected_peer_role: cleanText(expected_peer_role) || cleanText(expectedPeerRole),
2077
+ body_markdown: cleanText(body_markdown) || cleanText(bodyMarkdown),
2078
+ payload: isPlainObject(payload) ? payload : {},
2079
+ scope: isPlainObject(scope) ? scope : {},
2080
+ metadata: isPlainObject(metadata) ? metadata : {},
2081
+ workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId) || resolved.workflow_run_id,
2082
+ work_transfer_packet_id: cleanText(work_transfer_packet_id) || cleanText(workTransferPacketId) || cleanText(packet_id) || cleanText(packetId),
2083
+ proposal_id: cleanText(proposal_id) || cleanText(proposalId),
2084
+ expected_message_kind: cleanText(expected_message_kind) || cleanText(expectedMessageKind),
2085
+ },
2086
+ });
2087
+ }
2088
+
2089
+ async getCoordinationPingPongStatus({
2090
+ transferChannelId,
2091
+ transfer_channel_id,
2092
+ channelId,
2093
+ channel_id,
2094
+ workTransferPacketId,
2095
+ work_transfer_packet_id,
2096
+ packetId,
2097
+ packet_id,
2098
+ participantRole,
2099
+ participant_role,
2100
+ role,
2101
+ workflowRunId,
2102
+ workflow_run_id,
2103
+ } = {}) {
2104
+ const resolved = await this._resolveCoordinationChannelContext({
2105
+ transferChannelId,
2106
+ transfer_channel_id,
2107
+ channelId,
2108
+ channel_id,
2109
+ workTransferPacketId,
2110
+ work_transfer_packet_id,
2111
+ packetId,
2112
+ packet_id,
2113
+ workflowRunId,
2114
+ workflow_run_id,
2115
+ participantRole,
2116
+ participant_role,
2117
+ role,
2118
+ });
2119
+ if (resolved.stop_reason) {
2120
+ return {
2121
+ verified: false,
2122
+ stop_reason: resolved.stop_reason,
2123
+ };
2124
+ }
2125
+ return this._request(`/api/agent-communications/transfer-channels/${encodeURIComponent(resolved.channel_id)}/coordination/ping-pong/status`, {
2126
+ query: {
2127
+ participant_role: cleanText(participant_role) || cleanText(participantRole) || cleanText(role),
2128
+ workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId) || resolved.workflow_run_id,
2129
+ },
2130
+ });
2131
+ }
2132
+
2133
+ async stopCoordinationPingPong({
2134
+ transferChannelId,
2135
+ transfer_channel_id,
2136
+ channelId,
2137
+ channel_id,
2138
+ workTransferPacketId,
2139
+ work_transfer_packet_id,
2140
+ packetId,
2141
+ packet_id,
2142
+ participantRole,
2143
+ participant_role,
2144
+ role,
2145
+ workflowRunId,
2146
+ workflow_run_id,
2147
+ metadata = {},
2148
+ } = {}) {
2149
+ const resolved = await this._resolveCoordinationChannelContext({
2150
+ transferChannelId,
2151
+ transfer_channel_id,
2152
+ channelId,
2153
+ channel_id,
2154
+ workTransferPacketId,
2155
+ work_transfer_packet_id,
2156
+ packetId,
2157
+ packet_id,
2158
+ workflowRunId,
2159
+ workflow_run_id,
2160
+ participantRole,
2161
+ participant_role,
2162
+ role,
2163
+ });
2164
+ if (resolved.stop_reason) {
2165
+ return {
2166
+ stopped: false,
2167
+ stop_reason: resolved.stop_reason,
2168
+ };
2169
+ }
2170
+ return this._request(`/api/agent-communications/transfer-channels/${encodeURIComponent(resolved.channel_id)}/coordination/ping-pong/stop`, {
2171
+ method: 'POST',
2172
+ body: {
2173
+ transfer_channel_id: resolved.channel_id,
2174
+ participant_role: cleanText(participant_role) || cleanText(participantRole) || cleanText(role),
2175
+ workflow_run_id: cleanText(workflow_run_id) || cleanText(workflowRunId) || resolved.workflow_run_id,
2176
+ metadata: isPlainObject(metadata) ? metadata : {},
2177
+ },
2178
+ });
2179
+ }
2180
+
1679
2181
  async verifyMessageSent({ messageId, message_id } = {}) {
1680
2182
  const normalizedMessageId = cleanText(message_id) || cleanText(messageId);
1681
2183
  if (!normalizedMessageId) throw new Error('message_id is required.');
@@ -4954,17 +5456,27 @@ export class AIEngineClient {
4954
5456
  });
4955
5457
  const contentType = response.headers.get('content-type') || '';
4956
5458
  const contentDisposition = response.headers.get('content-disposition') || '';
5459
+ const readResponseText = async () => {
5460
+ if (typeof response.text === 'function') {
5461
+ return response.text();
5462
+ }
5463
+ if (typeof response.json === 'function') {
5464
+ const payload = await response.json();
5465
+ return typeof payload === 'string' ? payload : JSON.stringify(payload);
5466
+ }
5467
+ return '';
5468
+ };
4957
5469
  if (!response.ok) {
4958
5470
  const payload = contentType.includes('application/json')
4959
5471
  ? await response.json()
4960
- : { message: await response.text() };
5472
+ : { message: await readResponseText() };
4961
5473
  const error = new Error(payload?.message || payload?.error || `Request failed with status ${response.status}.`);
4962
5474
  error.status = response.status;
4963
5475
  error.payload = payload;
4964
5476
  throw error;
4965
5477
  }
4966
5478
  return {
4967
- text: await response.text(),
5479
+ text: await readResponseText(),
4968
5480
  contentType,
4969
5481
  fileName: parseContentDispositionFilename(contentDisposition),
4970
5482
  headers: Object.fromEntries(response.headers.entries()),