mobileops-managed-agent 0.3.1__tar.gz → 0.3.2__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.3.1/src/mobileops_managed_agent.egg-info → mobileops_managed_agent-0.3.2}/PKG-INFO +1 -1
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/pyproject.toml +1 -1
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/__init__.py +19 -0
- mobileops_managed_agent-0.3.2/src/mobileops_managed_agent/artifacts.py +134 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/assistant_model.py +1 -1
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/client.py +11 -1
- mobileops_managed_agent-0.3.2/src/mobileops_managed_agent/model_retry.py +64 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/resources.py +17 -3
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2/src/mobileops_managed_agent.egg-info}/PKG-INFO +1 -1
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent.egg-info/SOURCES.txt +2 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/MANIFEST.in +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/README.md +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/setup.cfg +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/assistants.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/auth.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/capabilities.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/credential_vault.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/files.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/response_schema.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/sandbox_profile.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/sandbox_profiles.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/tool_permissions.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/vaults.py +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent.egg-info/dependency_links.txt +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent.egg-info/requires.txt +0 -0
- {mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobileops-managed-agent
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Server-side SDK for the MobileOPS Managed Agent API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/InnoMindX/deepagent/tree/main/managed-agent-sdk/python
|
|
6
6
|
Project-URL: Repository, https://github.com/InnoMindX/deepagent
|
|
@@ -65,6 +65,14 @@ from .vaults import (
|
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
__all__ = [
|
|
68
|
+
"ModelRetryEvent",
|
|
69
|
+
"ModelRetryTracker",
|
|
70
|
+
"MobileOpsArtifactsClient",
|
|
71
|
+
"MobileOpsStorageClient",
|
|
72
|
+
"Artifact",
|
|
73
|
+
"ArtifactFile",
|
|
74
|
+
"ArtifactJob",
|
|
75
|
+
"StorageUsage",
|
|
68
76
|
"MobileOpsManagedAgentClient",
|
|
69
77
|
"MobileOpsAgentClient",
|
|
70
78
|
"MobileOpsFilesClient",
|
|
@@ -116,3 +124,14 @@ __all__ = [
|
|
|
116
124
|
]
|
|
117
125
|
|
|
118
126
|
from .tool_permissions import validate_tool_permissions, TOOL_PERMISSIONS_CONFIG_KEY
|
|
127
|
+
|
|
128
|
+
from .artifacts import (
|
|
129
|
+
MobileOpsArtifactsClient,
|
|
130
|
+
MobileOpsStorageClient,
|
|
131
|
+
Artifact,
|
|
132
|
+
ArtifactFile,
|
|
133
|
+
ArtifactJob,
|
|
134
|
+
StorageUsage,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
from .model_retry import ModelRetryEvent, ModelRetryTracker
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Persistent deliverables, independent of the sandbox and thread lifetime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from collections.abc import AsyncIterator
|
|
5
|
+
from typing import Any, TypedDict, Literal
|
|
6
|
+
from urllib.parse import quote, urlencode
|
|
7
|
+
from .resources import MobileOpsResourcesClient, MobileOpsResourcesError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ArtifactFile(TypedDict):
|
|
11
|
+
path: str
|
|
12
|
+
size: int
|
|
13
|
+
sha256: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Artifact(TypedDict):
|
|
17
|
+
id: str
|
|
18
|
+
thread_id: str
|
|
19
|
+
run_id: str
|
|
20
|
+
fingerprint: str
|
|
21
|
+
size_bytes: int
|
|
22
|
+
files: list[ArtifactFile]
|
|
23
|
+
created_at: str
|
|
24
|
+
expires_at: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ArtifactJob(TypedDict):
|
|
28
|
+
id: str
|
|
29
|
+
thread_id: str
|
|
30
|
+
run_id: str
|
|
31
|
+
status: Literal["pending", "downloading", "saved", "unchanged", "failed", "cancelled"]
|
|
32
|
+
artifact_id: str | None
|
|
33
|
+
error_code: str | None
|
|
34
|
+
error_message: str | None
|
|
35
|
+
error_status: int | None
|
|
36
|
+
created_at: str
|
|
37
|
+
expires_at: str
|
|
38
|
+
finished_at: str | None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MobileOpsArtifactJobsClient:
|
|
42
|
+
def __init__(self, transport: MobileOpsResourcesClient, base: str):
|
|
43
|
+
self._transport = transport
|
|
44
|
+
self._base = base
|
|
45
|
+
|
|
46
|
+
async def list(self, *, thread_id: str | None = None, run_id: str | None = None, limit: int = 50, offset: int = 0) -> list[ArtifactJob]:
|
|
47
|
+
query = {"limit": str(limit), "offset": str(offset)}
|
|
48
|
+
if thread_id is not None:
|
|
49
|
+
query["thread_id"] = thread_id
|
|
50
|
+
if run_id is not None:
|
|
51
|
+
query["run_id"] = run_id
|
|
52
|
+
return (await self._transport._request_json("GET", self._base + "?" + urlencode(query)))["jobs"]
|
|
53
|
+
|
|
54
|
+
async def retrieve(self, job_id: str) -> ArtifactJob:
|
|
55
|
+
return (await self._transport._request_json("GET", self._base + "/" + quote(job_id, safe="")))["job"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StorageUsage(TypedDict):
|
|
59
|
+
used_bytes: int
|
|
60
|
+
reserved_bytes: int
|
|
61
|
+
limit_bytes: int | None
|
|
62
|
+
default_limit_bytes: int
|
|
63
|
+
effective_limit_bytes: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MobileOpsArtifactsClient:
|
|
67
|
+
def __init__(self, **options: Any):
|
|
68
|
+
self._transport = MobileOpsResourcesClient(**options)
|
|
69
|
+
self._base = options["api_url"].rstrip("/") + "/context-hub/v1/artifacts"
|
|
70
|
+
self.jobs = MobileOpsArtifactJobsClient(self._transport, options["api_url"].rstrip("/") + "/context-hub/v1/artifact-jobs")
|
|
71
|
+
|
|
72
|
+
async def list(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
thread_id: str | None = None,
|
|
76
|
+
run_id: str | None = None,
|
|
77
|
+
limit: int = 50,
|
|
78
|
+
offset: int = 0,
|
|
79
|
+
) -> list[Artifact]:
|
|
80
|
+
query = {"limit": str(limit), "offset": str(offset)}
|
|
81
|
+
if thread_id is not None:
|
|
82
|
+
query["thread_id"] = thread_id
|
|
83
|
+
if run_id is not None:
|
|
84
|
+
query["run_id"] = run_id
|
|
85
|
+
return (
|
|
86
|
+
await self._transport._request_json(
|
|
87
|
+
"GET", self._base + "?" + urlencode(query)
|
|
88
|
+
)
|
|
89
|
+
)["artifacts"]
|
|
90
|
+
|
|
91
|
+
async def retrieve(self, artifact_id: str) -> Artifact:
|
|
92
|
+
return (
|
|
93
|
+
await self._transport._request_json(
|
|
94
|
+
"GET", self._base + "/" + quote(artifact_id, safe="")
|
|
95
|
+
)
|
|
96
|
+
)["artifact"]
|
|
97
|
+
|
|
98
|
+
async def download(self, artifact_id: str) -> AsyncIterator[bytes]:
|
|
99
|
+
"""Stream the ZIP without buffering the whole archive in the SDK."""
|
|
100
|
+
t = self._transport
|
|
101
|
+
async with t._client.stream(
|
|
102
|
+
"GET",
|
|
103
|
+
self._base + "/" + quote(artifact_id, safe="") + "/content",
|
|
104
|
+
headers=t._headers,
|
|
105
|
+
) as response:
|
|
106
|
+
if response.is_error:
|
|
107
|
+
await response.aread()
|
|
108
|
+
try:
|
|
109
|
+
body = response.json()
|
|
110
|
+
except ValueError:
|
|
111
|
+
body = response.text
|
|
112
|
+
raise MobileOpsResourcesError(response, body)
|
|
113
|
+
async for chunk in response.aiter_bytes():
|
|
114
|
+
yield chunk
|
|
115
|
+
|
|
116
|
+
async def delete(self, artifact_id: str) -> None:
|
|
117
|
+
await self._transport._request(
|
|
118
|
+
"DELETE", self._base + "/" + quote(artifact_id, safe="")
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def aclose(self) -> None:
|
|
122
|
+
await self._transport.aclose()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class MobileOpsStorageClient:
|
|
126
|
+
def __init__(self, **options: Any):
|
|
127
|
+
self._transport = MobileOpsResourcesClient(**options)
|
|
128
|
+
self._url = options["api_url"].rstrip("/") + "/context-hub/v1/storage/usage"
|
|
129
|
+
|
|
130
|
+
async def usage(self) -> StorageUsage:
|
|
131
|
+
return await self._transport._request_json("GET", self._url)
|
|
132
|
+
|
|
133
|
+
async def aclose(self) -> None:
|
|
134
|
+
await self._transport.aclose()
|
|
@@ -80,7 +80,7 @@ def _map_hub(
|
|
|
80
80
|
) -> dict[str, Any]:
|
|
81
81
|
entrypoints = item.get("entrypoints")
|
|
82
82
|
if kind == "memory":
|
|
83
|
-
if not entrypoints:
|
|
83
|
+
if "entrypoints" in item and not entrypoints:
|
|
84
84
|
raise TypeError("memory attachments require a non-empty entrypoints list")
|
|
85
85
|
elif entrypoints is not None:
|
|
86
86
|
raise TypeError(
|
|
@@ -11,6 +11,7 @@ from .assistants import MobileOpsAssistantsClient
|
|
|
11
11
|
from .resources import MobileOpsResourcesClient
|
|
12
12
|
from .sandbox_profiles import MobileOpsSandboxProfilesClient
|
|
13
13
|
from .vaults import MobileOpsVaultsClient
|
|
14
|
+
from .artifacts import MobileOpsArtifactsClient, MobileOpsStorageClient
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class MobileOpsAgentClient:
|
|
@@ -52,6 +53,9 @@ class MobileOpsManagedAgentClient:
|
|
|
52
53
|
headers=merged_headers,
|
|
53
54
|
)
|
|
54
55
|
)
|
|
56
|
+
artifact_options = dict(api_url=api_url, api_key=api_key, headers=headers, http_client=http_client)
|
|
57
|
+
self.artifacts = MobileOpsArtifactsClient(**artifact_options)
|
|
58
|
+
self.storage = MobileOpsStorageClient(**artifact_options)
|
|
55
59
|
self.files = MobileOpsFilesClient(
|
|
56
60
|
api_url=api_url,
|
|
57
61
|
api_key=api_key,
|
|
@@ -92,7 +96,13 @@ class MobileOpsManagedAgentClient:
|
|
|
92
96
|
try:
|
|
93
97
|
await self.vaults.aclose()
|
|
94
98
|
finally:
|
|
95
|
-
|
|
99
|
+
try:
|
|
100
|
+
await self.sandbox_profiles.aclose()
|
|
101
|
+
finally:
|
|
102
|
+
try:
|
|
103
|
+
await self.artifacts.aclose()
|
|
104
|
+
finally:
|
|
105
|
+
await self.storage.aclose()
|
|
96
106
|
|
|
97
107
|
async def __aenter__(self) -> "MobileOpsManagedAgentClient":
|
|
98
108
|
return self
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Track custom model retry events while consuming the native Run stream."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from typing import Any, Literal, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ModelRetryEvent(TypedDict):
|
|
9
|
+
type: Literal["model_retry"]
|
|
10
|
+
call_id: str
|
|
11
|
+
namespace: str
|
|
12
|
+
phase: Literal["retrying", "resumed", "succeeded", "exhausted"]
|
|
13
|
+
attempt: int
|
|
14
|
+
max_retries: int
|
|
15
|
+
failed_message_ids: list[str]
|
|
16
|
+
delay_ms: int
|
|
17
|
+
reason: str | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ModelRetryTracker:
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self._discarded: set[str] = set()
|
|
23
|
+
self._active: dict[str, ModelRetryEvent] = {}
|
|
24
|
+
|
|
25
|
+
def update(self, value: Any) -> ModelRetryEvent | None:
|
|
26
|
+
if not isinstance(value, dict) or value.get("type") != "model_retry":
|
|
27
|
+
return None
|
|
28
|
+
if (
|
|
29
|
+
not isinstance(value.get("call_id"), str)
|
|
30
|
+
or not isinstance(value.get("namespace"), str)
|
|
31
|
+
or value.get("phase")
|
|
32
|
+
not in ("retrying", "resumed", "succeeded", "exhausted")
|
|
33
|
+
or any(
|
|
34
|
+
type(value.get(k)) is not int or value[k] < 0
|
|
35
|
+
for k in ("attempt", "max_retries", "delay_ms")
|
|
36
|
+
)
|
|
37
|
+
or not isinstance(value.get("failed_message_ids"), list)
|
|
38
|
+
or not all(isinstance(i, str) for i in value["failed_message_ids"])
|
|
39
|
+
or "reason" not in value
|
|
40
|
+
or not (value.get("reason") is None or isinstance(value.get("reason"), str))
|
|
41
|
+
):
|
|
42
|
+
return None
|
|
43
|
+
self._discarded.update(value["failed_message_ids"])
|
|
44
|
+
if value["phase"] in ("retrying", "resumed"):
|
|
45
|
+
self._active[value["call_id"]] = value
|
|
46
|
+
else:
|
|
47
|
+
self._active.pop(value["call_id"], None)
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def pending(self) -> list[ModelRetryEvent]:
|
|
52
|
+
return list(self._active.values())
|
|
53
|
+
|
|
54
|
+
def filter(self, messages: Sequence[Mapping[str, Any]]) -> list[Mapping[str, Any]]:
|
|
55
|
+
return [m for m in messages if m.get("id") not in self._discarded]
|
|
56
|
+
|
|
57
|
+
def reset(self) -> None:
|
|
58
|
+
"""Start tracking a different conversation."""
|
|
59
|
+
self._discarded.clear()
|
|
60
|
+
self._active.clear()
|
|
61
|
+
|
|
62
|
+
def finish(self) -> None:
|
|
63
|
+
"""Clear status at Run end, keeping failed messages hidden."""
|
|
64
|
+
self._active.clear()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from collections.abc import Mapping
|
|
4
5
|
from typing import Any, NotRequired, TypedDict
|
|
5
6
|
from urllib.parse import quote, urlencode
|
|
@@ -16,6 +17,8 @@ class ResourceVersion(TypedDict):
|
|
|
16
17
|
ref: str
|
|
17
18
|
file_count: NotRequired[int]
|
|
18
19
|
created_at: NotRequired[str]
|
|
20
|
+
size_bytes: NotRequired[int]
|
|
21
|
+
files: NotRequired[list[ResourceFile]]
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
class ResourceSummary(TypedDict):
|
|
@@ -65,10 +68,21 @@ class MobileOpsResourceVersionsClient:
|
|
|
65
68
|
return payload["resource"]
|
|
66
69
|
|
|
67
70
|
async def list(
|
|
68
|
-
self,
|
|
71
|
+
self,
|
|
72
|
+
kind: CapabilityResourceKind,
|
|
73
|
+
slug: str,
|
|
74
|
+
*,
|
|
75
|
+
versions: list[str] | None = None,
|
|
76
|
+
include_files: bool = False,
|
|
69
77
|
) -> list[ResourceVersion]:
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
params = {"include_files": str(include_files).lower()}
|
|
79
|
+
if versions is not None:
|
|
80
|
+
params["versions"] = json.dumps(versions)
|
|
81
|
+
payload = await self._resources._request_json(
|
|
82
|
+
"GET",
|
|
83
|
+
f"{self._resources._resource_url(kind, slug)}/versions?{urlencode(params)}",
|
|
84
|
+
)
|
|
85
|
+
return payload["versions"]
|
|
72
86
|
|
|
73
87
|
|
|
74
88
|
class MobileOpsResourceFilesClient:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobileops-managed-agent
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Server-side SDK for the MobileOPS Managed Agent API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/InnoMindX/deepagent/tree/main/managed-agent-sdk/python
|
|
6
6
|
Project-URL: Repository, https://github.com/InnoMindX/deepagent
|
|
@@ -2,6 +2,7 @@ MANIFEST.in
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/mobileops_managed_agent/__init__.py
|
|
5
|
+
src/mobileops_managed_agent/artifacts.py
|
|
5
6
|
src/mobileops_managed_agent/assistant_model.py
|
|
6
7
|
src/mobileops_managed_agent/assistants.py
|
|
7
8
|
src/mobileops_managed_agent/auth.py
|
|
@@ -9,6 +10,7 @@ src/mobileops_managed_agent/capabilities.py
|
|
|
9
10
|
src/mobileops_managed_agent/client.py
|
|
10
11
|
src/mobileops_managed_agent/credential_vault.py
|
|
11
12
|
src/mobileops_managed_agent/files.py
|
|
13
|
+
src/mobileops_managed_agent/model_retry.py
|
|
12
14
|
src/mobileops_managed_agent/resources.py
|
|
13
15
|
src/mobileops_managed_agent/response_schema.py
|
|
14
16
|
src/mobileops_managed_agent/sandbox_profile.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mobileops_managed_agent-0.3.1 → mobileops_managed_agent-0.3.2}/src/mobileops_managed_agent/files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|