slim-bindings 0.3.6__cp312-cp312-manylinux_2_34_aarch64.whl
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.
Potentially problematic release.
This version of slim-bindings might be problematic. Click here for more details.
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ._slim_bindings import ( # type: ignore[attr-defined]
|
|
8
|
+
SESSION_UNSPECIFIED,
|
|
9
|
+
PyAgentType,
|
|
10
|
+
PyService,
|
|
11
|
+
PySessionConfiguration,
|
|
12
|
+
PySessionInfo,
|
|
13
|
+
PySessionType,
|
|
14
|
+
__version__,
|
|
15
|
+
build_info,
|
|
16
|
+
build_profile,
|
|
17
|
+
connect,
|
|
18
|
+
create_pyservice,
|
|
19
|
+
create_session,
|
|
20
|
+
delete_session,
|
|
21
|
+
disconnect,
|
|
22
|
+
get_default_session_config,
|
|
23
|
+
get_session_config,
|
|
24
|
+
publish,
|
|
25
|
+
receive,
|
|
26
|
+
remove_route,
|
|
27
|
+
run_server,
|
|
28
|
+
set_default_session_config,
|
|
29
|
+
set_route,
|
|
30
|
+
set_session_config,
|
|
31
|
+
stop_server,
|
|
32
|
+
subscribe,
|
|
33
|
+
unsubscribe,
|
|
34
|
+
)
|
|
35
|
+
from ._slim_bindings import (
|
|
36
|
+
PySessionDirection as PySessionDirection,
|
|
37
|
+
)
|
|
38
|
+
from ._slim_bindings import (
|
|
39
|
+
init_tracing as init_tracing,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_version():
|
|
44
|
+
"""
|
|
45
|
+
Get the version of the SLIM bindings.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
str: The version of the SLIM bindings.
|
|
49
|
+
"""
|
|
50
|
+
return __version__
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_build_profile():
|
|
54
|
+
"""
|
|
55
|
+
Get the build profile of the SLIM bindings.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: The build profile of the SLIM bindings.
|
|
59
|
+
"""
|
|
60
|
+
return build_profile
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_build_info():
|
|
64
|
+
"""
|
|
65
|
+
Get the build information of the SLIM bindings.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str: The build information of the SLIM bindings.
|
|
69
|
+
"""
|
|
70
|
+
return build_info
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SLIMTimeoutError(TimeoutError):
|
|
74
|
+
"""
|
|
75
|
+
Exception raised for SLIM timeout errors.
|
|
76
|
+
|
|
77
|
+
This exception is raised when an operation in an SLIM session times out.
|
|
78
|
+
It encapsulates detailed information about the timeout event, including the
|
|
79
|
+
ID of the message that caused the timeout and the session identifier. An
|
|
80
|
+
optional underlying exception can also be provided to offer additional context.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
message_id (int): The identifier associated with the message triggering the timeout.
|
|
84
|
+
session_id (int): The identifier of the session where the timeout occurred.
|
|
85
|
+
message (str): A brief description of the timeout error.
|
|
86
|
+
original_exception (Exception, optional): The underlying exception that caused the timeout, if any.
|
|
87
|
+
|
|
88
|
+
The string representation of the exception (via __str__) returns a full message that
|
|
89
|
+
includes the custom message, session ID, and message ID, as well as details of the
|
|
90
|
+
original exception (if present). This provides a richer context when the exception is logged
|
|
91
|
+
or printed.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
message_id: int,
|
|
97
|
+
session_id: int,
|
|
98
|
+
message: str = "SLIM timeout error",
|
|
99
|
+
original_exception: Optional[Exception] = None,
|
|
100
|
+
):
|
|
101
|
+
self.message_id = message_id
|
|
102
|
+
self.session_id = session_id
|
|
103
|
+
self.message = message
|
|
104
|
+
self.original_exception = original_exception
|
|
105
|
+
full_message = f"{message} for session {session_id} and message {message_id}"
|
|
106
|
+
if original_exception:
|
|
107
|
+
full_message = f"{full_message}. Caused by: {original_exception!r}"
|
|
108
|
+
super().__init__(full_message)
|
|
109
|
+
|
|
110
|
+
def __str__(self):
|
|
111
|
+
return self.args[0]
|
|
112
|
+
|
|
113
|
+
def __repr__(self):
|
|
114
|
+
return (
|
|
115
|
+
f"{self.__class__.__name__}(session_id={self.session_id!r}, "
|
|
116
|
+
f"message_id={self.message_id!r}, "
|
|
117
|
+
f"message={self.message!r}, original_exception={self.original_exception!r})"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class Slim:
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
svc: PyService,
|
|
125
|
+
organization: str,
|
|
126
|
+
namespace: str,
|
|
127
|
+
agent: str,
|
|
128
|
+
):
|
|
129
|
+
"""
|
|
130
|
+
Initialize a new SLIM instance. A SLIM instance is associated with a single
|
|
131
|
+
local agent. The agent is identified by its organization, namespace, and name.
|
|
132
|
+
The agent ID is determined by the provided service (svc).
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
svc (PyService): The Python service instance for SLIM.
|
|
136
|
+
organization (str): The organization of the agent.
|
|
137
|
+
namespace (str): The namespace of the agent.
|
|
138
|
+
agent (str): The name of the agent.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
# Initialize service
|
|
142
|
+
self.svc = svc
|
|
143
|
+
|
|
144
|
+
# Create sessions map
|
|
145
|
+
self.sessions: dict[int, tuple[Optional[PySessionInfo], asyncio.Queue]] = {
|
|
146
|
+
SESSION_UNSPECIFIED: (None, asyncio.Queue()),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Save local names
|
|
150
|
+
self.local_name = PyAgentType(organization, namespace, agent)
|
|
151
|
+
self.local_id = self.svc.id
|
|
152
|
+
|
|
153
|
+
# Create connection ID map
|
|
154
|
+
self.conn_ids: dict[str, int] = {}
|
|
155
|
+
|
|
156
|
+
async def __aenter__(self):
|
|
157
|
+
"""
|
|
158
|
+
Start the receiver loop in the background.
|
|
159
|
+
This function is called when the SLIM instance is used in a
|
|
160
|
+
context manager (with statement).
|
|
161
|
+
It will start the receiver loop in the background and return the
|
|
162
|
+
SLIM instance.
|
|
163
|
+
Args:
|
|
164
|
+
None
|
|
165
|
+
Returns:
|
|
166
|
+
Slim: The SLIM instance.
|
|
167
|
+
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
# Run receiver loop in the background
|
|
171
|
+
self.task = asyncio.create_task(self._receive_loop())
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
175
|
+
"""
|
|
176
|
+
Stop the receiver loop.
|
|
177
|
+
This function is called when the Slim instance is used in a
|
|
178
|
+
context manager (with statement).
|
|
179
|
+
It will stop the receiver loop and wait for it to finish.
|
|
180
|
+
Args:
|
|
181
|
+
exc_type: The exception type.
|
|
182
|
+
exc_value: The exception value.
|
|
183
|
+
traceback: The traceback object.
|
|
184
|
+
Returns:
|
|
185
|
+
None
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
# Cancel the receiver loop task
|
|
189
|
+
self.task.cancel()
|
|
190
|
+
|
|
191
|
+
# Wait for the task to finish
|
|
192
|
+
try:
|
|
193
|
+
await self.task
|
|
194
|
+
except asyncio.CancelledError:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
async def new(
|
|
199
|
+
cls,
|
|
200
|
+
organization: str,
|
|
201
|
+
namespace: str,
|
|
202
|
+
agent: str,
|
|
203
|
+
agent_id: Optional[int] = None,
|
|
204
|
+
) -> "Slim":
|
|
205
|
+
"""
|
|
206
|
+
Create a new SLIM instance. A SLIM instamce is associated to one single
|
|
207
|
+
local agent. The agent is identified by its organization, namespace and name.
|
|
208
|
+
The agent ID is optional. If not provided, the agent will be created with a new ID.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
organization (str): The organization of the agent.
|
|
212
|
+
namespace (str): The namespace of the agent.
|
|
213
|
+
agent (str): The name of the agent.
|
|
214
|
+
agent_id (int): The ID of the agent. If not provided, a new ID will be created.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Slim: A new SLIM instance
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
return cls(
|
|
221
|
+
await create_pyservice(organization, namespace, agent, agent_id),
|
|
222
|
+
organization,
|
|
223
|
+
namespace,
|
|
224
|
+
agent,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def get_agent_id(self) -> int:
|
|
228
|
+
"""
|
|
229
|
+
Get the ID of the agent.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
None
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
int: The ID of the agent.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
return self.svc.id
|
|
239
|
+
|
|
240
|
+
async def create_session(
|
|
241
|
+
self,
|
|
242
|
+
session_config: PySessionConfiguration,
|
|
243
|
+
queue_size: int = 0,
|
|
244
|
+
) -> PySessionInfo:
|
|
245
|
+
"""
|
|
246
|
+
Create a new streaming session.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
session_config (PySessionConfiguration): The session configuration.
|
|
250
|
+
queue_size (int): The size of the queue for the session.
|
|
251
|
+
If 0, the queue will be unbounded.
|
|
252
|
+
If a positive integer, the queue will be bounded to that size.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
ID of the session
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
session = await create_session(self.svc, session_config)
|
|
259
|
+
self.sessions[session.id] = (session, asyncio.Queue(queue_size))
|
|
260
|
+
return session
|
|
261
|
+
|
|
262
|
+
async def delete_session(self, session_id: int):
|
|
263
|
+
"""
|
|
264
|
+
Delete a session.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
session_id (int): The ID of the session to delete.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
None
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
ValueError: If the session ID is not found.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
# Check if the session ID is in the sessions map
|
|
277
|
+
if session_id not in self.sessions:
|
|
278
|
+
raise ValueError(f"session not found: {session_id}")
|
|
279
|
+
|
|
280
|
+
# Remove the session from the map
|
|
281
|
+
del self.sessions[session_id]
|
|
282
|
+
|
|
283
|
+
# Remove the session from SLIM
|
|
284
|
+
await delete_session(self.svc, session_id)
|
|
285
|
+
|
|
286
|
+
async def set_session_config(
|
|
287
|
+
self,
|
|
288
|
+
session_id: int,
|
|
289
|
+
session_config: PySessionConfiguration,
|
|
290
|
+
):
|
|
291
|
+
"""
|
|
292
|
+
Set the session configuration for a specific session.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
session_id (int): The ID of the session.
|
|
296
|
+
session_config (PySessionConfiguration): The new configuration for the session.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
None
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
ValueError: If the session ID is not found.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
# Check if the session ID is in the sessions map
|
|
306
|
+
if session_id not in self.sessions:
|
|
307
|
+
raise ValueError(f"session not found: {session_id}")
|
|
308
|
+
|
|
309
|
+
# Set the session configuration
|
|
310
|
+
await set_session_config(self.svc, session_id, session_config)
|
|
311
|
+
|
|
312
|
+
async def get_session_config(
|
|
313
|
+
self,
|
|
314
|
+
session_id: int,
|
|
315
|
+
) -> PySessionConfiguration:
|
|
316
|
+
"""
|
|
317
|
+
Get the session configuration for a specific session.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
session_id (int): The ID of the session.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
PySessionConfiguration: The configuration of the session.
|
|
324
|
+
|
|
325
|
+
Raises:
|
|
326
|
+
ValueError: If the session ID is not found.
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
# Check if the session ID is in the sessions map
|
|
330
|
+
if session_id not in self.sessions:
|
|
331
|
+
raise ValueError(f"session not found: {session_id}")
|
|
332
|
+
|
|
333
|
+
# Get the session configuration
|
|
334
|
+
return await get_session_config(self.svc, session_id)
|
|
335
|
+
|
|
336
|
+
async def set_default_session_config(
|
|
337
|
+
self,
|
|
338
|
+
session_config: PySessionConfiguration,
|
|
339
|
+
):
|
|
340
|
+
"""
|
|
341
|
+
Set the default session configuration.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
session_config (PySessionConfiguration): The new default session configuration.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
None
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
await set_default_session_config(self.svc, session_config)
|
|
351
|
+
|
|
352
|
+
async def get_default_session_config(
|
|
353
|
+
self,
|
|
354
|
+
session_type: PySessionType,
|
|
355
|
+
) -> PySessionConfiguration:
|
|
356
|
+
"""
|
|
357
|
+
Get the default session configuration.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
session_id (int): The ID of the session.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
PySessionConfiguration: The default configuration of the session.
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
return await get_default_session_config(self.svc, session_type)
|
|
367
|
+
|
|
368
|
+
async def run_server(self, config: dict):
|
|
369
|
+
"""
|
|
370
|
+
Start the server part of the SLIM service. The server will be started only
|
|
371
|
+
if its configuration is set. Otherwise, it will raise an error.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
None
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
None
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
await run_server(self.svc, config)
|
|
381
|
+
|
|
382
|
+
async def stop_server(self, endpoint: str):
|
|
383
|
+
"""
|
|
384
|
+
Stop the server part of the SLIM service.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
None
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
None
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
await stop_server(self.svc, endpoint)
|
|
394
|
+
|
|
395
|
+
async def connect(self, client_config: dict) -> int:
|
|
396
|
+
"""
|
|
397
|
+
Connect to a remote SLIM service.
|
|
398
|
+
This function will block until the connection is established.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
None
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
int: The connection ID.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
conn_id = await connect(
|
|
408
|
+
self.svc,
|
|
409
|
+
client_config,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Save the connection ID
|
|
413
|
+
self.conn_ids[client_config["endpoint"]] = conn_id
|
|
414
|
+
|
|
415
|
+
# For the moment we manage one connection only
|
|
416
|
+
self.conn_id = conn_id
|
|
417
|
+
|
|
418
|
+
# Subscribe to the local name
|
|
419
|
+
await subscribe(self.svc, conn_id, self.local_name, self.local_id)
|
|
420
|
+
|
|
421
|
+
# return the connection ID
|
|
422
|
+
return conn_id
|
|
423
|
+
|
|
424
|
+
async def disconnect(self, endpoint: str):
|
|
425
|
+
"""
|
|
426
|
+
Disconnect from a remote SLIM service.
|
|
427
|
+
This function will block until the disconnection is complete.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
None
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
None
|
|
434
|
+
|
|
435
|
+
"""
|
|
436
|
+
conn = self.conn_ids[endpoint]
|
|
437
|
+
await disconnect(self.svc, conn)
|
|
438
|
+
|
|
439
|
+
async def set_route(
|
|
440
|
+
self,
|
|
441
|
+
organization: str,
|
|
442
|
+
namespace: str,
|
|
443
|
+
agent: str,
|
|
444
|
+
id: Optional[int] = None,
|
|
445
|
+
):
|
|
446
|
+
"""
|
|
447
|
+
Set route for outgoing messages via the connected SLIM instance.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
organization (str): The organization of the agent.
|
|
451
|
+
namespace (str): The namespace of the agent.
|
|
452
|
+
agent (str): The name of the agent.
|
|
453
|
+
id (int): Optional ID of the agent.
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
None
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
name = PyAgentType(organization, namespace, agent)
|
|
460
|
+
await set_route(self.svc, self.conn_id, name, id)
|
|
461
|
+
|
|
462
|
+
async def remove_route(
|
|
463
|
+
self, organization: str, namespace: str, agent: str, id: Optional[int] = None
|
|
464
|
+
):
|
|
465
|
+
"""
|
|
466
|
+
Remove route for outgoing messages via the connected SLIM instance.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
organization (str): The organization of the agent.
|
|
470
|
+
namespace (str): The namespace of the agent.
|
|
471
|
+
agent (str): The name of the agent.
|
|
472
|
+
id (int): Optional ID of the agent.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
None
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
name = PyAgentType(organization, namespace, agent)
|
|
479
|
+
await remove_route(self.svc, self.conn_id, name, id)
|
|
480
|
+
|
|
481
|
+
async def subscribe(
|
|
482
|
+
self, organization: str, namespace: str, agent: str, id: Optional[int] = None
|
|
483
|
+
):
|
|
484
|
+
"""
|
|
485
|
+
Subscribe to receive messages for the given agent.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
organization (str): The organization of the agent.
|
|
489
|
+
namespace (str): The namespace of the agent.
|
|
490
|
+
agent (str): The name of the agent.
|
|
491
|
+
id (int): Optional ID of the agent.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
None
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
sub = PyAgentType(organization, namespace, agent)
|
|
498
|
+
await subscribe(self.svc, self.conn_id, sub, id)
|
|
499
|
+
|
|
500
|
+
async def unsubscribe(
|
|
501
|
+
self, organization: str, namespace: str, agent: str, id: Optional[int] = None
|
|
502
|
+
):
|
|
503
|
+
"""
|
|
504
|
+
Unsubscribe from receiving messages for the given agent.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
organization (str): The organization of the agent.
|
|
508
|
+
namespace (str): The namespace of the agent.
|
|
509
|
+
agent (str): The name of the agent.
|
|
510
|
+
id (int): Optional ID of the agent.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
None
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
unsub = PyAgentType(organization, namespace, agent)
|
|
517
|
+
await unsubscribe(self.svc, self.conn_id, unsub, id)
|
|
518
|
+
|
|
519
|
+
async def publish(
|
|
520
|
+
self,
|
|
521
|
+
session: PySessionInfo,
|
|
522
|
+
msg: bytes,
|
|
523
|
+
organization: str,
|
|
524
|
+
namespace: str,
|
|
525
|
+
agent: str,
|
|
526
|
+
agent_id: Optional[int] = None,
|
|
527
|
+
):
|
|
528
|
+
"""
|
|
529
|
+
Publish a message to an agent via normal matching in subscription table.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
session (PySessionInfo): The session information.
|
|
533
|
+
msg (str): The message to publish.
|
|
534
|
+
organization (str): The organization of the agent.
|
|
535
|
+
namespace (str): The namespace of the agent.
|
|
536
|
+
agent (str): The name of the agent.
|
|
537
|
+
agent_id (int): Optional ID of the agent.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
None
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
# Make sure the sessions exists
|
|
544
|
+
if session.id not in self.sessions:
|
|
545
|
+
raise Exception("session not found", session.id)
|
|
546
|
+
|
|
547
|
+
dest = PyAgentType(organization, namespace, agent)
|
|
548
|
+
await publish(self.svc, session, 1, msg, dest, agent_id)
|
|
549
|
+
|
|
550
|
+
async def request_reply(
|
|
551
|
+
self,
|
|
552
|
+
session: PySessionInfo,
|
|
553
|
+
msg: bytes,
|
|
554
|
+
organization: str,
|
|
555
|
+
namespace: str,
|
|
556
|
+
agent: str,
|
|
557
|
+
agent_id: Optional[int] = None,
|
|
558
|
+
) -> tuple[PySessionInfo, Optional[bytes]]:
|
|
559
|
+
"""
|
|
560
|
+
Publish a message and wait for the first response.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
msg (str): The message to publish.
|
|
564
|
+
session (PySessionInfo): The session information.
|
|
565
|
+
organization (str): The organization of the agent.
|
|
566
|
+
namespace (str): The namespace of the agent.
|
|
567
|
+
agent (str): The name of the agent.
|
|
568
|
+
agent_id (int): Optional ID of the agent.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
tuple: The PySessionInfo and the message.
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
# Make sure the sessions exists
|
|
575
|
+
if session.id not in self.sessions:
|
|
576
|
+
raise Exception("Session ID not found")
|
|
577
|
+
|
|
578
|
+
dest = PyAgentType(organization, namespace, agent)
|
|
579
|
+
await publish(self.svc, session, 1, msg, dest, agent_id)
|
|
580
|
+
|
|
581
|
+
# Wait for a reply in the corresponding session queue
|
|
582
|
+
session_info, message = await self.receive(session.id)
|
|
583
|
+
|
|
584
|
+
return session_info, message
|
|
585
|
+
|
|
586
|
+
async def publish_to(self, session, msg):
|
|
587
|
+
"""
|
|
588
|
+
Publish a message back to the agent that sent it.
|
|
589
|
+
The information regarding the source agent is stored in the session.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
session (PySessionInfo): The session information.
|
|
593
|
+
msg (str): The message to publish.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
None
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
await publish(self.svc, session, 1, msg)
|
|
600
|
+
|
|
601
|
+
async def receive(
|
|
602
|
+
self, session: Optional[int] = None
|
|
603
|
+
) -> tuple[PySessionInfo, Optional[bytes]]:
|
|
604
|
+
"""
|
|
605
|
+
Receive a message , optionally waiting for a specific session ID.
|
|
606
|
+
If session ID is None, it will wait for new sessions to be created.
|
|
607
|
+
This function will block until a message is received (if the session id is specified)
|
|
608
|
+
or until a new session is created (if the session id is None).
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
session (int): The session ID. If None, the function will wait for any message.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
tuple: The PySessionInfo and the message.
|
|
615
|
+
|
|
616
|
+
Raise:
|
|
617
|
+
Exception: If the session ID is not found.
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
# If session is None, wait for any message
|
|
621
|
+
if session is None:
|
|
622
|
+
return await self.sessions[SESSION_UNSPECIFIED][1].get()
|
|
623
|
+
else:
|
|
624
|
+
# Check if the session ID is in the sessions map
|
|
625
|
+
if session not in self.sessions:
|
|
626
|
+
raise Exception("Session ID not found")
|
|
627
|
+
|
|
628
|
+
# Get the queue for the session
|
|
629
|
+
queue = self.sessions[session][1]
|
|
630
|
+
|
|
631
|
+
# Wait for a message from the queue
|
|
632
|
+
ret = await queue.get()
|
|
633
|
+
|
|
634
|
+
# If message is am exception, raise it
|
|
635
|
+
if isinstance(ret, Exception):
|
|
636
|
+
raise ret
|
|
637
|
+
|
|
638
|
+
# Otherwise, return the message
|
|
639
|
+
return ret
|
|
640
|
+
|
|
641
|
+
async def _receive_loop(self) -> None:
|
|
642
|
+
"""
|
|
643
|
+
Receive messages in a loop running in the background.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
None
|
|
647
|
+
"""
|
|
648
|
+
|
|
649
|
+
while True:
|
|
650
|
+
try:
|
|
651
|
+
session_info_msg = await receive(self.svc)
|
|
652
|
+
|
|
653
|
+
id: int = session_info_msg[0].id
|
|
654
|
+
|
|
655
|
+
# Check if the session ID is in the sessions map
|
|
656
|
+
if id not in self.sessions:
|
|
657
|
+
# Create the entry in the sessions map
|
|
658
|
+
self.sessions[id] = (
|
|
659
|
+
session_info_msg,
|
|
660
|
+
asyncio.Queue(),
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
# Also add a queue for the session
|
|
664
|
+
await self.sessions[SESSION_UNSPECIFIED][1].put(session_info_msg)
|
|
665
|
+
|
|
666
|
+
await self.sessions[id][1].put(session_info_msg)
|
|
667
|
+
except asyncio.CancelledError:
|
|
668
|
+
raise
|
|
669
|
+
except Exception as e:
|
|
670
|
+
print("Error receiving message:", e)
|
|
671
|
+
# Try to parse the error message
|
|
672
|
+
try:
|
|
673
|
+
message_id, session_id, reason = parse_error_message(str(e))
|
|
674
|
+
|
|
675
|
+
# figure out what exception to raise based on the reason
|
|
676
|
+
if reason == "timeout":
|
|
677
|
+
err = SLIMTimeoutError(message_id, session_id)
|
|
678
|
+
else:
|
|
679
|
+
# we don't know the reason, just raise the original exception
|
|
680
|
+
raise e
|
|
681
|
+
|
|
682
|
+
if session_id in self.sessions:
|
|
683
|
+
await self.sessions[session_id][1].put(
|
|
684
|
+
err,
|
|
685
|
+
)
|
|
686
|
+
else:
|
|
687
|
+
print(self.sessions.keys())
|
|
688
|
+
except Exception:
|
|
689
|
+
raise e
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def parse_error_message(error_message):
|
|
693
|
+
import re
|
|
694
|
+
|
|
695
|
+
# Define the regular expression pattern
|
|
696
|
+
pattern = r"message=(\d+) session=(\d+): (.+)"
|
|
697
|
+
|
|
698
|
+
# Use re.search to find the pattern in the string
|
|
699
|
+
match = re.search(pattern, error_message)
|
|
700
|
+
|
|
701
|
+
if match:
|
|
702
|
+
# Extract message_id, session_id, and reason from the match groups
|
|
703
|
+
message_id = match.group(1)
|
|
704
|
+
session_id = match.group(2)
|
|
705
|
+
reason = match.group(3)
|
|
706
|
+
return int(message_id), int(session_id), reason
|
|
707
|
+
else:
|
|
708
|
+
raise ValueError("error message does not match the expected format.")
|
|
Binary file
|