notte-sdk 1.4.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,179 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ ignore.*
171
+ llm_usage.jsonl
172
+ llm_parsing_error.jsonl
173
+ traces/
174
+
175
+ **/__pycache__/**
176
+ .DS_Store
177
+ **/.DS_Store
178
+ old
179
+ notebook
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: notte-sdk
3
+ Version: 1.4.0
4
+ Summary: The SDK for Notte
5
+ Author-email: Notte Team <hello@notte.cc>
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: loguru>=0.7.3
8
+ Requires-Dist: notte-core>=1.3.3
9
+ Requires-Dist: pydantic>=2.11.3
10
+ Requires-Dist: requests>=2.32.3
File without changes
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "notte-sdk"
3
+ version = "1.4.0"
4
+ description = "The SDK for Notte"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Notte Team ", email = "hello@notte.cc" }
8
+ ]
9
+ packages = [
10
+ { include = "notte_sdk", from = "src" },
11
+ ]
12
+
13
+ requires-python = ">=3.11"
14
+ dependencies = [
15
+ "loguru>=0.7.3",
16
+ "pydantic>=2.11.3",
17
+ "requests>=2.32.3",
18
+ "notte-core>=1.3.3",
19
+ ]
20
+
21
+ [build-system]
22
+ requires = ["hatchling"]
23
+ build-backend = "hatchling.build"
@@ -0,0 +1,7 @@
1
+ from notte_core import check_notte_version
2
+
3
+ from notte_sdk.client import NotteClient
4
+
5
+ __version__ = check_notte_version("notte_sdk")
6
+
7
+ __all__ = ["NotteClient"]
@@ -0,0 +1,36 @@
1
+ from typing_extensions import final
2
+
3
+ from notte_sdk.endpoints.agents import AgentsClient
4
+ from notte_sdk.endpoints.env import EnvClient
5
+ from notte_sdk.endpoints.persona import PersonaClient
6
+ from notte_sdk.endpoints.sessions import SessionsClient
7
+ from notte_sdk.endpoints.vault import VaultClient
8
+
9
+
10
+ @final
11
+ class NotteClient:
12
+ """
13
+ Client for the Notte API.
14
+
15
+ Note: this client is only able to handle one session at a time.
16
+ If you need to handle multiple sessions, you need to create a new client for each session.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ api_key: str | None = None,
22
+ verbose: bool = False,
23
+ ):
24
+ """Initialize a NotteClient instance.
25
+
26
+ Initializes the NotteClient with the specified API key and server URL, creating instances
27
+ of SessionsClient, AgentsClient, and EnvClient.
28
+
29
+ Args:
30
+ api_key: Optional API key for authentication.
31
+ """
32
+ self.sessions: SessionsClient = SessionsClient(api_key=api_key, verbose=verbose)
33
+ self.agents: AgentsClient = AgentsClient(api_key=api_key, verbose=verbose)
34
+ self.env: EnvClient = EnvClient(api_key=api_key, verbose=verbose)
35
+ self.persona: PersonaClient = PersonaClient(api_key=api_key, verbose=verbose)
36
+ self.vault: VaultClient = VaultClient(api_key=api_key, persona_client=self.persona, verbose=verbose)
File without changes
@@ -0,0 +1,323 @@
1
+ import time
2
+ from collections.abc import Sequence
3
+ from typing import Unpack
4
+
5
+ import requests
6
+ from loguru import logger
7
+ from pydantic import BaseModel
8
+ from typing_extensions import final, override
9
+
10
+ from notte_sdk.endpoints.base import BaseClient, NotteEndpoint
11
+ from notte_sdk.types import (
12
+ AgentListRequest,
13
+ AgentResponse,
14
+ AgentRunRequest,
15
+ AgentRunRequestDict,
16
+ AgentStatus,
17
+ AgentStatusRequest,
18
+ AgentStatusRequestDict,
19
+ ListRequestDict,
20
+ )
21
+ from notte_sdk.types import AgentStatusResponse as _AgentStatusResponse
22
+
23
+
24
+ # proxy for: StepAgentOutput
25
+ class _AgentResponse(BaseModel):
26
+ state: BaseModel
27
+ actions: list[BaseModel]
28
+
29
+
30
+ AgentStatusResponse = _AgentStatusResponse[_AgentResponse]
31
+
32
+
33
+ @final
34
+ class AgentsClient(BaseClient):
35
+ """
36
+ Client for the Notte API.
37
+
38
+ Note: this client is only able to handle one session at a time.
39
+ If you need to handle multiple sessions, you need to create a new client for each session.
40
+ """
41
+
42
+ # Session
43
+ AGENT_RUN = "run"
44
+ AGENT_STOP = "{agent_id}/stop"
45
+ AGENT_STATUS = "{agent_id}"
46
+ AGENT_LIST = ""
47
+ # The following endpoints downloads a .webp file
48
+ AGENT_REPLAY = "{agent_id}/replay"
49
+
50
+ def __init__(
51
+ self,
52
+ api_key: str | None = None,
53
+ verbose: bool = False,
54
+ ):
55
+ """
56
+ Initialize an AgentsClient instance.
57
+
58
+ Configures the client to use the "agents" endpoint path and sets optional API key and server URL for authentication and server configuration. The initial state has no recorded agent response.
59
+
60
+ Args:
61
+ api_key: Optional API key for authenticating requests.
62
+ """
63
+ super().__init__(base_endpoint_path="agents", api_key=api_key, verbose=verbose)
64
+ self._last_agent_response: AgentResponse | None = None
65
+
66
+ @staticmethod
67
+ def agent_run_endpoint() -> NotteEndpoint[AgentResponse]:
68
+ """
69
+ Returns an endpoint for running an agent.
70
+
71
+ Creates a NotteEndpoint configured with the AGENT_RUN path, a POST method, and an expected AgentResponse.
72
+ """
73
+ return NotteEndpoint(path=AgentsClient.AGENT_RUN, response=AgentResponse, method="POST")
74
+
75
+ @staticmethod
76
+ def agent_stop_endpoint(agent_id: str | None = None) -> NotteEndpoint[AgentResponse]:
77
+ """
78
+ Constructs a DELETE endpoint for stopping an agent.
79
+
80
+ If an agent ID is provided, it is inserted into the endpoint URL. The returned
81
+ endpoint is configured with the DELETE HTTP method and expects an AgentStatusResponse.
82
+
83
+ Args:
84
+ agent_id (str, optional): The identifier of the agent to stop. If omitted,
85
+ the URL template will remain unformatted.
86
+
87
+ Returns:
88
+ NotteEndpoint[AgentResponse]: The endpoint object for stopping the agent.
89
+ """
90
+ path = AgentsClient.AGENT_STOP
91
+ if agent_id is not None:
92
+ path = path.format(agent_id=agent_id)
93
+ return NotteEndpoint(path=path, response=AgentStatusResponse, method="DELETE")
94
+
95
+ @staticmethod
96
+ def agent_status_endpoint(agent_id: str | None = None) -> NotteEndpoint[AgentStatusResponse]:
97
+ """
98
+ Creates an endpoint for retrieving an agent's status.
99
+
100
+ If an agent ID is provided, formats the endpoint path to target that specific agent.
101
+
102
+ Args:
103
+ agent_id: Optional identifier of the agent; if specified, the endpoint path will include this ID.
104
+
105
+ Returns:
106
+ NotteEndpoint configured with the GET method and AgentStatusResponse as the expected response.
107
+ """
108
+ path = AgentsClient.AGENT_STATUS
109
+ if agent_id is not None:
110
+ path = path.format(agent_id=agent_id)
111
+ return NotteEndpoint(path=path, response=AgentStatusResponse, method="GET")
112
+
113
+ @staticmethod
114
+ def agent_replay_endpoint(agent_id: str | None = None) -> NotteEndpoint[BaseModel]:
115
+ """
116
+ Creates an endpoint for downloading an agent's replay.
117
+ """
118
+ path = AgentsClient.AGENT_REPLAY
119
+ if agent_id is not None:
120
+ path = path.format(agent_id=agent_id)
121
+ return NotteEndpoint(path=path, response=BaseModel, method="GET")
122
+
123
+ @staticmethod
124
+ def agent_list_endpoint(params: AgentListRequest | None = None) -> NotteEndpoint[AgentResponse]:
125
+ """
126
+ Creates a NotteEndpoint for listing agents.
127
+
128
+ Returns an endpoint configured with the agent listing path and a GET method.
129
+ The optional params argument provides filtering or pagination details for the request.
130
+ """
131
+ return NotteEndpoint(
132
+ path=AgentsClient.AGENT_LIST,
133
+ response=AgentResponse,
134
+ method="GET",
135
+ request=None,
136
+ params=params,
137
+ )
138
+
139
+ @override
140
+ @staticmethod
141
+ def endpoints() -> Sequence[NotteEndpoint[BaseModel]]:
142
+ """
143
+ Returns a list of endpoints for agent operations.
144
+
145
+ Aggregates endpoints for running, stopping, checking status, and listing agents.
146
+ """
147
+ return [
148
+ AgentsClient.agent_run_endpoint(),
149
+ AgentsClient.agent_stop_endpoint(),
150
+ AgentsClient.agent_status_endpoint(),
151
+ AgentsClient.agent_list_endpoint(),
152
+ AgentsClient.agent_replay_endpoint(),
153
+ ]
154
+
155
+ @property
156
+ def agent_id(self) -> str | None:
157
+ """
158
+ Returns the agent ID from the last agent response, or None if no response exists.
159
+
160
+ This property retrieves the identifier from the most recent agent operation response.
161
+ If no agent has been run or if the response is missing, it returns None.
162
+ """
163
+ return self._last_agent_response.agent_id if self._last_agent_response is not None else None
164
+
165
+ def get_agent_id(self, agent_id: str | None = None) -> str:
166
+ """
167
+ Retrieves the agent ID to be used for agent operations.
168
+
169
+ If an `agent_id` is provided, it is returned directly. Otherwise, the method attempts to obtain the agent ID from the client's last agent response. Raises a ValueError if no agent ID is available.
170
+
171
+ Args:
172
+ agent_id (Optional[str]): An agent identifier. If omitted, the ID from the last agent response is used.
173
+
174
+ Raises:
175
+ ValueError: If no agent ID is provided and the client has no recorded agent response.
176
+
177
+ Returns:
178
+ str: The determined agent identifier.
179
+ """
180
+ if agent_id is None:
181
+ if self._last_agent_response is None:
182
+ raise ValueError("No agent to get agent id from")
183
+ agent_id = self._last_agent_response.agent_id
184
+ return agent_id
185
+
186
+ def run(self, **data: Unpack[AgentRunRequestDict]) -> AgentResponse:
187
+ """
188
+ Run an agent with the specified request parameters.
189
+
190
+ Validates the provided data using the AgentRunRequest model, sends a run request through the
191
+ designated endpoint, updates the last agent response, and returns the resulting AgentResponse.
192
+
193
+ Args:
194
+ **data: Keyword arguments representing the fields of an AgentRunRequest.
195
+
196
+ Returns:
197
+ AgentResponse: The response obtained from the agent run request.
198
+ """
199
+ request = AgentRunRequest.model_validate(data)
200
+ response = self.request(AgentsClient.agent_run_endpoint().with_request(request))
201
+ self._last_agent_response = response
202
+ return response
203
+
204
+ def wait_for_completion(
205
+ self,
206
+ agent_id: str | None = None,
207
+ polling_interval_seconds: int = 10,
208
+ max_attempts: int = 30,
209
+ ) -> AgentStatusResponse:
210
+ """
211
+ Waits for the specified agent to complete.
212
+
213
+ Args:
214
+ agent_id: The identifier of the agent to wait for.
215
+ polling_interval_seconds: The interval between status checks.
216
+ max_attempts: The maximum number of attempts to check the agent's status.
217
+
218
+ Returns:
219
+ AgentStatusResponse: The response from the agent status check.
220
+ """
221
+ agent_id = self.get_agent_id(agent_id)
222
+ last_step = 0
223
+ for _ in range(max_attempts):
224
+ response = self.status(agent_id=agent_id, replay=False)
225
+ if response.status == AgentStatus.closed:
226
+ return response
227
+ if len(response.steps) >= last_step:
228
+ for step in response.steps[last_step:]:
229
+ for action in step.actions:
230
+ logger.info(action.to_action().execution_message()) # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType, reportAttributeAccessIssue]
231
+ last_step = len(response.steps)
232
+ logger.info(
233
+ f"Waiting {polling_interval_seconds} seconds for agent to complete (current step: {last_step})..."
234
+ )
235
+ time.sleep(polling_interval_seconds)
236
+ raise TimeoutError("Agent did not complete in time")
237
+
238
+ def close(self, agent_id: str) -> AgentResponse:
239
+ """
240
+ Stops the specified agent and clears the last agent response.
241
+
242
+ Retrieves a valid agent identifier using the provided value or the last stored
243
+ response, sends a stop request to the API, resets the internal agent response,
244
+ and returns the resulting AgentResponse.
245
+
246
+ Args:
247
+ agent_id: The identifier of the agent to stop.
248
+
249
+ Returns:
250
+ AgentResponse: The response from the stop operation.
251
+
252
+ Raises:
253
+ ValueError: If a valid agent identifier cannot be determined.
254
+ """
255
+ agent_id = self.get_agent_id(agent_id)
256
+ endpoint = AgentsClient.agent_stop_endpoint(agent_id=agent_id)
257
+ response = self.request(endpoint)
258
+ self._last_agent_response = None
259
+ return response
260
+
261
+ def status(self, **data: Unpack[AgentStatusRequestDict]) -> AgentStatusResponse:
262
+ """
263
+ Retrieves the status of the specified agent.
264
+
265
+ Queries the API for the current status of an agent using a validated agent ID.
266
+ The provided ID is confirmed (or obtained from the last response if needed), and the
267
+ resulting status is stored internally before being returned.
268
+
269
+ Args:
270
+ agent_id: Unique identifier of the agent to check.
271
+
272
+ Returns:
273
+ AgentResponse: The current status information of the specified agent.
274
+
275
+ Raises:
276
+ ValueError: If no valid agent ID can be determined.
277
+ """
278
+ agent_id = self.get_agent_id(data["agent_id"])
279
+ request = AgentStatusRequest.model_validate(data)
280
+ endpoint = AgentsClient.agent_status_endpoint(agent_id=agent_id).with_params(request)
281
+ response = self.request(endpoint)
282
+ self._last_agent_response = response
283
+ return response
284
+
285
+ def list(self, **data: Unpack[ListRequestDict]) -> Sequence[AgentResponse]:
286
+ """
287
+ Lists agents matching specified criteria.
288
+
289
+ Validates the keyword arguments using the AgentListRequest model, constructs
290
+ the corresponding endpoint for listing agents, and returns a sequence of agent
291
+ responses.
292
+
293
+ Args:
294
+ data: Arbitrary keyword arguments representing filter criteria for agents.
295
+
296
+ Returns:
297
+ A sequence of AgentResponse objects.
298
+ """
299
+ params = AgentListRequest.model_validate(data)
300
+ endpoint = AgentsClient.agent_list_endpoint(params=params)
301
+ return self.request_list(endpoint)
302
+
303
+ def replay(
304
+ self,
305
+ agent_id: str | None = None,
306
+ output_file: str | None = None,
307
+ ) -> bytes:
308
+ """
309
+ Downloads the replay for the specified agent in webp format.
310
+ """
311
+ agent_id = self.get_agent_id(agent_id)
312
+ endpoint = self.request_path(AgentsClient.agent_replay_endpoint(agent_id=agent_id))
313
+ response = requests.get(
314
+ url=endpoint,
315
+ headers=self.headers(),
316
+ timeout=self.DEFAULT_REQUEST_TIMEOUT_SECONDS,
317
+ )
318
+ if b"not found" in response.content:
319
+ raise ValueError(f"Replay for agent {agent_id} is not available.")
320
+ if output_file is not None:
321
+ with open(output_file, "wb") as f:
322
+ _ = f.write(response.content)
323
+ return response.content