meshagent-cli 0.7.0__py3-none-any.whl → 0.23.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. meshagent/cli/agent.py +23 -13
  2. meshagent/cli/api_keys.py +4 -4
  3. meshagent/cli/async_typer.py +52 -4
  4. meshagent/cli/call.py +27 -36
  5. meshagent/cli/chatbot.py +1559 -177
  6. meshagent/cli/cli.py +23 -22
  7. meshagent/cli/cli_mcp.py +92 -28
  8. meshagent/cli/cli_secrets.py +10 -10
  9. meshagent/cli/common_options.py +19 -4
  10. meshagent/cli/containers.py +164 -16
  11. meshagent/cli/database.py +997 -0
  12. meshagent/cli/developer.py +3 -3
  13. meshagent/cli/exec.py +22 -6
  14. meshagent/cli/helper.py +101 -12
  15. meshagent/cli/helpers.py +65 -11
  16. meshagent/cli/host.py +41 -0
  17. meshagent/cli/mailbot.py +1104 -79
  18. meshagent/cli/mailboxes.py +223 -0
  19. meshagent/cli/meeting_transcriber.py +29 -15
  20. meshagent/cli/messaging.py +7 -10
  21. meshagent/cli/multi.py +357 -0
  22. meshagent/cli/oauth2.py +192 -40
  23. meshagent/cli/participant_token.py +5 -3
  24. meshagent/cli/port.py +70 -0
  25. meshagent/cli/queue.py +2 -2
  26. meshagent/cli/room.py +24 -212
  27. meshagent/cli/rooms.py +214 -0
  28. meshagent/cli/services.py +269 -37
  29. meshagent/cli/sessions.py +5 -5
  30. meshagent/cli/storage.py +5 -5
  31. meshagent/cli/sync.py +434 -0
  32. meshagent/cli/task_runner.py +1317 -0
  33. meshagent/cli/version.py +1 -1
  34. meshagent/cli/voicebot.py +544 -98
  35. meshagent/cli/webhook.py +7 -7
  36. meshagent/cli/worker.py +1403 -0
  37. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/METADATA +15 -13
  38. meshagent_cli-0.23.0.dist-info/RECORD +45 -0
  39. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/WHEEL +1 -1
  40. meshagent_cli-0.7.0.dist-info/RECORD +0 -36
  41. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/entry_points.txt +0 -0
  42. {meshagent_cli-0.7.0.dist-info → meshagent_cli-0.23.0.dist-info}/top_level.txt +0 -0
meshagent/cli/voicebot.py CHANGED
@@ -1,60 +1,213 @@
1
1
  import typer
2
+ import os
2
3
  from rich import print
3
4
  from typing import Annotated, Optional
4
5
  from meshagent.cli.common_options import ProjectIdOption, RoomOption
5
6
  from meshagent.api import RoomClient, WebSocketClientProtocol, RoomException
6
7
  from meshagent.api.helpers import meshagent_base_url, websocket_room_url
7
8
  from meshagent.cli import async_typer
8
- from meshagent.api import ParticipantToken, ApiScope
9
+ from meshagent.api import ParticipantToken, ApiScope, RemoteParticipant
9
10
  from meshagent.cli.helper import (
10
11
  get_client,
11
12
  resolve_project_id,
12
13
  resolve_room,
13
14
  resolve_key,
15
+ cleanup_args,
14
16
  )
15
17
  from typing import List
16
-
17
18
  from meshagent.api import RequiredToolkit, RequiredSchema
18
- from meshagent.api.services import ServiceHost
19
19
  from pathlib import Path
20
+ from meshagent.agents.config import RulesConfig
21
+ import logging
22
+
23
+ from meshagent.cli.host import get_service, run_services, get_deferred, service_specs
24
+ from meshagent.api.specs.service import AgentSpec, ANNOTATION_AGENT_TYPE
25
+
26
+ import yaml
27
+
28
+ import shlex
29
+ import sys
20
30
 
