waypoint-sdk 0.1.0__py3-none-any.whl
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.
- sdk/__init__.py +46 -0
- sdk/client.py +255 -0
- sdk/examples/agent_with_llm_mock.py +115 -0
- sdk/examples/simple_agent.py +69 -0
- sdk/exceptions.py +52 -0
- sdk/gateway.py +399 -0
- sdk/models.py +81 -0
- sdk/session.py +87 -0
- waypoint_sdk-0.1.0.dist-info/METADATA +172 -0
- waypoint_sdk-0.1.0.dist-info/RECORD +13 -0
- waypoint_sdk-0.1.0.dist-info/WHEEL +5 -0
- waypoint_sdk-0.1.0.dist-info/licenses/LICENSE +339 -0
- waypoint_sdk-0.1.0.dist-info/top_level.txt +1 -0
sdk/__init__.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from .client import WaypointClient
|
|
2
|
+
from .exceptions import (
|
|
3
|
+
CheckpointError,
|
|
4
|
+
CheckpointNotFoundError,
|
|
5
|
+
ExecutionNotFoundError,
|
|
6
|
+
GatewayError,
|
|
7
|
+
WaypointClientError,
|
|
8
|
+
WaypointConnectionError,
|
|
9
|
+
WaypointError,
|
|
10
|
+
WaypointTimeoutError,
|
|
11
|
+
)
|
|
12
|
+
from .gateway import Waypoint, checkpoint
|
|
13
|
+
from .models import (
|
|
14
|
+
CheckpointResponse,
|
|
15
|
+
ExecutionHistory,
|
|
16
|
+
ExecutionInfo,
|
|
17
|
+
ExecutionResult,
|
|
18
|
+
ExecutionStep,
|
|
19
|
+
ReplayState,
|
|
20
|
+
ResumeState,
|
|
21
|
+
StepStatus,
|
|
22
|
+
)
|
|
23
|
+
from .session import WaypointSession
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Waypoint",
|
|
27
|
+
"WaypointClient",
|
|
28
|
+
"WaypointSession",
|
|
29
|
+
"checkpoint",
|
|
30
|
+
"ExecutionInfo",
|
|
31
|
+
"WaypointError",
|
|
32
|
+
"GatewayError",
|
|
33
|
+
"CheckpointError",
|
|
34
|
+
"WaypointClientError",
|
|
35
|
+
"WaypointConnectionError",
|
|
36
|
+
"WaypointTimeoutError",
|
|
37
|
+
"ExecutionNotFoundError",
|
|
38
|
+
"CheckpointNotFoundError",
|
|
39
|
+
"ExecutionResult",
|
|
40
|
+
"CheckpointResponse",
|
|
41
|
+
"ExecutionHistory",
|
|
42
|
+
"ExecutionStep",
|
|
43
|
+
"ResumeState",
|
|
44
|
+
"ReplayState",
|
|
45
|
+
"StepStatus",
|
|
46
|
+
]
|
sdk/client.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
CheckpointNotFoundError,
|
|
10
|
+
ExecutionNotFoundError,
|
|
11
|
+
GatewayError,
|
|
12
|
+
WaypointClientError,
|
|
13
|
+
)
|
|
14
|
+
from .models import (
|
|
15
|
+
CheckpointResponse,
|
|
16
|
+
ExecutionHistory,
|
|
17
|
+
ExecutionInfo,
|
|
18
|
+
ExecutionStep,
|
|
19
|
+
ReplayState,
|
|
20
|
+
ResumeState,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(f"sdk.{__name__}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WaypointClient:
|
|
27
|
+
"""
|
|
28
|
+
Low-level HTTP client for the Waypoint API.
|
|
29
|
+
|
|
30
|
+
Manages reusable httpx clients (sync and async) with lazy initialisation,
|
|
31
|
+
thread-safety locks, and explicit close/aclose lifecycle.
|
|
32
|
+
|
|
33
|
+
Every public method maps to exactly one API endpoint.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
base_url: str,
|
|
40
|
+
agent_id: str,
|
|
41
|
+
timeout: float = 30.0,
|
|
42
|
+
api_key: str | None = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.agent_id = agent_id
|
|
46
|
+
self.timeout = timeout
|
|
47
|
+
self.api_key = api_key
|
|
48
|
+
|
|
49
|
+
self._sync_client: httpx.Client | None = None
|
|
50
|
+
self._async_client: httpx.AsyncClient | None = None
|
|
51
|
+
|
|
52
|
+
self._sync_lock = asyncio.Lock()
|
|
53
|
+
self._async_lock = asyncio.Lock()
|
|
54
|
+
|
|
55
|
+
# ── low-level HTTP helpers ──────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
def _auth_headers(self) -> dict[str, str]:
|
|
58
|
+
if self.api_key:
|
|
59
|
+
return {"X-WAYPOINT-API-KEY": f"{self.api_key}"}
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
def _http(self) -> httpx.Client:
|
|
63
|
+
if self._sync_client is None or self._sync_client.is_closed:
|
|
64
|
+
self._sync_client = httpx.Client(
|
|
65
|
+
base_url=self.base_url,
|
|
66
|
+
headers=self._auth_headers(),
|
|
67
|
+
timeout=self.timeout,
|
|
68
|
+
trust_env=False,
|
|
69
|
+
)
|
|
70
|
+
return self._sync_client
|
|
71
|
+
|
|
72
|
+
async def _ahttp(self) -> httpx.AsyncClient:
|
|
73
|
+
if self._async_client is None or self._async_client.is_closed:
|
|
74
|
+
self._async_client = httpx.AsyncClient(
|
|
75
|
+
base_url=self.base_url,
|
|
76
|
+
headers=self._auth_headers(),
|
|
77
|
+
timeout=self.timeout,
|
|
78
|
+
trust_env=False,
|
|
79
|
+
)
|
|
80
|
+
return self._async_client
|
|
81
|
+
|
|
82
|
+
def close(self) -> None:
|
|
83
|
+
if self._sync_client:
|
|
84
|
+
self._sync_client.close()
|
|
85
|
+
|
|
86
|
+
async def aclose(self) -> None:
|
|
87
|
+
if self._async_client:
|
|
88
|
+
await self._async_client.aclose()
|
|
89
|
+
|
|
90
|
+
# ── request dispatch ────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _build_path(
|
|
94
|
+
execution_id: UUID,
|
|
95
|
+
resource: str,
|
|
96
|
+
*,
|
|
97
|
+
include_load: bool = False,
|
|
98
|
+
) -> str:
|
|
99
|
+
if resource == "create_execution":
|
|
100
|
+
return "/executions/"
|
|
101
|
+
if resource == "checkpoint":
|
|
102
|
+
return "/checkpoints/"
|
|
103
|
+
if resource == "checkpoint_load":
|
|
104
|
+
return "/checkpoints/load"
|
|
105
|
+
if resource == "history":
|
|
106
|
+
return f"/executions/{execution_id}/history"
|
|
107
|
+
if resource == "resume":
|
|
108
|
+
return f"/executions/{execution_id}/resume"
|
|
109
|
+
if resource == "replay":
|
|
110
|
+
return f"/executions/{execution_id}/replay"
|
|
111
|
+
msg = f"Unknown resource: {resource}"
|
|
112
|
+
raise ValueError(msg)
|
|
113
|
+
|
|
114
|
+
async def _arequest(
|
|
115
|
+
self,
|
|
116
|
+
method: str,
|
|
117
|
+
path: str,
|
|
118
|
+
json_body: dict[str, Any] | None = None,
|
|
119
|
+
params: dict[str, str | int] | None = None,
|
|
120
|
+
) -> dict[str, Any]:
|
|
121
|
+
try:
|
|
122
|
+
http = await self._ahttp()
|
|
123
|
+
resp = await http.request(
|
|
124
|
+
method,
|
|
125
|
+
path,
|
|
126
|
+
json=json_body,
|
|
127
|
+
params=params,
|
|
128
|
+
)
|
|
129
|
+
body: dict[str, Any] = resp.json()
|
|
130
|
+
if not resp.is_success:
|
|
131
|
+
detail = body.get("detail", str(resp.reason_phrase))
|
|
132
|
+
if resp.status_code == 404:
|
|
133
|
+
msg = detail if isinstance(detail, str) else str(detail)
|
|
134
|
+
if "checkpoint" in msg.lower():
|
|
135
|
+
raise CheckpointNotFoundError(msg)
|
|
136
|
+
raise ExecutionNotFoundError(msg)
|
|
137
|
+
raise WaypointClientError(resp.status_code, detail)
|
|
138
|
+
return body
|
|
139
|
+
except httpx.ConnectError as exc:
|
|
140
|
+
raise GatewayError(self.base_url, exc) from exc
|
|
141
|
+
except httpx.TimeoutException as exc:
|
|
142
|
+
raise GatewayError(self.base_url, exc) from exc
|
|
143
|
+
|
|
144
|
+
# ── API methods ─────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
async def create_execution(
|
|
147
|
+
self,
|
|
148
|
+
agent_id: str,
|
|
149
|
+
initial_input: dict[str, Any] | None = None,
|
|
150
|
+
) -> ExecutionInfo:
|
|
151
|
+
body = await self._arequest(
|
|
152
|
+
"POST",
|
|
153
|
+
"/executions/",
|
|
154
|
+
json_body={
|
|
155
|
+
"agent_id": agent_id,
|
|
156
|
+
"initial_input": initial_input,
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
return ExecutionInfo(
|
|
160
|
+
id=UUID(body["id"]) if isinstance(body["id"], str) else body["id"],
|
|
161
|
+
agent_id=body["agent_id"],
|
|
162
|
+
status=body["status"],
|
|
163
|
+
started_at=body["started_at"],
|
|
164
|
+
initial_input=body.get("initial_input"),
|
|
165
|
+
created_at=body.get("created_at"),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
async def create_checkpoint(
|
|
169
|
+
self,
|
|
170
|
+
execution_id: UUID,
|
|
171
|
+
step_number: int,
|
|
172
|
+
step_name: str,
|
|
173
|
+
state: dict[str, Any],
|
|
174
|
+
*,
|
|
175
|
+
input_data: dict[str, Any] | None = None,
|
|
176
|
+
output_data: dict[str, Any] | None = None,
|
|
177
|
+
status: str = "completed",
|
|
178
|
+
duration_ms: int | None = None,
|
|
179
|
+
error: dict[str, Any] | None = None,
|
|
180
|
+
cached: bool = False,
|
|
181
|
+
) -> CheckpointResponse:
|
|
182
|
+
body = await self._arequest(
|
|
183
|
+
"POST",
|
|
184
|
+
"/checkpoints/",
|
|
185
|
+
json_body={
|
|
186
|
+
"execution_id": str(execution_id),
|
|
187
|
+
"step_number": step_number,
|
|
188
|
+
"step_name": step_name,
|
|
189
|
+
"state": state,
|
|
190
|
+
"input_data": input_data or {},
|
|
191
|
+
"output_data": output_data,
|
|
192
|
+
"status": status,
|
|
193
|
+
"duration_ms": duration_ms,
|
|
194
|
+
"error": error,
|
|
195
|
+
"cached": cached,
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
return CheckpointResponse(
|
|
199
|
+
id=UUID(body["id"]) if isinstance(body["id"], str) else body["id"],
|
|
200
|
+
execution_id=UUID(body["execution_id"])
|
|
201
|
+
if isinstance(body["execution_id"], str)
|
|
202
|
+
else body["execution_id"],
|
|
203
|
+
step_number=body["step_number"],
|
|
204
|
+
completed_at=body["completed_at"],
|
|
205
|
+
state_hash=body.get("state_hash"),
|
|
206
|
+
created_at=body.get("created_at"),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
async def resume_execution(self, execution_id: UUID) -> ResumeState:
|
|
210
|
+
body = await self._arequest(
|
|
211
|
+
"POST",
|
|
212
|
+
f"/executions/{execution_id}/resume",
|
|
213
|
+
)
|
|
214
|
+
return ResumeState(
|
|
215
|
+
execution_id=UUID(body["execution_id"])
|
|
216
|
+
if isinstance(body["execution_id"], str)
|
|
217
|
+
else body["execution_id"],
|
|
218
|
+
checkpoint_step=body.get("checkpoint_step", 0),
|
|
219
|
+
reconstructed_state=body.get("reconstructed_state", {}),
|
|
220
|
+
state_hash=body.get("state_hash"),
|
|
221
|
+
ready_to_resume=body.get("ready_to_resume", True),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
async def replay_from_step(
|
|
225
|
+
self,
|
|
226
|
+
execution_id: UUID,
|
|
227
|
+
step_number: int,
|
|
228
|
+
) -> ReplayState:
|
|
229
|
+
body = await self._arequest(
|
|
230
|
+
"POST",
|
|
231
|
+
f"/executions/{execution_id}/replay",
|
|
232
|
+
json_body={"step_number": step_number},
|
|
233
|
+
)
|
|
234
|
+
return ReplayState(
|
|
235
|
+
execution_id=UUID(body["execution_id"])
|
|
236
|
+
if isinstance(body["execution_id"], str)
|
|
237
|
+
else body["execution_id"],
|
|
238
|
+
replay_from_step=body.get("replay_from_step", step_number),
|
|
239
|
+
reconstructed_state=body.get("reconstructed_state", {}),
|
|
240
|
+
ready_to_resume=body.get("ready_to_resume", True),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
async def get_execution_history(self, execution_id: UUID) -> ExecutionHistory:
|
|
244
|
+
body = await self._arequest("GET", f"/executions/{execution_id}/history")
|
|
245
|
+
steps = [ExecutionStep(**s) for s in body.get("steps", [])]
|
|
246
|
+
return ExecutionHistory(
|
|
247
|
+
execution_id=UUID(body["execution_id"])
|
|
248
|
+
if isinstance(body["execution_id"], str)
|
|
249
|
+
else body["execution_id"],
|
|
250
|
+
agent_id=body["agent_id"],
|
|
251
|
+
status=body["status"],
|
|
252
|
+
started_at=body["started_at"],
|
|
253
|
+
completed_at=body.get("completed_at"),
|
|
254
|
+
steps=steps,
|
|
255
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent workflow with mocked LLM calls, demonstrating crash recovery.
|
|
3
|
+
|
|
4
|
+
Prerequisites:
|
|
5
|
+
- Waypoint API running at http://localhost:9654
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
uv run python -m sdk.examples.agent_with_llm_mock
|
|
9
|
+
|
|
10
|
+
This simulates:
|
|
11
|
+
1. Create a new execution
|
|
12
|
+
2. Normal execution with an LLM call
|
|
13
|
+
3. Resume after a simulated crash: the LLM response is served from cache
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
|
|
18
|
+
from sdk import Waypoint, checkpoint
|
|
19
|
+
|
|
20
|
+
AGENT_ID = "llm_agent"
|
|
21
|
+
API_BASE_URL = "http://localhost:9654/api/v1/"
|
|
22
|
+
|
|
23
|
+
waypoint = Waypoint(
|
|
24
|
+
base_url=API_BASE_URL,
|
|
25
|
+
agent_id=AGENT_ID,
|
|
26
|
+
).use()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@checkpoint("load_context", cache=True)
|
|
30
|
+
async def load_context(user_query: str):
|
|
31
|
+
return {
|
|
32
|
+
"query": user_query,
|
|
33
|
+
"context": {"user_id": "abc123", "session": "test"},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def mock_llm_call(prompt: str) -> dict:
|
|
38
|
+
"""Simulate an expensive LLM API call."""
|
|
39
|
+
await asyncio.sleep(0.05)
|
|
40
|
+
return {
|
|
41
|
+
"response": f"Analysis of: {prompt[:50]}...",
|
|
42
|
+
"tokens_used": 150,
|
|
43
|
+
"model": "gpt-4",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@checkpoint("call_llm", cache=True)
|
|
48
|
+
async def call_llm(context: dict):
|
|
49
|
+
print(" Calling LLM (this is expensive)...")
|
|
50
|
+
return await mock_llm_call(context["query"])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@checkpoint("format_output")
|
|
54
|
+
async def format_output(data: dict):
|
|
55
|
+
response = data["llm"]["response"]
|
|
56
|
+
return {"formatted": f"<result>{response}</result>", "meta": data["llm"]}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def first_run():
|
|
60
|
+
"""First execution: create, run steps, return execution_id for recovery demo."""
|
|
61
|
+
execution_id = await waypoint.create()
|
|
62
|
+
print(f"Created execution: {execution_id}")
|
|
63
|
+
|
|
64
|
+
context = await load_context(user_query="What is event sourcing?")
|
|
65
|
+
print("Step 1 (load_context): context loaded")
|
|
66
|
+
|
|
67
|
+
llm_result = await call_llm(context=context)
|
|
68
|
+
print("Step 2 (call_llm): LLM responded")
|
|
69
|
+
|
|
70
|
+
output = await format_output(data={"llm": llm_result, "context": context})
|
|
71
|
+
print(f"Step 3 (format_output): {output['formatted'][:100]}...")
|
|
72
|
+
|
|
73
|
+
print(f"\nExecution completed! Total steps: {waypoint.get_step_number()}")
|
|
74
|
+
return execution_id
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def crash_recovery_demo(execution_id):
|
|
78
|
+
"""
|
|
79
|
+
Simulate a crash and recovery.
|
|
80
|
+
A new Waypoint instance resumes the execution; the cached LLM response
|
|
81
|
+
is returned without re-invoking the LLM call.
|
|
82
|
+
"""
|
|
83
|
+
print("\n--- CRASH RECOVERY ---")
|
|
84
|
+
print(f"Resuming execution {execution_id}...")
|
|
85
|
+
|
|
86
|
+
waypoint2 = Waypoint(
|
|
87
|
+
base_url=API_BASE_URL,
|
|
88
|
+
agent_id=AGENT_ID,
|
|
89
|
+
).use()
|
|
90
|
+
|
|
91
|
+
resume = await waypoint2.resume(execution_id)
|
|
92
|
+
print(f"Resumed from step {resume.checkpoint_step}")
|
|
93
|
+
print(f"Recovered state keys: {list(waypoint2.get_state().keys())}")
|
|
94
|
+
|
|
95
|
+
context = await load_context(user_query="What is event sourcing?")
|
|
96
|
+
print("Step 1 (load_context, cached): context loaded (from cache)")
|
|
97
|
+
|
|
98
|
+
llm_result = await call_llm(context=context)
|
|
99
|
+
print("Step 2 (call_llm, cached): LLM responded (from cache, no re-execution)")
|
|
100
|
+
|
|
101
|
+
output = await format_output(data={"llm": llm_result, "context": context})
|
|
102
|
+
print(f"Step 3 (format_output, fresh): {output['formatted'][:100]}...")
|
|
103
|
+
|
|
104
|
+
print(f"\nRecovery complete! Total steps: {waypoint2.get_step_number()}")
|
|
105
|
+
await waypoint2.aclose()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def main():
|
|
109
|
+
execution_id = await first_run()
|
|
110
|
+
await crash_recovery_demo(execution_id)
|
|
111
|
+
await waypoint.aclose()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple 3-step agent workflow using the Waypoint SDK.
|
|
3
|
+
|
|
4
|
+
Prerequisites:
|
|
5
|
+
- Waypoint API running at http://localhost:9654
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
uv run python -m sdk.examples.simple_agent
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
|
|
13
|
+
from sdk import Waypoint, checkpoint
|
|
14
|
+
|
|
15
|
+
AGENT_ID = "simple_agent"
|
|
16
|
+
API_BASE_URL = "http://localhost:9654/api/v1/"
|
|
17
|
+
|
|
18
|
+
waypoint = Waypoint(
|
|
19
|
+
base_url=API_BASE_URL,
|
|
20
|
+
agent_id=AGENT_ID,
|
|
21
|
+
).use()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@checkpoint("load_query")
|
|
25
|
+
async def load_query(query: str):
|
|
26
|
+
return {"query": query, "normalized": query.lower()}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@checkpoint("search")
|
|
30
|
+
async def search(data: dict):
|
|
31
|
+
results = [
|
|
32
|
+
"Waypoint provides agent execution recovery",
|
|
33
|
+
"Event sourcing enables deterministic replay",
|
|
34
|
+
]
|
|
35
|
+
return {**data, "results": results}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@checkpoint("summarize", cache=True)
|
|
39
|
+
async def summarize(data: dict):
|
|
40
|
+
summary = "Waypoint is a fault-tolerant execution recovery system."
|
|
41
|
+
return {**data, "summary": summary}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
execution_id = await waypoint.create()
|
|
46
|
+
print(f"Created execution: {execution_id}")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
step1 = await load_query(query="What is Waypoint?")
|
|
50
|
+
print(f"Step 1 (load_query): {step1}")
|
|
51
|
+
|
|
52
|
+
step2 = await search(data=step1)
|
|
53
|
+
print(f"Step 2 (search): {step2}")
|
|
54
|
+
|
|
55
|
+
step3 = await summarize(data=step2)
|
|
56
|
+
print(f"Step 3 (summarize): {step3}")
|
|
57
|
+
|
|
58
|
+
print(f"\nExecution completed! Total steps: {waypoint.get_step_number()}")
|
|
59
|
+
print(f"Final state: {waypoint.get_state()}")
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Execution failed: {e}")
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
await waypoint.aclose()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
asyncio.run(main())
|
sdk/exceptions.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class WaypointError(Exception):
|
|
2
|
+
"""Base exception for all Waypoint SDK errors."""
|
|
3
|
+
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GatewayError(WaypointError):
|
|
8
|
+
"""Raised when the Waypoint API is unreachable and no fail-open policy applies."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, gateway_url: str, original: Exception) -> None:
|
|
11
|
+
self.gateway_url = gateway_url
|
|
12
|
+
self.original = original
|
|
13
|
+
super().__init__(f"Waypoint API at {gateway_url!r} unreachable: {original}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CheckpointError(WaypointError):
|
|
17
|
+
"""Raised when a checkpoint operation fails."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExecutionNotFoundError(WaypointError):
|
|
23
|
+
"""Raised when the execution does not exist on the server."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CheckpointNotFoundError(WaypointError):
|
|
29
|
+
"""Raised when no checkpoint exists for an execution."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WaypointClientError(WaypointError):
|
|
35
|
+
"""Raised when the API returns an unexpected error status."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, status_code: int, detail: str) -> None:
|
|
38
|
+
self.status_code = status_code
|
|
39
|
+
self.detail = detail
|
|
40
|
+
super().__init__(f"API error {status_code}: {detail}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class WaypointConnectionError(WaypointError):
|
|
44
|
+
"""Raised when the SDK cannot connect to the Waypoint API."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class WaypointTimeoutError(WaypointError):
|
|
50
|
+
"""Raised when a request to the Waypoint API times out."""
|
|
51
|
+
|
|
52
|
+
pass
|