meshagent-cli 0.7.0__py3-none-any.whl → 0.23.0__py3-none-any.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.
Files changed (42) hide show
  1. meshagent/cli/agent.py +23 -13
  2. meshagent/cli/api_keys.py +4 -4
  3. meshagent/cli/async_typer.py +52 -4
  4. meshagent/cli/call.py +27 -36
  5. meshagent/cli/chatbot.py +1559 -177
  6. meshagent/cli/cli.py +23 -22
  7. meshagent/cli/cli_mcp.py +92 -28
  8. meshagent/cli/cli_secrets.py +10 -10
  9. meshagent/cli/common_options.py +19 -4
  10. meshagent/cli/containers.py +164 -16
  11. meshagent/cli/database.py +997 -0
  12. meshagent/cli/developer.py +3 -3
  13. meshagent/cli/exec.py +22 -6
  14. meshagent/cli/helper.py +101 -12
  15. meshagent/cli/helpers.py +65 -11
  16. meshagent/cli/host.py +41 -0
  17. meshagent/cli/mailbot.py +1104 -79
  18. meshagent/cli/mailboxes.py +223 -0
  19. meshagent/cli/meeting_transcriber.py +29 -15
  20. meshagent/cli/messaging.py +7 -10
  21. meshagent/cli/multi.py +357 -0
  22. meshagent/cli/oauth2.py +192 -40
  23. meshagent/cli/participant_token.py +5 -3
  24. meshagent/cli/port.py +70 -0
  25. meshagent/cli/queue.py +2 -2
  26. meshagent/cli/room.py +24 -212
  27. meshagent/cli/rooms.py +214 -0
  28. meshagent/cli/services.py +269 -37
  29. meshagent/cli/sessions.py +5 -5
  30. meshagent/cli/storage.py +5 -5
  31. meshagent/cli/sync.py +434 -0
  32. meshagent/cli/task_runner.py +1317 -0
  33. meshagent/cli/version.py +1 -1
  34. meshagent/cli/voicebot.py +544 -98
  35. meshagent/cli/webhook.py +7 -7
  36. meshagent/cli/worker.py +1403 -0
  37. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/METADATA +15 -13
  38. meshagent_cli-0.23.0.dist-info/RECORD +45 -0
  39. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/WHEEL +1 -1
  40. meshagent_cli-0.7.0.dist-info/RECORD +0 -36
  41. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/entry_points.txt +0 -0
  42. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/top_level.txt +0 -0
meshagent/cli/mailbot.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import typer
2
- from meshagent.api import ParticipantToken
2
+ from meshagent.cli import async_typer
3
3
  from rich import print
4
+ import os
5
+
6
+ from meshagent.api import ParticipantToken
4
7
  from typing import Annotated, Optional
