meshagent-api 0.24.5__tar.gz → 0.25.0__tar.gz

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 (49) hide show
  1. {meshagent_api-0.24.5/meshagent_api.egg-info → meshagent_api-0.25.0}/PKG-INFO +3 -2
  2. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/client.py +37 -9
  3. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/helpers.py +7 -2
  4. meshagent_api-0.25.0/meshagent/api/http.py +14 -0
  5. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/participant_token.py +7 -0
  6. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/port_forward.py +2 -1
  7. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/room_server_client.py +359 -308
  8. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/specs/service.py +126 -27
  9. meshagent_api-0.25.0/meshagent/api/sql.py +223 -0
  10. meshagent_api-0.25.0/meshagent/api/sql_test.py +38 -0
  11. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/urls.py +1 -1
  12. meshagent_api-0.25.0/meshagent/api/version.py +1 -0
  13. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/websocket_protocol.py +18 -4
  14. {meshagent_api-0.24.5 → meshagent_api-0.25.0/meshagent_api.egg-info}/PKG-INFO +3 -2
  15. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent_api.egg-info/SOURCES.txt +3 -0
  16. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent_api.egg-info/requires.txt +2 -1
  17. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/pyproject.toml +3 -2
  18. meshagent_api-0.24.5/meshagent/api/version.py +0 -1
  19. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/LICENSE +0 -0
  20. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/MANIFEST.in +0 -0
  21. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/README.md +0 -0
  22. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/__init__.py +0 -0
  23. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/chan.py +0 -0
  24. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/crdt.py +0 -0
  25. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/entrypoint.js +0 -0
  26. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/keys.py +0 -0
  27. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/messaging.py +0 -0
  28. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/oauth.py +0 -0
  29. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/participant.py +0 -0
  30. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/participant_token_test.py +0 -0
  31. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/protocol.py +0 -0
  32. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/protocol_test.py +0 -0
  33. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/py.typed +0 -0
  34. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/reasoning_schema.py +0 -0
  35. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/runtime.py +0 -0
  36. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/runtime_test.py +0 -0
  37. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/schema.py +0 -0
  38. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/schema_document.py +0 -0
  39. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/schema_document_test.py +0 -0
  40. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/schema_registry.py +0 -0
  41. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/schema_test.py +0 -0
  42. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/schema_util.py +0 -0
  43. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/service_template_test.py +0 -0
  44. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/services.py +0 -0
  45. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/token_test.py +0 -0
  46. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent/api/webhooks.py +0 -0
  47. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent_api.egg-info/dependency_links.txt +0 -0
  48. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/meshagent_api.egg-info/top_level.txt +0 -0
  49. {meshagent_api-0.24.5 → meshagent_api-0.25.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.24.5
3
+ Version: 0.25.0
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -10,12 +10,13 @@ Requires-Python: >=3.13
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: pyjwt~=2.10
13
- Requires-Dist: aiohttp~=3.10
13
+ Requires-Dist: aiohttp[speedups]~=3.13.0
14
14
  Requires-Dist: jsonschema~=4.23
15
15
  Requires-Dist: pycrdt~=0.12.26
16
16
  Requires-Dist: opentelemetry-distro~=0.54b1
17
17
  Requires-Dist: pydantic~=2.11.7
18
18
  Requires-Dist: jinja2~=3.1.6
19
+ Requires-Dist: certifi~=2026.1.4
19
20
  Provides-Extra: all
20
21
  Provides-Extra: sync
21
22
  Provides-Extra: stpyv8
@@ -4,6 +4,7 @@ from pydantic import BaseModel, ValidationError, JsonValue, Field, ConfigDict
4
4
  from meshagent.api import RoomException
5
5
  from meshagent.api.participant_token import ApiScope
6
6
  from meshagent.api.helpers import meshagent_base_url
7
+ from meshagent.api.http import new_client_session
7
8
  from datetime import datetime
8
9
  from meshagent.api.specs.service import (
9
10
  ServiceSpec,
@@ -314,6 +315,7 @@ class Meshagent:
314
315
  *,
315
316
  base_url: str = meshagent_base_url(),
316
317
  token: str = os.getenv("MESHAGENT_API_KEY"),
318
+ session: aiohttp.ClientSession | None = None,
317
319
  ):
318
320
  """
319
321
  :param base_url: The root URL of your server, e.g. 'http://localhost:8080'.
@@ -321,11 +323,11 @@ class Meshagent:
321
323
  """
322
324
  self.base_url = base_url.rstrip("/")
323
325
  self.token = token # The "Bearer" token
324
-
325
- self._session = aiohttp.ClientSession()
326
+ self._session_external = session is not None
327
+ self._session = session or new_client_session()
326
328
 
327
329
  async def close(self):
328
- if not self._session.closed:
330
+ if not self._session_external and not self._session.closed:
329
331
  await self._session.close()
330
332
 
331
333
  async def __aenter__(self):
@@ -592,6 +594,32 @@ class Meshagent:
592
594
  await self._raise_for_status(resp)
593
595
  return await resp.json()
594
596
 
597
+ async def update_project_user(
598
+ self,
599
+ project_id: str,
600
+ user_id: str,
601
+ *,
602
+ is_admin: bool,
603
+ is_developer: bool,
604
+ can_create_rooms: bool,
605
+ ) -> Dict[str, Any]:
606
+ """
607
+ Corresponds to: PUT /accounts/projects/:project_id/users/:user_id
608
+ Body: { "is_admin", "is_developer", "can_create_rooms" }
609
+ Returns a JSON dict with { "ok": True } on success.
610
+ """
611
+ url = f"{self.base_url}/accounts/projects/{project_id}/users/{user_id}"
612
+ body = {
613
+ "is_admin": is_admin,
614
+ "is_developer": is_developer,
615
+ "can_create_rooms": can_create_rooms,
616
+ }
617
+ async with self._session.put(
618
+ url, headers=self._get_headers(), json=body
619
+ ) as resp:
620
+ await self._raise_for_status(resp)
621
+ return await resp.json()
622
+
595
623
  async def update_project_settings(
596
624
  self, project_id: str, settings: dict
597
625
  ) -> Dict[str, Any]:
@@ -1377,7 +1405,7 @@ class Meshagent:
1377
1405
  *,
1378
1406
  project_id: str,
1379
1407
  room_name: str,
1380
- template: ServiceTemplateSpec,
1408
+ template: str,
1381
1409
  values: Dict[str, str],
1382
1410
  ) -> ServiceSpec:
