meshagent-cli 0.7.0__py3-none-any.whl → 0.21.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 (41) hide show
  1. meshagent/cli/agent.py +15 -11
  2. meshagent/cli/api_keys.py +4 -4
  3. meshagent/cli/async_typer.py +52 -4
  4. meshagent/cli/call.py +12 -8
  5. meshagent/cli/chatbot.py +1007 -129
  6. meshagent/cli/cli.py +21 -20
  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 +62 -11
  15. meshagent/cli/helpers.py +66 -9
  16. meshagent/cli/host.py +37 -0
  17. meshagent/cli/mailbot.py +1004 -40
  18. meshagent/cli/mailboxes.py +223 -0
  19. meshagent/cli/meeting_transcriber.py +10 -4
  20. meshagent/cli/messaging.py +7 -7
  21. meshagent/cli/multi.py +402 -0
  22. meshagent/cli/oauth2.py +44 -21
  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 +20 -212
  27. meshagent/cli/rooms.py +214 -0
  28. meshagent/cli/services.py +32 -23
  29. meshagent/cli/sessions.py +5 -5
  30. meshagent/cli/storage.py +5 -5
  31. meshagent/cli/task_runner.py +770 -0
  32. meshagent/cli/version.py +1 -1
  33. meshagent/cli/voicebot.py +502 -76
  34. meshagent/cli/webhook.py +7 -7
  35. meshagent/cli/worker.py +1327 -0
  36. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/METADATA +13 -13
  37. meshagent_cli-0.21.0.dist-info/RECORD +44 -0
  38. meshagent_cli-0.7.0.dist-info/RECORD +0 -36
  39. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/WHEEL +0 -0
  40. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/entry_points.txt +0 -0
  41. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.21.0.dist-info}/top_level.txt +0 -0
meshagent/cli/chatbot.py CHANGED
@@ -3,7 +3,14 @@ from rich import print
3
3
  from typing import Annotated, Optional
4
4
  from meshagent.tools import Toolkit
5
5
  from meshagent.tools.storage import StorageToolkitBuilder
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,6 +31,7 @@ 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
@@ -40,13 +49,33 @@ 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
+ from meshagent.api.client import ConflictError
78
+
50
79
  logger = logging.getLogger("chatbot")
51
80
 
52
81
  app = async_typer.AsyncTyper(help="Join a chatbot to a room")
@@ -69,30 +98,33 @@ def build_chatbot(
69
98
  storage: Optional[str] = None,
70
99
  require_image_generation: Optional[str] = None,
71
100
  require_local_shell: Optional[str] = None,
72
- require_shell: Optional[str] = None,
101
+ require_shell: Optional[bool] = None,
73
102
  require_apply_patch: Optional[str] = None,
74
103
  require_computer_use: Optional[str] = None,
75
104
  require_web_search: Optional[str] = None,
76
105
  require_mcp: Optional[str] = None,
77
106
  require_storage: Optional[str] = None,
107
+ require_table_read: list[str] = None,
108
+ require_table_write: list[str] = None,
109
+ require_read_only_storage: Optional[str] = None,
110
+ require_time: bool = True,
111
+ require_uuid: bool = False,
78
112
  rules_file: Optional[str] = None,
79
- room_rules_path: Optional[str] = None,
113
+ room_rules_path: Optional[list[str]] = None,
114
+ require_discovery: Optional[str] = None,
115
+ require_document_authoring: Optional[str] = None,
80
116
  working_directory: Optional[str] = None,
117
+ llm_participant: Optional[str] = None,
118
+ database_namespace: Optional[list[str]] = None,
119
+ always_reply: Optional[bool] = None,
120
+ skill_dirs: Optional[list[str]] = None,
121
+ shell_image: Optional[str] = None,
122
+ log_llm_requests: Optional[bool] = None,
81
123
  ):
82
124
  from meshagent.agents.chat import ChatBot
83
125
 
84
126
  from meshagent.tools.storage import StorageToolkit
85
127
 
86
- from meshagent.agents.chat import (
87
- ChatBotThreadOpenAIImageGenerationToolkitBuilder,
88
- ChatBotThreadLocalShellToolkitBuilder,
89
- ChatBotThreadOpenAIImageGenerationTool,
90
- ChatBotThreadLocalShellTool,
91
- ChatBotThreadShellTool,
92
- ChatBotThreadShellToolkitBuilder,
93
- ImageGenerationConfig,
94
- )
95
-
96
128
  requirements = []
97
129
 
98
130
  toolkits = []
@@ -109,32 +141,32 @@ def build_chatbot(
109
141
  try:
110
142
  with open(Path(os.path.expanduser(rules_file)).resolve(), "r") as f:
111
143
  rules_config = RulesConfig.parse(f.read())
112
- rule = rules_config.rules
144
+ rule.extend(rules_config.rules)
113
145
  client_rules = rules_config.client_rules
114
146
 
115
147
  except FileNotFoundError:
116
148
  print(f"[yellow]rules file not found at {rules_file}[/yellow]")
117
149
 
118
150
  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
- },
151
+ if llm_participant:
152
+ llm_adapter = MessageStreamLLMAdapter(
153
+ participant_name=llm_participant,
133
154
  )
134
155
  else:
135
- llm_adapter = OpenAIResponsesAdapter(
136
- model=model,
137
- )
156
+ if computer_use or require_computer_use:
157
+ llm_adapter = OpenAIResponsesAdapter(
158
+ model=model,
159
+ response_options={
160
+ "reasoning": {"summary": "concise"},
161
+ "truncation": "auto",
162
+ },
163
+ log_requests=log_llm_requests,
164
+ )
165
+ else:
166
+ llm_adapter = OpenAIResponsesAdapter(
167
+ model=model,
168
+ log_requests=log_llm_requests,
169
+ )
138
170
 
139
171
  class CustomChatbot(BaseClass):
140
172
  def __init__(self):
@@ -145,8 +177,61 @@ def build_chatbot(
145
177
  toolkits=toolkits,
146
178
  rules=rule if len(rule) > 0 else None,
147
179
  client_rules=client_rules,
180
+ always_reply=always_reply,
181
+ skill_dirs=skill_dirs,
148
182
  )
149
183
 
