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