31
+ from meshagent.api.client import ConflictError
21
32
 
22
33
  app = async_typer.AsyncTyper(help="Join a voicebot to a room")
23
34
 
35
+ logger = logging.getLogger("voicebot")
36
+
37
+
38
+ def build_voicebot(
39
+ *,
40
+ rules: list[str],
41
+ rules_file: Optional[str] = None,
42
+ toolkits: list[str],
43
+ schemas: list[str],
44
+ auto_greet_message: Optional[str] = None,
45
+ auto_greet_prompt: Optional[str] = None,
46
+ room_rules_paths: list[str],
47
+ ):
48
+ requirements = []
49
+
50
+ for t in toolkits:
51
+ requirements.append(RequiredToolkit(name=t))
52
+
53
+ for t in schemas:
54
+ requirements.append(RequiredSchema(name=t))
55
+
56
+ if rules_file is not None:
57
+ try:
58
+ with open(Path(rules_file).resolve(), "r") as f:
59
+ rules.extend(f.read().splitlines())
60
+ except FileNotFoundError:
61
+ print(f"[yellow]rules file not found at {rules_file}[/yellow]")
62
+
63
+ try:
64
+ from meshagent.livekit.agents.voice import VoiceBot
65
+ except ImportError:
66
+
67
+ class VoiceBot:
68
+ def __init__(self, **kwargs):
69
+ raise RoomException(
70
+ "meshagent.livekit module not found, voicebots are not available"
71
+ )
72
+
73
+ class CustomVoiceBot(VoiceBot):
74
+ def __init__(self):
75
+ super().__init__(
76
+ auto_greet_message=auto_greet_message,
77
+ auto_greet_prompt=auto_greet_prompt,
78
+ requires=requirements,
79
+ rules=rules if len(rules) > 0 else None,
80
+ )
81
+
82
+ async def init_chat_context(self):
83
+ from meshagent.cli.helper import init_context_from_spec
84
+
85
+ context = await super().init_chat_context()
86
+ await init_context_from_spec(context)
87
+
88
+ return context
89
+
90
+ async def start(self, *, room: RoomClient):
91
+ await super().start(room=room)
92
+
93
+ if room_rules_paths is not None:
94
+ for p in room_rules_paths:
95
+ await self._load_room_rules(path=p)
96
+
97
+ async def _load_room_rules(
98
+ self,
99
+ *,
100
+ path: str,
101
+ participant: Optional[RemoteParticipant] = None,
102
+ ):
103
+ rules = []
104
+ try:
105
+ room_rules = await self.room.storage.download(path=path)
106
+
107
+ rules_txt = room_rules.data.decode()
108
+
109
+ rules_config = RulesConfig.parse(rules_txt)
110
+
111
+ if rules_config.rules is not None:
112
+ rules.extend(rules_config.rules)
113
+
114
+ if participant is not None:
115
+ client = participant.get_attribute("client")
116
+
117
+ if rules_config.client_rules is not None and client is not None:
118
+ cr = rules_config.client_rules.get(client)
119
+ if cr is not None:
120
+ rules.extend(cr)
121
+
122
+ except RoomException:
123
+ try:
124
+ logger.info("attempting to initialize rules file")
125
+ handle = await self.room.storage.open(path=path, overwrite=False)
126
+ await self.room.storage.write(
127
+ handle=handle,
128
+ data="# Add rules to this file to customize your agent's behavior, lines starting with # will be ignored.\n\n".encode(),
129
+ )
130
+ await self.room.storage.close(handle=handle)
131
+
132
+ except RoomException:
133
+ pass
134
+ logger.info(
135
+ f"unable to load rules from {path}, continuing with default rules"
136
+ )
137
+ pass
138
+
139
+ return rules
140
+
141
+ async def get_rules(self, *, participant: RemoteParticipant):
142
+ rules = [*self.rules] if self.rules is not None else []
143
+ if room_rules_paths is not None:
144
+ for p in room_rules_paths:
145
+ rules.extend(
146
+ await self._load_room_rules(participant=participant, path=p)
147
+ )
148
+
149
+ logger.info(f"voicebot using rules {rules}")
150
+
151
+ return rules
152
+
153
+ return CustomVoiceBot
154
+
24
155
 