1383
1411
  """
@@ -1402,12 +1430,12 @@ class Meshagent:
1402
1430
  url,
1403
1431
  headers=self._get_headers(),
1404
1432
  json={
1405
- "template": template.model_dump(mode="json"),
1433
+ "template": template,
1406
1434
  "values": values,
1407
1435
  },
1408
1436
  ) as resp:
1409
1437
  await self._raise_for_status(resp)
1410
- return ServiceSpec.model_validate_json(await resp.json())
1438
+ return ServiceSpec.model_validate(await resp.json())
1411
1439
 
1412
1440
  async def update_room_service_from_template(
1413
1441
  self,
@@ -1415,7 +1443,7 @@ class Meshagent:
1415
1443
  project_id: str,
1416
1444
  room_name: str,
1417
1445
  service_id: str,
1418
- template: ServiceTemplateSpec,
1446
+ template: str,
1419
1447
  values: Dict[str, str],
1420
1448
  ) -> ServiceSpec:
1421
1449
  """
@@ -1429,12 +1457,12 @@ class Meshagent:
1429
1457
  url,
1430
1458
  headers=self._get_headers(),
1431
1459
  json={
1432
- "template": template.model_dump(mode="json"),
1460
+ "template": template,
1433
1461
  "values": values,
1434
1462
  },
1435
1463
  ) as resp:
1436
1464
  await self._raise_for_status(resp)
1437
- return ServiceSpec.model_validate_json(await resp.json())
1465
+ return ServiceSpec.model_validate(await resp.json())
1438
1466
 
