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