25
156
  @app.async_command("join")
26
- async def make_call(
157
+ async def join(
27
158
  *,
28
- project_id: ProjectIdOption = None,
159
+ project_id: ProjectIdOption,
29
160
  room: RoomOption,
30
- agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
161
+ agent_name: Annotated[
162
+ Optional[str], typer.Option(..., help="Name of the agent to call")
163
+ ] = None,
31
164
  rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
32
165
  rules_file: Optional[str] = None,
166
+ require_toolkit: Annotated[
167
+ List[str],
168
+ typer.Option(
169
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
170
+ ),
171
+ ] = [],
172
+ require_schema: Annotated[
173
+ List[str],
174
+ typer.Option(
175
+ "--require-schema", "-rs", help="the name or url of a required schema"
176
+ ),
177
+ ] = [],
33
178
  toolkit: Annotated[
34
179
  List[str],
35
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
180
+ typer.Option(
181
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
182
+ ),
36
183
  ] = [],
37
184
  schema: Annotated[
38
185
  List[str],
39
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
186
+ typer.Option(
187
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
188
+ ),
40
189
  ] = [],
41
- auto_greet_message: Annotated[Optional[str], typer.Option()] = None,
42
- auto_greet_prompt: Annotated[Optional[str], typer.Option()] = None,
190
+ auto_greet_message: Annotated[
191
+ Optional[str],
192
+ typer.Option(help="Message to send automatically when the bot joins"),
193
+ ] = None,
194
+ auto_greet_prompt: Annotated[
195
+ Optional[str],
196
+ typer.Option(help="Prompt to generate an auto-greet message"),
197
+ ] = None,
43
198
  key: Annotated[
44
199
  str,
45
200
  typer.Option("--key", help="an api key to sign the token with"),
46
201
  ] = None,
202
+ room_rules: Annotated[
203
+ List[str],
204
+ typer.Option(
205
+ "--room-rules",
206
+ "-rr",
207
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
208
+ ),
209
+ ] = [],
47
210
  ):
48
- try:
49
- from meshagent.livekit.agents.voice import VoiceBot
50
- except ImportError:
51
-
52
- class VoiceBot:
53
- def __init__(self, **kwargs):
54
- raise RoomException(
55
- "meshagent.livekit module not found, voicebots are not available"
56
- )
57
-
58
211
  key = await resolve_key(project_id=project_id, key=key)
59
212
 
60
213
  account_client = await get_client()
@@ -62,56 +215,61 @@ async def make_call(
62
215
  project_id = await resolve_project_id(project_id=project_id)
63
216
  room = resolve_room(room)
64
217
 
65
- token = ParticipantToken(
66
- name=agent_name,
67
- )
218
+ jwt = os.getenv("MESHAGENT_TOKEN")
219
+ if jwt is None:
220
+ if agent_name is None:
221
+ print(
222
+ "[bold red]--agent-name must be specified when the MESHAGENT_TOKEN environment variable is not set[/bold red]"
223
+ )
224
+ raise typer.Exit(1)
68
225
 
69
- token.add_api_grant(ApiScope.agent_default())
226
+ token = ParticipantToken(
227
+ name=agent_name,
228
+ )
70
229
 
71
- token.add_role_grant(role="agent")
72
- token.add_room_grant(room)
230
+ token.add_api_grant(ApiScope.agent_default())
73
231
 
74
- jwt = token.to_jwt(api_key=key)
75
- if rules_file is not None:
76
- try:
77
- with open(Path(rules_file).resolve(), "r") as f:
78
- rule.extend(f.read().splitlines())
79
- except FileNotFoundError:
80
- print(f"[yellow]rules file not found at {rules_file}[/yellow]")
232
+ token.add_role_grant(role="agent")
233
+ token.add_room_grant(room)
81
234
 
82
- print("[bold green]Connecting to room...[/bold green]", flush=True)
83
- async with RoomClient(
84
- protocol=WebSocketClientProtocol(
85
- url=websocket_room_url(room_name=room, base_url=meshagent_base_url()),
86
- token=jwt,
87
- )
88
- ) as client:
89
- requirements = []
235
+ jwt = token.to_jwt(api_key=key)
90
236
 
91
- for t in toolkit:
92
- requirements.append(RequiredToolkit(name=t))
237
+ CustomVoiceBot = build_voicebot(
238
+ rules=rule,
239
+ rules_file=rules_file,
240
+ toolkits=require_toolkit + toolkit,
241
+ schemas=require_schema + schema,
242
+ auto_greet_message=auto_greet_message,
243
+ auto_greet_prompt=auto_greet_prompt,
244
+ room_rules_paths=room_rules,
245
+ )
93
246
 
94
- for t in schema:
95
- requirements.append(RequiredSchema(name=t))
247
+ bot = CustomVoiceBot()
96
248
 
97
- bot = VoiceBot(
98
- auto_greet_message=auto_greet_message,
99
- auto_greet_prompt=auto_greet_prompt,
100
- name=agent_name,
101
- requires=requirements,
102
- rules=rule if len(rule) > 0 else None,
103
- )
104
-
105
- await bot.start(room=client)
249
+ print("[bold green]Connecting to room...[/bold green]", flush=True)
250
+ if get_deferred():
251
+ from meshagent.cli.host import agents
106
252
 
107
- try:
108
- print(
109
- 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]",
110
- flush=True,
253
+ agents.append((bot, jwt))
254
+ else:
255
+ async with RoomClient(
256
+ protocol=WebSocketClientProtocol(
257
+ url=websocket_room_url(
258
+ room_name=room, base_url=meshagent_base_url()
259
+ ),
260
+ token=jwt,
111
261
  )
112
- await client.protocol.wait_for_close()
113
- except KeyboardInterrupt:
114
- await bot.stop()
262
+ ) as client:
263
+ await bot.start(room=client)
264
+
265
+ try:
266
+ print(
267
+ 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]",
268
+ flush=True,
269
+ )
270
+ await client.protocol.wait_for_close()
271
+ except KeyboardInterrupt:
272
+ await bot.stop()
115
273
 
116
274
  finally:
117
275
  await account_client.close()
@@ -123,56 +281,344 @@ async def service(
123
281
  agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
124
282
  rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
125
283
  rules_file: Optional[str] = None,
284
+ require_toolkit: Annotated[
285
+ List[str],
286
+ typer.Option(
287
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
288
+ ),
289
+ ] = [],
290
+ require_schema: Annotated[
291
+ List[str],
292
+ typer.Option(
293
+ "--require-schema", "-rs", help="the name or url of a required schema"
294
+ ),
295
+ ] = [],
296
+ toolkit: Annotated[
297
+ List[str],
298
+ typer.Option(
299
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
300
+ ),
301
+ ] = [],
302
+ schema: Annotated[
303
+ List[str],
304
+ typer.Option(
305
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
306
+ ),
307
+ ] = [],
308
+ auto_greet_message: Annotated[
309
+ Optional[str],
310
+ typer.Option(help="Message to send automatically when the bot joins"),
311
+ ] = None,
312
+ auto_greet_prompt: Annotated[
313
+ Optional[str],
314
+ typer.Option(help="Prompt to generate an auto-greet message"),
315
+ ] = None,
316
+ host: Annotated[
317
+ Optional[str], typer.Option(help="Host to bind the service on")
318
+ ] = None,
319
+ port: Annotated[
320
+ Optional[int], typer.Option(help="Port to bind the service on")
321
+ ] = None,
322
+ path: Annotated[
323
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
324
+ ] = None,
325
+ room_rules: Annotated[
326
+ List[str],
327
+ typer.Option(
328
+ "--room-rules",
329
+ "-rr",
330
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
331
+ ),
332
+ ] = [],
333
+ ):
334
+ CustomVoiceBot = build_voicebot(
335
+ rules=rule,
336
+ rules_file=rules_file,
337
+ toolkits=require_toolkit + toolkit,
338
+ schemas=require_schema + schema,
339
+ auto_greet_message=auto_greet_message,
340
+ auto_greet_prompt=auto_greet_prompt,
341
+ room_rules_paths=room_rules,
342
+ )
343
+
344
+ service = get_service(host=host, port=port)
345
+
346
+ service.agents.append(
347
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "VoiceBot"})
348
+ )
349
+
350
+ if path is None:
351
+ path = "/agent"
352
+ i = 0
353
+ while service.has_path(path):
354
+ i += 1
355
+ path = f"/agent{i}"
356
+
357
+ service.add_path(identity=agent_name, path=path, cls=CustomVoiceBot)
358
+
359
+ if not get_deferred():
360
+ await run_services()
361
+
362
+
363
+ @app.async_command("spec")
364
+ async def spec(
365
+ *,
366
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
367
+ service_description: Annotated[
368
+ Optional[str], typer.Option("--service-description", help="service description")
369
+ ] = None,
370
+ service_title: Annotated[
371
+ Optional[str],
372
+ typer.Option("--service-title", help="a display name for the service"),
373
+ ] = None,
374
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
375
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
376
+ rules_file: Optional[str] = None,
377
+ require_toolkit: Annotated[
378
+ List[str],
379
+ typer.Option(
380
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
381
+ ),
382
+ ] = [],
383
+ require_schema: Annotated[
384
+ List[str],
385
+ typer.Option(
386
+ "--require-schema", "-rs", help="the name or url of a required schema"
387
+ ),
388
+ ] = [],
126
389
  toolkit: Annotated[
127
390
  List[str],
128
- typer.Option("--toolkit", "-t", help="the name or url of a required toolkit"),
391
+ typer.Option(
392
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
393
+ ),
129
394
  ] = [],
130
395
  schema: Annotated[
131
396
  List[str],
132
- typer.Option("--schema", "-s", help="the name or url of a required schema"),
397
+ typer.Option(
398
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
399
+ ),
400
+ ] = [],
401
+ auto_greet_message: Annotated[
402
+ Optional[str],
403
+ typer.Option(help="Message to send automatically when the bot joins"),
404
+ ] = None,
405
+ auto_greet_prompt: Annotated[
406
+ Optional[str],
407
+ typer.Option(help="Prompt to generate an auto-greet message"),
408
+ ] = None,
409
+ host: Annotated[
410
+ Optional[str], typer.Option(help="Host to bind the service on")
411
+ ] = None,
412
+ port: Annotated[
413
+ Optional[int], typer.Option(help="Port to bind the service on")
414
+ ] = None,
415
+ path: Annotated[
416
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
417
+ ] = None,
418
+ room_rules: Annotated[
419
+ List[str],
420
+ typer.Option(
421
+ "--room-rules",
422
+ "-rr",
423
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
424
+ ),
133
425
  ] = [],
134
- auto_greet_message: Annotated[Optional[str], typer.Option()] = None,
135
- auto_greet_prompt: Annotated[Optional[str], typer.Option()] = None,
136
- host: Annotated[Optional[str], typer.Option()] = None,
137
- port: Annotated[Optional[int], typer.Option()] = None,
138
- path: Annotated[str, typer.Option()] = "/agent",
139
426
  ):
