mobileops-managed-agent 0.1.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.
- mobileops_managed_agent-0.1.0/MANIFEST.in +2 -0
- mobileops_managed_agent-0.1.0/PKG-INFO +36 -0
- mobileops_managed_agent-0.1.0/README.md +21 -0
- mobileops_managed_agent-0.1.0/pyproject.toml +32 -0
- mobileops_managed_agent-0.1.0/setup.cfg +4 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent/__init__.py +11 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent/auth.py +18 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent/client.py +53 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent/files.py +181 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent.egg-info/PKG-INFO +36 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent.egg-info/SOURCES.txt +12 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent.egg-info/dependency_links.txt +1 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent.egg-info/requires.txt +6 -0
- mobileops_managed_agent-0.1.0/src/mobileops_managed_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mobileops-managed-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Server-side SDK for the MobileOPS Managed Agent API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/InnoMindX/deepagent/tree/main/managed-agent-sdk/python
|
|
6
|
+
Project-URL: Repository, https://github.com/InnoMindX/deepagent
|
|
7
|
+
Project-URL: Issues, https://github.com/InnoMindX/deepagent/issues
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: langgraph-sdk<0.5.0,>=0.4.2
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# MobileOPS Managed Agent Python SDK
|
|
17
|
+
|
|
18
|
+
Server-side Python SDK for the MobileOPS Managed Agent API.
|
|
19
|
+
|
|
20
|
+
The SDK exposes two surfaces:
|
|
21
|
+
|
|
22
|
+
- `client.agent` wraps the LangGraph runtime client for Threads, Runs, Streaming, and State.
|
|
23
|
+
- `client.files` exposes MobileOPS Sandbox Workspace file routes.
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
from mobileops_managed_agent import MobileOpsManagedAgentClient
|
|
27
|
+
|
|
28
|
+
client = MobileOpsManagedAgentClient(api_url=api_url, api_key=api_key)
|
|
29
|
+
|
|
30
|
+
thread = await client.agent.threads.create()
|
|
31
|
+
await client.agent.runs.create(thread["thread_id"], assistant_id, input=input)
|
|
32
|
+
|
|
33
|
+
await client.files.list(thread["thread_id"], "/workspace")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Keep MobileOPS Managed Agent API keys in server-side code only.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MobileOPS Managed Agent Python SDK
|
|
2
|
+
|
|
3
|
+
Server-side Python SDK for the MobileOPS Managed Agent API.
|
|
4
|
+
|
|
5
|
+
The SDK exposes two surfaces:
|
|
6
|
+
|
|
7
|
+
- `client.agent` wraps the LangGraph runtime client for Threads, Runs, Streaming, and State.
|
|
8
|
+
- `client.files` exposes MobileOPS Sandbox Workspace file routes.
|
|
9
|
+
|
|
10
|
+
```py
|
|
11
|
+
from mobileops_managed_agent import MobileOpsManagedAgentClient
|
|
12
|
+
|
|
13
|
+
client = MobileOpsManagedAgentClient(api_url=api_url, api_key=api_key)
|
|
14
|
+
|
|
15
|
+
thread = await client.agent.threads.create()
|
|
16
|
+
await client.agent.runs.create(thread["thread_id"], assistant_id, input=input)
|
|
17
|
+
|
|
18
|
+
await client.files.list(thread["thread_id"], "/workspace")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Keep MobileOPS Managed Agent API keys in server-side code only.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mobileops-managed-agent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Server-side SDK for the MobileOPS Managed Agent API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.27.0",
|
|
13
|
+
"langgraph-sdk>=0.4.2,<0.5.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/InnoMindX/deepagent/tree/main/managed-agent-sdk/python"
|
|
18
|
+
Repository = "https://github.com/InnoMindX/deepagent"
|
|
19
|
+
Issues = "https://github.com/InnoMindX/deepagent/issues"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.0.0",
|
|
24
|
+
"pytest-asyncio>=0.23.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
asyncio_mode = "auto"
|
|
32
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_headers(
|
|
7
|
+
api_key: str,
|
|
8
|
+
headers: Mapping[str, str] | None = None,
|
|
9
|
+
) -> dict[str, str]:
|
|
10
|
+
safe_headers = {
|
|
11
|
+
name: value
|
|
12
|
+
for name, value in dict(headers or {}).items()
|
|
13
|
+
if name.lower() != "authorization"
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
**safe_headers,
|
|
17
|
+
"Authorization": f"Bearer {api_key}",
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .auth import build_headers
|
|
9
|
+
from .files import MobileOpsFilesClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MobileOpsManagedAgentClient:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
*,
|
|
16
|
+
api_url: str,
|
|
17
|
+
api_key: str,
|
|
18
|
+
headers: Mapping[str, str] | None = None,
|
|
19
|
+
http_client: httpx.AsyncClient | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
if not api_url:
|
|
22
|
+
raise ValueError("api_url is required")
|
|
23
|
+
if not api_key:
|
|
24
|
+
raise ValueError("api_key is required")
|
|
25
|
+
|
|
26
|
+
merged_headers = build_headers(api_key, headers)
|
|
27
|
+
|
|
28
|
+
from langgraph_sdk import get_client
|
|
29
|
+
|
|
30
|
+
self.agent: Any = get_client(
|
|
31
|
+
url=api_url,
|
|
32
|
+
headers=merged_headers,
|
|
33
|
+
)
|
|
34
|
+
self.files = MobileOpsFilesClient(
|
|
35
|
+
api_url=api_url,
|
|
36
|
+
api_key=api_key,
|
|
37
|
+
headers=headers,
|
|
38
|
+
http_client=http_client,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def aclose(self) -> None:
|
|
42
|
+
try:
|
|
43
|
+
agent_aclose = getattr(self.agent, "aclose", None)
|
|
44
|
+
if callable(agent_aclose):
|
|
45
|
+
await agent_aclose()
|
|
46
|
+
finally:
|
|
47
|
+
await self.files.aclose()
|
|
48
|
+
|
|
49
|
+
async def __aenter__(self) -> "MobileOpsManagedAgentClient":
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
async def __aexit__(self, *_args: object) -> None:
|
|
53
|
+
await self.aclose()
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator, Iterator, Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import urlencode, quote
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from .auth import build_headers
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
UploadData = bytes | str | Iterator[bytes] | AsyncIterator[bytes]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MobileOpsFilesError(Exception):
|
|
16
|
+
def __init__(self, response: httpx.Response, body: Any) -> None:
|
|
17
|
+
self.status_code = response.status_code
|
|
18
|
+
self.response = response
|
|
19
|
+
self.body = body
|
|
20
|
+
message = (
|
|
21
|
+
body.get("error")
|
|
22
|
+
if isinstance(body, dict) and isinstance(body.get("error"), str)
|
|
23
|
+
else f"MobileOPS Files request failed with status {response.status_code}"
|
|
24
|
+
)
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MobileOpsFilesClient:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
api_url: str,
|
|
33
|
+
api_key: str,
|
|
34
|
+
headers: Mapping[str, str] | None = None,
|
|
35
|
+
http_client: httpx.AsyncClient | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
if not api_url:
|
|
38
|
+
raise ValueError("api_url is required")
|
|
39
|
+
if not api_key:
|
|
40
|
+
raise ValueError("api_key is required")
|
|
41
|
+
|
|
42
|
+
self._api_url = api_url.rstrip("/")
|
|
43
|
+
self._headers = build_headers(api_key, headers)
|
|
44
|
+
self._client = http_client or httpx.AsyncClient()
|
|
45
|
+
self._owns_client = http_client is None
|
|
46
|
+
|
|
47
|
+
async def aclose(self) -> None:
|
|
48
|
+
if self._owns_client:
|
|
49
|
+
await self._client.aclose()
|
|
50
|
+
|
|
51
|
+
async def __aenter__(self) -> "MobileOpsFilesClient":
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
async def __aexit__(self, *_args: object) -> None:
|
|
55
|
+
await self.aclose()
|
|
56
|
+
|
|
57
|
+
async def list(self, thread_id: str, path: str = "/workspace") -> dict[str, Any]:
|
|
58
|
+
return await self._request_json(
|
|
59
|
+
"GET",
|
|
60
|
+
self._files_url(thread_id, "list", {"path": path}),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
async def read(
|
|
64
|
+
self,
|
|
65
|
+
thread_id: str,
|
|
66
|
+
path: str,
|
|
67
|
+
*,
|
|
68
|
+
offset: int | None = None,
|
|
69
|
+
limit: int | None = None,
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
return await self._request_json(
|
|
72
|
+
"GET",
|
|
73
|
+
self._files_url(
|
|
74
|
+
thread_id,
|
|
75
|
+
"read",
|
|
76
|
+
{"path": path, "offset": offset, "limit": limit},
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def download(self, thread_id: str, path: str) -> httpx.Response:
|
|
81
|
+
return await self._request(
|
|
82
|
+
"GET",
|
|
83
|
+
self._files_url(thread_id, "download", {"path": path}),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def write(self, thread_id: str, path: str, content: str) -> dict[str, Any]:
|
|
87
|
+
return await self._request_json(
|
|
88
|
+
"PUT",
|
|
89
|
+
self._files_url(thread_id, "write", {"path": path}),
|
|
90
|
+
content=content,
|
|
91
|
+
headers={"Content-Type": "text/plain; charset=utf-8"},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def upload(
|
|
95
|
+
self,
|
|
96
|
+
thread_id: str,
|
|
97
|
+
path: str,
|
|
98
|
+
data: UploadData,
|
|
99
|
+
) -> dict[str, Any]:
|
|
100
|
+
return await self._request_json(
|
|
101
|
+
"POST",
|
|
102
|
+
self._files_url(thread_id, "upload", {"path": path}),
|
|
103
|
+
content=data,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def delete(self, thread_id: str, path: str) -> dict[str, Any]:
|
|
107
|
+
return await self._request_json(
|
|
108
|
+
"DELETE",
|
|
109
|
+
self._files_root_url(thread_id, {"path": path}),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def mkdir(self, thread_id: str, path: str) -> dict[str, Any]:
|
|
113
|
+
return await self._request_json(
|
|
114
|
+
"POST",
|
|
115
|
+
self._files_url(thread_id, "mkdir"),
|
|
116
|
+
json={"path": path},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def move(
|
|
120
|
+
self,
|
|
121
|
+
thread_id: str,
|
|
122
|
+
source: str,
|
|
123
|
+
destination: str,
|
|
124
|
+
) -> dict[str, Any]:
|
|
125
|
+
return await self._request_json(
|
|
126
|
+
"POST",
|
|
127
|
+
self._files_url(thread_id, "move"),
|
|
128
|
+
json={"source": source, "destination": destination},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def _files_root_url(
|
|
132
|
+
self,
|
|
133
|
+
thread_id: str,
|
|
134
|
+
query: Mapping[str, object | None] | None = None,
|
|
135
|
+
) -> str:
|
|
136
|
+
encoded_thread_id = quote(thread_id, safe="")
|
|
137
|
+
url = f"{self._api_url}/sandbox/threads/{encoded_thread_id}/files"
|
|
138
|
+
return _with_query(url, query)
|
|
139
|
+
|
|
140
|
+
def _files_url(
|
|
141
|
+
self,
|
|
142
|
+
thread_id: str,
|
|
143
|
+
action: str,
|
|
144
|
+
query: Mapping[str, object | None] | None = None,
|
|
145
|
+
) -> str:
|
|
146
|
+
return _with_query(f"{self._files_root_url(thread_id)}/{action}", query)
|
|
147
|
+
|
|
148
|
+
async def _request_json(self, method: str, url: str, **kwargs: Any) -> dict[str, Any]:
|
|
149
|
+
response = await self._request(method, url, **kwargs)
|
|
150
|
+
return response.json()
|
|
151
|
+
|
|
152
|
+
async def _request(self, method: str, url: str, **kwargs: Any) -> httpx.Response:
|
|
153
|
+
headers = {
|
|
154
|
+
**self._headers,
|
|
155
|
+
**kwargs.pop("headers", {}),
|
|
156
|
+
}
|
|
157
|
+
response = await self._client.request(method, url, headers=headers, **kwargs)
|
|
158
|
+
if response.is_error:
|
|
159
|
+
raise MobileOpsFilesError(response, _read_error_body(response))
|
|
160
|
+
return response
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _with_query(url: str, query: Mapping[str, object | None] | None) -> str:
|
|
164
|
+
values = {
|
|
165
|
+
key: value
|
|
166
|
+
for key, value in (query or {}).items()
|
|
167
|
+
if value is not None
|
|
168
|
+
}
|
|
169
|
+
if not values:
|
|
170
|
+
return url
|
|
171
|
+
return f"{url}?{urlencode(values)}"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _read_error_body(response: httpx.Response) -> Any:
|
|
175
|
+
content_type = response.headers.get("content-type", "")
|
|
176
|
+
if "application/json" in content_type:
|
|
177
|
+
try:
|
|
178
|
+
return response.json()
|
|
179
|
+
except ValueError:
|
|
180
|
+
return None
|
|
181
|
+
return response.text
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mobileops-managed-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Server-side SDK for the MobileOPS Managed Agent API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/InnoMindX/deepagent/tree/main/managed-agent-sdk/python
|
|
6
|
+
Project-URL: Repository, https://github.com/InnoMindX/deepagent
|
|
7
|
+
Project-URL: Issues, https://github.com/InnoMindX/deepagent/issues
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: langgraph-sdk<0.5.0,>=0.4.2
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# MobileOPS Managed Agent Python SDK
|
|
17
|
+
|
|
18
|
+
Server-side Python SDK for the MobileOPS Managed Agent API.
|
|
19
|
+
|
|
20
|
+
The SDK exposes two surfaces:
|
|
21
|
+
|
|
22
|
+
- `client.agent` wraps the LangGraph runtime client for Threads, Runs, Streaming, and State.
|
|
23
|
+
- `client.files` exposes MobileOPS Sandbox Workspace file routes.
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
from mobileops_managed_agent import MobileOpsManagedAgentClient
|
|
27
|
+
|
|
28
|
+
client = MobileOpsManagedAgentClient(api_url=api_url, api_key=api_key)
|
|
29
|
+
|
|
30
|
+
thread = await client.agent.threads.create()
|
|
31
|
+
await client.agent.runs.create(thread["thread_id"], assistant_id, input=input)
|
|
32
|
+
|
|
33
|
+
await client.files.list(thread["thread_id"], "/workspace")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Keep MobileOPS Managed Agent API keys in server-side code only.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/mobileops_managed_agent/__init__.py
|
|
5
|
+
src/mobileops_managed_agent/auth.py
|
|
6
|
+
src/mobileops_managed_agent/client.py
|
|
7
|
+
src/mobileops_managed_agent/files.py
|
|
8
|
+
src/mobileops_managed_agent.egg-info/PKG-INFO
|
|
9
|
+
src/mobileops_managed_agent.egg-info/SOURCES.txt
|
|
10
|
+
src/mobileops_managed_agent.egg-info/dependency_links.txt
|
|
11
|
+
src/mobileops_managed_agent.egg-info/requires.txt
|
|
12
|
+
src/mobileops_managed_agent.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mobileops_managed_agent
|