1439
1467
  async def get_room_service(
1440
1468
  self, *, project_id: str, room_name: str, service_id: str
@@ -3,6 +3,7 @@ import json
3
3
  from .participant_token import ParticipantToken, ApiScope
4
4
  from typing import Optional
5
5
  import os
6
+ import aiohttp
6
7
  from .websocket_protocol import WebSocketClientProtocol
7
8
  import re
8
9
  from warnings import deprecated
@@ -68,7 +69,11 @@ def participant_token(
68
69
 
69
70
  @deprecated("create WebSocketClientProtocol directly instead")
70
71
  def websocket_protocol(
71
- *, participant_name: str, room_name: str, role: Optional[str] = None
72
+ *,
73
+ participant_name: str,
74
+ room_name: str,
75
+ role: Optional[str] = None,
76
+ session: aiohttp.ClientSession | None = None,
72
77
  ):
73
78
  url = websocket_room_url(room_name=room_name)
74
79
  token_jwt = os.getenv("MESHAGENT_TOKEN", None)
@@ -78,7 +83,7 @@ def websocket_protocol(
78
83
  )
79
84
  token_jwt = token.to_jwt(token=os.getenv("MESHAGENT_SECRET"))
80
85
 
81
- return WebSocketClientProtocol(url=url, token=token_jwt)
86
+ return WebSocketClientProtocol(url=url, token=token_jwt, session=session)
82
87
 
83
88
 
84
89
  # Pre-compile the pattern once if you’ll call this many times
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+ import ssl
5
+
6
+ from aiohttp import ClientSession, TCPConnector
7
+ import certifi
8
+
9
+
10
+ def new_client_session(*args: Any, **kwargs: Any) -> ClientSession:
11
+ if "connector" not in kwargs:
12
+ ssl_context = ssl.create_default_context(cafile=certifi.where())
13
+ kwargs["connector"] = TCPConnector(ssl=ssl_context)
14
+ return ClientSession(*args, **kwargs)
@@ -227,6 +227,10 @@ class TunnelsGrant(BaseModel):
227
227
  ports: Optional[list[str]] = None
228
228
 
229
229
 
230
+ class ServicesGrant(BaseModel):
231
+ list: bool = True
232
+
233
+
230
234
  class ApiScope(BaseModel):
231
235
  livekit: Optional[LivekitGrant] = None
232
236
  queues: Optional[QueuesGrant] = None
@@ -240,6 +244,7 @@ class ApiScope(BaseModel):
240
244
  admin: Optional[AdminGrant] = None
241
245
  secrets: Optional[SecretsGrant] = None
242
246
  tunnels: Optional[TunnelsGrant] = None
247
+ services: Optional[ServicesGrant] = None
243
248
 
244
249
  # no secrets access, no admin access by default for agents
245
250
  @staticmethod
@@ -254,6 +259,7 @@ class ApiScope(BaseModel):
254
259
  containers=ContainersGrant(),
255
260
  developer=DeveloperGrant(),
256
261
  agents=AgentsGrant(),
262
+ services=ServicesGrant(),
257
263
  tunnels=TunnelsGrant() if tunnels else None,
258
264
  )
259
265
 
@@ -272,6 +278,7 @@ class ApiScope(BaseModel):
272
278
  admin=AdminGrant(),
273
279
  secrets=SecretsGrant(),
274
280
  tunnels=TunnelsGrant(),
281
+ services=ServicesGrant(),
275
282
  )
276
283
 
277
284
 
@@ -7,6 +7,7 @@ from dataclasses import dataclass
7
7
  import aiohttp
8
8
 
9
9
  from meshagent.api.helpers import meshagent_base_url
10
+ from meshagent.api.http import new_client_session
10
11
 
11
12
 
12
13
  @dataclass
@@ -60,7 +61,7 @@ async def port_forward(
60
61
  sock_read=None,
61
62
  )
62
63
 
63
- session = aiohttp.ClientSession(timeout=timeout)
64
+ session = new_client_session(timeout=timeout)
64
65
 
65
66
  async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
66
67
  ws: aiohttp.ClientWebSocketResponse | None = None