140
- try:
141
- from meshagent.livekit.agents.voice import VoiceBot
142
- except ImportError:
427
+ CustomVoiceBot = build_voicebot(
428
+ rules=rule,
429
+ rules_file=rules_file,
430
+ toolkits=require_toolkit + toolkit,
431
+ schemas=require_schema + schema,
432
+ auto_greet_message=auto_greet_message,
433
+ auto_greet_prompt=auto_greet_prompt,
434
+ room_rules_paths=room_rules,
435
+ )
143
436
 
144
- class VoiceBot:
145
- def __init__(self, **kwargs):
146
- raise RoomException(
147
- "meshagent.livekit module not found, voicebots are not available"
148
- )
437
+ service = get_service(host=host, port=port)
149
438
 
150
- requirements = []
439
+ service.agents.append(
440
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "VoiceBot"})
441
+ )
151
442
 
152
- for t in toolkit:
153
- requirements.append(RequiredToolkit(name=t))
443
+ if path is None:
444
+ path = "/agent"
445
+ i = 0
446
+ while service.has_path(path):
447
+ i += 1
448
+ path = f"/agent{i}"
154
449
 
155
- for t in schema:
156
- requirements.append(RequiredSchema(name=t))
450
+ service.add_path(identity=agent_name, path=path, cls=CustomVoiceBot)
157
451
 
