admeasy 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.
@@ -0,0 +1,19 @@
1
+ # Python
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ build/
8
+ dist/
9
+
10
+ # Node
11
+ node_modules/
12
+ typescript/dist/
13
+ typescript/build/
14
+ *.tgz
15
+
16
+ # Never commit credentials
17
+ .admeasy/credentials
18
+ .env
19
+ .env.*
admeasy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.5
2
+ Name: admeasy
3
+ Version: 0.1.0
4
+ Summary: Python SDK and CLI for Admeasy AI, the AI employee infrastructure platform.
5
+ Author-email: Admeasy <aadesh.panwar@admeasy.in>
6
+ License: TBD
7
+ Keywords: admeasy,agents,ai-employees,cli,sdk
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.9
12
+ Requires-Dist: httpx<1,>=0.25
13
+ Requires-Dist: pydantic<3,>=2.4
14
+ Requires-Dist: typer>=0.9
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=7.4; extra == 'dev'
17
+ Requires-Dist: respx>=0.21; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # admeasy (Python)
21
+
22
+ Python SDK and CLI for **Admeasy AI**, the AI employee infrastructure platform.
23
+
24
+ The SDK is a client of the Admeasy control plane. Employees you create here
25
+ are the same records the Admeasy website shows. Nothing is stored locally
26
+ except your API key in `~/.admeasy/credentials`, written by `admeasy login`.
27
+
28
+ ```bash
29
+ pip install admeasy
30
+ export ADMEASY_API_KEY="..." # the key from the Admeasy website Integrations page
31
+ admeasy whoami
32
+ admeasy employee list
33
+ ```
34
+
35
+ ```python
36
+ from admeasy import Agent, Client, EmployeeLimitReachedError
37
+
38
+ client = Client() # reads ADMEASY_API_KEY / ADMEASY_API_URL
39
+ print(client.get_workspace(), client.get_limits())
40
+
41
+ try:
42
+ agent = Agent.create("sales-agent", model="sarvam", description="Follow up on new leads")
43
+ except EmployeeLimitReachedError as exc:
44
+ print(f"Plan is full: {exc.current}/{exc.limit}")
45
+ ```
46
+
47
+ Sarvam is the preferred currently available model, and `sarvam` is the default.
48
+
49
+ Release 0.1 is synchronous (`httpx.Client`). See the repository README and
50
+ `docs/API_CONTRACT.md` for the full contract, and `docs/BACKEND_GAPS.md` for
51
+ the backend changes the SDK still depends on.
52
+
53
+ ## Development
54
+
55
+ ```bash
56
+ python3 -m venv .venv && source .venv/bin/activate
57
+ pip install -e '.[dev]'
58
+ pytest
59
+ ```
@@ -0,0 +1,40 @@
1
+ # admeasy (Python)
2
+
3
+ Python SDK and CLI for **Admeasy AI**, the AI employee infrastructure platform.
4
+
5
+ The SDK is a client of the Admeasy control plane. Employees you create here
6
+ are the same records the Admeasy website shows. Nothing is stored locally
7
+ except your API key in `~/.admeasy/credentials`, written by `admeasy login`.
8
+
9
+ ```bash
10
+ pip install admeasy
11
+ export ADMEASY_API_KEY="..." # the key from the Admeasy website Integrations page
12
+ admeasy whoami
13
+ admeasy employee list
14
+ ```
15
+
16
+ ```python
17
+ from admeasy import Agent, Client, EmployeeLimitReachedError
18
+
19
+ client = Client() # reads ADMEASY_API_KEY / ADMEASY_API_URL
20
+ print(client.get_workspace(), client.get_limits())
21
+
22
+ try:
23
+ agent = Agent.create("sales-agent", model="sarvam", description="Follow up on new leads")
24
+ except EmployeeLimitReachedError as exc:
25
+ print(f"Plan is full: {exc.current}/{exc.limit}")
26
+ ```
27
+
28
+ Sarvam is the preferred currently available model, and `sarvam` is the default.
29
+
30
+ Release 0.1 is synchronous (`httpx.Client`). See the repository README and
31
+ `docs/API_CONTRACT.md` for the full contract, and `docs/BACKEND_GAPS.md` for
32
+ the backend changes the SDK still depends on.
33
+
34
+ ## Development
35
+
36
+ ```bash
37
+ python3 -m venv .venv && source .venv/bin/activate
38
+ pip install -e '.[dev]'
39
+ pytest
40
+ ```
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "admeasy"
7
+ version = "0.1.0"
8
+ description = "Python SDK and CLI for Admeasy AI, the AI employee infrastructure platform."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "TBD" }
12
+ authors = [{ name = "Admeasy", email = "aadesh.panwar@admeasy.in" }]
13
+ keywords = ["admeasy", "ai-employees", "agents", "sdk", "cli"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ ]
19
+ dependencies = [
20
+ "httpx>=0.25,<1",
21
+ "pydantic>=2.4,<3",
22
+ "typer>=0.9",
23
+ ]
24
+
25
+ [project.optional-dependencies]
26
+ dev = [
27
+ "pytest>=7.4",
28
+ "respx>=0.21",
29
+ ]
30
+
31
+ [project.scripts]
32
+ admeasy = "admeasy.cli:main"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/admeasy"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ markers = [
40
+ "integration: opt-in tests against a real Admeasy test backend; skipped unless ADMEASY_TEST_API_URL and ADMEASY_TEST_API_KEY are set",
41
+ ]
@@ -0,0 +1,64 @@
1
+ """Admeasy AI — Python SDK for the AI employee infrastructure platform.
2
+
3
+ One Admeasy account, one source of truth (the Admeasy control plane), many
4
+ interfaces. This package is a client of the same employee records the
5
+ Admeasy website uses; it never keeps a second database.
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ from .agent import Agent # noqa: E402
11
+ from .client import ROUTES, Client, Limits, Model, Route, User, Workspace # noqa: E402
12
+ from .config import DEFAULT_API_URL, DEFAULT_MODEL # noqa: E402
13
+ from .connections import Connection # noqa: E402
14
+ from .employees import Employee # noqa: E402
15
+ from .errors import ( # noqa: E402
16
+ AdmeasyAuthenticationError,
17
+ AdmeasyError,
18
+ APIConnectionError,
19
+ AuthenticationError,
20
+ ConfigurationError,
21
+ ConflictError,
22
+ ConnectionNotAvailableError,
23
+ EmployeeLimitReachedError,
24
+ MissingAPIKeyError,
25
+ NotFoundError,
26
+ PaymentRequiredError,
27
+ PermissionDeniedError,
28
+ PlanLimitError,
29
+ RateLimitError,
30
+ ServerError,
31
+ ValidationError,
32
+ )
33
+
34
+ __all__ = [
35
+ "__version__",
36
+ "Agent",
37
+ "Client",
38
+ "Connection",
39
+ "Employee",
40
+ "Limits",
41
+ "Model",
42
+ "Route",
43
+ "ROUTES",
44
+ "User",
45
+ "Workspace",
46
+ "DEFAULT_API_URL",
47
+ "DEFAULT_MODEL",
48
+ "AdmeasyError",
49
+ "AdmeasyAuthenticationError",
50
+ "AuthenticationError",
51
+ "MissingAPIKeyError",
52
+ "ConfigurationError",
53
+ "PermissionDeniedError",
54
+ "NotFoundError",
55
+ "ValidationError",
56
+ "ConflictError",
57
+ "PaymentRequiredError",
58
+ "PlanLimitError",
59
+ "EmployeeLimitReachedError",
60
+ "RateLimitError",
61
+ "ServerError",
62
+ "APIConnectionError",
63
+ "ConnectionNotAvailableError",
64
+ ]
@@ -0,0 +1,184 @@
1
+ """``Agent`` — a handle to one persistent Admeasy AI employee.
2
+
3
+ An ``Agent`` is never a local-only object. ``Agent.create`` asks the control
4
+ plane to create the employee (the website sees it immediately), ``Agent.get``
5
+ loads an existing one, and ``update``/``delete``/``connect`` all call the
6
+ server. Quota is enforced server-side; the SDK only surfaces
7
+ ``EmployeeLimitReachedError``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from .client import Client
15
+ from .config import DEFAULT_MODEL
16
+ from .connections import Connection
17
+ from .employees import Employee
18
+ from .errors import ConfigurationError, ConnectionNotAvailableError, NotFoundError
19
+
20
+
21
+ class Agent:
22
+ def __init__(
23
+ self,
24
+ name: Optional[str] = None,
25
+ *,
26
+ model: str = DEFAULT_MODEL,
27
+ description: Optional[str] = None,
28
+ client: Optional[Client] = None,
29
+ employee: Optional[Employee] = None,
30
+ ) -> None:
31
+ """Build a handle. ``Agent(name=...)`` alone does NOT create anything;
32
+ call ``Agent.create(...)``, ``Agent.get(id)`` or ``agent.resolve()``."""
33
+ self._client = client or Client()
34
+ self._employee = employee
35
+ self._name = name if employee is None else employee.name
36
+ self._model = model if employee is None else (employee.model or model)
37
+ self._description = description if employee is None else employee.description
38
+
39
+ # -- constructors --------------------------------------------------------
40
+ @classmethod
41
+ def create(
42
+ cls,
43
+ name: str,
44
+ *,
45
+ model: str = DEFAULT_MODEL,
46
+ description: Optional[str] = None,
47
+ instructions: Optional[str] = None,
48
+ connections: Optional[List[str]] = None,
49
+ department: Optional[str] = None,
50
+ schedule: Optional[str] = None,
51
+ client: Optional[Client] = None,
52
+ workspace_id: Optional[str] = None,
53
+ ) -> "Agent":
54
+ client = client or Client()
55
+ employee = client.create_employee(
56
+ name=name,
57
+ model=model,
58
+ description=description,
59
+ instructions=instructions,
60
+ connections=connections,
61
+ department=department,
62
+ schedule=schedule,
63
+ workspace_id=workspace_id,
64
+ )
65
+ return cls(client=client, employee=employee, model=model)
66
+
67
+ @classmethod
68
+ def get(cls, employee_id: str, *, client: Optional[Client] = None, workspace_id: Optional[str] = None) -> "Agent":
69
+ client = client or Client()
70
+ return cls(client=client, employee=client.get_employee(employee_id, workspace_id=workspace_id))
71
+
72
+ @classmethod
73
+ def list(cls, *, client: Optional[Client] = None, workspace_id: Optional[str] = None) -> List["Agent"]:
74
+ client = client or Client()
75
+ return [cls(client=client, employee=e) for e in client.list_employees(workspace_id=workspace_id)]
76
+
77
+ def resolve(self) -> "Agent":
78
+ """Bind this handle to the existing employee with the same name.
79
+ Never creates a duplicate; raises NotFoundError if none exists."""
80
+ if self._employee is not None:
81
+ return self
82
+ if not self._name:
83
+ raise ConfigurationError("Agent has no name or id to resolve")
84
+ found = self._client.employees.find(self._name)
85
+ if not found:
86
+ raise NotFoundError(
87
+ f"No Admeasy employee named {self._name!r} in this workspace. "
88
+ f"Use Agent.create({self._name!r}, model={self._model!r}) to create it.",
89
+ code="EMPLOYEE_NOT_FOUND",
90
+ )
91
+ self._employee = found
92
+ return self
93
+
94
+ # -- properties ----------------------------------------------------------
95
+ def _require(self) -> Employee:
96
+ if self._employee is None:
97
+ raise ConfigurationError(
98
+ "This Agent is not bound to an Admeasy employee yet. "
99
+ "Use Agent.create(...), Agent.get(id) or agent.resolve()."
100
+ )
101
+ return self._employee
102
+
103
+ @property
104
+ def employee(self) -> Optional[Employee]:
105
+ return self._employee
106
+
107
+ @property
108
+ def id(self) -> Optional[str]:
109
+ return self._employee.id if self._employee else None
110
+
111
+ @property
112
+ def name(self) -> Optional[str]:
113
+ return self._employee.name if self._employee else self._name
114
+
115
+ @property
116
+ def status(self) -> Optional[str]:
117
+ return self._employee.status if self._employee else None
118
+
119
+ @property
120
+ def model(self) -> Optional[str]:
121
+ return (self._employee.model if self._employee else None) or self._model
122
+
123
+ @property
124
+ def workspace(self) -> Optional[str]:
125
+ if self._employee and self._employee.workspace_id:
126
+ return self._employee.workspace_id
127
+ return self._client.workspace_id
128
+
129
+ @property
130
+ def connections(self) -> List[str]:
131
+ return list(self._employee.connections) if self._employee else []
132
+
133
+ def __repr__(self) -> str:
134
+ return f"Agent(id={self.id!r}, name={self.name!r}, status={self.status!r}, model={self.model!r})"
135
+
136
+ # -- server operations ---------------------------------------------------
137
+ def refresh(self) -> "Agent":
138
+ emp = self._require()
139
+ self._employee = self._client.get_employee(emp.id, workspace_id=emp.workspace_id)
140
+ return self
141
+
142
+ def update(self, **fields: Any) -> "Agent":
143
+ emp = self._require()
144
+ self._employee = self._client.update_employee(emp.id, workspace_id=emp.workspace_id, **fields)
145
+ return self
146
+
147
+ def delete(self) -> Dict[str, Any]:
148
+ emp = self._require()
149
+ result = self._client.delete_employee(emp.id, workspace_id=emp.workspace_id)
150
+ self._employee = None
151
+ return result
152
+
153
+ def connect(self, name: str) -> Connection:
154
+ """Attach a workspace connection to this employee.
155
+
156
+ Generic: the SDK asks the control plane which connections exist for
157
+ the workspace. If ``name`` is not connected there, it raises
158
+ ``ConnectionNotAvailableError`` with instructions instead of guessing.
159
+ """
160
+ emp = self._require()
161
+ connections = self._client.list_connections(workspace_id=emp.workspace_id)
162
+ match = next((c for c in connections if c.matches(name)), None)
163
+ if match is None or not match.connected:
164
+ connected = sorted(c.id for c in connections if c.connected)
165
+ state = "is not connected" if match else "is not available"
166
+ raise ConnectionNotAvailableError(
167
+ f"Connection {name!r} {state} in workspace {emp.workspace_id}. "
168
+ "Authorize it in the Admeasy website (Integrations page) with an account that "
169
+ "can grant access, then retry. Currently connected: "
170
+ + (", ".join(connected) if connected else "none")
171
+ + ".",
172
+ code="CONNECTION_NOT_AVAILABLE",
173
+ )
174
+ if match.id not in emp.connections:
175
+ self._employee = self._client.update_employee(
176
+ emp.id, workspace_id=emp.workspace_id, connections=[*emp.connections, match.id]
177
+ )
178
+ return match
179
+
180
+ async def run(self, task: str) -> Any: # pragma: no cover - Release 0.2
181
+ raise NotImplementedError(
182
+ "agent.run() arrives with the Admeasy runtime (Release 0.2). "
183
+ "Release 0.1 covers auth, employee CRUD, quota and sync."
184
+ )