meshagent-cli 0.21.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.
@@ -1,5 +1,6 @@
1
1
  import typer
2
2
  import json
3
+ import os
3
4
  from rich import print
4
5
  from typing import Annotated, Optional
5
6
  from meshagent.tools import Toolkit
@@ -21,6 +22,7 @@ from meshagent.api import (
21
22
  ParticipantToken,
22
23
  ApiScope,
23
24
  RoomException,
25
+ RemoteParticipant,
24
26
  )
25
27
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
26
28
  from meshagent.cli import async_typer
@@ -29,9 +31,11 @@ from meshagent.cli.helper import (
29
31
  resolve_project_id,
30
32
  resolve_room,
31
33
  resolve_key,
34
+ cleanup_args,
32
35
  )
33
36
 
34
37
  from meshagent.openai import OpenAIResponsesAdapter
38
+ from meshagent.anthropic import AnthropicOpenAIResponsesStreamAdapter
35
39
 
36
40
  from typing import List
37
41
  from pathlib import Path
@@ -55,17 +59,24 @@ from meshagent.openai.tools.responses_adapter import (
55
59
  ImageGenerationTool,
56
60
  )
57
61
 
62
+ from meshagent.cli.host import get_service, run_services, get_deferred, service_specs
58
63
  from meshagent.tools.database import DatabaseToolkitBuilder, DatabaseToolkitConfig
59
64
  from meshagent.agents.adapter import MessageStreamLLMAdapter
60
65
  from meshagent.agents.context import AgentCallContext
61
66
 
62
67
  from meshagent.api import RequiredToolkit, RequiredSchema
63
- from meshagent.api.services import ServiceHost
68
+ from meshagent.api.specs.service import AgentSpec, ANNOTATION_AGENT_TYPE
64
69
  import logging
65
70
  import os.path
66
71
 
67
72
  from urllib.request import urlopen
68
73
 
74
+ import yaml
75
+ import shlex
76
+ import sys
77
+
78
+ from meshagent.api.client import ConflictError
79
+
69
80
  logger = logging.getLogger("taskrunner")
70
81
 
71
82
  app = async_typer.AsyncTyper(help="Join a taskrunner to a room")