158
- if rules_file is not None:
452
+ spec = service_specs()[0]
453
+ spec.metadata.annotations = {
454
+ "meshagent.service.id": service_name,
455
+ }
456
+ spec.metadata.name = service_name
457
+ spec.metadata.description = service_description
458
+ spec.container.image = (
459
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
460
+ )
461
+ spec.container.command = shlex.join(
462
+ ["meshagent", "voicebot", "service", *cleanup_args(sys.argv[2:])]
463
+ )
464
+
465
+ print(yaml.dump(spec.model_dump(mode="json", exclude_none=True), sort_keys=False))
466
+
467
+
468
+ @app.async_command("deploy")
469
+ async def deploy(
470
+ *,
471
+ service_name: Annotated[str, typer.Option("--service-name", help="service name")],
472
+ service_description: Annotated[
473
+ Optional[str], typer.Option("--service-description", help="service description")
474
+ ] = None,
475
+ service_title: Annotated[
476
+ Optional[str],
477
+ typer.Option("--service-title", help="a display name for the service"),
478
+ ] = None,
479
+ agent_name: Annotated[str, typer.Option(..., help="Name of the agent to call")],
480
+ rule: Annotated[List[str], typer.Option("--rule", "-r", help="a system rule")] = [],
481
+ rules_file: Optional[str] = None,
482
+ require_toolkit: Annotated[
483
+ List[str],
484
+ typer.Option(
485
+ "--require-toolkit", "-rt", help="the name or url of a required toolkit"
486
+ ),
487
+ ] = [],
488
+ require_schema: Annotated[
489
+ List[str],
490
+ typer.Option(
491
+ "--require-schema", "-rs", help="the name or url of a required schema"
492
+ ),
493
+ ] = [],
494
+ toolkit: Annotated[
495
+ List[str],
496
+ typer.Option(
497
+ "--toolkit", "-t", help="the name or url of a required toolkit", hidden=True
498
+ ),
499
+ ] = [],
500
+ schema: Annotated[
501
+ List[str],
502
+ typer.Option(
503
+ "--schema", "-s", help="the name or url of a required schema", hidden=True
504
+ ),
505
+ ] = [],
506
+ auto_greet_message: Annotated[
507
+ Optional[str],
508
+ typer.Option(help="Message to send automatically when the bot joins"),
509
+ ] = None,
510
+ auto_greet_prompt: Annotated[
511
+ Optional[str],
512
+ typer.Option(help="Prompt to generate an auto-greet message"),
513
+ ] = None,
514
+ host: Annotated[
515
+ Optional[str], typer.Option(help="Host to bind the service on")
516
+ ] = None,
517
+ port: Annotated[
518
+ Optional[int], typer.Option(help="Port to bind the service on")
519
+ ] = None,
520
+ path: Annotated[
521
+ Optional[str], typer.Option(help="HTTP path to mount the service at")
522
+ ] = None,
523
+ room_rules: Annotated[
524
+ List[str],
525
+ typer.Option(
526
+ "--room-rules",
527
+ "-rr",
528
+ help="a path to a rules file within the room that can be used to customize the agent's behavior",
529
+ ),
530
+ ] = [],
531
+ project_id: ProjectIdOption,
532
+ room: Annotated[
533
+ Optional[str],
534
+ typer.Option("--room", help="The name of a room to create the service for"),
535
+ ] = None,
536
+ ):
537
+ project_id = await resolve_project_id(project_id=project_id)
538
+
539
+ CustomVoiceBot = build_voicebot(
540
+ rules=rule,
541
+ rules_file=rules_file,
542
+ toolkits=require_toolkit + toolkit,
543
+ schemas=require_schema + schema,
544
+ auto_greet_message=auto_greet_message,
545
+ auto_greet_prompt=auto_greet_prompt,
546
+ room_rules_paths=room_rules,
547
+ )
548
+
549
+ service = get_service(host=host, port=port)
550
+
551
+ service.agents.append(
552
+ AgentSpec(name=agent_name, annotations={ANNOTATION_AGENT_TYPE: "VoiceBot"})
553
+ )
554
+
555
+ if path is None:
556
+ path = "/agent"
557
+ i = 0
558
+ while service.has_path(path):
559
+ i += 1
560
+ path = f"/agent{i}"
561
+
562
+ service.add_path(identity=agent_name, path=path, cls=CustomVoiceBot)
563
+
564
+ spec = service_specs()[0]
565
+ spec.metadata.annotations = {
566
+ "meshagent.service.id": service_name,
567
+ }
568
+ spec.metadata.name = service_name
569
+ spec.metadata.description = service_description
570
+ spec.container.image = (
571
+ "us-central1-docker.pkg.dev/meshagent-public/images/cli:{SERVER_VERSION}-esgz"
572
+ )
573
+ spec.container.command = shlex.join(
574
+ ["meshagent", "voicebot", "service", *cleanup_args(sys.argv[2:])]
575
+ )
576
+
577
+ client = await get_client()
578
+ try:
579
+ id = None
159
580
  try:
