meshagent-agents 0.0.22__tar.gz → 0.0.24__tar.gz
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 meshagent-agents might be problematic. Click here for more details.
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/CHANGELOG.md +6 -0
- {meshagent_agents-0.0.22/meshagent_agents.egg-info → meshagent_agents-0.0.24}/PKG-INFO +9 -6
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/listener.py +1 -1
- meshagent_agents-0.0.24/meshagent/agents/mail.py +337 -0
- meshagent_agents-0.0.24/meshagent/agents/version.py +1 -0
- meshagent_agents-0.0.24/meshagent/agents/worker.py +105 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24/meshagent_agents.egg-info}/PKG-INFO +9 -6
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent_agents.egg-info/SOURCES.txt +1 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent_agents.egg-info/requires.txt +9 -5
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/pyproject.toml +11 -6
- meshagent_agents-0.0.22/meshagent/agents/version.py +0 -1
- meshagent_agents-0.0.22/meshagent/agents/worker.py +0 -114
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/LICENSE +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/MANIFEST.in +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/README.md +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/__init__.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/adapter.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/agent.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/chat.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/context.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/development.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/hosting.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/indexer.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/planning.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/prompt.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/pydantic.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/__init__.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/document.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/gallery.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/presentation.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/schema.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/super_editor_document.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/single_shot_writer.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/thread_schema.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/utils.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/writer.py +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent_agents.egg-info/dependency_links.txt +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent_agents.egg-info/top_level.txt +0 -0
- {meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-agents
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.24
|
|
4
4
|
Summary: Agent Building Blocks for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -12,18 +12,21 @@ License-File: LICENSE
|
|
|
12
12
|
Requires-Dist: pyjwt~=2.10.1
|
|
13
13
|
Requires-Dist: pytest~=8.3.5
|
|
14
14
|
Requires-Dist: pytest-asyncio~=0.26.0
|
|
15
|
-
Requires-Dist: meshagent-api~=0.0.
|
|
16
|
-
Requires-Dist: meshagent-tools~=0.0.
|
|
17
|
-
Requires-Dist: meshagent-openai~=0.0.
|
|
15
|
+
Requires-Dist: meshagent-api~=0.0.24
|
|
16
|
+
Requires-Dist: meshagent-tools~=0.0.24
|
|
17
|
+
Requires-Dist: meshagent-openai~=0.0.24
|
|
18
18
|
Requires-Dist: pydantic~=2.11.1
|
|
19
19
|
Requires-Dist: pydantic-ai~=0.0.48
|
|
20
20
|
Provides-Extra: all
|
|
21
|
-
Requires-Dist: meshagent-api[all]~=0.0.
|
|
21
|
+
Requires-Dist: meshagent-api[all]~=0.0.24; extra == "all"
|
|
22
22
|
Requires-Dist: chonkie~=0.5.1; extra == "all"
|
|
23
23
|
Requires-Dist: chonkie[semantic]~=0.5.1; extra == "all"
|
|
24
24
|
Requires-Dist: chonkie[openai]~=0.5.1; extra == "all"
|
|
25
|
+
Requires-Dist: aiosmtplib~=4.0.1; extra == "all"
|
|
25
26
|
Provides-Extra: sync
|
|
26
|
-
Requires-Dist: meshagent-api[sync]~=0.0.
|
|
27
|
+
Requires-Dist: meshagent-api[sync]~=0.0.24; extra == "sync"
|
|
28
|
+
Provides-Extra: mail
|
|
29
|
+
Requires-Dist: aiosmtplib~=4.0.1; extra == "mail"
|
|
27
30
|
Provides-Extra: rag
|
|
28
31
|
Requires-Dist: chonkie~=0.5.1; extra == "rag"
|
|
29
32
|
Requires-Dist: chonkie[semantic]~=0.5.1; extra == "rag"
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
from meshagent.agents.worker import Worker
|
|
2
|
+
from meshagent.tools import Tool, Toolkit
|
|
3
|
+
from meshagent.api.room_server_client import TextDataType
|
|
4
|
+
from email import message_from_bytes
|
|
5
|
+
from email.message import EmailMessage
|
|
6
|
+
from email.policy import default
|
|
7
|
+
import email.utils
|
|
8
|
+
from meshagent.api import ParticipantToken
|
|
9
|
+
|
|
10
|
+
from meshagent.api import RoomClient
|
|
11
|
+
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
|
|
14
|
+
import secrets
|
|
15
|
+
|
|
16
|
+
from typing import Literal, Optional
|
|
17
|
+
import json
|
|
18
|
+
|
|
19
|
+
import uuid
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import aiosmtplib
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("mail")
|
|
26
|
+
|
|
27
|
+
type MessageRole = Literal["user", "agent"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_reply_email_message(*, message: dict, from_address: str, body: str) -> EmailMessage:
|
|
32
|
+
|
|
33
|
+
subject : str = message.get("subject")
|
|
34
|
+
|
|
35
|
+
if not subject.startswith("RE:"):
|
|
36
|
+
subject = "RE: " + subject
|
|
37
|
+
|
|
38
|
+
_, addr = email.utils.parseaddr(from_address)
|
|
39
|
+
domain = addr.split("@")[-1].lower()
|
|
40
|
+
id = f"<{uuid.uuid4()}@{domain}>"
|
|
41
|
+
|
|
42
|
+
msg = EmailMessage()
|
|
43
|
+
msg["Message-ID"] = id
|
|
44
|
+
msg["Subject"] = subject
|
|
45
|
+
msg["From"] = from_address
|
|
46
|
+
msg["To"] = message.get("reply_to")
|
|
47
|
+
msg["In-Reply-To"] = message.get("id")
|
|
48
|
+
msg.set_content(body)
|
|
49
|
+
|
|
50
|
+
return msg
|
|
51
|
+
|
|
52
|
+
def message_to_json(*, message: EmailMessage, role: MessageRole):
|
|
53
|
+
|
|
54
|
+
body_part = message.get_body(('plain', 'html')) # returns the “best” part :contentReference[oaicite:0]{index=0}
|
|
55
|
+
if body_part:
|
|
56
|
+
body = body_part.get_content()
|
|
57
|
+
else: # simple, non-MIME message
|
|
58
|
+
body = message.get_content()
|
|
59
|
+
|
|
60
|
+
id = message.get("Message-ID")
|
|
61
|
+
if id == None:
|
|
62
|
+
mfrom = message.get("From")
|
|
63
|
+
_, addr = email.utils.parseaddr(mfrom)
|
|
64
|
+
domain = addr.split("@")[-1].lower()
|
|
65
|
+
id = f"{uuid.uuid4()}@{domain}"
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"id" : id,
|
|
69
|
+
"in_reply_to" : message.get("In-Reply-To"),
|
|
70
|
+
"reply_to" : message.get("Reply-To", message.get("From")),
|
|
71
|
+
"references" : message.get("References"),
|
|
72
|
+
"from" : message.get("From"),
|
|
73
|
+
"to" : message.get_all("To"),
|
|
74
|
+
"subject" : message.get("Subject"),
|
|
75
|
+
"body" : body,
|
|
76
|
+
"attachments" : [],
|
|
77
|
+
"role" : role
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async def load_message(*, room: RoomClient, message_id: str) -> dict | None:
|
|
81
|
+
|
|
82
|
+
messages = await room.database.search(
|
|
83
|
+
table="emails",
|
|
84
|
+
where={
|
|
85
|
+
"id" : message_id
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if len(messages) == 0:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
return json.loads(messages[0]["json"])
|
|
92
|
+
|
|
93
|
+
async def save_email_message(*, room: RoomClient, content: bytes, role: MessageRole) -> dict:
|
|
94
|
+
|
|
95
|
+
message = message_from_bytes(content, policy=default)
|
|
96
|
+
|
|
97
|
+
now = datetime.now(timezone.utc)
|
|
98
|
+
|
|
99
|
+
folder_path = now.strftime('%Y/%m/%d') +"/" + now.strftime('%H/%M/%S') + '/' + secrets.token_hex(3)
|
|
100
|
+
|
|
101
|
+
queued_message = message_to_json(message=message, role=role)
|
|
102
|
+
message_id = queued_message["id"]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
queued_message["role"] = role
|
|
106
|
+
|
|
107
|
+
queued_message["path"] = f".emails/{message_id}/message.json"
|
|
108
|
+
|
|
109
|
+
for part in message.iter_attachments(): # ↔ only the “real” attachments :contentReference[oaicite:0]{index=0}
|
|
110
|
+
fname = part.get_filename() or "attachment.bin" # RFC 2183 filename, if any :contentReference[oaicite:1]{index=1}
|
|
111
|
+
ctype = part.get_content_type() # e.g. image/png, application/pdf
|
|
112
|
+
# get_content() auto-decodes transfer-encodings; returns
|
|
113
|
+
# *str* for text/*, *bytes* for everything else :contentReference[oaicite:2]{index=2}
|
|
114
|
+
data = part.get_content()
|
|
115
|
+
|
|
116
|
+
# make sure we write binary data
|
|
117
|
+
bin_data = data.encode(part.get_content_charset('utf-8')) if isinstance(data, str) else data
|
|
118
|
+
|
|
119
|
+
path = f".emails/{folder_path}/attachments/{fname}"
|
|
120
|
+
handle = await room.storage.open(path=path)
|
|
121
|
+
try:
|
|
122
|
+
logger.info(f"writing content to {path}")
|
|
123
|
+
await room.storage.write(handle=handle, data=bin_data)
|
|
124
|
+
finally:
|
|
125
|
+
await room.storage.close(handle=handle)
|
|
126
|
+
|
|
127
|
+
queued_message["attachments"].append(path)
|
|
128
|
+
|
|
129
|
+
logger.info(f"received mail, {queued_message}")
|
|
130
|
+
|
|
131
|
+
# write email
|
|
132
|
+
path = f".emails/{folder_path}/message.eml"
|
|
133
|
+
handle = await room.storage.open(path=path)
|
|
134
|
+
try:
|
|
135
|
+
logger.info(f"writing source message.eml to {path}")
|
|
136
|
+
await room.storage.write(handle=handle, data=content)
|
|
137
|
+
finally:
|
|
138
|
+
await room.storage.close(handle=handle)
|
|
139
|
+
|
|
140
|
+
path = f".emails/{folder_path}/message.json"
|
|
141
|
+
handle = await room.storage.open(path=path)
|
|
142
|
+
try:
|
|
143
|
+
logger.info(f"writing source message.json to {path}")
|
|
144
|
+
await room.storage.write(handle=handle, data=json.dumps(queued_message, indent=4).encode("utf-8"))
|
|
145
|
+
finally:
|
|
146
|
+
await room.storage.close(handle=handle)
|
|
147
|
+
|
|
148
|
+
# create email table if it doesn't exist
|
|
149
|
+
tables = await room.database.list_tables()
|
|
150
|
+
|
|
151
|
+
if "emails" not in tables:
|
|
152
|
+
|
|
153
|
+
await room.database.create_table_with_schema(
|
|
154
|
+
name="emails",
|
|
155
|
+
schema={
|
|
156
|
+
"id" : TextDataType(),
|
|
157
|
+
"json" : TextDataType()
|
|
158
|
+
},
|
|
159
|
+
mode="create_if_not_exists"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
await room.database.create_scalar_index(table="emails", column="id")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
await room.database.insert(table="emails", records=[ { "id" : message_id, "json" : json.dumps(queued_message) } ])
|
|
166
|
+
|
|
167
|
+
return queued_message
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def load_thread(*, room: RoomClient, message: dict, thread: list[dict]):
|
|
171
|
+
|
|
172
|
+
in_reply_to = message.get("in_reply_to", None)
|
|
173
|
+
if in_reply_to != None:
|
|
174
|
+
|
|
175
|
+
source = await load_message(room=room, message_id=in_reply_to)
|
|
176
|
+
|
|
177
|
+
if source != None:
|
|
178
|
+
|
|
179
|
+
thread.insert(0, source)
|
|
180
|
+
|
|
181
|
+
await load_thread(room=room, message=source, thread=thread)
|
|
182
|
+
|
|
183
|
+
else:
|
|
184
|
+
|
|
185
|
+
logger.warning(f"message not found {in_reply_to}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SmtpConfiguration:
|
|
189
|
+
def __init__(self, username: Optional[str] = None, password: Optional[str] = None, port: Optional[int] = None, hostname: Optional[str] = None):
|
|
190
|
+
if username == None:
|
|
191
|
+
username = os.getenv("SMTP_USERNAME")
|
|
192
|
+
|
|
193
|
+
if password == None:
|
|
194
|
+
password = os.getenv("SMTP_PASSWORD")
|
|
195
|
+
|
|
196
|
+
if port == None:
|
|
197
|
+
port = int(os.getenv("SMTP_PORT", "587"))
|
|
198
|
+
|
|
199
|
+
if hostname == None:
|
|
200
|
+
hostname = os.getenv("SMTP_HOSTNAME")
|
|
201
|
+
|
|
202
|
+
self.username = username
|
|
203
|
+
self.password = password
|
|
204
|
+
self.port = port
|
|
205
|
+
self.hostname = hostname
|
|
206
|
+
|
|
207
|
+
class MailWorker(Worker):
|
|
208
|
+
|
|
209
|
+
def __init__(self, *,
|
|
210
|
+
queue: str = "email",
|
|
211
|
+
name,
|
|
212
|
+
title=None,
|
|
213
|
+
description=None,
|
|
214
|
+
requires=None,
|
|
215
|
+
llm_adapter,
|
|
216
|
+
tool_adapter = None,
|
|
217
|
+
toolkits = None,
|
|
218
|
+
rules = None,
|
|
219
|
+
domain: str = os.getenv("MESHAGENT_MAIL_DOMAIN", "mail.meshagent.com"),
|
|
220
|
+
smtp: Optional[SmtpConfiguration] = None):
|
|
221
|
+
|
|
222
|
+
if smtp == None:
|
|
223
|
+
smtp = SmtpConfiguration()
|
|
224
|
+
|
|
225
|
+
self._domain = domain
|
|
226
|
+
self._smtp = smtp
|
|
227
|
+
super().__init__(queue=queue, name=name, title=title, description=description, requires=requires, llm_adapter=llm_adapter, tool_adapter=tool_adapter, toolkits=toolkits, rules=rules)
|
|
228
|
+
|
|
229
|
+
async def start(self, *, room):
|
|
230
|
+
|
|
231
|
+
await super().start(room=room)
|
|
232
|
+
|
|
233
|
+
token = ParticipantToken.from_jwt(room.protocol.token, validate=False)
|
|
234
|
+
self._email_address = room_address(project_id=token.project_id, room_name=room.room_name, domain=self._domain)
|
|
235
|
+
|
|
236
|
+
async def append_message_context(self, *, room, message, chat_context):
|
|
237
|
+
|
|
238
|
+
thread = [
|
|
239
|
+
message
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
await load_thread(room=room, message=message, thread=thread)
|
|
243
|
+
|
|
244
|
+
for msg in thread:
|
|
245
|
+
|
|
246
|
+
if msg["role"] == "agent":
|
|
247
|
+
|
|
248
|
+
chat_context.append_assistant_message(json.dumps(msg))
|
|
249
|
+
|
|
250
|
+
else:
|
|
251
|
+
|
|
252
|
+
chat_context.append_user_message(json.dumps(msg))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# TODO: load previous messages
|
|
256
|
+
return await super().append_message_context(room=room, message=message, chat_context=chat_context)
|
|
257
|
+
|
|
258
|
+
async def process_message(self, *, chat_context, room, message, toolkits):
|
|
259
|
+
|
|
260
|
+
logger.info(f"processing message {message}")
|
|
261
|
+
body = await super().process_message(chat_context=chat_context, room=room, message=message, toolkits=toolkits)
|
|
262
|
+
|
|
263
|
+
msg = create_reply_email_message(message=message, from_address=self._email_address, body=body)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
reply_msg_dict = await save_email_message(room=room, content=msg.as_bytes(), role="agent")
|
|
267
|
+
|
|
268
|
+
logger.info(f"replying with message {reply_msg_dict}")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
username = self._smtp.username
|
|
272
|
+
if username == None:
|
|
273
|
+
username = self.room.local_participant.get_attribute("name")
|
|
274
|
+
|
|
275
|
+
password = self._smtp.password
|
|
276
|
+
if password == None:
|
|
277
|
+
password = self.room.protocol.token
|
|
278
|
+
|
|
279
|
+
await aiosmtplib.send(msg, hostname=self._smtp.hostname, port=self._smtp.port, username=username, password=password)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def base36encode(number: int):
|
|
284
|
+
if not isinstance(number, int):
|
|
285
|
+
raise TypeError('number must be an integer')
|
|
286
|
+
if number < 0:
|
|
287
|
+
raise ValueError('number must be non-negative')
|
|
288
|
+
|
|
289
|
+
alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
|
|
290
|
+
if number == 0:
|
|
291
|
+
return '0'
|
|
292
|
+
base36 = ''
|
|
293
|
+
while number:
|
|
294
|
+
number, i = divmod(number, 36)
|
|
295
|
+
base36 = alphabet[i] + base36
|
|
296
|
+
return base36
|
|
297
|
+
|
|
298
|
+
def compress_uuid(guid_string: str):
|
|
299
|
+
guid_int = int(guid_string.replace('-', ''), 16)
|
|
300
|
+
return base36encode(guid_int)
|
|
301
|
+
|
|
302
|
+
def base36decode(number_str: str) -> int:
|
|
303
|
+
"""Decode a base36-encoded string into an integer."""
|
|
304
|
+
alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
|
|
305
|
+
base = 36
|
|
306
|
+
number = 0
|
|
307
|
+
for char in number_str:
|
|
308
|
+
try:
|
|
309
|
+
value = alphabet.index(char)
|
|
310
|
+
except ValueError:
|
|
311
|
+
raise ValueError(f"Invalid character '{char}' for base36 encoding")
|
|
312
|
+
number = number * base + value
|
|
313
|
+
return number
|
|
314
|
+
|
|
315
|
+
def decompress_uuid(compressed_uuid: str) -> str:
|
|
316
|
+
"""
|
|
317
|
+
Reverse the compressed UUID to its standard 36-character UUID format.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
compressed_uuid: A base36 string that represents a UUID compressed from its standard form.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
A string in the UUID format (8-4-4-4-12 hexadecimal characters).
|
|
324
|
+
"""
|
|
325
|
+
# Decode the base36 string back to the original integer.
|
|
326
|
+
guid_int = base36decode(compressed_uuid)
|
|
327
|
+
|
|
328
|
+
# Convert the integer into a 32-digit hexadecimal string with leading zeros.
|
|
329
|
+
hex_str = f'{guid_int:032x}'
|
|
330
|
+
|
|
331
|
+
# Reinsert dashes to match the standard UUID format: 8-4-4-4-12.
|
|
332
|
+
standard_uuid = f'{hex_str[0:8]}-{hex_str[8:12]}-{hex_str[12:16]}-{hex_str[16:20]}-{hex_str[20:32]}'
|
|
333
|
+
return standard_uuid
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def room_address(*, project_id: str, room_name: str, domain: str = os.getenv("MESHAGENT_MAIL_DOMAIN", "mail.meshagent.com")):
|
|
337
|
+
return f"{compress_uuid(project_id)}+{room_name}@{domain}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.24"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from .agent import SingleRoomAgent
|
|
2
|
+
from meshagent.api.chan import Chan
|
|
3
|
+
from meshagent.api import RoomMessage, RoomException, RoomClient, RemoteParticipant
|
|
4
|
+
from meshagent.agents import AgentChatContext
|
|
5
|
+
from meshagent.tools import Toolkit
|
|
6
|
+
from .adapter import LLMAdapter, ToolResponseAdapter
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Optional
|
|
9
|
+
import json
|
|
10
|
+
from meshagent.tools import ToolContext
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("chat")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Worker(SingleRoomAgent):
|
|
17
|
+
def __init__(self, *, queue: str, name, title = None, description = None, requires = None, llm_adapter: LLMAdapter, tool_adapter: Optional[ToolResponseAdapter] = None, toolkits: Optional[list[Toolkit]] = None, rules : Optional[list[str]] = None):
|
|
18
|
+
super().__init__(
|
|
19
|
+
name=name,
|
|
20
|
+
title=title,
|
|
21
|
+
description=description,
|
|
22
|
+
requires=requires,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
self._queue = queue
|
|
26
|
+
|
|
27
|
+
if toolkits == None:
|
|
28
|
+
toolkits = []
|
|
29
|
+
|
|
30
|
+
self._llm_adapter = llm_adapter
|
|
31
|
+
self._tool_adapter = tool_adapter
|
|
32
|
+
|
|
33
|
+
self._message_channel = Chan[RoomMessage]()
|
|
34
|
+
|
|
35
|
+
self._room : RoomClient | None = None
|
|
36
|
+
self._toolkits = toolkits
|
|
37
|
+
|
|
38
|
+
if rules == None:
|
|
39
|
+
rules = []
|
|
40
|
+
|
|
41
|
+
self._rules = rules
|
|
42
|
+
self._done = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def start(self, *, room: RoomClient):
|
|
46
|
+
self._done = False
|
|
47
|
+
|
|
48
|
+
await super().start(room=room)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
self._main_task = asyncio.create_task(self.run(room=room))
|
|
52
|
+
|
|
53
|
+
async def stop(self):
|
|
54
|
+
|
|
55
|
+
self._done = True
|
|
56
|
+
|
|
57
|
+
await asyncio.gather(self._main_task)
|
|
58
|
+
|
|
59
|
+
await super().stop()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def append_message_context(self, *, room: RoomClient, message: dict, chat_context: AgentChatContext):
|
|
63
|
+
chat_context.append_user_message(message=json.dumps(message))
|
|
64
|
+
|
|
65
|
+
async def process_message(self, *, chat_context: AgentChatContext, room: RoomClient, message: dict, toolkits: list[Toolkit]):
|
|
66
|
+
|
|
67
|
+
return await self._llm_adapter.next(
|
|
68
|
+
context=chat_context,
|
|
69
|
+
room=room,
|
|
70
|
+
toolkits=toolkits,
|
|
71
|
+
tool_adapter=self._tool_adapter,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def run(self, *, room: RoomClient):
|
|
76
|
+
|
|
77
|
+
toolkits = [
|
|
78
|
+
*await self.get_required_toolkits(ToolContext(room=room, caller=room.local_participant)),
|
|
79
|
+
*self._toolkits
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
while not self._done:
|
|
83
|
+
|
|
84
|
+
message = await room.queues.receive(name=self._queue, create=True, wait=True)
|
|
85
|
+
if message != None:
|
|
86
|
+
|
|
87
|
+
logger.info(f"received message on worker queue {message}")
|
|
88
|
+
try:
|
|
89
|
+
|
|
90
|
+
chat_context = await self.init_chat_context()
|
|
91
|
+
|
|
92
|
+
chat_context.append_rules(
|
|
93
|
+
rules=[
|
|
94
|
+
*self._rules,
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
await self.append_message_context(room=room, message=message, chat_context=chat_context)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
await self.process_message(chat_context=chat_context, room=room, message=message, toolkits=toolkits)
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
|
|
105
|
+
logger.error(f"Failed to process a message {message}", exc_info=e)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-agents
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.24
|
|
4
4
|
Summary: Agent Building Blocks for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
@@ -12,18 +12,21 @@ License-File: LICENSE
|
|
|
12
12
|
Requires-Dist: pyjwt~=2.10.1
|
|
13
13
|
Requires-Dist: pytest~=8.3.5
|
|
14
14
|
Requires-Dist: pytest-asyncio~=0.26.0
|
|
15
|
-
Requires-Dist: meshagent-api~=0.0.
|
|
16
|
-
Requires-Dist: meshagent-tools~=0.0.
|
|
17
|
-
Requires-Dist: meshagent-openai~=0.0.
|
|
15
|
+
Requires-Dist: meshagent-api~=0.0.24
|
|
16
|
+
Requires-Dist: meshagent-tools~=0.0.24
|
|
17
|
+
Requires-Dist: meshagent-openai~=0.0.24
|
|
18
18
|
Requires-Dist: pydantic~=2.11.1
|
|
19
19
|
Requires-Dist: pydantic-ai~=0.0.48
|
|
20
20
|
Provides-Extra: all
|
|
21
|
-
Requires-Dist: meshagent-api[all]~=0.0.
|
|
21
|
+
Requires-Dist: meshagent-api[all]~=0.0.24; extra == "all"
|
|
22
22
|
Requires-Dist: chonkie~=0.5.1; extra == "all"
|
|
23
23
|
Requires-Dist: chonkie[semantic]~=0.5.1; extra == "all"
|
|
24
24
|
Requires-Dist: chonkie[openai]~=0.5.1; extra == "all"
|
|
25
|
+
Requires-Dist: aiosmtplib~=4.0.1; extra == "all"
|
|
25
26
|
Provides-Extra: sync
|
|
26
|
-
Requires-Dist: meshagent-api[sync]~=0.0.
|
|
27
|
+
Requires-Dist: meshagent-api[sync]~=0.0.24; extra == "sync"
|
|
28
|
+
Provides-Extra: mail
|
|
29
|
+
Requires-Dist: aiosmtplib~=4.0.1; extra == "mail"
|
|
27
30
|
Provides-Extra: rag
|
|
28
31
|
Requires-Dist: chonkie~=0.5.1; extra == "rag"
|
|
29
32
|
Requires-Dist: chonkie[semantic]~=0.5.1; extra == "rag"
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
pyjwt~=2.10.1
|
|
2
2
|
pytest~=8.3.5
|
|
3
3
|
pytest-asyncio~=0.26.0
|
|
4
|
-
meshagent-api~=0.0.
|
|
5
|
-
meshagent-tools~=0.0.
|
|
6
|
-
meshagent-openai~=0.0.
|
|
4
|
+
meshagent-api~=0.0.24
|
|
5
|
+
meshagent-tools~=0.0.24
|
|
6
|
+
meshagent-openai~=0.0.24
|
|
7
7
|
pydantic~=2.11.1
|
|
8
8
|
pydantic-ai~=0.0.48
|
|
9
9
|
|
|
10
10
|
[all]
|
|
11
|
-
meshagent-api[all]~=0.0.
|
|
11
|
+
meshagent-api[all]~=0.0.24
|
|
12
12
|
chonkie~=0.5.1
|
|
13
13
|
chonkie[semantic]~=0.5.1
|
|
14
14
|
chonkie[openai]~=0.5.1
|
|
15
|
+
aiosmtplib~=4.0.1
|
|
16
|
+
|
|
17
|
+
[mail]
|
|
18
|
+
aiosmtplib~=4.0.1
|
|
15
19
|
|
|
16
20
|
[rag]
|
|
17
21
|
chonkie~=0.5.1
|
|
@@ -19,4 +23,4 @@ chonkie[semantic]~=0.5.1
|
|
|
19
23
|
chonkie[openai]~=0.5.1
|
|
20
24
|
|
|
21
25
|
[sync]
|
|
22
|
-
meshagent-api[sync]~=0.0.
|
|
26
|
+
meshagent-api[sync]~=0.0.24
|
|
@@ -17,23 +17,28 @@ dependencies = [
|
|
|
17
17
|
"pyjwt~=2.10.1",
|
|
18
18
|
"pytest~=8.3.5",
|
|
19
19
|
"pytest-asyncio~=0.26.0",
|
|
20
|
-
"meshagent-api~=0.0.
|
|
21
|
-
"meshagent-tools~=0.0.
|
|
22
|
-
"meshagent-openai~=0.0.
|
|
20
|
+
"meshagent-api~=0.0.24",
|
|
21
|
+
"meshagent-tools~=0.0.24",
|
|
22
|
+
"meshagent-openai~=0.0.24",
|
|
23
23
|
"pydantic~=2.11.1",
|
|
24
24
|
"pydantic-ai~=0.0.48",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
28
|
all = [
|
|
29
|
-
"meshagent-api[all]~=0.0.
|
|
29
|
+
"meshagent-api[all]~=0.0.24",
|
|
30
30
|
"chonkie~=0.5.1",
|
|
31
31
|
"chonkie[semantic]~=0.5.1",
|
|
32
|
-
"chonkie[openai]~=0.5.1"
|
|
32
|
+
"chonkie[openai]~=0.5.1",
|
|
33
|
+
"aiosmtplib~=4.0.1"
|
|
33
34
|
]
|
|
34
35
|
|
|
35
36
|
sync = [
|
|
36
|
-
"meshagent-api[sync]~=0.0.
|
|
37
|
+
"meshagent-api[sync]~=0.0.24",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
mail = [
|
|
41
|
+
"aiosmtplib~=4.0.1"
|
|
37
42
|
]
|
|
38
43
|
|
|
39
44
|
rag = [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.22"
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
from .agent import TaskRunner, AgentCallContext
|
|
2
|
-
from meshagent.api.chan import Chan
|
|
3
|
-
from meshagent.api import RoomMessage, RoomException, RoomClient, RemoteParticipant
|
|
4
|
-
from meshagent.tools import Toolkit
|
|
5
|
-
from .adapter import LLMAdapter, ToolResponseAdapter
|
|
6
|
-
import asyncio
|
|
7
|
-
from typing import Optional
|
|
8
|
-
import json
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger("chat")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# todo: thread should stop when participant stops?
|
|
17
|
-
|
|
18
|
-
class Worker(TaskRunner):
|
|
19
|
-
def __init__(self, *, queue: str, prompt: str, name, title = None, description = None, requires = None, llm_adapter: LLMAdapter, tool_adapter: Optional[ToolResponseAdapter] = None, toolkits: Optional[list[Toolkit]] = None, rules : Optional[list[str]] = None, supports_tools: bool = True):
|
|
20
|
-
super().__init__(
|
|
21
|
-
name=name,
|
|
22
|
-
title=title,
|
|
23
|
-
description=description,
|
|
24
|
-
requires=requires,
|
|
25
|
-
output_schema=None,
|
|
26
|
-
supports_tools=supports_tools
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
self._queue = queue
|
|
30
|
-
self._prompt = prompt
|
|
31
|
-
|
|
32
|
-
if toolkits == None:
|
|
33
|
-
toolkits = []
|
|
34
|
-
|
|
35
|
-
self._llm_adapter = llm_adapter
|
|
36
|
-
self._tool_adapter = tool_adapter
|
|
37
|
-
|
|
38
|
-
self._message_channel = Chan[RoomMessage]()
|
|
39
|
-
|
|
40
|
-
self._room : RoomClient | None = None
|
|
41
|
-
self._toolkits = toolkits
|
|
42
|
-
|
|
43
|
-
if rules == None:
|
|
44
|
-
rules = []
|
|
45
|
-
|
|
46
|
-
self._rules = rules
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
async def ask(self, *, context: AgentCallContext, arguments: dict):
|
|
50
|
-
|
|
51
|
-
queue = self._queue
|
|
52
|
-
prompt = self._prompt
|
|
53
|
-
|
|
54
|
-
step_schema = {
|
|
55
|
-
"type" : "object",
|
|
56
|
-
"required" : ["text","finished"],
|
|
57
|
-
"additionalProperties" : False,
|
|
58
|
-
"description" : "execute a step",
|
|
59
|
-
"properties" : {
|
|
60
|
-
"text" : {
|
|
61
|
-
"description" : "a reply to the user or status to display during an intermediate step",
|
|
62
|
-
"type" : "string"
|
|
63
|
-
},
|
|
64
|
-
"finished" : {
|
|
65
|
-
"description" : "whether the agent has finished answering the user's last message, also should be set to true if we get stuck in a loop, or if the user did not make a request",
|
|
66
|
-
"type" : "boolean"
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# todo: add graceful exit
|
|
72
|
-
|
|
73
|
-
while True:
|
|
74
|
-
|
|
75
|
-
message = await self.room.queues.receive(name=queue, create=True, wait=True)
|
|
76
|
-
if message != None:
|
|
77
|
-
|
|
78
|
-
# for each message, create a new chat context
|
|
79
|
-
|
|
80
|
-
chat_context = await self.init_chat_context()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
chat_context.append_rules(
|
|
84
|
-
rules=[
|
|
85
|
-
*self._rules,
|
|
86
|
-
]
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
chat_context.append_user_message(message=prompt)
|
|
90
|
-
chat_context.append_user_message(message=json.dumps(message))
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
while True:
|
|
94
|
-
|
|
95
|
-
tool_target = context.caller
|
|
96
|
-
if context.on_behalf_of != None:
|
|
97
|
-
tool_target = context.on_behalf_of
|
|
98
|
-
|
|
99
|
-
response = await self._llm_adapter.next(
|
|
100
|
-
context=chat_context,
|
|
101
|
-
room=self._room,
|
|
102
|
-
toolkits=context.toolkits,
|
|
103
|
-
tool_adapter=self._tool_adapter,
|
|
104
|
-
output_schema=step_schema,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
if response["finished"] or len(context.toolkits) == 0:
|
|
108
|
-
break
|
|
109
|
-
else:
|
|
110
|
-
chat_context.append_user_message(message="proceed to the next step if you are ready")
|
|
111
|
-
|
|
112
|
-
except Exception as e:
|
|
113
|
-
|
|
114
|
-
logger.error(f"Failed to process a message {message}", exc_info=e)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent/agents/schemas/presentation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meshagent_agents-0.0.22 → meshagent_agents-0.0.24}/meshagent_agents.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|