184
+ async def start(self, *, room: RoomClient):
185
+ await super().start(room=room)
186
+
187
+ if room_rules_path is not None:
188
+ for p in room_rules_path:
189
+ await self._load_room_rules(path=p)
190
+
191
+ async def _load_room_rules(
192
+ self,
193
+ *,
194
+ path: str,
195
+ participant: Optional[RemoteParticipant] = None,
196
+ ):
197
+ rules = []
198
+ try:
199
+ room_rules = await self.room.storage.download(path=path)
200
+
201
+ rules_txt = room_rules.data.decode()
202
+
203
+ rules_config = RulesConfig.parse(rules_txt)
204
+
205
+ if rules_config.rules is not None:
206
+ rules.extend(rules_config.rules)
207
+
208
+ if participant is not None:
209
+ client = participant.get_attribute("client")
210
+
211
+ if rules_config.client_rules is not None and client is not None:
212
+ cr = rules_config.client_rules.get(client)
213
+ if cr is not None:
214
+ rules.extend(cr)
215
+
216
+ except RoomException:
217
+ try:
218
+ logger.info("attempting to initialize rules file")
219
+ handle = await self.room.storage.open(path=path, overwrite=False)
220
+ await self.room.storage.write(
221
+ handle=handle,
222
+ data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
223
+ )
224
+ await self.room.storage.close(handle=handle)
225
+
226
+ except RoomException:
227
+ pass
228
+ logger.info(
229
+ f"unable to load rules from {path}, continuing with default rules"
230
+ )
231
+ pass
232
+
233
+ return rules
234
+
150
235
  async def get_rules(self, *, thread_context, participant):