5
8
  from meshagent.cli.common_options import (
6
9
  ProjectIdOption,
@@ -9,24 +12,53 @@ from meshagent.cli.common_options import (
9
12
  from meshagent.tools import Toolkit
10
13
  from meshagent.api import RoomClient, WebSocketClientProtocol, ApiScope
11
14
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
12
- from meshagent.cli import async_typer
13
15
  from meshagent.cli.helper import (
14
16
  get_client,
15
17
  resolve_project_id,
16
18
  resolve_room,
17
19
  resolve_key,
20
+ cleanup_args,
18
21
  )
19
22
  from meshagent.openai import OpenAIResponsesAdapter
20
- from meshagent.openai.tools.responses_adapter import ImageGenerationTool, LocalShellTool
21
- from meshagent.api.services import ServiceHost
23
+ from meshagent.anthropic import AnthropicOpenAIResponsesStreamAdapter
22
24
 
25
+ from meshagent.agents.config import RulesConfig
23
26
 
24
27
  from typing import List
25
28
  from pathlib import Path
26
29
 
27
- from meshagent.api import RequiredToolkit, RequiredSchema
28
- from meshagent.openai.tools.responses_adapter import WebSearchTool
30
+ from meshagent.api import RequiredToolkit, RequiredSchema, RoomException
31
+
32
+ import logging
33
+
34
+ from meshagent.tools.database import DatabaseToolkitBuilder, DatabaseToolkitConfig
35
+
36
+ from meshagent.tools.storage import StorageToolkit
37
+ from meshagent.tools.datetime import DatetimeToolkit
38
+ from meshagent.tools.uuid import UUIDToolkit
39
+
40
+ from meshagent.openai.tools.responses_adapter import (
41
+ WebSearchTool,
42
+ ShellConfig,
43
+ ApplyPatchConfig,
44
+ ApplyPatchTool,
45
+ ShellTool,
46
+ LocalShellTool,
47
+ ImageGenerationTool,
48
+ )
49
+
50
+ from meshagent.cli.host import get_service, run_services, get_deferred, service_specs
51
+ from meshagent.api.specs.service import AgentSpec, ANNOTATION_AGENT_TYPE
29
52
 
53
+ import yaml
54
+
55
+ import shlex
56
+ import sys
57
+
58
+ from meshagent.api.client import ConflictError
59
+ from meshagent.agents.adapter import MessageStreamLLMAdapter
60
+
61
+ logger = logging.getLogger("mailbot")
30
62
 
31
63
  app = async_typer.AsyncTyper(help="Join a mailbot to a room")
32
64
 
@@ -34,7 +66,6 @@ app = async_typer.AsyncTyper(help="Join a mailbot to a room")
34
66
  def build_mailbot(
35
67
  *,
36
68
  model: str,
37
- agent_name: str,
38
69
  rule: List[str],
39
70
  toolkit: List[str],
40
71
  schema: List[str],
@@ -45,10 +76,36 @@ def build_mailbot(
45
76
  web_search: Annotated[
46
77
  Optional[bool], typer.Option(..., help="Enable web search tool calling")
47
78
  ] = False,
48
- queue: str,
79
+ toolkit_name: Optional[str] = None,
80
+ queue: Optional[str] = None,
49
81
  email_address: str,
82
+ room_rules_paths: list[str],
83
+ whitelist=list[str],
84
+ require_shell: Optional[bool] = None,
85
+ require_apply_patch: Optional[bool] = None,
86
+ require_storage: Optional[str] = None,
87
+ require_read_only_storage: Optional[str] = None,
88
+ require_time: bool = True,
89
+ require_uuid: bool = False,
90
+ require_table_read: bool,
91
+ require_table_write: bool,
92
+ require_computer_use: bool,
93
+ reply_all: bool,
94
+ database_namespace: Optional[list[str]] = None,
95
+ enable_attachments: bool,
96
+ working_directory: Optional[str] = None,
97
+ skill_dirs: Optional[list[str]] = None,
98
+ shell_image: Optional[str] = None,
99
+ llm_participant: Optional[str] = None,
100
+ delegate_shell_token: Optional[bool] = None,
101
+ log_llm_requests: Optional[bool] = None,
50
102
  ):
51
- from meshagent.agents.mail import MailWorker
103
+ from meshagent.agents.mail import MailBot
104
+
105
+ if (require_storage or require_read_only_storage) and len(whitelist) == 0:
106
+ logger.warning(
107
+ "you have enabled storage tools without a whilelist, anyone who can send to this mailbox will be able to ask it about files"
108
+ )
52
109
 
53
110
  requirements = []
54
111
 
@@ -67,29 +124,117 @@ def build_mailbot(
67
124
  except FileNotFoundError:
68
125
  print(f"[yellow]rules file not found at {rules_file}[/yellow]")
69
126
 
70
- BaseClass = MailWorker
71
- if computer_use:
72
- raise ValueError("computer use is not yet supported for the mail agent")
127
+ BaseClass = MailBot
128
+ if llm_participant:
129
+ llm_adapter = MessageStreamLLMAdapter(
130
+ participant_name=llm_participant,
131
+ )
73
132
  else:
74
- llm_adapter = OpenAIResponsesAdapter(model=model)
133
+ if computer_use or require_computer_use:
134
+ llm_adapter = OpenAIResponsesAdapter(
135
+ model=model,
136
+ response_options={
137
+ "reasoning": {"summary": "concise"},
138
+ "truncation": "auto",
139
+ },
140
+ log_requests=log_llm_requests,
141
+ )
142
+
143
+ else:
144
+ if model.startswith("claude-"):
145
+ llm_adapter = AnthropicOpenAIResponsesStreamAdapter(
146
+ model=model,
147
+ log_requests=log_llm_requests,
148
+ )
149
+ else:
150
+ llm_adapter = OpenAIResponsesAdapter(
151
+ model=model,
152
+ log_requests=log_llm_requests,
153
+ )
154
+
155
+ parsed_whitelist = []
156
+ if len(whitelist) > 0:
157
+ for w in whitelist:
158
+ for s in w.split(","):
159
+ s = s.strip()
160
+ if len(s) > 0:
161
+ parsed_whitelist.append(s)
75
162
 
76
163
  class CustomMailbot(BaseClass):
77
164
  def __init__(self):
78
165
  super().__init__(
79
166
  llm_adapter=llm_adapter,
80
- name=agent_name,
81
167
  requires=requirements,
82
168
  toolkits=toolkits,
83
169
  queue=queue,
84
170
  email_address=email_address,
171
+ toolkit_name=toolkit_name,
85
172
  rules=rule if len(rule) > 0 else None,
173
+ whitelist=parsed_whitelist if len(parsed_whitelist) > 0 else None,
174
+ reply_all=reply_all,
175
+ enable_attachments=enable_attachments,
176
+ skill_dirs=skill_dirs,
86
177
  )
87
178
 
179
+ async def init_chat_context(self):
180
+ from meshagent.cli.helper import init_context_from_spec
181
+
182
+ context = await super().init_chat_context()
183
+ await init_context_from_spec(context)
184
+
185
+ return context
186
+
88
187
  async def start(self, *, room: RoomClient):
89
188
  print(
90
189
  "[bold green]Configure and send an email interact with your mailbot[/bold green]"
91
190
  )
92
- return await super().start(room=room)
191
+ await super().start(room=room)
192
+ if room_rules_paths is not None:
193
+ for p in room_rules_paths:
194
+ await self._load_room_rules(path=p)
195
+
196
+ async def get_rules(self):
197
+ rules = [*await super().get_rules()]
198
+ if room_rules_paths is not None:
199
+ for p in room_rules_paths:
200
+ rules.extend(await self._load_room_rules(path=p))
201
+
202
+ return rules
203
+
204
+ async def _load_room_rules(
205
+ self,
206
+ *,
207
+ path: str,
208
+ ):
209
+ rules = []
210
+ try:
211
+ room_rules = await self.room.storage.download(path=path)
212
+
213
+ rules_txt = room_rules.data.decode()
214
+
215
+ rules_config = RulesConfig.parse(rules_txt)
216
+
217
+ if rules_config.rules is not None:
218
+ rules.extend(rules_config.rules)
219
+
220
+ except RoomException:
221
+ try:
222
+ logger.info("attempting to initialize rules file")
223
+ handle = await self.room.storage.open(path=path, overwrite=False)
224
+ await self.room.storage.write(
225
+ handle=handle,
226
+ data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
227
+ )
228
+ await self.room.storage.close(handle=handle)
229
+
230
+ except RoomException:
231
+ pass
232
+ logger.info(
233
+ f"unable to load rules from {path}, continuing with default rules"
234
+ )
235
+ pass
236
+
237
+ return rules
93
238
 
94
239
  async def get_thread_toolkits(self, *, thread_context):
95
240
  toolkits = await super().get_thread_toolkits(thread_context=thread_context)
@@ -101,6 +246,27 @@ def build_mailbot(
101
246
  LocalShellTool(thread_context=thread_context)
102
247
  )
103
248
 
249
+ env = {}
250
+ if delegate_shell_token:
251
+ env["MESHAGENT_TOKEN"] = self.room.protocol.token
252
+
253
+ if require_shell:
254
+ thread_toolkit.tools.append(
255
+ ShellTool(
256
+ working_directory=working_directory,
257
+ config=ShellConfig(name="shell"),
258
+ image=shell_image or "python:3.13",
259
+ env=env,
260
+ )
261
+ )
262
+
263
+ if require_apply_patch:
264
+ thread_toolkit.tools.append(
265
+ ApplyPatchTool(
266
+ config=ApplyPatchConfig(name="apply_patch"),
267
+ )
268
+ )
269
+
104
270
  if image_generation is not None:
105
271
  print("adding openai image gen to thread", flush=True)
106
272
  thread_toolkit.tools.append(
@@ -114,6 +280,55 @@ def build_mailbot(
114
280
  if web_search:
115
281
  thread_toolkit.tools.append(WebSearchTool())
116
282
 
283
+ if require_storage:
284
+ thread_toolkit.tools.extend(StorageToolkit().tools)
285
+
286
+ if require_read_only_storage:
287
+ thread_toolkit.tools.extend(StorageToolkit(read_only=True).tools)
288
+
289
+ if len(require_table_read) > 0:
290
+ thread_toolkit.tools.extend(
291
+ (
292
+ await DatabaseToolkitBuilder().make(
293
+ room=self.room,
294
+ model=model,
295
+ config=DatabaseToolkitConfig(
296
+ tables=require_table_read,
297
+ read_only=True,
298
+ namespace=database_namespace,
299
+ ),
300
+ )
301
+ ).tools
302
+ )
303
+
304
+ if len(require_table_write) > 0:
305
+ thread_toolkit.tools.extend(
306
+ (
307
+ await DatabaseToolkitBuilder().make(
308
+ room=self.room,
309
+ model=model,
310
+ config=DatabaseToolkitConfig(
311
+ tables=require_table_write,
312
+ read_only=False,
313
+ namespace=database_namespace,
314
+ ),
315
+ )
316
+ ).tools
317
+ )
318
+
319
+ if require_time:
320
+ thread_toolkit.tools.extend(DatetimeToolkit().tools)
321
+
322
+ if require_uuid:
323
+ thread_toolkit.tools.extend(UUIDToolkit().tools)
324
+
325
+ if require_computer_use:
326
+ from meshagent.computers.agent import ComputerToolkit
327
+
328
+ computer_toolkit = ComputerToolkit(room=self.room, render_screen=None)
329
+
330
+ toolkits.append(computer_toolkit)
331
+
117
332
  toolkits.append(thread_toolkit)
118
333
  return toolkits
119
334
 
@@ -121,14 +336,28 @@ def build_mailbot(
121
336
 
122
337
 
123
338
  @app.async_command("join")
124
- async def make_call(
339
+ async def join(
125
340
  *,
126
- project_id: ProjectIdOption = None,
341
+ project_id: ProjectIdOption,
127
342
  room: RoomOption,
128
343
  role: str = "agent",
129
- agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
344
+ agent_name: Annotated[
345
+ Optional[str], typer.Option(..., help="Name of the agent to call")
346
+ ] = None,
130
347
  rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
131
348
  rules_file: Optional[str] = None,
349
+ require_toolkit: Annotated[
350
+ List[str],
351
+ typer.Option(
352
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
353
+ ),
354
+ ] = [],
355
+ require_schema: Annotated[
356
+ List[str],
357
+ typer.Option(
358
+ "--require-schema", "-rs", help="the name or url of a required schema"
359
+ ),
360
+ ] = [],
132
361
  toolkit: Annotated[
133
362
  List[str],
134
363
  typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
@@ -139,21 +368,121 @@ async def make_call(
139
368
  ] = [],
140
369
  model: Annotated[
141
370
  str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
142
- ] = "gpt-5",
143
- local_shell: Annotated[
371
+ ] = "gpt-5.2",
372
+ require_shell: Annotated[
373
+ Optional[bool],
374
+ typer.Option(..., help="Enable function shell tool calling"),
375
+ ] = False,
376
+ require_local_shell: Annotated[
144
377
  Optional[bool], typer.Option(..., help="Enable local shell tool calling")
145
378
  ] = False,
146
- web_search: Annotated[
379
+ require_web_search: Annotated[
147
380
  Optional[bool], typer.Option(..., help="Enable web search tool calling")
148
381
  ] = False,
382
+ require_apply_patch: Annotated[
383
+ Optional[bool],
384
+ typer.Option(..., help="Enable apply patch tool calling"),
385
+ ] = False,
149
386
  key: Annotated[
150
387
  str,
151
388
  typer.Option("--key", help="an api key to sign the token with"),
152
389
  ] = None,
153
- queue: Annotated[str, typer.Option(..., help="the name of the mail queue")],
390
+ queue: Annotated[
391
+ Optional[str], typer.Option(..., help="the name of the mail queue")
392
+ ] = None,
154
393
  email_address: Annotated[
155
394
  str, typer.Option(..., help="the email address of the agent")
156
395
  ],
396
+ toolkit_name: Annotated[
397
+ Optional[str],
398
+ typer.Option(..., help="the name of a toolkit to expose mail operations"),
399
+ ] = None,
400
+ room_rules: Annotated[
401
+ List[str],
402
+ typer.Option(
403
+ "--room-rules",
404
+ "-rr",
405
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
406
+ ),
407
+ ] = [],
408
+ whitelist: Annotated[
409
+ List[str],
410
+ typer.Option(
411
+ "--whitelist",
412
+ help="an email to whitelist",
413
+ ),
414
+ ] = [],
415
+ require_storage: Annotated[
416
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
417
+ ] = False,
418
+ require_read_only_storage: Annotated[
419
+ Optional[bool],
420
+ typer.Option(..., help="Enable read only storage toolkit"),
421
+ ] = False,
422
+ require_time: Annotated[
423
+ bool,
424
+ typer.Option(
425
+ ...,
426
+ help="Enable time/datetime tools",
427
+ ),
428
+ ] = True,
429
+ require_uuid: Annotated[
430
+ bool,
431
+ typer.Option(
432
+ ...,
433
+ help="Enable UUID generation tools",
434
+ ),
435
+ ] = False,
436
+ database_namespace: Annotated[
437
+ Optional[str],
438
+ typer.Option(..., help="Use a specific database namespace"),
439
+ ] = None,
440
+ require_table_read: Annotated[
441
+ list[str],
442
+ typer.Option(..., help="Enable table read tools for a specific table"),
443
+ ] = [],
444
+ require_table_write: Annotated[
445
+ list[str],
446
+ typer.Option(..., help="Enable table write tools for a specific table"),
447
+ ] = [],
448
+ require_computer_use: Annotated[
449
+ Optional[bool],
450
+ typer.Option(
451
+ ...,
452
+ help="Enable computer use (requires computer-use-preview model)",
453
+ hidden=True,
454
+ ),
455
+ ] = False,
456
+ reply_all: Annotated[
457
+ bool, typer.Option(help="Reply-all when responding to emails")
458
+ ] = False,
459
+ enable_attachments: Annotated[
460
+ bool, typer.Option(help="Allow downloading and processing email attachments")
461
+ ] = False,
462
+ working_directory: Annotated[
463
+ Optional[str],
464
+ typer.Option(..., help="The default working directory for shell commands"),
465
+ ] = None,
466
+ skill_dir: Annotated[
467
+ list[str],
468
+ typer.Option(..., help="an agent skills directory"),
469
+ ] = [],
470
+ llm_participant: Annotated[
471
+ Optional[str],
472
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
473
+ ] = None,
474
+ shell_image: Annotated[
475
+ Optional[str],
476
+ typer.Option(..., help="an image tag to use to run shell commands in"),
477
+ ] = None,
478
+ delegate_shell_token: Annotated[
479
+ Optional[bool],
480
+ typer.Option(..., help="Delegate the room token to shell tools"),
481
+ ] = False,
482
+ log_llm_requests: Annotated[
483
+ Optional[bool],
484
+ typer.Option(..., help="log all requests to the llm"),
485
+ ] = False,
157
486
  ):
158
487
  key = await resolve_key(project_id=project_id, key=key)
159
488
 
@@ -163,57 +492,84 @@ async def make_call(
163
492
 
164
493
  room = resolve_room(room)
165
494
 
166
- token = ParticipantToken(
167
- name=agent_name,
168
- )
169
-
170
- token.add_api_grant(ApiScope.agent_default())
495
+ jwt = os.getenv("MESHAGENT_TOKEN")
496
+ if jwt is None:
497
+ if agent_name is None:
498
+ print(
499
+ "[bold red]--agent-name must be specified when the MESHAGENT_TOKEN environment variable is not set[/bold red]"
500
+ )
501
+ raise typer.Exit(1)
171
502
 
172
- token.add_role_grant(role=role)
173
- token.add_room_grant(room)
503
+ token = ParticipantToken(
504
+ name=agent_name,
505
+ )
174
506
 
175
- jwt = token.to_jwt(api_key=key)
507
+ token.add_api_grant(ApiScope.agent_default(tunnels=require_computer_use))
176
508
 
177
- print("[bold green]Connecting to room...[/bold green]", flush=True)
178
- async with RoomClient(
179
- protocol=WebSocketClientProtocol(
180
- url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
181
- token=jwt,
182
- )
183
- ) as client:
184
- requirements = []
509
+ token.add_role_grant(role=role)
510
+ token.add_room_grant(room)
185
511
 
186
- for t in toolkit:
187
- requirements.append(RequiredToolkit(name=t))
512
+ jwt = token.to_jwt(api_key=key)
188
513
 
189
- for t in schema:
190
- requirements.append(RequiredSchema(name=t))
514
+ print("[bold green]Connecting to room...[/bold green]", flush=True)
515
+ CustomMailbot = build_mailbot(
516
+ computer_use=None,
517
+ model=model,
518
+ local_shell=require_local_shell,
519
+ rule=rule,
520
+ schema=require_schema + schema,
521
+ toolkit=require_toolkit + toolkit,
522
+ image_generation=None,
523
+ web_search=require_web_search,
524
+ rules_file=rules_file,
525
+ queue=queue,
526
+ email_address=email_address,
527
+ toolkit_name=toolkit_name,
528
+ room_rules_paths=room_rules,
529
+ whitelist=whitelist,
530
+ require_shell=require_shell,
531
+ require_apply_patch=require_apply_patch,
532
+ require_storage=require_storage,
533
+ require_read_only_storage=require_read_only_storage,
534
+ require_time=require_time,
535
+ require_uuid=require_uuid,
536
+ require_table_read=require_table_read,
537
+ require_table_write=require_table_write,
538
+ require_computer_use=require_computer_use,
539
+ reply_all=reply_all,
540
+ database_namespace=database_namespace,
541
+ enable_attachments=enable_attachments,
542
+ working_directory=working_directory,
543
+ skill_dirs=skill_dir,
544
+ shell_image=shell_image,
545
+ llm_participant=llm_participant,
546
+ delegate_shell_token=delegate_shell_token,
547
+ log_llm_requests=log_llm_requests,
548
+ )
191
549
 
192
- CustomMailbot = build_mailbot(
193
- computer_use=None,
194
- model=model,
195
- local_shell=local_shell,
196
- agent_name=agent_name,
197
- rule=rule,
198
- toolkit=toolkit,
199
- schema=schema,
200
- image_generation=None,
201
- web_search=web_search,
202
- rules_file=rules_file,
203
- queue=queue,
204
- email_address=email_address,
205
- )
550
+ bot = CustomMailbot()
206
551
 
207
- bot = CustomMailbot()
552
+ if get_deferred():
553
+ from meshagent.cli.host import agents
208
554
 
209
- await bot.start(room=client)
210
- try:
211
- print(
212
- flush=True,
555
+ agents.append((bot, jwt))
556
+ else:
557
+ async with RoomClient(
558
+ protocol=WebSocketClientProtocol(
559
+ url=websocket_room_url(
560
+ room_name=room, base_url=meshagent_base_url()
561
+ ),
562
+ token=jwt,
213
563
  )
214
- await client.protocol.wait_for_close()
215
- except KeyboardInterrupt:
216
- await bot.stop()
564
+ ) as client:
565
+ await bot.start(room=client)
566
+ try:
567
+ print(
568
+ flush=True,
569
+ )
570
+ await client.protocol.wait_for_close()
571
+ except KeyboardInterrupt:
572
+ await bot.stop()
217
573
 
218
574
  finally:
219
575
  await account_client.close()
@@ -225,46 +581,715 @@ async def service(
225
581
  agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
226
582
  rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
227
583
  rules_file: Optional[str] = None,
584
+ require_toolkit: Annotated[
585
+ List[str],
586
+ typer.Option(
587
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
588
+ ),
589
+ ] = [],
590
+ require_schema: Annotated[
591
+ List[str],
592
+ typer.Option(
593
+ "--require-schema", "-rs", help="the name or url of a required schema"
594
+ ),
595
+ ] = [],
228
596
  toolkit: Annotated[
229
597
  List[str],
230
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
598
+ typer.Option(
599
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
600
+ ),
231
601
  ] = [],
232
602
  schema: Annotated[
233
603
  List[str],
234
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
604
+ typer.Option(
605
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
606
+ ),
607
+ ] = [],
608
+ model: Annotated[
609
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
610
+ ] = "gpt-5.2",
611
+ require_shell: Annotated[
612
+ Optional[bool],
613
+ typer.Option(..., help="Enable function shell tool calling"),
614
+ ] = False,
615
+ require_local_shell: Annotated[
616
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
617
+ ] = False,
618
+ require_web_search: Annotated[
619
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
620
+ ] = False,
621
+ require_apply_patch: Annotated[
622
+ Optional[bool],
623
+ typer.Option(..., help="Enable apply patch tool calling"),
624
+ ] = False,
625
+ host: Annotated[
626
+ Optional[str], typer.Option(help="Host to bind the service on")
627
+ ] = None,
628
+ port: Annotated[
629
+ Optional[int], typer.Option(help="Port to bind the service on")
630
+ ] = None,
631
+ path: Annotated[
632
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
633
+ ] = None,
634
+ queue: Annotated[
635
+ Optional[str], typer.Option(..., help="the name of the mail queue")
636
+ ] = None,
637
+ email_address: Annotated[
638
+ str, typer.Option(..., help="the email address of the agent")
639
+ ],
640
+ toolkit_name: Annotated[
641
+ Optional[str],
642
+ typer.Option(..., help="the name of a toolkit to expose mail operations"),
643
+ ] = None,
644
+ room_rules: Annotated[
645
+ List[str],
646
+ typer.Option(
647
+ "--room-rules",
648
+ "-rr",
649
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
650
+ ),
651
+ ] = [],
652
+ whitelist: Annotated[
653
+ List[str],
654
+ typer.Option(
655
+ "--whitelist",
656
+ help="an email to whitelist",
657
+ ),
658
+ ] = [],
659
+ require_storage: Annotated[
660
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
661
+ ] = False,
662
+ require_read_only_storage: Annotated[
663
+ Optional[bool],
664
+ typer.Option(..., help="Enable read only storage toolkit"),
665
+ ] = False,
666
+ require_time: Annotated[
667
+ bool,
668
+ typer.Option(
669
+ ...,
670
+ help="Enable time/datetime tools",
671
+ ),
672
+ ] = True,
673
+ require_uuid: Annotated[
674
+ bool,
675
+ typer.Option(
676
+ ...,
677
+ help="Enable UUID generation tools",
678
+ ),
679
+ ] = False,
680
+ database_namespace: Annotated[
681
+ Optional[str],
682
+ typer.Option(..., help="Use a specific database namespace"),
683
+ ] = None,
684
+ require_table_read: Annotated[
685
+ list[str],
686
+ typer.Option(..., help="Enable table read tools for a specific table"),
687
+ ] = [],
688
+ require_table_write: Annotated[
689
+ list[str],
690
+ typer.Option(..., help="Enable table write tools for a specific table"),
691
+ ] = [],
692
+ require_computer_use: Annotated[
693
+ Optional[bool],
694
+ typer.Option(
695
+ ...,
696
+ help="Enable computer use (requires computer-use-preview model)",
697
+ hidden=True,
698
+ ),
699
+ ] = False,
700
+ reply_all: Annotated[
701
+ bool, typer.Option(help="Reply-all when responding to emails")
702
+ ] = False,
703
+ enable_attachments: Annotated[
704
+ bool, typer.Option(help="Allow downloading and processing email attachments")
705
+ ] = False,
706
+ working_directory: Annotated[
707
+ Optional[str],
708
+ typer.Option(..., help="The default working directory for shell commands"),
709
+ ] = None,
710
+ skill_dir: Annotated[
711
+ list[str],
712
+ typer.Option(..., help="an agent skills directory"),
713
+ ] = [],
714
+ llm_participant: Annotated[
715
+ Optional[str],
716
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
717
+ ] = None,
718
+ shell_image: Annotated[
719
+ Optional[str],
720
+ typer.Option(..., help="an image tag to use to run shell commands in"),
721
+ ] = None,
722
+ delegate_shell_token: Annotated[
723
+ Optional[bool],
724
+ typer.Option(..., help="Delegate the room token to shell tools"),
725
+ ] = False,
726
+ log_llm_requests: Annotated[
727
+ Optional[bool],
728
+ typer.Option(..., help="log all requests to the llm"),
729
+ ] = False,
730
+ ):
731
+ service = get_service(host=host, port=port)
732
+ if path is None:
733
+ path = "/agent"
734
+ i = 0
735
+ while service.has_path(path):
736
+ i += 1
737
+ path = f"/agent{i}"
738
+
739
+ service.agents.append(
740
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "MailBot"})
741
+ )
742
+
743
+ service.add_path(
744
+ identity=agent_name,
745
+ path=path,
746
+ cls=build_mailbot(
747
+ queue=queue,
748
+ computer_use=None,
749
+ model=model,
750
+ local_shell=require_local_shell,
751
+ web_search=require_web_search,
752
+ rule=rule,
753
+ schema=require_schema + schema,
754
+ toolkit=require_toolkit + toolkit,
755
+ image_generation=None,
756
+ rules_file=rules_file,
757
+ email_address=email_address,
758
+ toolkit_name=toolkit_name,
759
+ room_rules_paths=room_rules,
760
+ whitelist=whitelist,
761
+ require_shell=require_shell,
762
+ require_apply_patch=require_apply_patch,
763
+ require_storage=require_storage,
764
+ require_read_only_storage=require_read_only_storage,
765
+ require_time=require_time,
766
+ require_uuid=require_uuid,
767
+ require_table_read=require_table_read,
768
+ require_table_write=require_table_write,
769
+ require_computer_use=require_computer_use,
770
+ reply_all=reply_all,
771
+ database_namespace=database_namespace,
772
+ enable_attachments=enable_attachments,
773
+ working_directory=working_directory,
774
+ skill_dirs=skill_dir,
775
+ shell_image=shell_image,
776
+ llm_participant=llm_participant,
777
+ delegate_shell_token=delegate_shell_token,
778
+ log_llm_requests=log_llm_requests,
779
+ ),
780
+ )
781
+
782
+ if not get_deferred():
783
+ await run_services()
784
+
785
+
786
+ @app.async_command("spec")
787
+ async def spec(
788
+ *,
789
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
790
+ service_description: Annotated[
791
+ Optional[str], typer.Option("--service-description", help="service description")
792
+ ] = None,
793
+ service_title: Annotated[
794
+ Optional[str],
795
+ typer.Option("--service-title", help="a display name for the service"),
796
+ ] = None,
797
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
798
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
799
+ rules_file: Optional[str] = None,
800
+ require_toolkit: Annotated[
801
+ List[str],
802
+ typer.Option(
803
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
804
+ ),
805
+ ] = [],
806
+ require_schema: Annotated[
807
+ List[str],
808
+ typer.Option(
809
+ "--require-schema", "-rs", help="the name or url of a required schema"
810
+ ),
811
+ ] = [],
812
+ toolkit: Annotated[
813
+ List[str],
814
+ typer.Option(
815
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
816
+ ),
817
+ ] = [],
818
+ schema: Annotated[
819
+ List[str],
820
+ typer.Option(
821
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
822
+ ),
235
823
  ] = [],
236
824
  model: Annotated[
237
825
  str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
238
- ] = "gpt-5",
239
- local_shell: Annotated[
826
+ ] = "gpt-5.2",
827
+ require_shell: Annotated[
828
+ Optional[bool],
829
+ typer.Option(..., help="Enable function shell tool calling"),
830
+ ] = False,
831
+ require_local_shell: Annotated[
240
832
  Optional[bool], typer.Option(..., help="Enable local shell tool calling")
241
833
  ] = False,
242
- host: Annotated[Optional[str], typer.Option()] = None,
243
- port: Annotated[Optional[int], typer.Option()] = None,
244
- path: Annotated[str, typer.Option()] = "/agent",
245
- queue: Annotated[str, typer.Option(..., help="the name of the mail queue")],
834
+ require_web_search: Annotated[
835
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
836
+ ] = False,
837
+ require_apply_patch: Annotated[
838
+ Optional[bool],
839
+ typer.Option(..., help="Enable apply patch tool calling"),
840
+ ] = False,
841
+ host: Annotated[
842
+ Optional[str], typer.Option(help="Host to bind the service on")
843
+ ] = None,
844
+ port: Annotated[
845
+ Optional[int], typer.Option(help="Port to bind the service on")
846
+ ] = None,
847
+ path: Annotated[
848
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
849
+ ] = None,
850
+ queue: Annotated[
851
+ Optional[str], typer.Option(..., help="the name of the mail queue")
852
+ ] = None,
246
853
  email_address: Annotated[
247
854
  str, typer.Option(..., help="the email address of the agent")
248
855
  ],
856
+ toolkit_name: Annotated[
857
+ Optional[str],
858
+ typer.Option(..., help="the name of a toolkit to expose mail operations"),
859
+ ] = None,
860
+ room_rules: Annotated[
861
+ List[str],
862
+ typer.Option(
863
+ "--room-rules",
864
+ "-rr",
865
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
866
+ ),
867
+ ] = [],
868
+ whitelist: Annotated[
869
+ List[str],
870
+ typer.Option(
871
+ "--whitelist",
872
+ help="an email to whitelist",
873
+ ),
874
+ ] = [],
875
+ require_storage: Annotated[
876
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
877
+ ] = False,
878
+ require_read_only_storage: Annotated[
879
+ Optional[bool],
880
+ typer.Option(..., help="Enable read only storage toolkit"),
881
+ ] = False,
882
+ require_time: Annotated[
883
+ bool,
884
+ typer.Option(
885
+ ...,
886
+ help="Enable time/datetime tools",
887
+ ),
888
+ ] = True,
889
+ require_uuid: Annotated[
890
+ bool,
891
+ typer.Option(
892
+ ...,
893
+ help="Enable UUID generation tools",
894
+ ),
895
+ ] = False,
896
+ database_namespace: Annotated[
897
+ Optional[str],
898
+ typer.Option(..., help="Use a specific database namespace"),
899
+ ] = None,
900
+ require_table_read: Annotated[
901
+ list[str],
902
+ typer.Option(..., help="Enable table read tools for a specific table"),
903
+ ] = [],
904
+ require_table_write: Annotated[
905
+ list[str],
906
+ typer.Option(..., help="Enable table write tools for a specific table"),
907
+ ] = [],
908
+ require_computer_use: Annotated[
909
+ Optional[bool],
910
+ typer.Option(
911
+ ...,
912
+ help="Enable computer use (requires computer-use-preview model)",
913
+ hidden=True,
914
+ ),
915
+ ] = False,
916
+ reply_all: Annotated[
917
+ bool, typer.Option(help="Reply-all when responding to emails")
918
+ ] = False,
919
+ enable_attachments: Annotated[
920
+ bool, typer.Option(help="Allow downloading and processing email attachments")
921
+ ] = False,
922
+ working_directory: Annotated[
923
+ Optional[str],
924
+ typer.Option(..., help="The default working directory for shell commands"),
925
+ ] = None,
926
+ skill_dir: Annotated[
927
+ list[str],
928
+ typer.Option(..., help="an agent skills directory"),
929
+ ] = [],
930
+ llm_participant: Annotated[
931
+ Optional[str],
932
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
933
+ ] = None,
934
+ shell_image: Annotated[
935
+ Optional[str],
936
+ typer.Option(..., help="an image tag to use to run shell commands in"),
937
+ ] = None,
938
+ delegate_shell_token: Annotated[
939
+ Optional[bool],
940
+ typer.Option(..., help="Delegate the room token to shell tools"),
941
+ ] = False,
942
+ log_llm_requests: Annotated[
943
+ Optional[bool],
944
+ typer.Option(..., help="log all requests to the llm"),
945
+ ] = False,
249
946
  ):
250
- print("[bold green]Connecting to room...[/bold green]", flush=True)
947
+ service = get_service(host=host, port=port)
948
+ if path is None:
949
+ path = "/agent"
950
+ i = 0
951
+ while service.has_path(path):
952
+ i += 1
953
+ path = f"/agent{i}"
954
+
955
+ service.agents.append(
956
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "MailBot"})
957
+ )
251
958
 
252
- service = ServiceHost(host=host, port=port)
253
959
  service.add_path(
960
+ identity=agent_name,
254
961
  path=path,
255
962
  cls=build_mailbot(
256
963
  queue=queue,
257
964
  computer_use=None,
258
965
  model=model,
259
- local_shell=local_shell,
260
- agent_name=agent_name,
966
+ local_shell=require_local_shell,
967
+ web_search=require_web_search,
261
968
  rule=rule,
262
- toolkit=toolkit,
263
- schema=schema,
969
+ schema=require_schema + schema,
970
+ toolkit=require_toolkit + toolkit,
264
971
  image_generation=None,
265
972
  rules_file=rules_file,
266
973
  email_address=email_address,
974
+ toolkit_name=toolkit_name,
975
+ room_rules_paths=room_rules,
976
+ whitelist=whitelist,
977
+ require_shell=require_shell,
978
+ require_apply_patch=require_apply_patch,
979
+ require_storage=require_storage,
980
+ require_read_only_storage=require_read_only_storage,
981
+ require_time=require_time,
982
+ require_uuid=require_uuid,
983
+ require_table_read=require_table_read,
984
+ require_table_write=require_table_write,
985
+ require_computer_use=require_computer_use,
986
+ reply_all=reply_all,
987
+ database_namespace=database_namespace,
988
+ enable_attachments=enable_attachments,
989
+ working_directory=working_directory,
990
+ skill_dirs=skill_dir,
991
+ shell_image=shell_image,
992
+ llm_participant=llm_participant,
993
+ delegate_shell_token=delegate_shell_token,
994
+ log_llm_requests=log_llm_requests,
267
995
  ),
268
996
  )
269
997
 
270
- await service.run()
998
+ spec = service_specs()[0]
999
+ spec.metadata.annotations = {
1000
+ "meshagent.service.id": service_name,
1001
+ }
1002
+
1003
+ spec.metadata.name = service_name
1004
+ spec.metadata.description = service_description
1005
+ spec.container.image = (
1006
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
1007
+ )
1008
+ spec.container.command = shlex.join(
1009
+ ["meshagent", "mailbot", "service", *cleanup_args(sys.argv[2:])]
1010
+ )
1011
+
1012
+ print(yaml.dump(spec.model_dump(mode="json", exclude_none=True), sort_keys=False))
1013
+
1014
+
1015
+ @app.async_command("deploy")
1016
+ async def deploy(
1017
+ *,
1018
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
1019
+ service_description: Annotated[
1020
+ Optional[str], typer.Option("--service-description", help="service description")
1021
+ ] = None,
1022
+ service_title: Annotated[
1023
+ Optional[str],
1024
+ typer.Option("--service-title", help="a display name for the service"),
1025
+ ] = None,
1026
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
1027
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
1028
+ rules_file: Optional[str] = None,
1029
+ require_toolkit: Annotated[
1030
+ List[str],
1031
+ typer.Option(
1032
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
1033
+ ),
1034
+ ] = [],
1035
+ require_schema: Annotated[
1036
+ List[str],
1037
+ typer.Option(
1038
+ "--require-schema", "-rs", help="the name or url of a required schema"
1039
+ ),
1040
+ ] = [],
1041
+ toolkit: Annotated[
1042
+ List[str],
1043
+ typer.Option(
1044
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
1045
+ ),
1046
+ ] = [],
1047
+ schema: Annotated[
1048
+ List[str],
1049
+ typer.Option(
1050
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
1051
+ ),
1052
+ ] = [],
1053
+ model: Annotated[
1054
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
1055
+ ] = "gpt-5.2",
1056
+ require_shell: Annotated[
1057
+ Optional[bool],
1058
+ typer.Option(..., help="Enable function shell tool calling"),
1059
+ ] = False,
1060
+ require_local_shell: Annotated[
1061
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
1062
+ ] = False,
1063
+ require_web_search: Annotated[
1064
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
1065
+ ] = False,
1066
+ require_apply_patch: Annotated[
1067
+ Optional[bool],
1068
+ typer.Option(..., help="Enable apply patch tool calling"),
1069
+ ] = False,
1070
+ host: Annotated[
1071
+ Optional[str], typer.Option(help="Host to bind the service on")
1072
+ ] = None,
1073
+ port: Annotated[
1074
+ Optional[int], typer.Option(help="Port to bind the service on")
1075
+ ] = None,
1076
+ path: Annotated[
1077
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
1078
+ ] = None,
1079
+ queue: Annotated[
1080
+ Optional[str], typer.Option(..., help="the name of the mail queue")
1081
+ ] = None,
1082
+ email_address: Annotated[
1083
+ str, typer.Option(..., help="the email address of the agent")
1084
+ ],
1085
+ toolkit_name: Annotated[
1086
+ Optional[str],
1087
+ typer.Option(..., help="the name of a toolkit to expose mail operations"),
1088
+ ] = None,
1089
+ room_rules: Annotated[
1090
+ List[str],
1091
+ typer.Option(
1092
+ "--room-rules",
1093
+ "-rr",
1094
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
1095
+ ),
1096
+ ] = [],
1097
+ whitelist: Annotated[
1098
+ List[str],
1099
+ typer.Option(
1100
+ "--whitelist",
1101
+ help="an email to whitelist",
1102
+ ),
1103
+ ] = [],
1104
+ require_storage: Annotated[
1105
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1106
+ ] = False,
1107
+ require_read_only_storage: Annotated[
1108
+ Optional[bool],
1109
+ typer.Option(..., help="Enable read only storage toolkit"),
1110
+ ] = False,
1111
+ require_time: Annotated[
1112
+ bool,
1113
+ typer.Option(
1114
+ ...,
1115
+ help="Enable time/datetime tools",
1116
+ ),
1117
+ ] = True,
1118
+ require_uuid: Annotated[
1119
+ bool,
1120
+ typer.Option(
1121
+ ...,
1122
+ help="Enable UUID generation tools",
1123
+ ),
1124
+ ] = False,
1125
+ database_namespace: Annotated[
1126
+ Optional[str],
1127
+ typer.Option(..., help="Use a specific database namespace"),
1128
+ ] = None,
1129
+ require_table_read: Annotated[
1130
+ list[str],
1131
+ typer.Option(..., help="Enable table read tools for a specific table"),
1132
+ ] = [],
1133
+ require_table_write: Annotated[
1134
+ list[str],
1135
+ typer.Option(..., help="Enable table write tools for a specific table"),
1136
+ ] = [],
1137
+ require_computer_use: Annotated[
1138
+ Optional[bool],
1139
+ typer.Option(
1140
+ ...,
1141
+ help="Enable computer use (requires computer-use-preview model)",
1142
+ hidden=True,
1143
+ ),
1144
+ ] = False,
1145
+ reply_all: Annotated[
1146
+ bool, typer.Option(help="Reply-all when responding to emails")
1147
+ ] = False,
1148
+ enable_attachments: Annotated[
1149
+ bool, typer.Option(help="Allow downloading and processing email attachments")
1150
+ ] = False,
1151
+ working_directory: Annotated[
1152
+ Optional[str],
1153
+ typer.Option(..., help="The default working directory for shell commands"),
1154
+ ] = None,
1155
+ skill_dir: Annotated[
1156
+ list[str],
1157
+ typer.Option(..., help="an agent skills directory"),
1158
+ ] = [],
1159
+ llm_participant: Annotated[
1160
+ Optional[str],
1161
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
1162
+ ] = None,
1163
+ shell_image: Annotated[
1164
+ Optional[str],
1165
+ typer.Option(..., help="an image tag to use to run shell commands in"),
1166
+ ] = None,
1167
+ delegate_shell_token: Annotated[
1168
+ Optional[bool],
1169
+ typer.Option(..., help="Delegate the room token to shell tools"),
1170
+ ] = False,
1171
+ log_llm_requests: Annotated[
1172
+ Optional[bool],
1173
+ typer.Option(..., help="log all requests to the llm"),
1174
+ ] = False,
1175
+ project_id: ProjectIdOption,
1176
+ room: Annotated[
1177
+ Optional[str],
1178
+ typer.Option("--room", help="The name of a room to create the service for"),
1179
+ ] = None,
1180
+ ):
1181
+ project_id = await resolve_project_id(project_id=project_id)
1182
+
1183
+ service = get_service(host=host, port=port)
1184
+ if path is None:
1185
+ path = "/agent"
1186
+ i = 0
1187
+ while service.has_path(path):
1188
+ i += 1
1189
+ path = f"/agent{i}"
1190
+
1191
+ service.agents.append(
1192
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "MailBot"})
1193
+ )
1194
+
1195
+ service.add_path(
1196
+ identity=agent_name,
1197
+ path=path,
1198
+ cls=build_mailbot(
1199
+ queue=queue,
1200
+ computer_use=None,
1201
+ model=model,
1202
+ local_shell=require_local_shell,
1203
+ web_search=require_web_search,
1204
+ rule=rule,
1205
+ schema=require_schema + schema,
1206
+ toolkit=require_toolkit + toolkit,
1207
+ image_generation=None,
1208
+ rules_file=rules_file,
1209
+ email_address=email_address,
1210
+ toolkit_name=toolkit_name,
1211
+ room_rules_paths=room_rules,
1212
+ whitelist=whitelist,
1213
+ require_shell=require_shell,
1214
+ require_apply_patch=require_apply_patch,
1215
+ require_storage=require_storage,
1216
+ require_read_only_storage=require_read_only_storage,
1217
+ require_time=require_time,
1218
+ require_uuid=require_uuid,
1219
+ require_table_read=require_table_read,
1220
+ require_table_write=require_table_write,
1221
+ require_computer_use=require_computer_use,
1222
+ reply_all=reply_all,
1223
+ database_namespace=database_namespace,
1224
+ enable_attachments=enable_attachments,
1225
+ working_directory=working_directory,
1226
+ skill_dirs=skill_dir,
1227
+ shell_image=shell_image,
1228
+ llm_participant=llm_participant,
1229
+ delegate_shell_token=delegate_shell_token,
1230
+ log_llm_requests=log_llm_requests,
1231
+ ),
1232
+ )
1233
+
1234
+ spec = service_specs()[0]
1235
+ spec.metadata.annotations = {
1236
+ "meshagent.service.id": service_name,
1237
+ }
1238
+
1239
+ spec.metadata.name = service_name
1240
+ spec.metadata.description = service_description
1241
+ spec.container.image = (
1242
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
1243
+ )
1244
+ spec.container.command = shlex.join(
1245
+ ["meshagent", "mailbot", *cleanup_args(sys.argv[:2])]
1246
+ )
1247
+
1248
+ client = await get_client()
1249
+ try:
1250
+ id = None
1251
+ try:
1252
+ if id is None:
1253
+ if room is None:
1254
+ services = await client.list_services(project_id=project_id)
1255
+ else:
1256
+ services = await client.list_room_services(
1257
+ project_id=project_id, room_name=room
1258
+ )
1259
+
1260
+ for s in services:
1261
+ if s.metadata.name == spec.metadata.name:
1262
+ id = s.id
1263
+
1264
+ if id is None:
1265
+ if room is None:
1266
+ id = await client.create_service(
1267
+ project_id=project_id, service=spec
1268
+ )
1269
+ else:
1270
+ id = await client.create_room_service(
1271
+ project_id=project_id, service=spec, room_name=room
1272
+ )
1273
+
1274
+ else:
1275
+ spec.id = id
1276
+ if room is None:
1277
+ await client.update_service(
1278
+ project_id=project_id, service_id=id, service=spec
1279
+ )
1280
+ else:
1281
+ await client.update_room_service(
1282
+ project_id=project_id,
1283
+ service_id=id,
1284
+ service=spec,
1285
+ room_name=room,
1286
+ )
1287
+
1288
+ except ConflictError:
1289
+ print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
1290
+ raise typer.Exit(code=1)
1291
+ else:
1292
+ print(f"[green]Deployed service:[/] {id}")
1293
+
1294
+ finally:
1295
+ await client.close()