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/chatbot.py CHANGED
@@ -1,9 +1,16 @@
1
1
  import typer
2
2
  from rich import print
3
- from typing import Annotated, Optional
4
- from meshagent.tools import Toolkit
5
- from meshagent.tools.storage import StorageToolkitBuilder
3
+ from typing import Annotated, Optional, List
4
+ from meshagent.tools import Toolkit, ToolkitConfig
5
+ from meshagent.tools.storage import StorageToolkitBuilder, StorageToolkitConfig
6
+ from meshagent.tools.datetime import DatetimeToolkit
7
+ from meshagent.tools.uuid import UUIDToolkit
8
+ from meshagent.tools.document_tools import (
9
+ DocumentAuthoringToolkit,
10
+ DocumentTypeAuthoringToolkit,
11
+ )
6
12
  from meshagent.agents.config import RulesConfig
13
+ from meshagent.agents.widget_schema import widget_schema
7
14
 
8
15
  from meshagent.cli.common_options import (
9
16
  ProjectIdOption,
@@ -15,6 +22,7 @@ from meshagent.api import (
15
22
  ParticipantToken,
16
23
  ApiScope,
17
24
  RoomException,
25
+ RemoteParticipant,
18
26
  )
19
27
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
20
28
  from meshagent.cli import async_typer
@@ -23,11 +31,12 @@ from meshagent.cli.helper import (
23
31
  resolve_project_id,
24
32
  resolve_room,
25
33
  resolve_key,
34
+ cleanup_args,
26
35
  )
27
36
 
28
37
  from meshagent.openai import OpenAIResponsesAdapter
38
+ from meshagent.anthropic import AnthropicOpenAIResponsesStreamAdapter
29
39
 
30
- from typing import List
31
40
  from pathlib import Path
32
41
 
33
42
  from meshagent.openai.tools.responses_adapter import (
@@ -40,13 +49,36 @@ from meshagent.openai.tools.responses_adapter import (
40
49
  ApplyPatchConfig,
41
50
  ApplyPatchTool,
42
51
  ApplyPatchToolkitBuilder,
52
+ ShellToolkitBuilder,
53
+ ShellTool,
54
+ LocalShellToolkitBuilder,
55
+ LocalShellTool,
56
+ ImageGenerationConfig,
57
+ ImageGenerationToolkitBuilder,
58
+ ImageGenerationTool,
43
59
  )
44
60
 
61
+ from meshagent.tools.database import DatabaseToolkitBuilder, DatabaseToolkitConfig
62
+ from meshagent.agents.adapter import MessageStreamLLMAdapter
63
+
45
64
  from meshagent.api import RequiredToolkit, RequiredSchema
46
- from meshagent.api.services import ServiceHost
47
65
  import logging
48
66
  import os.path
49
67
 
68
+ from meshagent.api.specs.service import AgentSpec, ANNOTATION_AGENT_TYPE
69
+
70
+ from meshagent.cli.host import get_service, run_services, get_deferred, service_specs
71
+
72
+ import yaml
73
+
74
+ import shlex
75
+ import sys
76
+
77
+ import asyncio
78
+
79
+
80
+ from meshagent.api.client import ConflictError
81
+
50
82
  logger = logging.getLogger("chatbot")
51
83
 
52
84
  app = async_typer.AsyncTyper(help="Join a chatbot to a room")
@@ -55,7 +87,6 @@ app = async_typer.AsyncTyper(help="Join a chatbot to a room")
55
87
  def build_chatbot(
56
88
  *,
57
89
  model: str,
58
- agent_name: str,
59
90
  rule: List[str],
60
91
  toolkit: List[str],
61
92
  schema: List[str],
@@ -69,30 +100,34 @@ def build_chatbot(
69
100
  storage: Optional[str] = None,
70
101
  require_image_generation: Optional[str] = None,
71
102
  require_local_shell: Optional[str] = None,
72
- require_shell: Optional[str] = None,
103
+ require_shell: Optional[bool] = None,
73
104
  require_apply_patch: Optional[str] = None,
74
105
  require_computer_use: Optional[str] = None,
75
106
  require_web_search: Optional[str] = None,
76
107
  require_mcp: Optional[str] = None,
77
108
  require_storage: Optional[str] = None,
109
+ require_table_read: list[str] = None,
110
+ require_table_write: list[str] = None,
111
+ require_read_only_storage: Optional[str] = None,
112
+ require_time: bool = True,
113
+ require_uuid: bool = False,
78
114
  rules_file: Optional[str] = None,
79
- room_rules_path: Optional[str] = None,
115
+ room_rules_path: Optional[list[str]] = None,
116
+ require_discovery: Optional[str] = None,
117
+ require_document_authoring: Optional[str] = None,
80
118
  working_directory: Optional[str] = None,
119
+ llm_participant: Optional[str] = None,
120
+ database_namespace: Optional[list[str]] = None,
121
+ always_reply: Optional[bool] = None,
122
+ skill_dirs: Optional[list[str]] = None,
123
+ shell_image: Optional[str] = None,
124
+ log_llm_requests: Optional[bool] = None,
125
+ delegate_shell_token: Optional[bool] = None,
81
126
  ):
82
127
  from meshagent.agents.chat import ChatBot
83
128
 
84
129
  from meshagent.tools.storage import StorageToolkit
85
130
 
86
- from meshagent.agents.chat import (
87
- ChatBotThreadOpenAIImageGenerationToolkitBuilder,
88
- ChatBotThreadLocalShellToolkitBuilder,
89
- ChatBotThreadOpenAIImageGenerationTool,
90
- ChatBotThreadLocalShellTool,
91
- ChatBotThreadShellTool,
92
- ChatBotThreadShellToolkitBuilder,
93
- ImageGenerationConfig,
94
- )
95
-
96
131
  requirements = []
97
132
 
98
133
  toolkits = []
@@ -109,44 +144,113 @@ def build_chatbot(
109
144
  try:
110
145
  with open(Path(os.path.expanduser(rules_file)).resolve(), "r") as f:
111
146
  rules_config = RulesConfig.parse(f.read())
112
- rule = rules_config.rules
147
+ rule.extend(rules_config.rules)
113
148
  client_rules = rules_config.client_rules
114
149
 
115
150
  except FileNotFoundError:
116
151
  print(f"[yellow]rules file not found at {rules_file}[/yellow]")
117
152
 
118
153
  BaseClass = ChatBot
119
- if computer_use:
120
- from meshagent.computers.agent import ComputerAgent
121
-
122
- if ComputerAgent is None:
123
- raise RuntimeError(
124
- "Computer use is enabled, but meshagent.computers is not installed."
125
- )
126
- BaseClass = ComputerAgent
127
- llm_adapter = OpenAIResponsesAdapter(
128
- model=model,
129
- response_options={
130
- "reasoning": {"generate_summary": "concise"},
131
- "truncation": "auto",
132
- },
154
+ decision_model = None
155
+ if llm_participant:
156
+ llm_adapter = MessageStreamLLMAdapter(
157
+ participant_name=llm_participant,
133
158
  )
134
159
  else:
135
- llm_adapter = OpenAIResponsesAdapter(
136
- model=model,
137
- )
160
+ if computer_use or require_computer_use:
161
+ llm_adapter = OpenAIResponsesAdapter(
162
+ model=model,
163
+ response_options={
164
+ "reasoning": {"summary": "concise"},
165
+ "truncation": "auto",
166
+ },
167
+ log_requests=log_llm_requests,
168
+ )
169
+ else:
170
+ if model.startswith("claude-"):
171
+ llm_adapter = AnthropicOpenAIResponsesStreamAdapter(
172
+ model=model,
173
+ log_requests=log_llm_requests,
174
+ )
175
+ decision_model = model
176
+ else:
177
+ llm_adapter = OpenAIResponsesAdapter(
178
+ model=model,
179
+ log_requests=log_llm_requests,
180
+ )
138
181
 
139
182
  class CustomChatbot(BaseClass):
140
183
  def __init__(self):
141
184
  super().__init__(
142
185
  llm_adapter=llm_adapter,
143
- name=agent_name,
144
186
  requires=requirements,
145
187
  toolkits=toolkits,
146
188
  rules=rule if len(rule) > 0 else None,
147
189
  client_rules=client_rules,
190
+ always_reply=always_reply,
191
+ skill_dirs=skill_dirs,
192
+ decision_model=decision_model,
148
193
  )
149
194
 
195
+ async def start(self, *, room: RoomClient):
196
+ await super().start(room=room)
197
+
198
+ if room_rules_path is not None:
199
+ for p in room_rules_path:
200
+ await self._load_room_rules(path=p)
201
+
202
+ async def init_chat_context(self):
203
+ from meshagent.cli.helper import init_context_from_spec
204
+
205
+ context = await super().init_chat_context()
206
+ await init_context_from_spec(context)
207
+
208
+ return context
209
+
210
+ async def _load_room_rules(
211
+ self,
212
+ *,
213
+ path: str,
214
+ participant: Optional[RemoteParticipant] = None,
215
+ ):
216
+ rules = []
217
+ try:
218
+ room_rules = await self.room.storage.download(path=path)
219
+
220
+ rules_txt = room_rules.data.decode()
221
+
222
+ rules_config = RulesConfig.parse(rules_txt)
223
+
224
+ if rules_config.rules is not None:
225
+ rules.extend(rules_config.rules)
226
+
227
+ if participant is not None:
228
+ client = participant.get_attribute("client")
229
+
230
+ if rules_config.client_rules is not None and client is not None:
231
+ cr = rules_config.client_rules.get(client)
232
+ if cr is not None:
233
+ rules.extend(cr)
234
+
235
+ except RoomException:
236
+ try:
237
+ logger.info("attempting to initialize rules file")
238
+ handle = await self.room.storage.open(path=path, overwrite=False)
239
+ await self.room.storage.write(
240
+ handle=handle,
241
+ data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
242
+ )
243
+ await self.room.storage.close(handle=handle)
244
+
245
+ except RoomException:
246
+ pass
247
+ logger.info(
248
+ f"unable to load rules from {path}, continuing with default rules"
249
+ )
250
+ pass
251
+
252
+ return rules
253
+
150
254
  async def get_rules(self, *, thread_context, participant):
151
255
  rules = await super().get_rules(
152
256
  thread_context=thread_context, participant=participant
@@ -154,43 +258,11 @@ def build_chatbot(
154
258
 
155
259
  if room_rules_path is not None:
156
260
  for p in room_rules_path:
157
- try:
158
- room_rules = await self.room.storage.download(path=p)
159
-
160
- rules_txt = room_rules.data.decode()
161
-
162
- rules_config = RulesConfig.parse(rules_txt)
163
-
164
- if rules_config.rules is not None:
165
- rules.extend(rules_config.rules)
166
-
167
- client = participant.get_attribute("client")
168
-
169
- if rules_config.client_rules is not None and client is not None:
170
- cr = rules_config.client_rules.get(client)
171
- if cr is not None:
172
- rules.extend(cr)
173
-
174
- except RoomException:
175
- try:
176
- logger.info("attempting to initialize rules file")
177
- handle = await self.room.storage.open(
178
- path=p, overwrite=False
179
- )
180
- await self.room.storage.write(
181
- handle=handle,
182
- data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
183
- )
184
- await self.room.storage.close(handle=handle)
185
-
186
- except RoomException:
187
- pass
188
- logger.info(
189
- f"unable to load rules from {room_rules_path}, continuing with default rules"
190
- )
191
- pass
261
+ rules.extend(
262
+ await self._load_room_rules(path=p, participant=participant)
263
+ )
192
264
 
193
- print(f"using rules {rules}", flush=True)
265
+ logging.info(f"using rules {rules}")
194
266
 
195
267
  return rules
196
268
 
@@ -199,8 +271,7 @@ def build_chatbot(
199
271
 
200
272
  if require_image_generation:
201
273
  providers.append(
202
- ChatBotThreadOpenAIImageGenerationTool(
203
- thread_context=thread_context,
274
+ ImageGenerationTool(
204
275
  config=ImageGenerationConfig(
205
276
  name="image_generation",
206
277
  partial_images=3,
@@ -210,19 +281,24 @@ def build_chatbot(
210
281
 
211
282
  if require_local_shell:
212
283
  providers.append(
213
- ChatBotThreadLocalShellTool(
284
+ LocalShellTool(
214
285
  working_directory=working_directory,
215
- thread_context=thread_context,
216
286
  config=LocalShellConfig(name="local_shell"),
217
287
  )
218
288
  )
219
289
 
290
+ env = {}
291
+
292
+ if delegate_shell_token:
293
+ env["MESHAGENT_TOKEN"] = self.room.protocol.token
294
+
220
295
  if require_shell:
221
296
  providers.append(
222
- ChatBotThreadShellTool(
223
- thread_context=thread_context,
297
+ ShellTool(
224
298
  working_directory=working_directory,
225
299
  config=ShellConfig(name="shell"),
300
+ image=shell_image or "python:3.13",
301
+ env=env,
226
302
  )
227
303
  )
228
304
 
@@ -246,9 +322,80 @@ def build_chatbot(
246
322
  if require_storage:
247
323
  providers.extend(StorageToolkit().tools)
248
324
 
325
+ if len(require_table_read) > 0:
326
+ providers.extend(
327
+ (
328
+ await DatabaseToolkitBuilder().make(
329
+ room=self.room,
330
+ model=model,
331
+ config=DatabaseToolkitConfig(
332
+ tables=require_table_read,
333
+ read_only=True,
334
+ namespace=database_namespace,
335
+ ),
336
+ )
337
+ ).tools
338
+ )
339
+
340
+ if require_time:
341
+ providers.extend((DatetimeToolkit()).tools)
342
+
343
+ if require_uuid:
344
+ providers.extend((UUIDToolkit()).tools)
345
+
346
+ if len(require_table_write) > 0:
347
+ providers.extend(
348
+ (
349
+ await DatabaseToolkitBuilder().make(
350
+ room=self.room,
351
+ model=model,
352
+ config=DatabaseToolkitConfig(
353
+ tables=require_table_write,
354
+ read_only=False,
355
+ namespace=database_namespace,
356
+ ),
357
+ )
358
+ ).tools
359
+ )
360
+
361
+ if require_read_only_storage:
362
+ providers.extend(StorageToolkit(read_only=True).tools)
363
+
364
+ if require_document_authoring:
365
+ providers.extend(DocumentAuthoringToolkit().tools)
366
+ providers.extend(
367
+ DocumentTypeAuthoringToolkit(
368
+ schema=widget_schema, document_type="widget"
369
+ ).tools
370
+ )
371
+
372
+ if require_discovery:
373
+ from meshagent.tools.discovery import DiscoveryToolkit
374
+
375
+ providers.extend(DiscoveryToolkit().tools)
376
+
249
377
  tk = await super().get_thread_toolkits(
250
378
  thread_context=thread_context, participant=participant
251
379
  )
380
+
381
+ if require_computer_use:
382
+ from meshagent.computers.agent import ComputerToolkit
383
+
384
+ def render_screen(image_bytes: bytes):
385
+ for participant in thread_context.participants:
386
+ self.room.messaging.send_message_nowait(
387
+ to=participant,
388
+ type="computer_screen",
389
+ message={},
390
+ attachment=image_bytes,
391
+ )
392
+
393
+ computer_toolkit = ComputerToolkit(
394
+ room=self.room, render_screen=render_screen
395
+ )
396
+
397
+ tk.append(computer_toolkit)
398
+
252
399
  return [
253
400
  *(
254
401
  [Toolkit(name="tools", tools=providers)]
@@ -258,34 +405,27 @@ def build_chatbot(
258
405
  *tk,
259
406
  ]
260
407
 
261
- async def get_thread_toolkit_builders(self, *, thread_context, participant):
408
+ def get_toolkit_builders(self):
262
409
  providers = []
263
410
 
264
411
  if image_generation:
265
- providers.append(
266
- ChatBotThreadOpenAIImageGenerationToolkitBuilder(
267
- thread_context=thread_context
268
- )
269
- )
412
+ providers.append(ImageGenerationToolkitBuilder())
270
413
 
271
414
  if apply_patch:
272
- providers.append(
273
- ApplyPatchToolkitBuilder(thread_context=thread_context)
274
- )
415
+ providers.append(ApplyPatchToolkitBuilder())
275
416
 
276
417
  if local_shell:
277
418
  providers.append(
278
- ChatBotThreadLocalShellToolkitBuilder(
279
- thread_context=thread_context,
419
+ LocalShellToolkitBuilder(
280
420
  working_directory=working_directory,
281
421
  )
282
422
  )
283
423
 
284
424
  if shell:
285
425
  providers.append(
286
- ChatBotThreadShellToolkitBuilder(
287
- thread_context=thread_context,
426
+ ShellToolkitBuilder(
288
427
  working_directory=working_directory,
428
+ image=shell_image,
289
429
  )
290
430
  )
291
431
 
@@ -304,12 +444,14 @@ def build_chatbot(
304
444
 
305
445
 
306
446
  @app.async_command("join")
307
- async def make_call(
447
+ async def join(
308
448
  *,
309
- project_id: ProjectIdOption = None,
449
+ project_id: ProjectIdOption,
310
450
  room: RoomOption,
311
451
  role: str = "agent",
312
- agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
452
+ agent_name: Annotated[
453
+ Optional[str], typer.Option(..., help="Name of the agent to call")
454
+ ] = None,
313
455
  rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
314
456
  room_rules: Annotated[
315
457
  List[str],
@@ -320,17 +462,33 @@ async def make_call(
320
462
  ),
321
463
  ] = [],
322
464
  rules_file: Optional[str] = None,
465
+ require_toolkit: Annotated[
466
+ List[str],
467
+ typer.Option(
468
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
469
+ ),
470
+ ] = [],
471
+ require_schema: Annotated[
472
+ List[str],
473
+ typer.Option(
474
+ "--require-schema", "-rs", help="the name or url of a required schema"
475
+ ),
476
+ ] = [],
323
477
  toolkit: Annotated[
324
478
  List[str],
325
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
479
+ typer.Option(
480
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
481
+ ),
326
482
  ] = [],
327
483
  schema: Annotated[
328
484
  List[str],
329
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
485
+ typer.Option(
486
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
487
+ ),
330
488
  ] = [],
331
489
  model: Annotated[
332
490
  str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
333
- ] = "gpt-5.1",
491
+ ] = "gpt-5.2",
334
492
  image_generation: Annotated[
335
493
  Optional[str], typer.Option(..., help="Name of an image gen model")
336
494
  ] = None,
@@ -359,7 +517,7 @@ async def make_call(
359
517
  Optional[bool], typer.Option(..., help="Enable storage toolkit")
360
518
  ] = False,
361
519
  require_image_generation: Annotated[
362
- Optional[str], typer.Option(..., help="Name of an image gen model", hidden=True)
520
+ Optional[str], typer.Option(..., help="Name of an image gen model")
363
521
  ] = None,
364
522
  require_computer_use: Annotated[
365
523
  Optional[bool],
@@ -371,25 +529,63 @@ async def make_call(
371
529
  ] = False,
372
530
  require_local_shell: Annotated[
373
531
  Optional[bool],
374
- typer.Option(..., help="Enable local shell tool calling", hidden=True),
532
+ typer.Option(..., help="Enable local shell tool calling"),
375
533
  ] = False,
376
534
  require_shell: Annotated[
377
535
  Optional[bool],
378
- typer.Option(..., help="Enable function shell tool calling", hidden=True),
536
+ typer.Option(..., help="Enable function shell tool calling"),
379
537
  ] = False,
380
538
  require_apply_patch: Annotated[
381
539
  Optional[bool],
382
- typer.Option(..., help="Enable apply patch tool calling", hidden=True),
540
+ typer.Option(..., help="Enable apply patch tool calling"),
383
541
  ] = False,
384
542
  require_web_search: Annotated[
385
543
  Optional[bool],
386
- typer.Option(..., help="Enable web search tool calling", hidden=True),
544
+ typer.Option(..., help="Enable web search tool calling"),
387
545
  ] = False,
388
546
  require_mcp: Annotated[
389
- Optional[bool], typer.Option(..., help="Enable mcp tool calling", hidden=True)
547
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
390
548
  ] = False,
391
549
  require_storage: Annotated[
392
- Optional[bool], typer.Option(..., help="Enable storage toolkit", hidden=True)
550
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
551
+ ] = False,
552
+ database_namespace: Annotated[
553
+ Optional[str],
554
+ typer.Option(..., help="Use a specific database namespace"),
555
+ ] = None,
556
+ require_table_read: Annotated[
557
+ list[str],
558
+ typer.Option(..., help="Enable table read tools for a specific table"),
559
+ ] = [],
560
+ require_table_write: Annotated[
561
+ list[str],
562
+ typer.Option(..., help="Enable table write tools for a specific table"),
563
+ ] = [],
564
+ require_read_only_storage: Annotated[
565
+ Optional[bool],
566
+ typer.Option(..., help="Enable read only storage toolkit"),
567
+ ] = False,
568
+ require_time: Annotated[
569
+ bool,
570
+ typer.Option(
571
+ ...,
572
+ help="Enable time/datetime tools",
573
+ ),
574
+ ] = True,
575
+ require_uuid: Annotated[
576
+ bool,
577
+ typer.Option(
578
+ ...,
579
+ help="Enable UUID generation tools",
580
+ ),
581
+ ] = False,
582
+ require_document_authoring: Annotated[
583
+ Optional[bool],
584
+ typer.Option(..., help="Enable MeshDocument authoring"),
585
+ ] = False,
586
+ require_discovery: Annotated[
587
+ Optional[bool],
588
+ typer.Option(..., help="Enable discovery of agents and tools"),
393
589
  ] = False,
394
590
  working_directory: Annotated[
395
591
  Optional[str],
@@ -399,77 +595,126 @@ async def make_call(
399
595
  str,
400
596
  typer.Option("--key", help="an api key to sign the token with"),
401
597
  ] = None,
598
+ llm_participant: Annotated[
599
+ Optional[str],
600
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
601
+ ] = None,
602
+ always_reply: Annotated[
603
+ Optional[bool],
604
+ typer.Option(..., help="Always reply"),
605
+ ] = None,
606
+ skill_dir: Annotated[
607
+ list[str],
608
+ typer.Option(..., help="an agent skills directory"),
609
+ ] = [],
610
+ shell_image: Annotated[
611
+ Optional[str],
612
+ typer.Option(..., help="an image tag to use to run shell commands in"),
613
+ ] = None,
614
+ delegate_shell_token: Annotated[
615
+ Optional[bool],
616
+ typer.Option(..., help="log all requests to the llm"),
617
+ ] = False,
618
+ log_llm_requests: Annotated[
619
+ Optional[bool],
620
+ typer.Option(..., help="log all requests to the llm"),
621
+ ] = False,
402
622
  ):
623
+ if database_namespace is not None:
624
+ database_namespace = database_namespace.split("::")
625
+
403
626
  key = await resolve_key(project_id=project_id, key=key)
404
627
  account_client = await get_client()
405
628
  try:
406
629
  project_id = await resolve_project_id(project_id=project_id)
407
630
  room = resolve_room(room)
408
631
 
409
- token = ParticipantToken(
410
- name=agent_name,
411
- )
632
+ jwt = os.getenv("MESHAGENT_TOKEN")
633
+ if jwt is None:
634
+ if agent_name is None:
635
+ print(
636
+ "[bold red]--agent-name must be specified when the MESHAGENT_TOKEN environment variable is not set[/bold red]"
637
+ )
638
+ raise typer.Exit(1)
412
639
 
413
- token.add_api_grant(ApiScope.agent_default())
640
+ token = ParticipantToken(
641
+ name=agent_name,
642
+ )
414
643
 
415
- token.add_role_grant(role=role)
416
- token.add_room_grant(room)
644
+ token.add_api_grant(ApiScope.agent_default(tunnels=require_computer_use))
417
645
 
418
- jwt = token.to_jwt(api_key=key)
646
+ token.add_role_grant(role=role)
647
+ token.add_room_grant(room)
419
648
 
420
- print("[bold green]Connecting to room...[/bold green]", flush=True)
421
- async with RoomClient(
422
- protocol=WebSocketClientProtocol(
423
- url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
424
- token=jwt,
425
- )
426
- ) as client:
427
- requirements = []
649
+ jwt = token.to_jwt(api_key=key)
428
650
 
429
- for t in toolkit:
430
- requirements.append(RequiredToolkit(name=t))
651
+ print("[bold green]Connecting to room...[/bold green]", flush=True)
431
652
 
432
- for t in schema:
433
- requirements.append(RequiredSchema(name=t))
653
+ CustomChatbot = build_chatbot(
654
+ computer_use=computer_use,
655
+ require_computer_use=require_computer_use,
656
+ model=model,
657
+ rule=rule,
658
+ toolkit=require_toolkit + toolkit,
659
+ schema=require_schema + schema,
660
+ rules_file=rules_file,
661
+ local_shell=local_shell,
662
+ shell=shell,
663
+ apply_patch=apply_patch,
664
+ image_generation=image_generation,
665
+ web_search=web_search,
666
+ mcp=mcp,
667
+ storage=storage,
668
+ require_apply_patch=require_apply_patch,
669
+ require_web_search=require_web_search,
670
+ require_local_shell=require_local_shell,
671
+ require_shell=require_shell,
672
+ require_image_generation=require_image_generation,
673
+ require_mcp=require_mcp,
674
+ require_storage=require_storage,
675
+ require_table_read=require_table_read,
676
+ require_table_write=require_table_write,
677
+ require_read_only_storage=require_read_only_storage,
678
+ require_time=require_time,
679
+ require_uuid=require_uuid,
680
+ room_rules_path=room_rules,
681
+ require_document_authoring=require_document_authoring,
682
+ require_discovery=require_discovery,
683
+ working_directory=working_directory,
684
+ llm_participant=llm_participant,
685
+ always_reply=always_reply,
686
+ database_namespace=database_namespace,
687
+ skill_dirs=skill_dir,
688
+ shell_image=shell_image,
689
+ delegate_shell_token=delegate_shell_token,
690
+ log_llm_requests=log_llm_requests,
691
+ )
434
692
 
435
- CustomChatbot = build_chatbot(
436
- computer_use=computer_use,
437
- model=model,
438
- local_shell=local_shell,
439
- shell=shell,
440
- apply_patch=apply_patch,
441
- agent_name=agent_name,
442
- rule=rule,
443
- toolkit=toolkit,
444
- schema=schema,
445
- rules_file=rules_file,
446
- image_generation=image_generation,
447
- web_search=web_search,
448
- mcp=mcp,
449
- storage=storage,
450
- require_apply_patch=require_apply_patch,
451
- require_web_search=require_web_search,
452
- require_local_shell=require_local_shell,
453
- require_shell=require_shell,
454
- require_image_generation=require_image_generation,
455
- require_mcp=require_mcp,
456
- require_storage=require_storage,
457
- room_rules_path=room_rules,
458
- working_directory=working_directory,
459
- )
693
+ bot = CustomChatbot()
460
694
 
461
- bot = CustomChatbot()
695
+ if get_deferred():
696
+ from meshagent.cli.host import agents
462
697
 
463
- await bot.start(room=client)
464
- try:
465
- print(
466
- f"[bold green]Open the studio to interact with your agent: {meshagent_base_url().replace('api.', 'studio.')}/projects/{project_id}/rooms/{client.room_name}[/bold green]",
467
- flush=True,
698
+ agents.append((bot, jwt))
699
+ else:
700
+ async with RoomClient(
701
+ protocol=WebSocketClientProtocol(
702
+ url=websocket_room_url(
703
+ room_name=room, base_url=meshagent_base_url()
704
+ ),
705
+ token=jwt,
468
706
  )
469
- await client.protocol.wait_for_close()
470
- except KeyboardInterrupt:
471
- await bot.stop()
707
+ ) as client:
708
+ await bot.start(room=client)
709
+ try:
710
+ print(
711
+ f"[bold green]Open the studio to interact with your agent: {meshagent_base_url().replace('api.', 'studio.')}/projects/{project_id}/rooms/{client.room_name}[/bold green]",
712
+ flush=True,
713
+ )
714
+ await client.protocol.wait_for_close()
472
715
 
716
+ except KeyboardInterrupt:
717
+ await bot.stop()
473
718
  finally:
474
719
  await account_client.close()
475
720
 
@@ -488,17 +733,33 @@ async def service(
488
733
  help="a path to a rules file within the room that can be used to customize the agent's behavior",
489
734
  ),
490
735
  ] = [],
736
+ require_toolkit: Annotated[
737
+ List[str],
738
+ typer.Option(
739
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
740
+ ),
741
+ ] = [],
742
+ require_schema: Annotated[
743
+ List[str],
744
+ typer.Option(
745
+ "--require-schema", "-rs", help="the name or url of a required schema"
746
+ ),
747
+ ] = [],
491
748
  toolkit: Annotated[
492
749
  List[str],
493
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
750
+ typer.Option(
751
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
752
+ ),
494
753
  ] = [],
495
754
  schema: Annotated[
496
755
  List[str],
497
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
756
+ typer.Option(
757
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
758
+ ),
498
759
  ] = [],
499
760
  model: Annotated[
500
761
  str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
501
- ] = "gpt-5.1",
762
+ ] = "gpt-5.2",
502
763
  image_generation: Annotated[
503
764
  Optional[str], typer.Option(..., help="Name of an image gen model")
504
765
  ] = None,
@@ -527,7 +788,7 @@ async def service(
527
788
  Optional[bool], typer.Option(..., help="Enable storage toolkit")
528
789
  ] = False,
529
790
  require_image_generation: Annotated[
530
- Optional[str], typer.Option(..., help="Name of an image gen model", hidden=True)
791
+ Optional[str], typer.Option(..., help="Name of an image gen model")
531
792
  ] = None,
532
793
  require_computer_use: Annotated[
533
794
  Optional[bool],
@@ -539,53 +800,136 @@ async def service(
539
800
  ] = False,
540
801
  require_local_shell: Annotated[
541
802
  Optional[bool],
542
- typer.Option(..., help="Enable local shell tool calling", hidden=True),
803
+ typer.Option(..., help="Enable local shell tool calling"),
543
804
  ] = False,
544
805
  require_shell: Annotated[
545
806
  Optional[bool],
546
- typer.Option(..., help="Enable function shell tool calling", hidden=True),
807
+ typer.Option(..., help="Enable function shell tool calling"),
547
808
  ] = False,
548
809
  require_apply_patch: Annotated[
549
- Optional[bool], typer.Option(..., help="Enable apply patch tool", hidden=True)
810
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
550
811
  ] = False,
551
812
  require_web_search: Annotated[
552
813
  Optional[bool],
553
- typer.Option(..., help="Enable web search tool calling", hidden=True),
814
+ typer.Option(..., help="Enable web search tool calling"),
554
815
  ] = False,
555
816
  require_mcp: Annotated[
556
- Optional[bool], typer.Option(..., help="Enable mcp tool calling", hidden=True)
817
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
557
818
  ] = False,
558
819
  require_storage: Annotated[
559
- Optional[bool], typer.Option(..., help="Enable storage toolkit", hidden=True)
820
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
821
+ ] = False,
822
+ database_namespace: Annotated[
823
+ Optional[str],
824
+ typer.Option(..., help="Use a specific database namespace"),
825
+ ] = None,
826
+ require_table_read: Annotated[
827
+ list[str],
828
+ typer.Option(..., help="Enable table read tools for a specific table"),
829
+ ] = [],
830
+ require_table_write: Annotated[
831
+ list[str],
832
+ typer.Option(..., help="Enable table write tools for a specific table"),
833
+ ] = [],
834
+ require_read_only_storage: Annotated[
835
+ Optional[bool],
836
+ typer.Option(..., help="Enable read only storage toolkit"),
837
+ ] = False,
838
+ require_time: Annotated[
839
+ bool,
840
+ typer.Option(
841
+ ...,
842
+ help="Enable time/datetime tools",
843
+ ),
844
+ ] = True,
845
+ require_uuid: Annotated[
846
+ bool,
847
+ typer.Option(
848
+ ...,
849
+ help="Enable UUID generation tools",
850
+ ),
560
851
  ] = False,
561
852
  working_directory: Annotated[
562
853
  Optional[str],
563
854
  typer.Option(..., help="The default working directory for shell commands"),
564
855
  ] = None,
565
- host: Annotated[Optional[str], typer.Option()] = None,
566
- port: Annotated[Optional[int], typer.Option()] = None,
567
- path: Annotated[str, typer.Option()] = "/agent",
856
+ require_document_authoring: Annotated[
857
+ Optional[bool],
858
+ typer.Option(..., help="Enable document authoring"),
859
+ ] = False,
860
+ require_discovery: Annotated[
861
+ Optional[bool],
862
+ typer.Option(..., help="Enable discovery of agents and tools"),
863
+ ] = False,
864
+ llm_participant: Annotated[
865
+ Optional[str],
866
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
867
+ ] = None,
868
+ host: Annotated[
869
+ Optional[str], typer.Option(help="Host to bind the service on")
870
+ ] = None,
871
+ port: Annotated[
872
+ Optional[int], typer.Option(help="Port to bind the service on")
873
+ ] = None,
874
+ path: Annotated[
875
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
876
+ ] = None,
877
+ always_reply: Annotated[
878
+ Optional[bool],
879
+ typer.Option(..., help="Always reply"),
880
+ ] = None,
881
+ skill_dir: Annotated[
882
+ list[str],
883
+ typer.Option(..., help="an agent skills directory"),
884
+ ] = [],
885
+ shell_image: Annotated[
886
+ Optional[str],
887
+ typer.Option(..., help="an image tag to use to run shell commands in"),
888
+ ] = None,
889
+ delegate_shell_token: Annotated[
890
+ Optional[bool],
891
+ typer.Option(..., help="log all requests to the llm"),
892
+ ] = False,
893
+ log_llm_requests: Annotated[
894
+ Optional[bool],
895
+ typer.Option(..., help="log all requests to the llm"),
896
+ ] = False,
568
897
  ):
569
- print("[bold green]Connecting to room...[/bold green]", flush=True)
898
+ if database_namespace is not None:
899
+ database_namespace = database_namespace.split("::")
900
+
901
+ service = get_service(host=host, port=port)
902
+
903
+ if path is None:
904
+ path = "/agent"
905
+ i = 0
906
+ while service.has_path(path):
907
+ i += 1
908
+ path = f"/agent{i}"
909
+
910
+ service.agents.append(
911
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "ChatBot"})
912
+ )
570
913
 
571
- service = ServiceHost(host=host, port=port)
572
914
  service.add_path(
915
+ identity=agent_name,
573
916
  path=path,
574
917
  cls=build_chatbot(
575
918
  computer_use=computer_use,
919
+ require_computer_use=require_computer_use,
576
920
  model=model,
577
921
  local_shell=local_shell,
578
922
  shell=shell,
579
923
  apply_patch=apply_patch,
580
- agent_name=agent_name,
581
924
  rule=rule,
582
- toolkit=toolkit,
583
- schema=schema,
925
+ toolkit=require_toolkit + toolkit,
926
+ schema=require_schema + schema,
584
927
  rules_file=rules_file,
585
928
  web_search=web_search,
586
929
  image_generation=image_generation,
587
930
  mcp=mcp,
588
931
  storage=storage,
932
+ database_namespace=database_namespace,
589
933
  require_web_search=require_web_search,
590
934
  require_shell=require_shell,
591
935
  require_apply_patch=require_apply_patch,
@@ -593,9 +937,1047 @@ async def service(
593
937
  require_image_generation=require_image_generation,
594
938
  require_mcp=require_mcp,
595
939
  require_storage=require_storage,
940
+ require_table_write=require_table_write,
941
+ require_table_read=require_table_read,
942
+ require_read_only_storage=require_read_only_storage,
943
+ require_time=require_time,
944
+ require_uuid=require_uuid,
596
945
  room_rules_path=room_rules,
597
946
  working_directory=working_directory,
947
+ require_document_authoring=require_document_authoring,
948
+ require_discovery=require_discovery,
949
+ llm_participant=llm_participant,
950
+ always_reply=always_reply,
951
+ skill_dirs=skill_dir,
952
+ shell_image=shell_image,
953
+ delegate_shell_token=delegate_shell_token,
954
+ log_llm_requests=log_llm_requests,
598
955
  ),
599
956
  )
600
957
 
601
- await service.run()
958
+ if not get_deferred():
959
+ await run_services()
960
+
961
+
962
+ @app.async_command("spec")
963
+ async def spec(
964
+ *,
965
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
966
+ service_description: Annotated[
967
+ Optional[str], typer.Option("--service-description", help="service description")
968
+ ] = None,
969
+ service_title: Annotated[
970
+ Optional[str],
971
+ typer.Option("--service-title", help="a display name for the service"),
972
+ ] = None,
973
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
974
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
975
+ rules_file: Optional[str] = None,
976
+ room_rules: Annotated[
977
+ List[str],
978
+ typer.Option(
979
+ "--room-rules",
980
+ "-rr",
981
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
982
+ ),
983
+ ] = [],
984
+ require_toolkit: Annotated[
985
+ List[str],
986
+ typer.Option(
987
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
988
+ ),
989
+ ] = [],
990
+ require_schema: Annotated[
991
+ List[str],
992
+ typer.Option(
993
+ "--require-schema", "-rs", help="the name or url of a required schema"
994
+ ),
995
+ ] = [],
996
+ toolkit: Annotated[
997
+ List[str],
998
+ typer.Option(
999
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
1000
+ ),
1001
+ ] = [],
1002
+ schema: Annotated[
1003
+ List[str],
1004
+ typer.Option(
1005
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
1006
+ ),
1007
+ ] = [],
1008
+ model: Annotated[
1009
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
1010
+ ] = "gpt-5.2",
1011
+ image_generation: Annotated[
1012
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1013
+ ] = None,
1014
+ local_shell: Annotated[
1015
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
1016
+ ] = False,
1017
+ shell: Annotated[
1018
+ Optional[bool], typer.Option(..., help="Enable function shell tool calling")
1019
+ ] = False,
1020
+ apply_patch: Annotated[
1021
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1022
+ ] = False,
1023
+ computer_use: Annotated[
1024
+ Optional[bool],
1025
+ typer.Option(
1026
+ ..., help="Enable computer use (requires computer-use-preview model)"
1027
+ ),
1028
+ ] = False,
1029
+ web_search: Annotated[
1030
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
1031
+ ] = False,
1032
+ mcp: Annotated[
1033
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1034
+ ] = False,
1035
+ storage: Annotated[
1036
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1037
+ ] = False,
1038
+ require_image_generation: Annotated[
1039
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1040
+ ] = None,
1041
+ require_computer_use: Annotated[
1042
+ Optional[bool],
1043
+ typer.Option(
1044
+ ...,
1045
+ help="Enable computer use (requires computer-use-preview model)",
1046
+ hidden=True,
1047
+ ),
1048
+ ] = False,
1049
+ require_local_shell: Annotated[
1050
+ Optional[bool],
1051
+ typer.Option(..., help="Enable local shell tool calling"),
1052
+ ] = False,
1053
+ require_shell: Annotated[
1054
+ Optional[bool],
1055
+ typer.Option(..., help="Enable function shell tool calling"),
1056
+ ] = False,
1057
+ require_apply_patch: Annotated[
1058
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1059
+ ] = False,
1060
+ require_web_search: Annotated[
1061
+ Optional[bool],
1062
+ typer.Option(..., help="Enable web search tool calling"),
1063
+ ] = False,
1064
+ require_mcp: Annotated[
1065
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1066
+ ] = False,
1067
+ require_storage: Annotated[
1068
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1069
+ ] = False,
1070
+ database_namespace: Annotated[
1071
+ Optional[str],
1072
+ typer.Option(..., help="Use a specific database namespace"),
1073
+ ] = None,
1074
+ require_table_read: Annotated[
1075
+ list[str],
1076
+ typer.Option(..., help="Enable table read tools for a specific table"),
1077
+ ] = [],
1078
+ require_table_write: Annotated[
1079
+ list[str],
1080
+ typer.Option(..., help="Enable table write tools for a specific table"),
1081
+ ] = [],
1082
+ require_read_only_storage: Annotated[
1083
+ Optional[bool],
1084
+ typer.Option(..., help="Enable read only storage toolkit"),
1085
+ ] = False,
1086
+ require_time: Annotated[
1087
+ bool,
1088
+ typer.Option(
1089
+ ...,
1090
+ help="Enable time/datetime tools",
1091
+ ),
1092
+ ] = True,
1093
+ require_uuid: Annotated[
1094
+ bool,
1095
+ typer.Option(
1096
+ ...,
1097
+ help="Enable UUID generation tools",
1098
+ ),
1099
+ ] = False,
1100
+ working_directory: Annotated[
1101
+ Optional[str],
1102
+ typer.Option(..., help="The default working directory for shell commands"),
1103
+ ] = None,
1104
+ require_document_authoring: Annotated[
1105
+ Optional[bool],
1106
+ typer.Option(..., help="Enable document authoring"),
1107
+ ] = False,
1108
+ require_discovery: Annotated[
1109
+ Optional[bool],
1110
+ typer.Option(..., help="Enable discovery of agents and tools"),
1111
+ ] = False,
1112
+ llm_participant: Annotated[
1113
+ Optional[str],
1114
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
1115
+ ] = None,
1116
+ host: Annotated[
1117
+ Optional[str], typer.Option(help="Host to bind the service on")
1118
+ ] = None,
1119
+ port: Annotated[
1120
+ Optional[int], typer.Option(help="Port to bind the service on")
1121
+ ] = None,
1122
+ path: Annotated[
1123
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
1124
+ ] = None,
1125
+ always_reply: Annotated[
1126
+ Optional[bool],
1127
+ typer.Option(..., help="Always reply"),
1128
+ ] = None,
1129
+ skill_dir: Annotated[
1130
+ list[str],
1131
+ typer.Option(..., help="an agent skills directory"),
1132
+ ] = [],
1133
+ shell_image: Annotated[
1134
+ Optional[str],
1135
+ typer.Option(..., help="an image tag to use to run shell commands in"),
1136
+ ] = None,
1137
+ delegate_shell_token: Annotated[
1138
+ Optional[bool],
1139
+ typer.Option(..., help="log all requests to the llm"),
1140
+ ] = False,
1141
+ log_llm_requests: Annotated[
1142
+ Optional[bool],
1143
+ typer.Option(..., help="log all requests to the llm"),
1144
+ ] = False,
1145
+ ):
1146
+ if database_namespace is not None:
1147
+ database_namespace = database_namespace.split("::")
1148
+
1149
+ service = get_service(host=host, port=port)
1150
+
1151
+ if path is None:
1152
+ path = "/agent"
1153
+ i = 0
1154
+ while service.has_path(path):
1155
+ i += 1
1156
+ path = f"/agent{i}"
1157
+
1158
+ service.agents.append(
1159
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "ChatBot"})
1160
+ )
1161
+
1162
+ service.add_path(
1163
+ identity=agent_name,
1164
+ path=path,
1165
+ cls=build_chatbot(
1166
+ computer_use=computer_use,
1167
+ require_computer_use=require_computer_use,
1168
+ model=model,
1169
+ local_shell=local_shell,
1170
+ shell=shell,
1171
+ apply_patch=apply_patch,
1172
+ rule=rule,
1173
+ toolkit=require_toolkit + toolkit,
1174
+ schema=require_schema + schema,
1175
+ rules_file=rules_file,
1176
+ web_search=web_search,
1177
+ image_generation=image_generation,
1178
+ mcp=mcp,
1179
+ storage=storage,
1180
+ database_namespace=database_namespace,
1181
+ require_web_search=require_web_search,
1182
+ require_shell=require_shell,
1183
+ require_apply_patch=require_apply_patch,
1184
+ require_local_shell=require_local_shell,
1185
+ require_image_generation=require_image_generation,
1186
+ require_mcp=require_mcp,
1187
+ require_storage=require_storage,
1188
+ require_table_write=require_table_write,
1189
+ require_table_read=require_table_read,
1190
+ require_read_only_storage=require_read_only_storage,
1191
+ require_time=require_time,
1192
+ require_uuid=require_uuid,
1193
+ room_rules_path=room_rules,
1194
+ working_directory=working_directory,
1195
+ require_document_authoring=require_document_authoring,
1196
+ require_discovery=require_discovery,
1197
+ llm_participant=llm_participant,
1198
+ always_reply=always_reply,
1199
+ skill_dirs=skill_dir,
1200
+ shell_image=shell_image,
1201
+ delegate_shell_token=delegate_shell_token,
1202
+ log_llm_requests=log_llm_requests,
1203
+ ),
1204
+ )
1205
+
1206
+ spec = service_specs()[0]
1207
+ spec.metadata.annotations = {
1208
+ "meshagent.service.id": service_name,
1209
+ }
1210
+
1211
+ spec.metadata.name = service_name
1212
+ spec.metadata.description = service_description
1213
+ spec.container.image = (
1214
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
1215
+ )
1216
+ spec.container.command = shlex.join(
1217
+ ["meshagent", "chatbot", "service", *cleanup_args(sys.argv[2:])]
1218
+ )
1219
+
1220
+ print(yaml.dump(spec.model_dump(mode="json", exclude_none=True), sort_keys=False))
1221
+
1222
+
1223
+ @app.async_command("deploy")
1224
+ async def deploy(
1225
+ *,
1226
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
1227
+ service_description: Annotated[
1228
+ Optional[str], typer.Option("--service-description", help="service description")
1229
+ ] = None,
1230
+ service_title: Annotated[
1231
+ Optional[str],
1232
+ typer.Option("--service-title", help="a display name for the service"),
1233
+ ] = None,
1234
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
1235
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
1236
+ rules_file: Optional[str] = None,
1237
+ room_rules: Annotated[
1238
+ List[str],
1239
+ typer.Option(
1240
+ "--room-rules",
1241
+ "-rr",
1242
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
1243
+ ),
1244
+ ] = [],
1245
+ require_toolkit: Annotated[
1246
+ List[str],
1247
+ typer.Option(
1248
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
1249
+ ),
1250
+ ] = [],
1251
+ require_schema: Annotated[
1252
+ List[str],
1253
+ typer.Option(
1254
+ "--require-schema", "-rs", help="the name or url of a required schema"
1255
+ ),
1256
+ ] = [],
1257
+ toolkit: Annotated[
1258
+ List[str],
1259
+ typer.Option(
1260
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
1261
+ ),
1262
+ ] = [],
1263
+ schema: Annotated[
1264
+ List[str],
1265
+ typer.Option(
1266
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
1267
+ ),
1268
+ ] = [],
1269
+ model: Annotated[
1270
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
1271
+ ] = "gpt-5.2",
1272
+ image_generation: Annotated[
1273
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1274
+ ] = None,
1275
+ local_shell: Annotated[
1276
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
1277
+ ] = False,
1278
+ shell: Annotated[
1279
+ Optional[bool], typer.Option(..., help="Enable function shell tool calling")
1280
+ ] = False,
1281
+ apply_patch: Annotated[
1282
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1283
+ ] = False,
1284
+ computer_use: Annotated[
1285
+ Optional[bool],
1286
+ typer.Option(
1287
+ ..., help="Enable computer use (requires computer-use-preview model)"
1288
+ ),
1289
+ ] = False,
1290
+ web_search: Annotated[
1291
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
1292
+ ] = False,
1293
+ mcp: Annotated[
1294
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1295
+ ] = False,
1296
+ storage: Annotated[
1297
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1298
+ ] = False,
1299
+ require_image_generation: Annotated[
1300
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1301
+ ] = None,
1302
+ require_computer_use: Annotated[
1303
+ Optional[bool],
1304
+ typer.Option(
1305
+ ...,
1306
+ help="Enable computer use (requires computer-use-preview model)",
1307
+ hidden=True,
1308
+ ),
1309
+ ] = False,
1310
+ require_local_shell: Annotated[
1311
+ Optional[bool],
1312
+ typer.Option(..., help="Enable local shell tool calling"),
1313
+ ] = False,
1314
+ require_shell: Annotated[
1315
+ Optional[bool],
1316
+ typer.Option(..., help="Enable function shell tool calling"),
1317
+ ] = False,
1318
+ require_apply_patch: Annotated[
1319
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1320
+ ] = False,
1321
+ require_web_search: Annotated[
1322
+ Optional[bool],
1323
+ typer.Option(..., help="Enable web search tool calling"),
1324
+ ] = False,
1325
+ require_mcp: Annotated[
1326
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1327
+ ] = False,
1328
+ require_storage: Annotated[
1329
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1330
+ ] = False,
1331
+ database_namespace: Annotated[
1332
+ Optional[str],
1333
+ typer.Option(..., help="Use a specific database namespace"),
1334
+ ] = None,
1335
+ require_table_read: Annotated[
1336
+ list[str],
1337
+ typer.Option(..., help="Enable table read tools for a specific table"),
1338
+ ] = [],
1339
+ require_table_write: Annotated[
1340
+ list[str],
1341
+ typer.Option(..., help="Enable table write tools for a specific table"),
1342
+ ] = [],
1343
+ require_read_only_storage: Annotated[
1344
+ Optional[bool],
1345
+ typer.Option(..., help="Enable read only storage toolkit"),
1346
+ ] = False,
1347
+ require_time: Annotated[
1348
+ bool,
1349
+ typer.Option(
1350
+ ...,
1351
+ help="Enable time/datetime tools",
1352
+ ),
1353
+ ] = True,
1354
+ require_uuid: Annotated[
1355
+ bool,
1356
+ typer.Option(
1357
+ ...,
1358
+ help="Enable UUID generation tools",
1359
+ ),
1360
+ ] = False,
1361
+ working_directory: Annotated[
1362
+ Optional[str],
1363
+ typer.Option(..., help="The default working directory for shell commands"),
1364
+ ] = None,
1365
+ require_document_authoring: Annotated[
1366
+ Optional[bool],
1367
+ typer.Option(..., help="Enable document authoring"),
1368
+ ] = False,
1369
+ require_discovery: Annotated[
1370
+ Optional[bool],
1371
+ typer.Option(..., help="Enable discovery of agents and tools"),
1372
+ ] = False,
1373
+ llm_participant: Annotated[
1374
+ Optional[str],
1375
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
1376
+ ] = None,
1377
+ host: Annotated[
1378
+ Optional[str], typer.Option(help="Host to bind the service on")
1379
+ ] = None,
1380
+ port: Annotated[
1381
+ Optional[int], typer.Option(help="Port to bind the service on")
1382
+ ] = None,
1383
+ path: Annotated[
1384
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
1385
+ ] = None,
1386
+ always_reply: Annotated[
1387
+ Optional[bool],
1388
+ typer.Option(..., help="Always reply"),
1389
+ ] = None,
1390
+ skill_dir: Annotated[
1391
+ list[str],
1392
+ typer.Option(..., help="an agent skills directory"),
1393
+ ] = [],
1394
+ shell_image: Annotated[
1395
+ Optional[str],
1396
+ typer.Option(..., help="an image tag to use to run shell commands in"),
1397
+ ] = None,
1398
+ delegate_shell_token: Annotated[
1399
+ Optional[bool],
1400
+ typer.Option(..., help="log all requests to the llm"),
1401
+ ] = False,
1402
+ log_llm_requests: Annotated[
1403
+ Optional[bool],
1404
+ typer.Option(..., help="log all requests to the llm"),
1405
+ ] = False,
1406
+ project_id: ProjectIdOption,
1407
+ room: Annotated[
1408
+ Optional[str],
1409
+ typer.Option("--room", help="The name of a room to create the service for"),
1410
+ ] = None,
1411
+ ):
1412
+ project_id = await resolve_project_id(project_id=project_id)
1413
+
1414
+ if database_namespace is not None:
1415
+ database_namespace = database_namespace.split("::")
1416
+
1417
+ service = get_service(host=host, port=port)
1418
+
1419
+ if path is None:
1420
+ path = "/agent"
1421
+ i = 0
1422
+ while service.has_path(path):
1423
+ i += 1
1424
+ path = f"/agent{i}"
1425
+
1426
+ service.agents.append(
1427
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "ChatBot"})
1428
+ )
1429
+
1430
+ service.add_path(
1431
+ identity=agent_name,
1432
+ path=path,
1433
+ cls=build_chatbot(
1434
+ computer_use=computer_use,
1435
+ require_computer_use=require_computer_use,
1436
+ model=model,
1437
+ local_shell=local_shell,
1438
+ shell=shell,
1439
+ apply_patch=apply_patch,
1440
+ rule=rule,
1441
+ toolkit=require_toolkit + toolkit,
1442
+ schema=require_schema + schema,
1443
+ rules_file=rules_file,
1444
+ web_search=web_search,
1445
+ image_generation=image_generation,
1446
+ mcp=mcp,
1447
+ storage=storage,
1448
+ database_namespace=database_namespace,
1449
+ require_web_search=require_web_search,
1450
+ require_shell=require_shell,
1451
+ require_apply_patch=require_apply_patch,
1452
+ require_local_shell=require_local_shell,
1453
+ require_image_generation=require_image_generation,
1454
+ require_mcp=require_mcp,
1455
+ require_storage=require_storage,
1456
+ require_table_write=require_table_write,
1457
+ require_table_read=require_table_read,
1458
+ require_read_only_storage=require_read_only_storage,
1459
+ require_time=require_time,
1460
+ require_uuid=require_uuid,
1461
+ room_rules_path=room_rules,
1462
+ working_directory=working_directory,
1463
+ require_document_authoring=require_document_authoring,
1464
+ require_discovery=require_discovery,
1465
+ llm_participant=llm_participant,
1466
+ always_reply=always_reply,
1467
+ skill_dirs=skill_dir,
1468
+ shell_image=shell_image,
1469
+ delegate_shell_token=delegate_shell_token,
1470
+ log_llm_requests=log_llm_requests,
1471
+ ),
1472
+ )
1473
+
1474
+ spec = service_specs()[0]
1475
+
1476
+ for port in spec.ports:
1477
+ port
1478
+
1479
+ spec.metadata.annotations = {
1480
+ "meshagent.service.id": service_name,
1481
+ }
1482
+
1483
+ spec.metadata.name = service_name
1484
+ spec.metadata.description = service_description
1485
+ spec.container.image = (
1486
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
1487
+ )
1488
+ spec.container.command = shlex.join(
1489
+ ["meshagent", "chatbot", "service", *cleanup_args(sys.argv[2:])]
1490
+ )
1491
+
1492
+ project_id = await resolve_project_id(project_id)
1493
+
1494
+ client = await get_client()
1495
+ try:
1496
+ id = None
1497
+ try:
1498
+ if id is None:
1499
+ if room is None:
1500
+ services = await client.list_services(project_id=project_id)
1501
+ else:
1502
+ services = await client.list_room_services(
1503
+ project_id=project_id, room_name=room
1504
+ )
1505
+
1506
+ for s in services:
1507
+ if s.metadata.name == spec.metadata.name:
1508
+ id = s.id
1509
+
1510
+ if id is None:
1511
+ if room is None:
1512
+ id = await client.create_service(
1513
+ project_id=project_id, service=spec
1514
+ )
1515
+ else:
1516
+ id = await client.create_room_service(
1517
+ project_id=project_id, service=spec, room_name=room
1518
+ )
1519
+
1520
+ else:
1521
+ spec.id = id
1522
+ if room is None:
1523
+ await client.update_service(
1524
+ project_id=project_id, service_id=id, service=spec
1525
+ )
1526
+ else:
1527
+ await client.update_room_service(
1528
+ project_id=project_id,
1529
+ service_id=id,
1530
+ service=spec,
1531
+ room_name=room,
1532
+ )
1533
+
1534
+ except ConflictError:
1535
+ print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
1536
+ raise typer.Exit(code=1)
1537
+ else:
1538
+ print(f"[green]Deployed service:[/] {id}")
1539
+
1540
+ finally:
1541
+ await client.close()
1542
+
1543
+
1544
+ async def chat_with(
1545
+ *,
1546
+ participant_name: str,
1547
+ project_id: str,
1548
+ room: str,
1549
+ thread_path: str,
1550
+ message: Optional[str] = None,
1551
+ use_web_search: bool = False,
1552
+ use_image_gen: bool = False,
1553
+ use_storage: bool = False,
1554
+ ):
1555
+ from prompt_toolkit.shortcuts import PromptSession
1556
+ from prompt_toolkit.key_binding import KeyBindings
1557
+ from meshagent.agents.chat import ChatBotClient
1558
+
1559
+ kb = KeyBindings()
1560
+
1561
+ session = PromptSession("> ", key_bindings=kb)
1562
+
1563
+ account_client = await get_client()
1564
+ try:
1565
+ connection = await account_client.connect_room(project_id=project_id, room=room)
1566
+ async with RoomClient(
1567
+ protocol=WebSocketClientProtocol(
1568
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
1569
+ token=connection.jwt,
1570
+ ),
1571
+ ) as user_client:
1572
+ await user_client.messaging.enable()
1573
+
1574
+ if thread_path is None:
1575
+ thread_path = f".threads/{participant_name}/{user_client.local_participant.get_attribute('name')}.thread"
1576
+
1577
+ async with ChatBotClient(
1578
+ room=user_client,
1579
+ participant_name=participant_name,
1580
+ thread_path=thread_path,
1581
+ ) as chat_client:
1582
+
1583
+ @kb.add("c-l")
1584
+ def _(event):
1585
+ event.app.renderer.clear()
1586
+ asyncio.ensure_future(chat_client.clear())
1587
+
1588
+ while True:
1589
+ user_input = message or await session.prompt_async()
1590
+
1591
+ if user_input == "/clear":
1592
+ await chat_client.clear()
1593
+
1594
+ else:
1595
+ tools: list[ToolkitConfig] = []
1596
+
1597
+ if use_web_search:
1598
+ tools.append(WebSearchConfig())
1599
+
1600
+ elif use_image_gen:
1601
+ tools.append(ImageGenerationConfig())
1602
+
1603
+ elif use_storage:
1604
+ tools.append(StorageToolkitConfig())
1605
+
1606
+ await chat_client.send(text=user_input, tools=tools)
1607
+
1608
+ response = await chat_client.receive()
1609
+
1610
+ print(response)
1611
+
1612
+ if message:
1613
+ break
1614
+
1615
+ except asyncio.CancelledError:
1616
+ pass
1617
+
1618
+ finally:
1619
+ await account_client.close()
1620
+
1621
+
1622
+ @app.async_command("run")
1623
+ async def run(
1624
+ *,
1625
+ project_id: ProjectIdOption,
1626
+ room: RoomOption,
1627
+ role: str = "agent",
1628
+ agent_name: Annotated[
1629
+ Optional[str], typer.Option(..., help="Name of the agent to call")
1630
+ ] = None,
1631
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
1632
+ room_rules: Annotated[
1633
+ List[str],
1634
+ typer.Option(
1635
+ "--room-rules",
1636
+ "-rr",
1637
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
1638
+ ),
1639
+ ] = [],
1640
+ rules_file: Optional[str] = None,
1641
+ require_toolkit: Annotated[
1642
+ List[str],
1643
+ typer.Option(
1644
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
1645
+ ),
1646
+ ] = [],
1647
+ require_schema: Annotated[
1648
+ List[str],
1649
+ typer.Option(
1650
+ "--require-schema", "-rs", help="the name or url of a required schema"
1651
+ ),
1652
+ ] = [],
1653
+ toolkit: Annotated[
1654
+ List[str],
1655
+ typer.Option(
1656
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
1657
+ ),
1658
+ ] = [],
1659
+ schema: Annotated[
1660
+ List[str],
1661
+ typer.Option(
1662
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
1663
+ ),
1664
+ ] = [],
1665
+ model: Annotated[
1666
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
1667
+ ] = "gpt-5.2",
1668
+ image_generation: Annotated[
1669
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1670
+ ] = None,
1671
+ computer_use: Annotated[
1672
+ Optional[bool],
1673
+ typer.Option(
1674
+ ..., help="Enable computer use (requires computer-use-preview model)"
1675
+ ),
1676
+ ] = False,
1677
+ local_shell: Annotated[
1678
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
1679
+ ] = False,
1680
+ shell: Annotated[
1681
+ Optional[bool], typer.Option(..., help="Enable function shell tool calling")
1682
+ ] = False,
1683
+ apply_patch: Annotated[
1684
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1685
+ ] = False,
1686
+ web_search: Annotated[
1687
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
1688
+ ] = False,
1689
+ mcp: Annotated[
1690
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1691
+ ] = False,
1692
+ storage: Annotated[
1693
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1694
+ ] = False,
1695
+ require_image_generation: Annotated[
1696
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1697
+ ] = None,
1698
+ require_computer_use: Annotated[
1699
+ Optional[bool],
1700
+ typer.Option(
1701
+ ...,
1702
+ help="Enable computer use (requires computer-use-preview model)",
1703
+ hidden=True,
1704
+ ),
1705
+ ] = False,
1706
+ require_local_shell: Annotated[
1707
+ Optional[bool],
1708
+ typer.Option(..., help="Enable local shell tool calling"),
1709
+ ] = False,
1710
+ require_shell: Annotated[
1711
+ Optional[bool],
1712
+ typer.Option(..., help="Enable function shell tool calling"),
1713
+ ] = False,
1714
+ require_apply_patch: Annotated[
1715
+ Optional[bool],
1716
+ typer.Option(..., help="Enable apply patch tool calling"),
1717
+ ] = False,
1718
+ require_web_search: Annotated[
1719
+ Optional[bool],
1720
+ typer.Option(..., help="Enable web search tool calling"),
1721
+ ] = False,
1722
+ require_mcp: Annotated[
1723
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1724
+ ] = False,
1725
+ require_storage: Annotated[
1726
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1727
+ ] = False,
1728
+ database_namespace: Annotated[
1729
+ Optional[str],
1730
+ typer.Option(..., help="Use a specific database namespace"),
1731
+ ] = None,
1732
+ require_table_read: Annotated[
1733
+ list[str],
1734
+ typer.Option(..., help="Enable table read tools for a specific table"),
1735
+ ] = [],
1736
+ require_table_write: Annotated[
1737
+ list[str],
1738
+ typer.Option(..., help="Enable table write tools for a specific table"),
1739
+ ] = [],
1740
+ require_read_only_storage: Annotated[
1741
+ Optional[bool],
1742
+ typer.Option(..., help="Enable read only storage toolkit"),
1743
+ ] = False,
1744
+ require_time: Annotated[
1745
+ bool,
1746
+ typer.Option(
1747
+ ...,
1748
+ help="Enable time/datetime tools",
1749
+ ),
1750
+ ] = True,
1751
+ require_uuid: Annotated[
1752
+ bool,
1753
+ typer.Option(
1754
+ ...,
1755
+ help="Enable UUID generation tools",
1756
+ ),
1757
+ ] = False,
1758
+ require_document_authoring: Annotated[
1759
+ Optional[bool],
1760
+ typer.Option(..., help="Enable MeshDocument authoring"),
1761
+ ] = False,
1762
+ require_discovery: Annotated[
1763
+ Optional[bool],
1764
+ typer.Option(..., help="Enable discovery of agents and tools"),
1765
+ ] = False,
1766
+ working_directory: Annotated[
1767
+ Optional[str],
1768
+ typer.Option(..., help="The default working directory for shell commands"),
1769
+ ] = None,
1770
+ key: Annotated[
1771
+ str,
1772
+ typer.Option("--key", help="an api key to sign the token with"),
1773
+ ] = None,
1774
+ llm_participant: Annotated[
1775
+ Optional[str],
1776
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
1777
+ ] = None,
1778
+ always_reply: Annotated[
1779
+ Optional[bool],
1780
+ typer.Option(..., help="Always reply"),
1781
+ ] = None,
1782
+ skill_dir: Annotated[
1783
+ list[str],
1784
+ typer.Option(..., help="an agent skills directory"),
1785
+ ] = [],
1786
+ shell_image: Annotated[
1787
+ Optional[str],
1788
+ typer.Option(..., help="an image tag to use to run shell commands in"),
1789
+ ] = None,
1790
+ delegate_shell_token: Annotated[
1791
+ Optional[bool],
1792
+ typer.Option(..., help="log all requests to the llm"),
1793
+ ] = False,
1794
+ log_llm_requests: Annotated[
1795
+ Optional[bool],
1796
+ typer.Option(..., help="log all requests to the llm"),
1797
+ ] = False,
1798
+ thread_path: Annotated[
1799
+ Optional[str],
1800
+ typer.Option(..., help="log all requests to the llm"),
1801
+ ] = None,
1802
+ message: Annotated[
1803
+ Optional[str],
1804
+ typer.Option(..., help="the input message to use"),
1805
+ ] = None,
1806
+ use_web_search: Annotated[
1807
+ Optional[bool],
1808
+ typer.Option(..., help="request the web search tool"),
1809
+ ] = None,
1810
+ use_image_gen: Annotated[
1811
+ Optional[bool],
1812
+ typer.Option(..., help="request the image gen tool"),
1813
+ ] = None,
1814
+ use_storage: Annotated[
1815
+ Optional[bool],
1816
+ typer.Option(..., help="request the storage tool"),
1817
+ ] = None,
1818
+ ):
1819
+ root = logging.getLogger()
1820
+ root.setLevel(logging.ERROR)
1821
+
1822
+ if database_namespace is not None:
1823
+ database_namespace = database_namespace.split("::")
1824
+
1825
+ key = await resolve_key(project_id=project_id, key=key)
1826
+ account_client = await get_client()
1827
+ try:
1828
+ project_id = await resolve_project_id(project_id=project_id)
1829
+ room = resolve_room(room)
1830
+
1831
+ jwt = os.getenv("MESHAGENT_TOKEN")
1832
+ if jwt is None:
1833
+ if agent_name is None:
1834
+ print(
1835
+ "[bold red]--agent-name must be specified when the MESHAGENT_TOKEN environment variable is not set[/bold red]"
1836
+ )
1837
+ raise typer.Exit(1)
1838
+
1839
+ token = ParticipantToken(
1840
+ name=agent_name,
1841
+ )
1842
+
1843
+ token.add_api_grant(ApiScope.agent_default(tunnels=require_computer_use))
1844
+
1845
+ token.add_role_grant(role=role)
1846
+ token.add_room_grant(room)
1847
+
1848
+ jwt = token.to_jwt(api_key=key)
1849
+
1850
+ async with RoomClient(
1851
+ protocol=WebSocketClientProtocol(
1852
+ url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
1853
+ token=jwt,
1854
+ )
1855
+ ) as client:
1856
+ CustomChatbot = build_chatbot(
1857
+ computer_use=computer_use,
1858
+ require_computer_use=require_computer_use,
1859
+ model=model,
1860
+ rule=rule,
1861
+ toolkit=require_toolkit + toolkit,
1862
+ schema=require_schema + schema,
1863
+ rules_file=rules_file,
1864
+ local_shell=local_shell,
1865
+ shell=shell,
1866
+ apply_patch=apply_patch,
1867
+ image_generation=image_generation,
1868
+ web_search=web_search,
1869
+ mcp=mcp,
1870
+ storage=storage,
1871
+ require_apply_patch=require_apply_patch,
1872
+ require_web_search=require_web_search,
1873
+ require_local_shell=require_local_shell,
1874
+ require_shell=require_shell,
1875
+ require_image_generation=require_image_generation,
1876
+ require_mcp=require_mcp,
1877
+ require_storage=require_storage,
1878
+ require_table_read=require_table_read,
1879
+ require_table_write=require_table_write,
1880
+ require_read_only_storage=require_read_only_storage,
1881
+ require_time=require_time,
1882
+ require_uuid=require_uuid,
1883
+ room_rules_path=room_rules,
1884
+ require_document_authoring=require_document_authoring,
1885
+ require_discovery=require_discovery,
1886
+ working_directory=working_directory,
1887
+ llm_participant=llm_participant,
1888
+ always_reply=always_reply,
1889
+ database_namespace=database_namespace,
1890
+ skill_dirs=skill_dir,
1891
+ shell_image=shell_image,
1892
+ delegate_shell_token=delegate_shell_token,
1893
+ log_llm_requests=log_llm_requests,
1894
+ )
1895
+
1896
+ bot = CustomChatbot()
1897
+
1898
+ await bot.start(room=client)
1899
+
1900
+ _, pending = await asyncio.wait(
1901
+ [
1902
+ asyncio.create_task(client.protocol.wait_for_close()),
1903
+ asyncio.create_task(
1904
+ chat_with(
1905
+ participant_name=client.local_participant.get_attribute(
1906
+ "name"
1907
+ ),
1908
+ room=room,
1909
+ project_id=project_id,
1910
+ thread_path=thread_path,
1911
+ message=message,
1912
+ use_web_search=use_web_search,
1913
+ use_image_gen=use_image_gen,
1914
+ use_storage=use_storage,
1915
+ )
1916
+ ),
1917
+ ],
1918
+ return_when="FIRST_COMPLETED",
1919
+ )
1920
+
1921
+ for t in pending:
1922
+ t.cancel()
1923
+
1924
+ except asyncio.CancelledError:
1925
+ return
1926
+
1927
+ finally:
1928
+ await account_client.close()
1929
+
1930
+
1931
+ @app.async_command("use")
1932
+ async def use(
1933
+ *,
1934
+ project_id: ProjectIdOption,
1935
+ room: RoomOption,
1936
+ agent_name: Annotated[
1937
+ Optional[str], typer.Option(..., help="Name of the agent to call")
1938
+ ] = None,
1939
+ thread_path: Annotated[
1940
+ Optional[str],
1941
+ typer.Option(..., help="log all requests to the llm"),
1942
+ ] = None,
1943
+ message: Annotated[
1944
+ Optional[str],
1945
+ typer.Option(..., help="the input message to use"),
1946
+ ] = None,
1947
+ use_web_search: Annotated[
1948
+ Optional[bool],
1949
+ typer.Option(..., help="request the web search tool"),
1950
+ ] = None,
1951
+ use_image_gen: Annotated[
1952
+ Optional[bool],
1953
+ typer.Option(..., help="request the image gen tool"),
1954
+ ] = None,
1955
+ use_storage: Annotated[
1956
+ Optional[bool],
1957
+ typer.Option(..., help="request the storage tool"),
1958
+ ] = None,
1959
+ ):
1960
+ root = logging.getLogger()
1961
+ root.setLevel(logging.ERROR)
1962
+
1963
+ account_client = await get_client()
1964
+ try:
1965
+ project_id = await resolve_project_id(project_id=project_id)
1966
+ room = resolve_room(room)
1967
+
1968
+ await chat_with(
1969
+ participant_name=agent_name,
1970
+ room=room,
1971
+ project_id=project_id,
1972
+ thread_path=thread_path,
1973
+ message=message,
1974
+ use_web_search=use_web_search,
1975
+ use_image_gen=use_image_gen,
1976
+ use_storage=use_storage,
1977
+ )
1978
+
1979
+ except asyncio.CancelledError:
1980
+ return
1981
+
1982
+ finally:
1983
+ await account_client.close()