meshagent-cli 0.5.15__py3-none-any.whl → 0.6.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.
Potentially problematic release.
This version of meshagent-cli might be problematic. Click here for more details.
- meshagent/cli/agent.py +11 -62
- meshagent/cli/api_keys.py +46 -93
- meshagent/cli/auth_async.py +225 -68
- meshagent/cli/call.py +82 -19
- meshagent/cli/chatbot.py +83 -49
- meshagent/cli/cli.py +26 -70
- meshagent/cli/cli_mcp.py +61 -27
- meshagent/cli/cli_secrets.py +1 -1
- meshagent/cli/common_options.py +2 -10
- meshagent/cli/containers.py +577 -0
- meshagent/cli/developer.py +7 -25
- meshagent/cli/exec.py +162 -76
- meshagent/cli/helper.py +35 -67
- meshagent/cli/helpers.py +131 -0
- meshagent/cli/mailbot.py +31 -26
- meshagent/cli/meeting_transcriber.py +124 -0
- meshagent/cli/messaging.py +12 -51
- meshagent/cli/oauth2.py +189 -0
- meshagent/cli/participant_token.py +32 -21
- meshagent/cli/queue.py +6 -37
- meshagent/cli/services.py +300 -335
- meshagent/cli/storage.py +24 -89
- meshagent/cli/version.py +1 -1
- meshagent/cli/voicebot.py +39 -28
- meshagent/cli/webhook.py +3 -3
- {meshagent_cli-0.5.15.dist-info → meshagent_cli-0.6.0.dist-info}/METADATA +17 -11
- meshagent_cli-0.6.0.dist-info/RECORD +35 -0
- meshagent/cli/otel.py +0 -122
- meshagent_cli-0.5.15.dist-info/RECORD +0 -32
- {meshagent_cli-0.5.15.dist-info → meshagent_cli-0.6.0.dist-info}/WHEEL +0 -0
- {meshagent_cli-0.5.15.dist-info → meshagent_cli-0.6.0.dist-info}/entry_points.txt +0 -0
- {meshagent_cli-0.5.15.dist-info → meshagent_cli-0.6.0.dist-info}/top_level.txt +0 -0
meshagent/cli/auth_async.py
CHANGED
|
@@ -1,79 +1,153 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
|
+
import time
|
|
4
|
+
import base64
|
|
5
|
+
import hashlib
|
|
6
|
+
import secrets
|
|
3
7
|
import webbrowser
|
|
4
8
|
import asyncio
|
|
5
9
|
from pathlib import Path
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
AUTH_URL = os.getenv("MESHAGENT_AUTH_URL", "https://infra.meshagent.com")
|
|
15
|
-
AUTH_ANON_KEY = os.getenv(
|
|
16
|
-
"MESHAGENT_AUTH_ANON_KEY",
|
|
17
|
-
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5memhyeWpoc3RjZXdkeWdvampzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ2MzU2MjgsImV4cCI6MjA1MDIxMTYyOH0.ujx9CIbYEvWbA77ogB1gg1Jrv3KtpB1rWh_LRRLpcow",
|
|
18
|
-
)
|
|
10
|
+
from urllib.parse import urlencode
|
|
11
|
+
from aiohttp import web, ClientSession
|
|
12
|
+
|
|
13
|
+
# -----------------------------------------------------------------------------
|
|
14
|
+
# Config
|
|
15
|
+
# -----------------------------------------------------------------------------
|
|
16
|
+
|
|
19
17
|
CACHE_FILE = Path.home() / ".meshagent" / "session.json"
|
|
20
18
|
REDIRECT_PORT = 8765
|
|
21
19
|
REDIRECT_URL = f"http://localhost:{REDIRECT_PORT}/callback"
|
|
22
20
|
|
|
23
|
-
#
|
|
21
|
+
# Expected env vars:
|
|
22
|
+
# - MESHAGENT_API_URL (required): e.g., https://api.meshagent.com
|
|
23
|
+
# - MESHAGENT_OAUTH_CLIENT_ID (required)
|
|
24
|
+
# - MESHAGENT_OAUTH_CLIENT_SECRET (optional; only if your server requires it)
|
|
25
|
+
# - MESHAGENT_OAUTH_SCOPES (optional; defaults to "openid email profile")
|
|
26
|
+
|
|
27
|
+
# -----------------------------------------------------------------------------
|
|
28
|
+
# Helpers
|
|
29
|
+
# -----------------------------------------------------------------------------
|
|
24
30
|
|
|
25
31
|
|
|
26
32
|
def _ensure_cache_dir():
|
|
27
33
|
CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
28
34
|
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
36
|
+
def _now() -> int:
|
|
37
|
+
return int(time.time())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _b64url_no_pad(data: bytes) -> str:
|
|
41
|
+
return base64.urlsafe_b64encode(data).decode().rstrip("=")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _pkce_pair():
|
|
45
|
+
"""
|
|
46
|
+
Returns (code_verifier, code_challenge) using S256 per RFC 7636.
|
|
47
|
+
"""
|
|
48
|
+
verifier = _b64url_no_pad(secrets.token_bytes(32))
|
|
49
|
+
digest = hashlib.sha256(verifier.encode()).digest()
|
|
50
|
+
challenge = _b64url_no_pad(digest)
|
|
51
|
+
return verifier, challenge
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _api_base() -> str:
|
|
55
|
+
api = os.getenv("MESHAGENT_API_URL", "https://api.meshagent.com")
|
|
56
|
+
if not api:
|
|
57
|
+
raise RuntimeError("MESHAGENT_API_URL is not set")
|
|
58
|
+
return api.rstrip("/")
|
|
59
|
+
|
|
41
60
|
|
|
61
|
+
def _authorization_url() -> str:
|
|
62
|
+
return f"{_api_base()}/oauth/authorize"
|
|
42
63
|
|
|
43
|
-
|
|
64
|
+
|
|
65
|
+
def _token_url() -> str:
|
|
66
|
+
return f"{_api_base()}/oauth/token"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _client_id() -> str:
|
|
70
|
+
cid = os.getenv("MESHAGENT_OAUTH_CLIENT_ID", "p8xy1ZUi73jJUJbNfTg92HUSDpCSZJcc")
|
|
71
|
+
if not cid:
|
|
72
|
+
raise RuntimeError("MESHAGENT_OAUTH_CLIENT_ID is not set")
|
|
73
|
+
return cid
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _client_secret() -> str | None:
|
|
77
|
+
return os.getenv("MESHAGENT_OAUTH_CLIENT_SECRET")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _scopes() -> str:
|
|
81
|
+
return os.getenv("MESHAGENT_OAUTH_SCOPES", "admin")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _save(tokens: dict):
|
|
85
|
+
"""
|
|
86
|
+
Persist minimal token info to disk.
|
|
87
|
+
Expected keys: access_token, refresh_token (optional), expires_at (epoch int).
|
|
88
|
+
"""
|
|
44
89
|
_ensure_cache_dir()
|
|
45
90
|
CACHE_FILE.write_text(
|
|
46
91
|
json.dumps(
|
|
47
92
|
{
|
|
48
|
-
"access_token":
|
|
49
|
-
"refresh_token":
|
|
50
|
-
"expires_at":
|
|
93
|
+
"access_token": tokens.get("access_token"),
|
|
94
|
+
"refresh_token": tokens.get("refresh_token"),
|
|
95
|
+
"expires_at": tokens.get("expires_at"),
|
|
96
|
+
"token_type": tokens.get("token_type", "Bearer"),
|
|
97
|
+
"scope": tokens.get("scope"),
|
|
98
|
+
"id_token": tokens.get("id_token"),
|
|
51
99
|
}
|
|
52
100
|
)
|
|
53
101
|
)
|
|
54
102
|
|
|
55
103
|
|
|
56
|
-
def _load():
|
|
104
|
+
def _load() -> dict | None:
|
|
57
105
|
_ensure_cache_dir()
|
|
58
106
|
if CACHE_FILE.exists():
|
|
59
107
|
return json.loads(CACHE_FILE.read_text())
|
|
60
108
|
|
|
61
109
|
|
|
62
|
-
|
|
110
|
+
async def _post_form(url: str, form: dict) -> dict:
|
|
111
|
+
"""
|
|
112
|
+
POST application/x-www-form-urlencoded and return parsed JSON or raise.
|
|
113
|
+
"""
|
|
114
|
+
headers = {"Accept": "application/json"}
|
|
115
|
+
async with ClientSession() as s:
|
|
116
|
+
async with s.post(url, data=form, headers=headers) as resp:
|
|
117
|
+
text = await resp.text()
|
|
118
|
+
if resp.status >= 400:
|
|
119
|
+
raise RuntimeError(f"Token endpoint error {resp.status}: {text}")
|
|
120
|
+
try:
|
|
121
|
+
return json.loads(text)
|
|
122
|
+
except json.JSONDecodeError:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
f"Unexpected non-JSON response from token endpoint: {text}"
|
|
125
|
+
)
|
|
126
|
+
|
|
63
127
|
|
|
128
|
+
# -----------------------------------------------------------------------------
|
|
129
|
+
# Local HTTP callback
|
|
130
|
+
# -----------------------------------------------------------------------------
|
|
64
131
|
|
|
65
|
-
|
|
66
|
-
|
|
132
|
+
|
|
133
|
+
async def _wait_for_code(expected_state: str) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Spin up a one-shot aiohttp server and await ?code=…&state=…
|
|
136
|
+
Validates 'state' if provided. Returns the 'code'.
|
|
137
|
+
"""
|
|
67
138
|
app = web.Application()
|
|
68
139
|
code_fut: asyncio.Future[str] = asyncio.get_event_loop().create_future()
|
|
69
140
|
|
|
70
141
|
async def callback(request):
|
|
71
142
|
code = request.query.get("code")
|
|
143
|
+
state = request.query.get("state")
|
|
144
|
+
if expected_state and state != expected_state:
|
|
145
|
+
return web.Response(status=400, text="State mismatch. Close this tab.")
|
|
72
146
|
if code:
|
|
73
147
|
if not code_fut.done():
|
|
74
148
|
code_fut.set_result(code)
|
|
75
149
|
return web.Response(text="You may close this tab.")
|
|
76
|
-
return web.Response(status=400)
|
|
150
|
+
return web.Response(status=400, text="Missing 'code'.")
|
|
77
151
|
|
|
78
152
|
app.add_routes([web.get("/callback", callback)])
|
|
79
153
|
runner = web.AppRunner(app, access_log=None)
|
|
@@ -87,52 +161,135 @@ async def _wait_for_code() -> str:
|
|
|
87
161
|
await runner.cleanup()
|
|
88
162
|
|
|
89
163
|
|
|
90
|
-
#
|
|
164
|
+
# -----------------------------------------------------------------------------
|
|
165
|
+
# OAuth flows
|
|
166
|
+
# -----------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def _exchange_code_for_tokens(code: str, code_verifier: str) -> dict:
|
|
170
|
+
form = {
|
|
171
|
+
"grant_type": "authorization_code",
|
|
172
|
+
"code": code,
|
|
173
|
+
"redirect_uri": REDIRECT_URL,
|
|
174
|
+
"client_id": _client_id(),
|
|
175
|
+
"code_verifier": code_verifier,
|
|
176
|
+
}
|
|
177
|
+
# Include client_secret only if provided (public clients typically omit)
|
|
178
|
+
client_secret = _client_secret()
|
|
179
|
+
if client_secret:
|
|
180
|
+
form["client_secret"] = client_secret
|
|
181
|
+
|
|
182
|
+
token_json = await _post_form(_token_url(), form)
|
|
183
|
+
|
|
184
|
+
# Compute absolute expiry; default to 3600s if expires_in missing
|
|
185
|
+
expires_in = int(token_json.get("expires_in", 3600))
|
|
186
|
+
token_json["expires_at"] = _now() + max(0, expires_in - 30) # small safety skew
|
|
187
|
+
return token_json
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def _refresh_tokens(tokens: dict) -> dict:
|
|
191
|
+
if not tokens or not tokens.get("refresh_token"):
|
|
192
|
+
raise RuntimeError("No refresh token available to refresh access token.")
|
|
193
|
+
|
|
194
|
+
form = {
|
|
195
|
+
"grant_type": "refresh_token",
|
|
196
|
+
"refresh_token": tokens["refresh_token"],
|
|
197
|
+
"client_id": _client_id(),
|
|
198
|
+
}
|
|
199
|
+
client_secret = _client_secret()
|
|
200
|
+
if client_secret:
|
|
201
|
+
form["client_secret"] = client_secret
|
|
202
|
+
|
|
203
|
+
token_json = await _post_form(_token_url(), form)
|
|
204
|
+
|
|
205
|
+
# Some servers rotate refresh tokens; keep old one if none returned
|
|
206
|
+
token_json["refresh_token"] = token_json.get(
|
|
207
|
+
"refresh_token", tokens.get("refresh_token")
|
|
208
|
+
)
|
|
209
|
+
expires_in = int(token_json.get("expires_in", 3600))
|
|
210
|
+
token_json["expires_at"] = _now() + max(0, expires_in - 30)
|
|
211
|
+
return token_json
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# -----------------------------------------------------------------------------
|
|
215
|
+
# Public API (unchanged names)
|
|
216
|
+
# -----------------------------------------------------------------------------
|
|
91
217
|
|
|
92
218
|
|
|
93
219
|
async def login():
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
220
|
+
"""
|
|
221
|
+
Launches the system browser for OAuth 2.0 Authorization Code + PKCE.
|
|
222
|
+
Persists tokens to ~/.meshagent/session.json
|
|
223
|
+
"""
|
|
224
|
+
authz = _authorization_url()
|
|
225
|
+
client_id = _client_id()
|
|
226
|
+
scope = _scopes()
|
|
227
|
+
|
|
228
|
+
code_verifier, code_challenge = _pkce_pair()
|
|
229
|
+
state = _b64url_no_pad(secrets.token_bytes(16))
|
|
230
|
+
|
|
231
|
+
query = {
|
|
232
|
+
"response_type": "code",
|
|
233
|
+
"client_id": client_id,
|
|
234
|
+
"redirect_uri": REDIRECT_URL,
|
|
235
|
+
"scope": scope,
|
|
236
|
+
"code_challenge": code_challenge,
|
|
237
|
+
"code_challenge_method": "S256",
|
|
238
|
+
"state": state,
|
|
239
|
+
}
|
|
240
|
+
auth_url = f"{authz}?{urlencode(query)}"
|
|
241
|
+
|
|
242
|
+
# Kick user to browser without blocking the loop
|
|
243
|
+
await asyncio.to_thread(webbrowser.open, auth_url)
|
|
244
|
+
print(f"Waiting for auth redirect on {auth_url}…")
|
|
245
|
+
|
|
246
|
+
# Await the auth code, then exchange for tokens
|
|
247
|
+
auth_code = await _wait_for_code(state)
|
|
111
248
|
print("Got code, exchanging…")
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
249
|
+
|
|
250
|
+
tokens = await _exchange_code_for_tokens(auth_code, code_verifier)
|
|
251
|
+
_save(tokens)
|
|
252
|
+
print("✅ Logged in (tokens cached).")
|
|
115
253
|
|
|
116
254
|
|
|
117
255
|
async def session():
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
256
|
+
"""
|
|
257
|
+
Returns a tuple (client, tokens_dict)
|
|
258
|
+
- client is None (kept for backward compatibility with prior signature).
|
|
259
|
+
- tokens_dict contains access_token, refresh_token, expires_at, token_type, scope, id_token.
|
|
260
|
+
Will auto-refresh if expired/near-expiry and update the cache.
|
|
261
|
+
"""
|
|
262
|
+
tokens = _load()
|
|
263
|
+
if not tokens:
|
|
264
|
+
return None, None
|
|
265
|
+
|
|
266
|
+
# Refresh if expired or within 5 min of expiry
|
|
267
|
+
if not tokens.get("expires_at") or tokens["expires_at"] <= _now() + 5 * 60:
|
|
268
|
+
try:
|
|
269
|
+
tokens = await _refresh_tokens(tokens)
|
|
270
|
+
_save(tokens)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
# If refresh fails, wipe session to force re-login
|
|
273
|
+
print(f"⚠️ Token refresh failed: {e}")
|
|
274
|
+
return None, None
|
|
275
|
+
|
|
276
|
+
return None, tokens
|
|
126
277
|
|
|
127
278
|
|
|
128
279
|
async def logout():
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
280
|
+
"""
|
|
281
|
+
Clears the cached tokens. (If your OAuth server supports revocation,
|
|
282
|
+
you can add a call here; not provided in the spec.)
|
|
283
|
+
"""
|
|
284
|
+
_, tokens = await session()
|
|
285
|
+
# Optional: call a revocation endpoint here if your server provides one.
|
|
132
286
|
CACHE_FILE.unlink(missing_ok=True)
|
|
133
287
|
print("👋 Signed out")
|
|
134
288
|
|
|
135
289
|
|
|
136
290
|
async def get_access_token():
|
|
137
|
-
|
|
138
|
-
|
|
291
|
+
"""
|
|
292
|
+
Returns a fresh access token, refreshing if needed.
|
|
293
|
+
"""
|
|
294
|
+
_, tokens = await session()
|
|
295
|
+
return tokens["access_token"] if tokens else None
|
meshagent/cli/call.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
from rich import print
|
|
3
3
|
from typing import Annotated, Optional
|
|
4
|
-
from meshagent.cli.common_options import ProjectIdOption,
|
|
4
|
+
from meshagent.cli.common_options import ProjectIdOption, RoomOption
|
|
5
5
|
import json
|
|
6
6
|
import aiohttp
|
|
7
7
|
from meshagent.api import (
|
|
@@ -9,16 +9,20 @@ from meshagent.api import (
|
|
|
9
9
|
ParticipantToken,
|
|
10
10
|
WebSocketClientProtocol,
|
|
11
11
|
ParticipantGrant,
|
|
12
|
+
ApiScope,
|
|
12
13
|
)
|
|
13
14
|
from meshagent.api.helpers import meshagent_base_url, websocket_room_url
|
|
14
15
|
from meshagent.api.services import send_webhook
|
|
15
16
|
from meshagent.cli import async_typer
|
|
16
|
-
from meshagent.cli.helper import get_client, resolve_project_id
|
|
17
|
-
from meshagent.cli.helper import
|
|
17
|
+
from meshagent.cli.helper import get_client, resolve_project_id, resolve_key
|
|
18
|
+
from meshagent.cli.helper import resolve_room
|
|
18
19
|
from urllib.parse import urlparse
|
|
19
20
|
from pathlib import PurePath
|
|
20
21
|
import socket
|
|
21
22
|
import ipaddress
|
|
23
|
+
import pathlib
|
|
24
|
+
from pydantic_yaml import parse_yaml_raw_as
|
|
25
|
+
from meshagent.api.participant_token import ParticipantTokenSpec
|
|
22
26
|
|
|
23
27
|
app = async_typer.AsyncTyper()
|
|
24
28
|
|
|
@@ -72,7 +76,6 @@ async def make_call(
|
|
|
72
76
|
*,
|
|
73
77
|
project_id: ProjectIdOption = None,
|
|
74
78
|
room: RoomOption,
|
|
75
|
-
api_key_id: ApiKeyIdOption = None,
|
|
76
79
|
role: str = "agent",
|
|
77
80
|
local: Optional[bool] = None,
|
|
78
81
|
agent_name: Annotated[
|
|
@@ -87,12 +90,76 @@ async def make_call(
|
|
|
87
90
|
arguments: Annotated[
|
|
88
91
|
str, typer.Option(..., help="JSON string with arguments for the call")
|
|
89
92
|
] = {},
|
|
93
|
+
permissions: Annotated[
|
|
94
|
+
Optional[str],
|
|
95
|
+
typer.Option(
|
|
96
|
+
"--permissions",
|
|
97
|
+
"-p",
|
|
98
|
+
help="File path to a token definition, if not specified default agent permissions will be used",
|
|
99
|
+
),
|
|
100
|
+
] = None,
|
|
101
|
+
key: Annotated[
|
|
102
|
+
str,
|
|
103
|
+
typer.Option("--key", help="an api key to sign the token with"),
|
|
104
|
+
] = None,
|
|
105
|
+
):
|
|
106
|
+
key = await resolve_key(project_id=project_id, key=key)
|
|
107
|
+
|
|
108
|
+
if permissions is not None:
|
|
109
|
+
with open(str(pathlib.Path(permissions).expanduser().resolve()), "rb") as f:
|
|
110
|
+
spec = parse_yaml_raw_as(ParticipantTokenSpec, f.read())
|
|
111
|
+
|
|
112
|
+
token = ParticipantToken(
|
|
113
|
+
name=spec.identity,
|
|
114
|
+
)
|
|
115
|
+
token.add_role_grant(role=role)
|
|
116
|
+
token.add_room_grant(room)
|
|
117
|
+
token.add_api_grant(spec.api)
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
token = None
|
|
121
|
+
|
|
122
|
+
await _make_call(
|
|
123
|
+
project_id=project_id,
|
|
124
|
+
room=room,
|
|
125
|
+
role=role,
|
|
126
|
+
local=local,
|
|
127
|
+
agent_name=agent_name,
|
|
128
|
+
name=name,
|
|
129
|
+
participant_name=participant_name,
|
|
130
|
+
url=url,
|
|
131
|
+
arguments=arguments,
|
|
132
|
+
token=token,
|
|
133
|
+
key=key,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def _make_call(
|
|
138
|
+
*,
|
|
139
|
+
project_id: ProjectIdOption = None,
|
|
140
|
+
room: RoomOption,
|
|
141
|
+
role: str = "agent",
|
|
142
|
+
local: Optional[bool] = None,
|
|
143
|
+
agent_name: Annotated[
|
|
144
|
+
Optional[str], typer.Option(..., help="deprecated and unused", hidden=True)
|
|
145
|
+
] = None,
|
|
146
|
+
name: Annotated[str, typer.Option(..., help="deprecated", hidden=True)] = None,
|
|
147
|
+
participant_name: Annotated[
|
|
148
|
+
Optional[str],
|
|
149
|
+
typer.Option(..., help="the participant name to be used by the callee"),
|
|
150
|
+
] = None,
|
|
151
|
+
url: Annotated[str, typer.Option(..., help="URL the agent should call")],
|
|
152
|
+
arguments: Annotated[
|
|
153
|
+
str, typer.Option(..., help="JSON string with arguments for the call")
|
|
154
|
+
] = {},
|
|
155
|
+
token: Optional[ParticipantToken] = None,
|
|
156
|
+
permissions: Optional[ApiScope] = None,
|
|
157
|
+
key: str,
|
|
90
158
|
):
|
|
91
159
|
"""
|
|
92
160
|
Instruct an agent to 'call' a given URL with specific arguments.
|
|
93
161
|
|
|
94
162
|
"""
|
|
95
|
-
|
|
96
163
|
if name is not None:
|
|
97
164
|
print("[yellow]name is deprecated and should no longer be passed[/yellow]")
|
|
98
165
|
|
|
@@ -109,21 +176,17 @@ async def make_call(
|
|
|
109
176
|
account_client = await get_client()
|
|
110
177
|
try:
|
|
111
178
|
project_id = await resolve_project_id(project_id=project_id)
|
|
112
|
-
|
|
179
|
+
|
|
113
180
|
room = resolve_room(room)
|
|
114
181
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
182
|
+
if token is None:
|
|
183
|
+
token = ParticipantToken(
|
|
184
|
+
name=participant_name,
|
|
118
185
|
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
name=
|
|
123
|
-
)
|
|
124
|
-
token.add_role_grant(role=role)
|
|
125
|
-
token.add_room_grant(room)
|
|
126
|
-
token.grants.append(ParticipantGrant(name="tunnel_ports", scope="9000"))
|
|
186
|
+
token.add_api_grant(permissions or ApiScope.agent_default())
|
|
187
|
+
token.add_role_grant(role=role)
|
|
188
|
+
token.add_room_grant(room)
|
|
189
|
+
token.grants.append(ParticipantGrant(name="tunnel_ports", scope="9000"))
|
|
127
190
|
|
|
128
191
|
if local is None:
|
|
129
192
|
local = is_local_url(url)
|
|
@@ -134,7 +197,7 @@ async def make_call(
|
|
|
134
197
|
data = {
|
|
135
198
|
"room_url": websocket_room_url(room_name=room),
|
|
136
199
|
"room_name": room,
|
|
137
|
-
"token": token.to_jwt(
|
|
200
|
+
"token": token.to_jwt(api_key=key),
|
|
138
201
|
"arguments": arguments,
|
|
139
202
|
}
|
|
140
203
|
|
|
@@ -148,7 +211,7 @@ async def make_call(
|
|
|
148
211
|
url=websocket_room_url(
|
|
149
212
|
room_name=room, base_url=meshagent_base_url()
|
|
150
213
|
),
|
|
151
|
-
token=token.to_jwt(
|
|
214
|
+
token=token.to_jwt(api_key=key),
|
|
152
215
|
)
|
|
153
216
|
) as client:
|
|
154
217
|
print("[bold green]Making agent call...[/bold green]")
|