@@ -74,7 +85,6 @@ app = async_typer.AsyncTyper(help="Join a taskrunner to a room")
74
85
  def build_task_runner(
75
86
  *,
76
87
  model: str,
77
- agent_name: str,
78
88
  rule: List[str],
79
89
  toolkit: List[str],
80
90
  schema: List[str],
@@ -107,6 +117,7 @@ def build_task_runner(
107
117
  title: Optional[str] = None,
108
118
  description: Optional[str] = None,
109
119
  shell_image: Optional[str] = None,
120
+ delegate_shell_token: Optional[bool] = None,
110
121
  ):
111
122
  output_schema = None
112
123
  if output_schema_str is not None:
@@ -153,15 +164,15 @@ def build_task_runner(
153
164
  participant_name=llm_participant,
154
165
  )
155
166
  else:
156
- llm_adapter = OpenAIResponsesAdapter(
157
- model=model,
158
- )
167
+ if model.startswith("claude-"):
168
+ llm_adapter = AnthropicOpenAIResponsesStreamAdapter(model=model)
169
+ else:
170
+ llm_adapter = OpenAIResponsesAdapter(model=model)
159
171
 
160
172
  class CustomTaskRunner(BaseClass):
161
173
  def __init__(self):
162
174
  super().__init__(
163
175
  llm_adapter=llm_adapter,
164
- name=agent_name,
165
176
  requires=requirements,
166
177
  toolkits=toolkits,
167
178
  rules=rule if len(rule) > 0 else None,
@@ -179,13 +190,20 @@ def build_task_runner(
179
190
  for p in room_rules_path:
180
191
  await self._load_room_rules(path=p)
181
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
+
182
201
  async def _load_room_rules(
183
202
  self,
184
203
  *,
185
204
  path: str,
186
- context: AgentCallContext,
205
+ participant: Optional[RemoteParticipant] = None,
187
206
  ):
188
- participant = context.caller
189
207
  rules = []
190
208
  try:
191
209
  room_rules = await self.room.storage.download(path=path)
@@ -229,7 +247,9 @@ def build_task_runner(
229
247
 
230
248
  if room_rules_path is not None:
231
249
  for p in room_rules_path:
232
- rules.extend(await self._load_room_rules(path=p, context=context))
250
+ rules.extend(
251
+ await self._load_room_rules(path=p, participant=context.caller)
252
+ )
233
253
 
234
254
  logging.info(f"using rules {rules}")
235
255
 
@@ -256,12 +276,17 @@ def build_task_runner(
256
276
  )
257
277
  )
258
278
 
279
+ env = {}
280
+ if delegate_shell_token:
281
+ env["MESHAGENT_TOKEN"] = self.room.protocol.token
282
+
259
283
  if require_shell:
260
284
  providers.append(
261
285
  ShellTool(
262
286
  working_directory=working_directory,
263
287
  config=ShellConfig(name="shell"),
264
288
  image=shell_image or "python:3.13",
289
+ env=env,
265
290
  )
266
291
  )
267
292
 
@@ -375,12 +400,14 @@ def build_task_runner(
375
400
 
376
401
 
377
402
  @app.async_command("join")
378
- async def make_call(
403
+ async def join(
379
404
  *,
380
405
  project_id: ProjectIdOption,
381
406
  room: RoomOption,
382
407
  role: str = "agent",
383
- agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
408
+ agent_name: Annotated[
409
+ Optional[str], typer.Option(..., help="Name of the agent to call")
410
+ ] = None,
384
411
  rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
385
412
  room_rules: Annotated[
386
413
  List[str],
@@ -476,6 +503,10 @@ async def make_call(
476
503
  Optional[str],
477
504
  typer.Option(..., help="The default working directory for shell commands"),
478
505
  ] = None,
506
+ delegate_shell_token: Annotated[
507
+ Optional[bool],
508
+ typer.Option(..., help="Delegate the room token to shell tools"),
509
+ ] = False,
479
510
  key: Annotated[
480
511
  str,
481
512
  typer.Option("--key", help="an api key to sign the token with"),
@@ -513,79 +544,94 @@ async def make_call(
513
544
  project_id = await resolve_project_id(project_id=project_id)
514
545
  room = resolve_room(room)
515
546
 
516
- token = ParticipantToken(
517
- name=agent_name,
518
- )
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)
519
554
 
520
- token.add_api_grant(ApiScope.agent_default())
555
+ token = ParticipantToken(
556
+ name=agent_name,
557
+ )
558
+
559
+ token.add_api_grant(ApiScope.agent_default())
521
560
 
522
- token.add_role_grant(role=role)
523
- token.add_room_grant(room)
561
+ token.add_role_grant(role=role)
562
+ token.add_room_grant(room)
524
563
 
525
- jwt = token.to_jwt(api_key=key)
564
+ jwt = token.to_jwt(api_key=key)
526
565
 
527
566
  print("[bold green]Connecting to room...[/bold green]", flush=True)
528
- async with RoomClient(
529
- protocol=WebSocketClientProtocol(
530
- url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
531
- token=jwt,
532
- )
533
- ) as client:
534
- requirements = []
567
+ requirements = []
535
568
 
536
- for t in toolkit:
537
- requirements.append(RequiredToolkit(name=t))
569
+ for t in toolkit:
570
+ requirements.append(RequiredToolkit(name=t))
538
571
 
539
- for t in schema:
540
- requirements.append(RequiredSchema(name=t))
572
+ for t in schema:
573
+ requirements.append(RequiredSchema(name=t))
541
574
 
542
- CustomTaskRunner = build_task_runner(
543
- title=title,
544
- description=description,
545
- model=model,
546
- local_shell=local_shell,
547
- shell=shell,
548
- apply_patch=apply_patch,
549
- agent_name=agent_name,
550
- rule=rule,
551
- toolkit=toolkit,
552
- schema=schema,
553
- rules_file=rules_file,
554
- image_generation=image_generation,
555
- web_search=web_search,
556
- mcp=mcp,
557
- storage=storage,
558
- require_apply_patch=require_apply_patch,
559
- require_web_search=require_web_search,
560
- require_local_shell=require_local_shell,
561
- require_shell=require_shell,
562
- require_image_generation=require_image_generation,
563
- require_mcp=require_mcp,
564
- require_storage=require_storage,
565
- require_table_read=require_table_read,
566
- require_table_write=require_table_write,
567
- require_read_only_storage=require_read_only_storage,
568
- room_rules_path=room_rules,
569
- require_document_authoring=require_document_authoring,
570
- require_discovery=require_discovery,
571
- working_directory=working_directory,
572
- llm_participant=llm_participant,
573
- output_schema_str=output_schema,
574
- output_schema_path=output_schema_path,
575
- annotations=json.loads(annotations) if annotations != "" else {},
576
- )
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
+ )
577
610
 
578
- bot = CustomTaskRunner()
611
+ bot = CustomTaskRunner()
579
612
 
580
- await bot.start(room=client)
581
- try:
582
- print(
583
- 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]",
584
- flush=True,
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,
585
624
  )
586
- await client.protocol.wait_for_close()
587
- except KeyboardInterrupt:
588
- await bot.stop()
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()
589
635
 
590
636
  finally:
591
637
  await account_client.close()
@@ -681,6 +727,10 @@ async def service(
681
727
  Optional[str],
682
728
  typer.Option(..., help="The default working directory for shell commands"),
683
729
  ] = None,
730
+ delegate_shell_token: Annotated[
731
+ Optional[bool],
732
+ typer.Option(..., help="Delegate the room token to shell tools"),
733
+ ] = False,
684
734
  require_document_authoring: Annotated[
685
735
  Optional[bool],
686
736
  typer.Option(..., help="Enable document authoring", hidden=True),
@@ -702,8 +752,8 @@ async def service(
702
752
  Optional[int], typer.Option(help="Port to bind the service on")
703
753
  ] = None,
704
754
  path: Annotated[
705
- str, typer.Option(help="HTTP path to mount the service at")
706
- ] = "/agent",
755
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
756
+ ] = None,
707
757
  output_schema: Annotated[
708
758
  Optional[str],
709
759
  typer.Option(..., help="an output schema to use", hidden=True),
@@ -727,15 +777,26 @@ async def service(
727
777
  ):
728
778
  print("[bold green]Connecting to room...[/bold green]", flush=True)
729
779
 
730
- service = ServiceHost(host=host, port=port)
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
+
731
792
  service.add_path(
793
+ identity=agent_name,
732
794
  path=path,
733
795
  cls=build_task_runner(
734
796
  model=model,
735
797
  local_shell=local_shell,
736
798
  shell=shell,
737
799
  apply_patch=apply_patch,
738
- agent_name=agent_name,
739
800
  title=title,
740
801
  description=description,
741
802
  rule=rule,
@@ -758,6 +819,7 @@ async def service(
758
819
  require_read_only_storage=require_read_only_storage,
759
820
  room_rules_path=room_rules,
760
821
  working_directory=working_directory,
822
+ delegate_shell_token=delegate_shell_token,
761
823
  require_document_authoring=require_document_authoring,
762
824
  require_discovery=require_discovery,
763
825
  llm_participant=llm_participant,
@@ -767,4 +829,489 @@ async def service(
767
829
  ),
768
830
  )
769
831
 
770
- await service.run()
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()