160
- with open(Path(rules_file).resolve(), "r") as f:
161
- rule.extend(f.read().splitlines())
162
- except FileNotFoundError:
163
- print(f"[yellow]rules file not found at {rules_file}[/yellow]")
581
+ if id is None:
582
+ if room is None:
583
+ services = await client.list_services(project_id=project_id)
584
+ else:
585
+ services = await client.list_room_services(
586
+ project_id=project_id, room_name=room
587
+ )
164
588
 
165
- service = ServiceHost(host=host, port=port)
589
+ for s in services:
590
+ if s.metadata.name == spec.metadata.name:
591
+ id = s.id
166
592
 
167
- @service.path(path=path)
168
- class CustomVoiceBot(VoiceBot):
169
- def __init__(self):
170
- super().__init__(
171
- auto_greet_message=auto_greet_message,
172
- auto_greet_prompt=auto_greet_prompt,
173
- name=agent_name,
174
- requires=requirements,
175
- rules=rule if len(rule) > 0 else None,
176
- )
593
+ if id is None:
594
+ if room is None:
595
+ id = await client.create_service(
596
+ project_id=project_id, service=spec
597
+ )
598
+ else:
599
+ id = await client.create_room_service(
600
+ project_id=project_id, service=spec, room_name=room
601
+ )
177
602
 
178
- await service.run()
603
+ else:
604
+ spec.id = id
605
+ if room is None:
606
+ await client.update_service(
607
+ project_id=project_id, service_id=id, service=spec
608
+ )
609
+ else:
610
+ await client.update_room_service(
611
+ project_id=project_id,
612
+ service_id=id,
613
+ service=spec,
614
+ room_name=room,
615
+ )
616
+
617
+ except ConflictError:
618
+ print(f"[red]Service name already in use: {spec.metadata.name}[/red]")
619
+ raise typer.Exit(code=1)
620
+ else:
621
+ print(f"[green]Deployed service:[/] {id}")
622
+
623
+ finally:
624
+ await client.close()