151
236
  rules = await super().get_rules(
152
237
  thread_context=thread_context, participant=participant
@@ -154,43 +239,11 @@ def build_chatbot(
154
239
 
155
240
  if room_rules_path is not None:
156
241
  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
242
+ rules.extend(
243
+ await self._load_room_rules(path=p, participant=participant)
244
+ )
192
245
 
193
- print(f"using rules {rules}", flush=True)
246
+ logging.info(f"using rules {rules}")
194
247
 
195
248
  return rules
196
249
 
@@ -199,8 +252,7 @@ def build_chatbot(
199
252
 
200
253
  if require_image_generation:
201
254
  providers.append(
202
- ChatBotThreadOpenAIImageGenerationTool(
203
- thread_context=thread_context,
255
+ ImageGenerationTool(
204
256
  config=ImageGenerationConfig(
205
257
  name="image_generation",
206
258
  partial_images=3,
@@ -210,19 +262,18 @@ def build_chatbot(
210
262
 
211
263
  if require_local_shell:
212
264
  providers.append(
213
- ChatBotThreadLocalShellTool(
265
+ LocalShellTool(
214
266
  working_directory=working_directory,
215
- thread_context=thread_context,
216
267
  config=LocalShellConfig(name="local_shell"),
217
268
  )
218
269
  )
219
270
 
220
271
  if require_shell:
221
272
  providers.append(
222
- ChatBotThreadShellTool(
223
- thread_context=thread_context,
273
+ ShellTool(
224
274
  working_directory=working_directory,
225
275
  config=ShellConfig(name="shell"),
276
+ image=shell_image or "python:3.13",
226
277
  )
227
278
  )
228
279
 
@@ -246,9 +297,80 @@ def build_chatbot(
246
297
  if require_storage:
247
298
  providers.extend(StorageToolkit().tools)
248
299
 
300
+ if len(require_table_read) > 0:
301
+ providers.extend(
302
+ (
303
+ await DatabaseToolkitBuilder().make(
304
+ room=self.room,
305
+ model=model,
306
+ config=DatabaseToolkitConfig(
307
+ tables=require_table_read,
308
+ read_only=True,
309
+ namespace=database_namespace,
310
+ ),
311
+ )
312
+ ).tools
313
+ )
314
+
315
+ if require_time:
316
+ providers.extend((DatetimeToolkit()).tools)
317
+
318
+ if require_uuid:
319
+ providers.extend((UUIDToolkit()).tools)
320
+
321
+ if len(require_table_write) > 0:
322
+ providers.extend(
323
+ (
324
+ await DatabaseToolkitBuilder().make(
325
+ room=self.room,
326
+ model=model,
327
+ config=DatabaseToolkitConfig(
328
+ tables=require_table_write,
329
+ read_only=False,
330
+ namespace=database_namespace,
331
+ ),
332
+ )
333
+ ).tools
334
+ )
335
+
336
+ if require_read_only_storage:
337
+ providers.extend(StorageToolkit(read_only=True).tools)
338
+
339
+ if require_document_authoring:
340
+ providers.extend(DocumentAuthoringToolkit().tools)
341
+ providers.extend(
342
+ DocumentTypeAuthoringToolkit(
343
+ schema=widget_schema, document_type="widget"
344
+ ).tools
345
+ )
346
+
347
+ if require_discovery:
348
+ from meshagent.tools.discovery import DiscoveryToolkit
349
+
350
+ providers.extend(DiscoveryToolkit().tools)
351
+
249
352
  tk = await super().get_thread_toolkits(
250
353
  thread_context=thread_context, participant=participant
251
354
  )
355
+
356
+ if require_computer_use:
357
+ from meshagent.computers.agent import ComputerToolkit
358
+
359
+ def render_screen(image_bytes: bytes):
360
+ for participant in thread_context.participants:
361
+ self.room.messaging.send_message_nowait(
362
+ to=participant,
363
+ type="computer_screen",
364
+ message={},
365
+ attachment=image_bytes,
366
+ )
367
+
368
+ computer_toolkit = ComputerToolkit(
369
+ room=self.room, render_screen=render_screen
370
+ )
371
+
372
+ tk.append(computer_toolkit)
373
+
252
374
  return [
253
375
  *(
254
376
  [Toolkit(name="tools", tools=providers)]
@@ -258,34 +380,27 @@ def build_chatbot(
258
380
  *tk,
259
381
  ]
260
382
 
261
- async def get_thread_toolkit_builders(self, *, thread_context, participant):
383
+ def get_toolkit_builders(self):
262
384
  providers = []
263
385
 
264
386
  if image_generation:
265
- providers.append(
266
- ChatBotThreadOpenAIImageGenerationToolkitBuilder(
267
- thread_context=thread_context
268
- )
269
- )
387
+ providers.append(ImageGenerationToolkitBuilder())
270
388
 
271
389
  if apply_patch:
272
- providers.append(
273
- ApplyPatchToolkitBuilder(thread_context=thread_context)
274
- )
390
+ providers.append(ApplyPatchToolkitBuilder())
275
391
 
276
392
  if local_shell:
277
393
  providers.append(
278
- ChatBotThreadLocalShellToolkitBuilder(
279
- thread_context=thread_context,
394
+ LocalShellToolkitBuilder(
280
395
  working_directory=working_directory,
281
396
  )
282
397
  )
283
398
 
284
399
  if shell:
285
400
  providers.append(
286
- ChatBotThreadShellToolkitBuilder(
287
- thread_context=thread_context,
401
+ ShellToolkitBuilder(
288
402
  working_directory=working_directory,
403
+ image=shell_image,
289
404
  )
290
405
  )
291
406
 
@@ -306,7 +421,7 @@ def build_chatbot(
306
421
  @app.async_command("join")
307
422
  async def make_call(
308
423
  *,
309
- project_id: ProjectIdOption = None,
424
+ project_id: ProjectIdOption,
310
425
  room: RoomOption,
311
426
  role: str = "agent",
312
427
  agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
@@ -320,17 +435,33 @@ async def make_call(
320
435
  ),
321
436
  ] = [],
322
437
  rules_file: Optional[str] = None,
438
+ require_toolkit: Annotated[
439
+ List[str],
440
+ typer.Option(
441
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
442
+ ),
443
+ ] = [],
444
+ require_schema: Annotated[
445
+ List[str],
446
+ typer.Option(
447
+ "--require-schema", "-rs", help="the name or url of a required schema"
448
+ ),
449
+ ] = [],
323
450
  toolkit: Annotated[
324
451
  List[str],
325
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
452
+ typer.Option(
453
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
454
+ ),
326
455
  ] = [],
327
456
  schema: Annotated[
328
457
  List[str],
329
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
458
+ typer.Option(
459
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
460
+ ),
330
461
  ] = [],
331
462
  model: Annotated[
332
463
  str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
333
- ] = "gpt-5.1",
464
+ ] = "gpt-5.2",
334
465
  image_generation: Annotated[
335
466
  Optional[str], typer.Option(..., help="Name of an image gen model")
336
467
  ] = None,
@@ -359,7 +490,7 @@ async def make_call(
359
490
  Optional[bool], typer.Option(..., help="Enable storage toolkit")
360
491
  ] = False,
361
492
  require_image_generation: Annotated[
362
- Optional[str], typer.Option(..., help="Name of an image gen model", hidden=True)
493
+ Optional[str], typer.Option(..., help="Name of an image gen model")
363
494
  ] = None,
364
495
  require_computer_use: Annotated[
365
496
  Optional[bool],
@@ -371,25 +502,63 @@ async def make_call(
371
502
  ] = False,
372
503
  require_local_shell: Annotated[
373
504
  Optional[bool],
374
- typer.Option(..., help="Enable local shell tool calling", hidden=True),
505
+ typer.Option(..., help="Enable local shell tool calling"),
375
506
  ] = False,
376
507
  require_shell: Annotated[
377
508
  Optional[bool],
378
- typer.Option(..., help="Enable function shell tool calling", hidden=True),
509
+ typer.Option(..., help="Enable function shell tool calling"),
379
510
  ] = False,
380
511
  require_apply_patch: Annotated[
381
512
  Optional[bool],
382
- typer.Option(..., help="Enable apply patch tool calling", hidden=True),
513
+ typer.Option(..., help="Enable apply patch tool calling"),
383
514
  ] = False,
384
515
  require_web_search: Annotated[
385
516
  Optional[bool],
386
- typer.Option(..., help="Enable web search tool calling", hidden=True),
517
+ typer.Option(..., help="Enable web search tool calling"),
387
518
  ] = False,
388
519
  require_mcp: Annotated[
389
- Optional[bool], typer.Option(..., help="Enable mcp tool calling", hidden=True)
520
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
390
521
  ] = False,
391
522
  require_storage: Annotated[
392
- Optional[bool], typer.Option(..., help="Enable storage toolkit", hidden=True)
523
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
524
+ ] = False,
525
+ database_namespace: Annotated[
526
+ Optional[str],
527
+ typer.Option(..., help="Use a specific database namespace"),
528
+ ] = None,
529
+ require_table_read: Annotated[
530
+ list[str],
531
+ typer.Option(..., help="Enable table read tools for a specific table"),
532
+ ] = [],
533
+ require_table_write: Annotated[
534
+ list[str],
535
+ typer.Option(..., help="Enable table write tools for a specific table"),
536
+ ] = [],
537
+ require_read_only_storage: Annotated[
538
+ Optional[bool],
539
+ typer.Option(..., help="Enable read only storage toolkit"),
540
+ ] = False,
541
+ require_time: Annotated[
542
+ bool,
543
+ typer.Option(
544
+ ...,
545
+ help="Enable time/datetime tools",
546
+ ),
547
+ ] = True,
548
+ require_uuid: Annotated[
549
+ bool,
550
+ typer.Option(
551
+ ...,
552
+ help="Enable UUID generation tools",
553
+ ),
554
+ ] = False,
555
+ require_document_authoring: Annotated[
556
+ Optional[bool],
557
+ typer.Option(..., help="Enable MeshDocument authoring"),
558
+ ] = False,
559
+ require_discovery: Annotated[
560
+ Optional[bool],
561
+ typer.Option(..., help="Enable discovery of agents and tools"),
393
562
  ] = False,
394
563
  working_directory: Annotated[
395
564
  Optional[str],
@@ -399,7 +568,30 @@ async def make_call(
399
568
  str,
400
569
  typer.Option("--key", help="an api key to sign the token with"),
401
570
  ] = None,
571
+ llm_participant: Annotated[
572
+ Optional[str],
573
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
574
+ ] = None,
575
+ always_reply: Annotated[
576
+ Optional[bool],
577
+ typer.Option(..., help="Always reply"),
578
+ ] = None,
579
+ skill_dir: Annotated[
580
+ list[str],
581
+ typer.Option(..., help="an agent skills directory"),
582
+ ] = [],
583
+ shell_image: Annotated[
584
+ Optional[str],
585
+ typer.Option(..., help="an image tag to use to run shell commands in"),
586
+ ] = None,
587
+ log_llm_requests: Annotated[
588
+ Optional[bool],
589
+ typer.Option(..., help="log all requests to the llm"),
590
+ ] = False,
402
591
  ):
592
+ if database_namespace is not None:
593
+ database_namespace = database_namespace.split("::")
594
+
403
595
  key = await resolve_key(project_id=project_id, key=key)
404
596
  account_client = await get_client()
405
597
  try:
@@ -410,7 +602,7 @@ async def make_call(
410
602
  name=agent_name,
411
603
  )
412
604
 
413
- token.add_api_grant(ApiScope.agent_default())
605
+ token.add_api_grant(ApiScope.agent_default(tunnels=require_computer_use))
414
606
 
415
607
  token.add_role_grant(role=role)
416
608
  token.add_room_grant(room)
@@ -424,25 +616,18 @@ async def make_call(
424
616
  token=jwt,
425
617
  )
426
618
  ) as client:
427
- requirements = []
428
-
429
- for t in toolkit:
430
- requirements.append(RequiredToolkit(name=t))
431
-
432
- for t in schema:
433
- requirements.append(RequiredSchema(name=t))
434
-
435
619
  CustomChatbot = build_chatbot(
436
620
  computer_use=computer_use,
621
+ require_computer_use=require_computer_use,
437
622
  model=model,
438
- local_shell=local_shell,
439
- shell=shell,
440
- apply_patch=apply_patch,
441
623
  agent_name=agent_name,
442
624
  rule=rule,
443
- toolkit=toolkit,
444
- schema=schema,
625
+ toolkit=require_toolkit + toolkit,
626
+ schema=require_schema + schema,
445
627
  rules_file=rules_file,
628
+ local_shell=local_shell,
629
+ shell=shell,
630
+ apply_patch=apply_patch,
446
631
  image_generation=image_generation,
447
632
  web_search=web_search,
448
633
  mcp=mcp,
@@ -454,8 +639,21 @@ async def make_call(
454
639
  require_image_generation=require_image_generation,
455
640
  require_mcp=require_mcp,
456
641
  require_storage=require_storage,
642
+ require_table_read=require_table_read,
643
+ require_table_write=require_table_write,
644
+ require_read_only_storage=require_read_only_storage,
645
+ require_time=require_time,
646
+ require_uuid=require_uuid,
457
647
  room_rules_path=room_rules,
648
+ require_document_authoring=require_document_authoring,
649
+ require_discovery=require_discovery,
458
650
  working_directory=working_directory,
651
+ llm_participant=llm_participant,
652
+ always_reply=always_reply,
653
+ database_namespace=database_namespace,
654
+ skill_dirs=skill_dir,
655
+ shell_image=shell_image,
656
+ log_llm_requests=log_llm_requests,
459
657
  )
460
658
 
461
659
  bot = CustomChatbot()
@@ -488,17 +686,33 @@ async def service(
488
686
  help="a path to a rules file within the room that can be used to customize the agent's behavior",
489
687
  ),
490
688
  ] = [],
689
+ require_toolkit: Annotated[
690
+ List[str],
691
+ typer.Option(
692
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
693
+ ),
694
+ ] = [],
695
+ require_schema: Annotated[
696
+ List[str],
697
+ typer.Option(
698
+ "--require-schema", "-rs", help="the name or url of a required schema"
699
+ ),
700
+ ] = [],
491
701
  toolkit: Annotated[
492
702
  List[str],
493
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
703
+ typer.Option(
704
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
705
+ ),
494
706
  ] = [],
495
707
  schema: Annotated[
496
708
  List[str],
497
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
709
+ typer.Option(
710
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
711
+ ),
498
712
  ] = [],
499
713
  model: Annotated[
500
714
  str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
501
- ] = "gpt-5.1",
715
+ ] = "gpt-5.2",
502
716
  image_generation: Annotated[
503
717
  Optional[str], typer.Option(..., help="Name of an image gen model")
504
718
  ] = None,
@@ -527,7 +741,7 @@ async def service(
527
741
  Optional[bool], typer.Option(..., help="Enable storage toolkit")
528
742
  ] = False,
529
743
  require_image_generation: Annotated[
530
- Optional[str], typer.Option(..., help="Name of an image gen model", hidden=True)
744
+ Optional[str], typer.Option(..., help="Name of an image gen model")
531
745
  ] = None,
532
746
  require_computer_use: Annotated[
533
747
  Optional[bool],
@@ -539,53 +753,133 @@ async def service(
539
753
  ] = False,
540
754
  require_local_shell: Annotated[
541
755
  Optional[bool],
542
- typer.Option(..., help="Enable local shell tool calling", hidden=True),
756
+ typer.Option(..., help="Enable local shell tool calling"),
543
757
  ] = False,
544
758
  require_shell: Annotated[
545
759
  Optional[bool],
546
- typer.Option(..., help="Enable function shell tool calling", hidden=True),
760
+ typer.Option(..., help="Enable function shell tool calling"),
547
761
  ] = False,
548
762
  require_apply_patch: Annotated[
549
- Optional[bool], typer.Option(..., help="Enable apply patch tool", hidden=True)
763
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
550
764
  ] = False,
551
765
  require_web_search: Annotated[
552
766
  Optional[bool],
553
- typer.Option(..., help="Enable web search tool calling", hidden=True),
767
+ typer.Option(..., help="Enable web search tool calling"),
554
768
  ] = False,
555
769
  require_mcp: Annotated[
556
- Optional[bool], typer.Option(..., help="Enable mcp tool calling", hidden=True)
770
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
557
771
  ] = False,
558
772
  require_storage: Annotated[
559
- Optional[bool], typer.Option(..., help="Enable storage toolkit", hidden=True)
773
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
774
+ ] = False,
775
+ database_namespace: Annotated[
776
+ Optional[str],
777
+ typer.Option(..., help="Use a specific database namespace"),
778
+ ] = None,
779
+ require_table_read: Annotated[
780
+ list[str],
781
+ typer.Option(..., help="Enable table read tools for a specific table"),
782
+ ] = [],
783
+ require_table_write: Annotated[
784
+ list[str],
785
+ typer.Option(..., help="Enable table write tools for a specific table"),
786
+ ] = [],
787
+ require_read_only_storage: Annotated[
788
+ Optional[bool],
789
+ typer.Option(..., help="Enable read only storage toolkit"),
790
+ ] = False,
791
+ require_time: Annotated[
792
+ bool,
793
+ typer.Option(
794
+ ...,
795
+ help="Enable time/datetime tools",
796
+ ),
797
+ ] = True,
798
+ require_uuid: Annotated[
799
+ bool,
800
+ typer.Option(
801
+ ...,
802
+ help="Enable UUID generation tools",
803
+ ),
560
804
  ] = False,
561
805
  working_directory: Annotated[
562
806
  Optional[str],
563
807
  typer.Option(..., help="The default working directory for shell commands"),
564
808
  ] = None,
565
- host: Annotated[Optional[str], typer.Option()] = None,
566
- port: Annotated[Optional[int], typer.Option()] = None,
567
- path: Annotated[str, typer.Option()] = "/agent",
809
+ require_document_authoring: Annotated[
810
+ Optional[bool],
811
+ typer.Option(..., help="Enable document authoring"),
812
+ ] = False,
813
+ require_discovery: Annotated[
814
+ Optional[bool],
815
+ typer.Option(..., help="Enable discovery of agents and tools"),
816
+ ] = False,
817
+ llm_participant: Annotated[
818
+ Optional[str],
819
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
820
+ ] = None,
821
+ host: Annotated[
822
+ Optional[str], typer.Option(help="Host to bind the service on")
823
+ ] = None,
824
+ port: Annotated[
825
+ Optional[int], typer.Option(help="Port to bind the service on")
826
+ ] = None,
827
+ path: Annotated[
828
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
829
+ ] = None,
830
+ always_reply: Annotated[
831
+ Optional[bool],
832
+ typer.Option(..., help="Always reply"),
833
+ ] = None,
834
+ skill_dir: Annotated[
835
+ list[str],
836
+ typer.Option(..., help="an agent skills directory"),
837
+ ] = [],
838
+ shell_image: Annotated[
839
+ Optional[str],
840
+ typer.Option(..., help="an image tag to use to run shell commands in"),
841
+ ] = None,
842
+ log_llm_requests: Annotated[
843
+ Optional[bool],
844
+ typer.Option(..., help="log all requests to the llm"),
845
+ ] = False,
568
846
  ):
569
- print("[bold green]Connecting to room...[/bold green]", flush=True)
847
+ if database_namespace is not None:
848
+ database_namespace = database_namespace.split("::")
849
+
850
+ service = get_service(host=host, port=port)
851
+
852
+ if path is None:
853
+ path = "/agent"
854
+ i = 0
855
+ while service.has_path(path):
856
+ i += 1
857
+ path = f"/agent{i}"
858
+
859
+ service.agents.append(
860
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "ChatBot"})
861
+ )
570
862
 
571
- service = ServiceHost(host=host, port=port)
572
863
  service.add_path(
864
+ identity=agent_name,
573
865
  path=path,
574
866
  cls=build_chatbot(
575
867
  computer_use=computer_use,
868
+ require_computer_use=require_computer_use,
576
869
  model=model,
577
870
  local_shell=local_shell,
578
871
  shell=shell,
579
872
  apply_patch=apply_patch,
580
873
  agent_name=agent_name,
581
874
  rule=rule,
582
- toolkit=toolkit,
583
- schema=schema,
875
+ toolkit=require_toolkit + toolkit,
876
+ schema=require_schema + schema,
584
877
  rules_file=rules_file,
585
878
  web_search=web_search,
586
879
  image_generation=image_generation,
587
880
  mcp=mcp,
588
881
  storage=storage,
882
+ database_namespace=database_namespace,
589
883
  require_web_search=require_web_search,
590
884
  require_shell=require_shell,
591
885
  require_apply_patch=require_apply_patch,
@@ -593,9 +887,593 @@ async def service(
593
887
  require_image_generation=require_image_generation,
594
888
  require_mcp=require_mcp,
595
889
  require_storage=require_storage,
890
+ require_table_write=require_table_write,
891
+ require_table_read=require_table_read,
892
+ require_read_only_storage=require_read_only_storage,
893
+ require_time=require_time,
894
+ require_uuid=require_uuid,
596
895
  room_rules_path=room_rules,
597
896
  working_directory=working_directory,
897
+ require_document_authoring=require_document_authoring,
898
+ require_discovery=require_discovery,
899
+ llm_participant=llm_participant,
900
+ always_reply=always_reply,
901
+ skill_dirs=skill_dir,
902
+ shell_image=shell_image,
903
+ log_llm_requests=log_llm_requests,
598
904
  ),
599
905
  )
600
906
 
601
- await service.run()
907
+ if not get_deferred():
908
+ await run_services()
909
+
910
+
911
+ @app.async_command("spec")
912
+ async def spec(
913
+ *,
914
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
915
+ service_description: Annotated[
916
+ Optional[str], typer.Option("--service-description", help="service description")
917
+ ] = None,
918
+ service_title: Annotated[
919
+ Optional[str],
920
+ typer.Option("--service-title", help="a display name for the service"),
921
+ ] = None,
922
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
923
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
924
+ rules_file: Optional[str] = None,
925
+ room_rules: Annotated[
926
+ List[str],
927
+ typer.Option(
928
+ "--room-rules",
929
+ "-rr",
930
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
931
+ ),
932
+ ] = [],
933
+ require_toolkit: Annotated[
934
+ List[str],
935
+ typer.Option(
936
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
937
+ ),
938
+ ] = [],
939
+ require_schema: Annotated[
940
+ List[str],
941
+ typer.Option(
942
+ "--require-schema", "-rs", help="the name or url of a required schema"
943
+ ),
944
+ ] = [],
945
+ toolkit: Annotated[
946
+ List[str],
947
+ typer.Option(
948
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
949
+ ),
950
+ ] = [],
951
+ schema: Annotated[
952
+ List[str],
953
+ typer.Option(
954
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
955
+ ),
956
+ ] = [],
957
+ model: Annotated[
958
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
959
+ ] = "gpt-5.2",
960
+ image_generation: Annotated[
961
+ Optional[str], typer.Option(..., help="Name of an image gen model")
962
+ ] = None,
963
+ local_shell: Annotated[
964
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
965
+ ] = False,
966
+ shell: Annotated[
967
+ Optional[bool], typer.Option(..., help="Enable function shell tool calling")
968
+ ] = False,
969
+ apply_patch: Annotated[
970
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
971
+ ] = False,
972
+ computer_use: Annotated[
973
+ Optional[bool],
974
+ typer.Option(
975
+ ..., help="Enable computer use (requires computer-use-preview model)"
976
+ ),
977
+ ] = False,
978
+ web_search: Annotated[
979
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
980
+ ] = False,
981
+ mcp: Annotated[
982
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
983
+ ] = False,
984
+ storage: Annotated[
985
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
986
+ ] = False,
987
+ require_image_generation: Annotated[
988
+ Optional[str], typer.Option(..., help="Name of an image gen model")
989
+ ] = None,
990
+ require_computer_use: Annotated[
991
+ Optional[bool],
992
+ typer.Option(
993
+ ...,
994
+ help="Enable computer use (requires computer-use-preview model)",
995
+ hidden=True,
996
+ ),
997
+ ] = False,
998
+ require_local_shell: Annotated[
999
+ Optional[bool],
1000
+ typer.Option(..., help="Enable local shell tool calling"),
1001
+ ] = False,
1002
+ require_shell: Annotated[
1003
+ Optional[bool],
1004
+ typer.Option(..., help="Enable function shell tool calling"),
1005
+ ] = False,
1006
+ require_apply_patch: Annotated[
1007
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1008
+ ] = False,
1009
+ require_web_search: Annotated[
1010
+ Optional[bool],
1011
+ typer.Option(..., help="Enable web search tool calling"),
1012
+ ] = False,
1013
+ require_mcp: Annotated[
1014
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1015
+ ] = False,
1016
+ require_storage: Annotated[
1017
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1018
+ ] = False,
1019
+ database_namespace: Annotated[
1020
+ Optional[str],
1021
+ typer.Option(..., help="Use a specific database namespace"),
1022
+ ] = None,
1023
+ require_table_read: Annotated[
1024
+ list[str],
1025
+ typer.Option(..., help="Enable table read tools for a specific table"),
1026
+ ] = [],
1027
+ require_table_write: Annotated[
1028
+ list[str],
1029
+ typer.Option(..., help="Enable table write tools for a specific table"),
1030
+ ] = [],
1031
+ require_read_only_storage: Annotated[
1032
+ Optional[bool],
1033
+ typer.Option(..., help="Enable read only storage toolkit"),
1034
+ ] = False,
1035
+ require_time: Annotated[
1036
+ bool,
1037
+ typer.Option(
1038
+ ...,
1039
+ help="Enable time/datetime tools",
1040
+ ),
1041
+ ] = True,
1042
+ require_uuid: Annotated[
1043
+ bool,
1044
+ typer.Option(
1045
+ ...,
1046
+ help="Enable UUID generation tools",
1047
+ ),
1048
+ ] = False,
1049
+ working_directory: Annotated[
1050
+ Optional[str],
1051
+ typer.Option(..., help="The default working directory for shell commands"),
1052
+ ] = None,
1053
+ require_document_authoring: Annotated[
1054
+ Optional[bool],
1055
+ typer.Option(..., help="Enable document authoring"),
1056
+ ] = False,
1057
+ require_discovery: Annotated[
1058
+ Optional[bool],
1059
+ typer.Option(..., help="Enable discovery of agents and tools"),
1060
+ ] = False,
1061
+ llm_participant: Annotated[
1062
+ Optional[str],
1063
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
1064
+ ] = None,
1065
+ host: Annotated[
1066
+ Optional[str], typer.Option(help="Host to bind the service on")
1067
+ ] = None,
1068
+ port: Annotated[
1069
+ Optional[int], typer.Option(help="Port to bind the service on")
1070
+ ] = None,
1071
+ path: Annotated[
1072
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
1073
+ ] = None,
1074
+ always_reply: Annotated[
1075
+ Optional[bool],
1076
+ typer.Option(..., help="Always reply"),
1077
+ ] = None,
1078
+ skill_dir: Annotated[
1079
+ list[str],
1080
+ typer.Option(..., help="an agent skills directory"),
1081
+ ] = [],
1082
+ shell_image: Annotated[
1083
+ Optional[str],
1084
+ typer.Option(..., help="an image tag to use to run shell commands in"),
1085
+ ] = None,
1086
+ log_llm_requests: Annotated[
1087
+ Optional[bool],
1088
+ typer.Option(..., help="log all requests to the llm"),
1089
+ ] = False,
1090
+ ):
1091
+ if database_namespace is not None:
1092
+ database_namespace = database_namespace.split("::")
1093
+
1094
+ service = get_service(host=host, port=port)
1095
+
1096
+ if path is None:
1097
+ path = "/agent"
1098
+ i = 0
1099
+ while service.has_path(path):
1100
+ i += 1
1101
+ path = f"/agent{i}"
1102
+
1103
+ service.agents.append(
1104
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "ChatBot"})
1105
+ )
1106
+
1107
+ service.add_path(
1108
+ identity=agent_name,
1109
+ path=path,
1110
+ cls=build_chatbot(
1111
+ computer_use=computer_use,
1112
+ require_computer_use=require_computer_use,
1113
+ model=model,
1114
+ local_shell=local_shell,
1115
+ shell=shell,
1116
+ apply_patch=apply_patch,
1117
+ agent_name=agent_name,
1118
+ rule=rule,
1119
+ toolkit=require_toolkit + toolkit,
1120
+ schema=require_schema + schema,
1121
+ rules_file=rules_file,
1122
+ web_search=web_search,
1123
+ image_generation=image_generation,
1124
+ mcp=mcp,
1125
+ storage=storage,
1126
+ database_namespace=database_namespace,
1127
+ require_web_search=require_web_search,
1128
+ require_shell=require_shell,
1129
+ require_apply_patch=require_apply_patch,
1130
+ require_local_shell=require_local_shell,
1131
+ require_image_generation=require_image_generation,
1132
+ require_mcp=require_mcp,
1133
+ require_storage=require_storage,
1134
+ require_table_write=require_table_write,
1135
+ require_table_read=require_table_read,
1136
+ require_read_only_storage=require_read_only_storage,
1137
+ require_time=require_time,
1138
+ require_uuid=require_uuid,
1139
+ room_rules_path=room_rules,
1140
+ working_directory=working_directory,
1141
+ require_document_authoring=require_document_authoring,
1142
+ require_discovery=require_discovery,
1143
+ llm_participant=llm_participant,
1144
+ always_reply=always_reply,
1145
+ skill_dirs=skill_dir,
1146
+ shell_image=shell_image,
1147
+ log_llm_requests=log_llm_requests,
1148
+ ),
1149
+ )
1150
+
1151
+ spec = service_specs()[0]
1152
+ spec.metadata.annotations = {
1153
+ "meshagent.service.id": service_name,
1154
+ }
1155
+
1156
+ spec.metadata.name = service_name
1157
+ spec.metadata.description = service_description
1158
+ spec.container.image = (
1159
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
1160
+ )
1161
+ spec.container.command = shlex.join(
1162
+ ["meshagent", "chatbot", "service", *cleanup_args(sys.argv[2:])]
1163
+ )
1164
+
1165
+ print(yaml.dump(spec.model_dump(mode="json", exclude_none=True), sort_keys=False))
1166
+
1167
+
1168
+ @app.async_command("deploy")
1169
+ async def deploy(
1170
+ *,
1171
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
1172
+ service_description: Annotated[
1173
+ Optional[str], typer.Option("--service-description", help="service description")
1174
+ ] = None,
1175
+ service_title: Annotated[
1176
+ Optional[str],
1177
+ typer.Option("--service-title", help="a display name for the service"),
1178
+ ] = None,
1179
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
1180
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
1181
+ rules_file: Optional[str] = None,
1182
+ room_rules: Annotated[
1183
+ List[str],
1184
+ typer.Option(
1185
+ "--room-rules",
1186
+ "-rr",
1187
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
1188
+ ),
1189
+ ] = [],
1190
+ require_toolkit: Annotated[
1191
+ List[str],
1192
+ typer.Option(
1193
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
1194
+ ),
1195
+ ] = [],
1196
+ require_schema: Annotated[
1197
+ List[str],
1198
+ typer.Option(
1199
+ "--require-schema", "-rs", help="the name or url of a required schema"
1200
+ ),
1201
+ ] = [],
1202
+ toolkit: Annotated[
1203
+ List[str],
1204
+ typer.Option(
1205
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
1206
+ ),
1207
+ ] = [],
1208
+ schema: Annotated[
1209
+ List[str],
1210
+ typer.Option(
1211
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
1212
+ ),
1213
+ ] = [],
1214
+ model: Annotated[
1215
+ str, typer.Option(..., help="Name of the LLM model to use for the chatbot")
1216
+ ] = "gpt-5.2",
1217
+ image_generation: Annotated[
1218
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1219
+ ] = None,
1220
+ local_shell: Annotated[
1221
+ Optional[bool], typer.Option(..., help="Enable local shell tool calling")
1222
+ ] = False,
1223
+ shell: Annotated[
1224
+ Optional[bool], typer.Option(..., help="Enable function shell tool calling")
1225
+ ] = False,
1226
+ apply_patch: Annotated[
1227
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1228
+ ] = False,
1229
+ computer_use: Annotated[
1230
+ Optional[bool],
1231
+ typer.Option(
1232
+ ..., help="Enable computer use (requires computer-use-preview model)"
1233
+ ),
1234
+ ] = False,
1235
+ web_search: Annotated[
1236
+ Optional[bool], typer.Option(..., help="Enable web search tool calling")
1237
+ ] = False,
1238
+ mcp: Annotated[
1239
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1240
+ ] = False,
1241
+ storage: Annotated[
1242
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1243
+ ] = False,
1244
+ require_image_generation: Annotated[
1245
+ Optional[str], typer.Option(..., help="Name of an image gen model")
1246
+ ] = None,
1247
+ require_computer_use: Annotated[
1248
+ Optional[bool],
1249
+ typer.Option(
1250
+ ...,
1251
+ help="Enable computer use (requires computer-use-preview model)",
1252
+ hidden=True,
1253
+ ),
1254
+ ] = False,
1255
+ require_local_shell: Annotated[
1256
+ Optional[bool],
1257
+ typer.Option(..., help="Enable local shell tool calling"),
1258
+ ] = False,
1259
+ require_shell: Annotated[
1260
+ Optional[bool],
1261
+ typer.Option(..., help="Enable function shell tool calling"),
1262
+ ] = False,
1263
+ require_apply_patch: Annotated[
1264
+ Optional[bool], typer.Option(..., help="Enable apply patch tool")
1265
+ ] = False,
1266
+ require_web_search: Annotated[
1267
+ Optional[bool],
1268
+ typer.Option(..., help="Enable web search tool calling"),
1269
+ ] = False,
1270
+ require_mcp: Annotated[
1271
+ Optional[bool], typer.Option(..., help="Enable mcp tool calling")
1272
+ ] = False,
1273
+ require_storage: Annotated[
1274
+ Optional[bool], typer.Option(..., help="Enable storage toolkit")
1275
+ ] = False,
1276
+ database_namespace: Annotated[
1277
+ Optional[str],
1278
+ typer.Option(..., help="Use a specific database namespace"),
1279
+ ] = None,
1280
+ require_table_read: Annotated[
1281
+ list[str],
1282
+ typer.Option(..., help="Enable table read tools for a specific table"),
1283
+ ] = [],
1284
+ require_table_write: Annotated[
1285
+ list[str],
1286
+ typer.Option(..., help="Enable table write tools for a specific table"),
1287
+ ] = [],
1288
+ require_read_only_storage: Annotated[
1289
+ Optional[bool],
1290
+ typer.Option(..., help="Enable read only storage toolkit"),
1291
+ ] = False,
1292
+ require_time: Annotated[
1293
+ bool,
1294
+ typer.Option(
1295
+ ...,
1296
+ help="Enable time/datetime tools",
1297
+ ),
1298
+ ] = True,
1299
+ require_uuid: Annotated[
1300
+ bool,
1301
+ typer.Option(
1302
+ ...,
1303
+ help="Enable UUID generation tools",
1304
+ ),
1305
+ ] = False,
1306
+ working_directory: Annotated[
1307
+ Optional[str],
1308
+ typer.Option(..., help="The default working directory for shell commands"),
1309
+ ] = None,
1310
+ require_document_authoring: Annotated[
1311
+ Optional[bool],
1312
+ typer.Option(..., help="Enable document authoring"),
1313
+ ] = False,
1314
+ require_discovery: Annotated[
1315
+ Optional[bool],
1316
+ typer.Option(..., help="Enable discovery of agents and tools"),
1317
+ ] = False,
1318
+ llm_participant: Annotated[
1319
+ Optional[str],
1320
+ typer.Option(..., help="Delegate LLM interactions to a remote participant"),
1321
+ ] = None,
1322
+ host: Annotated[
1323
+ Optional[str], typer.Option(help="Host to bind the service on")
1324
+ ] = None,
1325
+ port: Annotated[
1326
+ Optional[int], typer.Option(help="Port to bind the service on")
1327
+ ] = None,
1328
+ path: Annotated[
1329
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
1330
+ ] = None,
1331
+ always_reply: Annotated[
1332
+ Optional[bool],
1333
+ typer.Option(..., help="Always reply"),
1334
+ ] = None,
1335
+ skill_dir: Annotated[
1336
+ list[str],
1337
+ typer.Option(..., help="an agent skills directory"),
1338
+ ] = [],
1339
+ shell_image: Annotated[
1340
+ Optional[str],
1341
+ typer.Option(..., help="an image tag to use to run shell commands in"),
1342
+ ] = None,
1343
+ log_llm_requests: Annotated[
1344
+ Optional[bool],
1345
+ typer.Option(..., help="log all requests to the llm"),
1346
+ ] = False,
1347
+ project_id: ProjectIdOption,
1348
+ room: Annotated[
1349
+ Optional[str],
1350
+ typer.Option("--room", help="The name of a room to create the service for"),
1351
+ ] = None,
1352
+ ):
1353
+ project_id = await resolve_project_id(project_id=project_id)
1354
+
1355
+ if database_namespace is not None:
1356
+ database_namespace = database_namespace.split("::")
1357
+
1358
+ service = get_service(host=host, port=port)
1359
+
1360
+ if path is None:
1361
+ path = "/agent"
1362
+ i = 0
1363
+ while service.has_path(path):
1364
+ i += 1
1365
+ path = f"/agent{i}"
1366
+
1367
+ service.agents.append(
1368
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "ChatBot"})
1369
+ )
1370
+
1371
+ service.add_path(
1372
+ identity=agent_name,
1373
+ path=path,
1374
+ cls=build_chatbot(
1375
+ computer_use=computer_use,
1376
+ require_computer_use=require_computer_use,
1377
+ model=model,
1378
+ local_shell=local_shell,
1379
+ shell=shell,
1380
+ apply_patch=apply_patch,
1381
+ agent_name=agent_name,
1382
+ rule=rule,
1383
+ toolkit=require_toolkit + toolkit,
1384
+ schema=require_schema + schema,
1385
+ rules_file=rules_file,
1386
+ web_search=web_search,
1387
+ image_generation=image_generation,
1388
+ mcp=mcp,
1389
+ storage=storage,
1390
+ database_namespace=database_namespace,
1391
+ require_web_search=require_web_search,
1392
+ require_shell=require_shell,
1393
+ require_apply_patch=require_apply_patch,
1394
+ require_local_shell=require_local_shell,
1395
+ require_image_generation=require_image_generation,
1396
+ require_mcp=require_mcp,
1397
+ require_storage=require_storage,
1398
+ require_table_write=require_table_write,
1399
+ require_table_read=require_table_read,
1400
+ require_read_only_storage=require_read_only_storage,
1401
+ require_time=require_time,
1402
+ require_uuid=require_uuid,
1403
+ room_rules_path=room_rules,
1404
+ working_directory=working_directory,
1405
+ require_document_authoring=require_document_authoring,
1406
+ require_discovery=require_discovery,
1407
+ llm_participant=llm_participant,
1408
+ always_reply=always_reply,
1409
+ skill_dirs=skill_dir,
1410
+ shell_image=shell_image,
1411
+ log_llm_requests=log_llm_requests,
1412
+ ),
1413
+ )
1414
+
1415
+ spec = service_specs()[0]
1416
+
1417
+ spec.metadata.annotations = {
1418
+ "meshagent.service.id": service_name,
1419
+ }
1420
+
1421
+ spec.metadata.name = service_name
1422
+ spec.metadata.description = service_description
1423
+ spec.container.image = (
1424
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
1425
+ )
1426
+ spec.container.command = shlex.join(
1427
+ ["meshagent", "chatbot", "service", *cleanup_args(sys.argv[2:])]
1428
+ )
1429
+
1430
+ project_id = await resolve_project_id(project_id)
1431
+
1432
+ client = await get_client()
1433
+ try:
1434
+ id = None
1435
+ try:
1436
+ if id is None:
1437
+ if room is None:
1438
+ services = await client.list_services(project_id=project_id)
1439
+ else:
1440
+ services = await client.list_room_services(
1441
+ project_id=project_id, room_name=room
1442
+ )
1443
+
1444
+ for s in services:
1445
+ if s.metadata.name == spec.metadata.name:
1446
+ id = s.id
1447
+
1448
+ if id is None:
1449
+ if room is None:
1450
+ id = await client.create_service(
1451
+ project_id=project_id, service=spec
1452
+ )
1453
+ else:
1454
+ id = await client.create_room_service(
1455
+ project_id=project_id, service=spec, room_name=room
1456
+ )
1457
+
1458
+ else:
1459
+ spec.id = id
1460
+ if room is None:
1461
+ await client.update_service(
1462
+ project_id=project_id, service_id=id, service=spec
1463
+ )
1464
+ else:
1465
+ await client.update_room_service(
1466
+ project_id=project_id,
1467
+ service_id=id,
1468
+ service=spec,
1469
+ room_name=room,
1470
+ )
1471
+
1472
+ except ConflictError:
1473
+ print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
1474
+ raise typer.Exit(code=1)
1475
+ else:
1476
+ print(f"[green]Deployed service:[/] {id}")
1477
+
1478
+ finally:
1479
+ await client.close()