rugbe-client 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.
- rugbe_client-0.1.0/PKG-INFO +48 -0
- rugbe_client-0.1.0/README.md +39 -0
- rugbe_client-0.1.0/pyproject.toml +28 -0
- rugbe_client-0.1.0/src/rugbe_client/__init__.py +50 -0
- rugbe_client-0.1.0/src/rugbe_client/_calls.py +408 -0
- rugbe_client-0.1.0/src/rugbe_client/_transport.py +263 -0
- rugbe_client-0.1.0/src/rugbe_client/client.py +674 -0
- rugbe_client-0.1.0/src/rugbe_client/errors.py +156 -0
- rugbe_client-0.1.0/src/rugbe_client/events.py +203 -0
- rugbe_client-0.1.0/src/rugbe_client/models.py +198 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: rugbe-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed HTTP + SSE client (sync and async) for rugbe-agent-runtime
|
|
5
|
+
Requires-Dist: httpx>=0.27,<1.0
|
|
6
|
+
Requires-Dist: pydantic>=2.6,<3.0
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# rugbe-client (Python)
|
|
11
|
+
|
|
12
|
+
Typed HTTP + SSE client for rugbe-agent-runtime, sync and async. Backend subset of
|
|
13
|
+
`@bhanux/rugbe-client`: workflows, runs (streaming), sessions, files, skills, models,
|
|
14
|
+
me, node types, health.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from rugbe_client import RuntimeClient
|
|
18
|
+
|
|
19
|
+
with RuntimeClient(
|
|
20
|
+
"https://runtime.example.com",
|
|
21
|
+
get_auth_headers=lambda: {"Authorization": f"Bearer {key}"},
|
|
22
|
+
on_auth_error=lambda: refresh_key(), # optional: called once on 401
|
|
23
|
+
) as rt:
|
|
24
|
+
session = rt.sessions.create(subject="user-42")
|
|
25
|
+
for event in rt.runs.stream(session.workflow_id, input={"message": "hi"}, view="chat"):
|
|
26
|
+
if event.type == "text":
|
|
27
|
+
print(event.content, end="")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`AsyncRuntimeClient` has the same surface with `async`/`await` (`async for` on
|
|
31
|
+
`runs.stream`); its credential hooks may be coroutines.
|
|
32
|
+
|
|
33
|
+
- Errors: every non-2xx raises `RuntimeApiError` (`status`, `kind`, `issues`, `detail`).
|
|
34
|
+
`to_draft_conflict(err)` reads a lost-update 409 on `workflows.save_draft`.
|
|
35
|
+
- Only **401** triggers `on_auth_error`, once; a refreshed credential that is refused
|
|
36
|
+
again surfaces. 403 is never retried.
|
|
37
|
+
- Unknown stream event types arrive as `UnknownEvent` instead of raising.
|
|
38
|
+
|
|
39
|
+
## Development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv sync
|
|
43
|
+
uv run pytest
|
|
44
|
+
uv run ruff check . --fix && uv run ruff format src tests
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`tests/test_conformance.py` compares model fields to
|
|
48
|
+
`packages/rugbe-client/openapi.json` (refresh with `bun run generate-types` there).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# rugbe-client (Python)
|
|
2
|
+
|
|
3
|
+
Typed HTTP + SSE client for rugbe-agent-runtime, sync and async. Backend subset of
|
|
4
|
+
`@bhanux/rugbe-client`: workflows, runs (streaming), sessions, files, skills, models,
|
|
5
|
+
me, node types, health.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from rugbe_client import RuntimeClient
|
|
9
|
+
|
|
10
|
+
with RuntimeClient(
|
|
11
|
+
"https://runtime.example.com",
|
|
12
|
+
get_auth_headers=lambda: {"Authorization": f"Bearer {key}"},
|
|
13
|
+
on_auth_error=lambda: refresh_key(), # optional: called once on 401
|
|
14
|
+
) as rt:
|
|
15
|
+
session = rt.sessions.create(subject="user-42")
|
|
16
|
+
for event in rt.runs.stream(session.workflow_id, input={"message": "hi"}, view="chat"):
|
|
17
|
+
if event.type == "text":
|
|
18
|
+
print(event.content, end="")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`AsyncRuntimeClient` has the same surface with `async`/`await` (`async for` on
|
|
22
|
+
`runs.stream`); its credential hooks may be coroutines.
|
|
23
|
+
|
|
24
|
+
- Errors: every non-2xx raises `RuntimeApiError` (`status`, `kind`, `issues`, `detail`).
|
|
25
|
+
`to_draft_conflict(err)` reads a lost-update 409 on `workflows.save_draft`.
|
|
26
|
+
- Only **401** triggers `on_auth_error`, once; a refreshed credential that is refused
|
|
27
|
+
again surfaces. 403 is never retried.
|
|
28
|
+
- Unknown stream event types arrive as `UnknownEvent` instead of raising.
|
|
29
|
+
|
|
30
|
+
## Development
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv sync
|
|
34
|
+
uv run pytest
|
|
35
|
+
uv run ruff check . --fix && uv run ruff format src tests
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`tests/test_conformance.py` compares model fields to
|
|
39
|
+
`packages/rugbe-client/openapi.json` (refresh with `bun run generate-types` there).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rugbe-client"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Typed HTTP + SSE client (sync and async) for rugbe-agent-runtime"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"httpx>=0.27,<1.0",
|
|
9
|
+
"pydantic>=2.6,<3.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[dependency-groups]
|
|
13
|
+
dev = [
|
|
14
|
+
"pytest>=8.0",
|
|
15
|
+
"pytest-asyncio>=0.24",
|
|
16
|
+
"respx>=0.21",
|
|
17
|
+
"ruff>=0.6",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.8.14,<0.9.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
23
|
+
|
|
24
|
+
[tool.ruff]
|
|
25
|
+
line-length = 88
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Typed HTTP + SSE client (sync and async) for rugbe-agent-runtime."""
|
|
2
|
+
|
|
3
|
+
from .client import AsyncRuntimeClient, RuntimeClient
|
|
4
|
+
from .errors import (
|
|
5
|
+
DraftConflict,
|
|
6
|
+
RuntimeApiError,
|
|
7
|
+
ValidationIssue,
|
|
8
|
+
to_draft_conflict,
|
|
9
|
+
)
|
|
10
|
+
from .events import RunStreamEvent, UnknownEvent, parse_event
|
|
11
|
+
from .models import (
|
|
12
|
+
Me,
|
|
13
|
+
ModelOption,
|
|
14
|
+
RunOut,
|
|
15
|
+
RunStatus,
|
|
16
|
+
RunStepOut,
|
|
17
|
+
RunSummary,
|
|
18
|
+
SessionOut,
|
|
19
|
+
Skill,
|
|
20
|
+
ValidationResult,
|
|
21
|
+
WorkflowFile,
|
|
22
|
+
WorkflowGraph,
|
|
23
|
+
WorkflowOut,
|
|
24
|
+
WorkflowSummary,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"AsyncRuntimeClient",
|
|
29
|
+
"DraftConflict",
|
|
30
|
+
"Me",
|
|
31
|
+
"ModelOption",
|
|
32
|
+
"RunOut",
|
|
33
|
+
"RunStatus",
|
|
34
|
+
"RunStepOut",
|
|
35
|
+
"RunStreamEvent",
|
|
36
|
+
"RunSummary",
|
|
37
|
+
"RuntimeApiError",
|
|
38
|
+
"RuntimeClient",
|
|
39
|
+
"SessionOut",
|
|
40
|
+
"Skill",
|
|
41
|
+
"UnknownEvent",
|
|
42
|
+
"ValidationIssue",
|
|
43
|
+
"ValidationResult",
|
|
44
|
+
"WorkflowFile",
|
|
45
|
+
"WorkflowGraph",
|
|
46
|
+
"WorkflowOut",
|
|
47
|
+
"WorkflowSummary",
|
|
48
|
+
"parse_event",
|
|
49
|
+
"to_draft_conflict",
|
|
50
|
+
]
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Every endpoint as a :class:`Call` builder, shared by the sync and async clients.
|
|
2
|
+
|
|
3
|
+
Keeping the path/body/parse knowledge here means the two clients are two-line
|
|
4
|
+
wrappers that cannot drift from each other.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import mimetypes
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, BinaryIO
|
|
12
|
+
from urllib.parse import quote
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, TypeAdapter
|
|
15
|
+
|
|
16
|
+
from ._transport import Call
|
|
17
|
+
from .models import (
|
|
18
|
+
ElicitationReceipt,
|
|
19
|
+
Me,
|
|
20
|
+
ModelOption,
|
|
21
|
+
RunOut,
|
|
22
|
+
RunReceipt,
|
|
23
|
+
RunStepOut,
|
|
24
|
+
RunSummary,
|
|
25
|
+
RuntimeReadiness,
|
|
26
|
+
SessionOut,
|
|
27
|
+
Skill,
|
|
28
|
+
SkillFile,
|
|
29
|
+
SkillUpload,
|
|
30
|
+
ValidationResult,
|
|
31
|
+
WorkflowFile,
|
|
32
|
+
WorkflowGraph,
|
|
33
|
+
WorkflowOut,
|
|
34
|
+
WorkflowSummary,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
FileInput = str | os.PathLike[str] | bytes | BinaryIO
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _model(cls: type[BaseModel]) -> Any:
|
|
41
|
+
return cls.model_validate
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _list(cls: type[BaseModel]) -> Any:
|
|
45
|
+
adapter = TypeAdapter(list[cls]) # type: ignore[valid-type]
|
|
46
|
+
return adapter.validate_python
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _dump(value: BaseModel | dict[str, Any]) -> dict[str, Any]:
|
|
50
|
+
if isinstance(value, BaseModel):
|
|
51
|
+
return value.model_dump(mode="json", exclude_none=True)
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _seg(value: str) -> str:
|
|
56
|
+
return quote(value, safe="")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _upload(file: FileInput, name: str | None) -> tuple[str, Any, str]:
|
|
60
|
+
if isinstance(file, (str, os.PathLike)):
|
|
61
|
+
path = os.fspath(file)
|
|
62
|
+
with open(path, "rb") as fh:
|
|
63
|
+
content = fh.read()
|
|
64
|
+
name = name or os.path.basename(path)
|
|
65
|
+
elif isinstance(file, bytes):
|
|
66
|
+
content = file
|
|
67
|
+
if name is None:
|
|
68
|
+
raise ValueError("name is required when uploading raw bytes")
|
|
69
|
+
else:
|
|
70
|
+
content = file.read()
|
|
71
|
+
name = name or os.path.basename(getattr(file, "name", "") or "") or None
|
|
72
|
+
if name is None:
|
|
73
|
+
raise ValueError("name is required for a file object without a name")
|
|
74
|
+
mime = mimetypes.guess_type(name)[0] or "application/octet-stream"
|
|
75
|
+
return name, content, mime
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# -- workflows ---------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def workflows_list() -> Call:
|
|
82
|
+
return Call("GET", "/v1/workflows", parse=_list(WorkflowSummary))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def workflows_get(workflow_id: str) -> Call:
|
|
86
|
+
return Call("GET", f"/v1/workflows/{_seg(workflow_id)}", parse=_model(WorkflowOut))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def workflows_create(
|
|
90
|
+
name: str | None, description: str | None, graph: WorkflowGraph | None
|
|
91
|
+
) -> Call:
|
|
92
|
+
body: dict[str, Any] = {}
|
|
93
|
+
if name is not None:
|
|
94
|
+
body["name"] = name
|
|
95
|
+
if description is not None:
|
|
96
|
+
body["description"] = description
|
|
97
|
+
if graph is not None:
|
|
98
|
+
body["graph"] = _dump(graph)
|
|
99
|
+
return Call("POST", "/v1/workflows", body=body, parse=_model(WorkflowOut))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def workflows_update(
|
|
103
|
+
workflow_id: str, name: str | None, description: str | None
|
|
104
|
+
) -> Call:
|
|
105
|
+
body = {
|
|
106
|
+
k: v
|
|
107
|
+
for k, v in {"name": name, "description": description}.items()
|
|
108
|
+
if v is not None
|
|
109
|
+
}
|
|
110
|
+
return Call(
|
|
111
|
+
"PATCH",
|
|
112
|
+
f"/v1/workflows/{_seg(workflow_id)}",
|
|
113
|
+
body=body,
|
|
114
|
+
parse=_model(WorkflowOut),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def workflows_delete(workflow_id: str) -> Call:
|
|
119
|
+
return Call("DELETE", f"/v1/workflows/{_seg(workflow_id)}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def workflows_save_draft(workflow_id: str, graph: WorkflowGraph, if_match: int) -> Call:
|
|
123
|
+
return Call(
|
|
124
|
+
"PUT",
|
|
125
|
+
f"/v1/workflows/{_seg(workflow_id)}/draft",
|
|
126
|
+
body={"graph": _dump(graph)},
|
|
127
|
+
headers={"If-Match": str(if_match)},
|
|
128
|
+
parse=_model(WorkflowOut),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def workflows_validate(workflow_id: str, graph: WorkflowGraph) -> Call:
|
|
133
|
+
return Call(
|
|
134
|
+
"POST",
|
|
135
|
+
f"/v1/workflows/{_seg(workflow_id)}/validate",
|
|
136
|
+
body={"graph": _dump(graph)},
|
|
137
|
+
parse=_model(ValidationResult),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def workflows_publish(workflow_id: str) -> Call:
|
|
142
|
+
return Call(
|
|
143
|
+
"POST", f"/v1/workflows/{_seg(workflow_id)}/publish", parse=_model(WorkflowOut)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def workflows_contract(workflow_id: str) -> Call:
|
|
148
|
+
return Call("GET", f"/v1/workflows/{_seg(workflow_id)}/contract")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def workflows_transcript(workflow_id: str, thread_id: str) -> Call:
|
|
152
|
+
return Call(
|
|
153
|
+
"GET",
|
|
154
|
+
f"/v1/workflows/{_seg(workflow_id)}/threads/{_seg(thread_id)}/transcript",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def workflows_publish_event(
|
|
159
|
+
workflow_id: str,
|
|
160
|
+
event_type: str,
|
|
161
|
+
data: Any = None,
|
|
162
|
+
*,
|
|
163
|
+
id: str | None = None,
|
|
164
|
+
subject: str | None = None,
|
|
165
|
+
draft: bool = False,
|
|
166
|
+
) -> Call:
|
|
167
|
+
body: dict[str, Any] = {"event_type": event_type, "draft": draft}
|
|
168
|
+
if data is not None:
|
|
169
|
+
body["data"] = data
|
|
170
|
+
if id is not None:
|
|
171
|
+
body["id"] = id
|
|
172
|
+
if subject is not None:
|
|
173
|
+
body["subject"] = subject
|
|
174
|
+
return Call("POST", f"/v1/workflows/{_seg(workflow_id)}/events", body=body)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# -- runs --------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def runs_stream_path(workflow_id: str) -> str:
|
|
181
|
+
return f"/v1/workflows/{_seg(workflow_id)}/runs"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def runs_body(
|
|
185
|
+
*,
|
|
186
|
+
input: dict[str, Any] | None = None,
|
|
187
|
+
params: dict[str, Any] | None = None,
|
|
188
|
+
answers: dict[str, Any] | None = None,
|
|
189
|
+
approvals: list[dict[str, Any]] | None = None,
|
|
190
|
+
surface_action: dict[str, Any] | None = None,
|
|
191
|
+
thread_id: str | None = None,
|
|
192
|
+
turn_id: str | None = None,
|
|
193
|
+
globals: dict[str, Any] | None = None,
|
|
194
|
+
view: str | None = None,
|
|
195
|
+
) -> dict[str, Any]:
|
|
196
|
+
fields = {
|
|
197
|
+
"input": input,
|
|
198
|
+
"params": params,
|
|
199
|
+
"answers": answers,
|
|
200
|
+
"approvals": approvals,
|
|
201
|
+
"surface_action": surface_action,
|
|
202
|
+
"thread_id": thread_id,
|
|
203
|
+
"turn_id": turn_id,
|
|
204
|
+
"globals": globals,
|
|
205
|
+
"view": view,
|
|
206
|
+
}
|
|
207
|
+
return {k: v for k, v in fields.items() if v is not None}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def runs_answer_elicitation(
|
|
211
|
+
workflow_id: str, elicitation_id: str, action: str, content: Any, thread_id: str
|
|
212
|
+
) -> Call:
|
|
213
|
+
return Call(
|
|
214
|
+
"POST",
|
|
215
|
+
f"/v1/workflows/{_seg(workflow_id)}/elicitations/{_seg(elicitation_id)}",
|
|
216
|
+
body={"action": action, "content": content, "thread_id": thread_id},
|
|
217
|
+
parse=_model(ElicitationReceipt),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def runs_trigger(workflow_id: str, params: dict[str, Any] | None) -> Call:
|
|
222
|
+
return Call(
|
|
223
|
+
"POST",
|
|
224
|
+
f"/v1/workflows/{_seg(workflow_id)}/trigger",
|
|
225
|
+
body={"params": params or {}},
|
|
226
|
+
parse=_model(RunReceipt),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _run_query(
|
|
231
|
+
limit: int | None, status: str | None, before: str | None
|
|
232
|
+
) -> dict[str, Any]:
|
|
233
|
+
return {"limit": limit, "status": status, "before": before}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def runs_list(
|
|
237
|
+
workflow_id: str, limit: int | None, status: str | None, before: str | None
|
|
238
|
+
) -> Call:
|
|
239
|
+
return Call(
|
|
240
|
+
"GET",
|
|
241
|
+
f"/v1/workflows/{_seg(workflow_id)}/runs",
|
|
242
|
+
params=_run_query(limit, status, before),
|
|
243
|
+
parse=_list(RunSummary),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def runs_get(run_id: str) -> Call:
|
|
248
|
+
return Call("GET", f"/v1/runs/{_seg(run_id)}", parse=_model(RunOut))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def runs_steps(run_id: str) -> Call:
|
|
252
|
+
return Call("GET", f"/v1/runs/{_seg(run_id)}/steps", parse=_list(RunStepOut))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def automation_list(
|
|
256
|
+
workflow_id: str, limit: int | None, status: str | None, before: str | None
|
|
257
|
+
) -> Call:
|
|
258
|
+
return Call(
|
|
259
|
+
"GET",
|
|
260
|
+
f"/v1/workflows/{_seg(workflow_id)}/automation/runs",
|
|
261
|
+
params=_run_query(limit, status, before),
|
|
262
|
+
parse=_list(RunSummary),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def automation_get(workflow_id: str, run_id: str) -> Call:
|
|
267
|
+
return Call(
|
|
268
|
+
"GET",
|
|
269
|
+
f"/v1/workflows/{_seg(workflow_id)}/automation/runs/{_seg(run_id)}",
|
|
270
|
+
parse=_model(RunOut),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# -- sessions ----------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def sessions_create(
|
|
278
|
+
*,
|
|
279
|
+
context: dict[str, Any] | None = None,
|
|
280
|
+
secrets: dict[str, str] | None = None,
|
|
281
|
+
subject: str | None = None,
|
|
282
|
+
tenant_id: str | None = None,
|
|
283
|
+
ttl_seconds: int | None = None,
|
|
284
|
+
max_runs: int | None = None,
|
|
285
|
+
resume_thread_id: str | None = None,
|
|
286
|
+
thread_key: str | None = None,
|
|
287
|
+
) -> Call:
|
|
288
|
+
fields = {
|
|
289
|
+
"context": context,
|
|
290
|
+
"secrets": secrets,
|
|
291
|
+
"subject": subject,
|
|
292
|
+
"tenant_id": tenant_id,
|
|
293
|
+
"ttl_seconds": ttl_seconds,
|
|
294
|
+
"max_runs": max_runs,
|
|
295
|
+
"resume_thread_id": resume_thread_id,
|
|
296
|
+
"thread_key": thread_key,
|
|
297
|
+
}
|
|
298
|
+
body = {k: v for k, v in fields.items() if v is not None}
|
|
299
|
+
return Call("POST", "/v1/sessions", body=body, parse=_model(SessionOut))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def sessions_refresh() -> Call:
|
|
303
|
+
return Call("POST", "/v1/sessions/refresh", parse=_model(SessionOut))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def sessions_revoke(session_id: str) -> Call:
|
|
307
|
+
return Call("DELETE", f"/v1/sessions/{_seg(session_id)}")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# -- files -------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def files_list(workflow_id: str, thread_id: str | None) -> Call:
|
|
314
|
+
return Call(
|
|
315
|
+
"GET",
|
|
316
|
+
f"/v1/workflows/{_seg(workflow_id)}/files",
|
|
317
|
+
params={"thread_id": thread_id},
|
|
318
|
+
parse=_list(WorkflowFile),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def files_upload(
|
|
323
|
+
workflow_id: str, thread_id: str, file: FileInput, name: str | None
|
|
324
|
+
) -> Call:
|
|
325
|
+
return Call(
|
|
326
|
+
"POST",
|
|
327
|
+
f"/v1/workflows/{_seg(workflow_id)}/files",
|
|
328
|
+
files={"file": _upload(file, name)},
|
|
329
|
+
data={"thread_id": thread_id},
|
|
330
|
+
parse=_model(WorkflowFile),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def files_get(file_id: str) -> Call:
|
|
335
|
+
return Call("GET", f"/v1/files/{_seg(file_id)}", parse=_model(WorkflowFile))
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def files_content(file_id: str) -> Call:
|
|
339
|
+
return Call("GET", f"/v1/files/{_seg(file_id)}/content", raw=True)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def files_delete(file_id: str) -> Call:
|
|
343
|
+
return Call("DELETE", f"/v1/files/{_seg(file_id)}")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# -- skills ------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def skills_list(workflow_id: str) -> Call:
|
|
350
|
+
return Call("GET", f"/v1/workflows/{_seg(workflow_id)}/skills", parse=_list(Skill))
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def skills_upload(
|
|
354
|
+
workflow_id: str, file: FileInput, name: str | None, allow_unknown_types: bool
|
|
355
|
+
) -> Call:
|
|
356
|
+
return Call(
|
|
357
|
+
"POST",
|
|
358
|
+
f"/v1/workflows/{_seg(workflow_id)}/skills",
|
|
359
|
+
files={"file": _upload(file, name)},
|
|
360
|
+
data={"allow_unknown_types": "true"} if allow_unknown_types else {},
|
|
361
|
+
parse=_model(SkillUpload),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def skills_get(skill_id: str) -> Call:
|
|
366
|
+
return Call("GET", f"/v1/skills/{_seg(skill_id)}", parse=_model(Skill))
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _skill_files(value: Any) -> dict[str, SkillFile]:
|
|
370
|
+
return {k: SkillFile.model_validate(v) for k, v in value["files"].items()}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def skills_files(skill_id: str) -> Call:
|
|
374
|
+
return Call("GET", f"/v1/skills/{_seg(skill_id)}/files", parse=_skill_files)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def skills_file_content(skill_id: str, path: str) -> Call:
|
|
378
|
+
# ``path`` may hold slashes: the route takes the rest of the path.
|
|
379
|
+
return Call(
|
|
380
|
+
"GET", f"/v1/skills/{_seg(skill_id)}/files/{quote(path, safe='/')}", raw=True
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def skills_delete(skill_id: str) -> Call:
|
|
385
|
+
return Call("DELETE", f"/v1/skills/{_seg(skill_id)}")
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# -- small reads -------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def me_get() -> Call:
|
|
392
|
+
return Call("GET", "/v1/me", parse=_model(Me))
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def models_list() -> Call:
|
|
396
|
+
return Call("GET", "/v1/models", parse=_list(ModelOption))
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def node_types_list() -> Call:
|
|
400
|
+
return Call("GET", "/v1/node-types")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def health_live() -> Call:
|
|
404
|
+
return Call("GET", "/health")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def health_ready() -> Call:
|
|
408
|
+
return Call("GET", "/health/ready", parse=_model(RuntimeReadiness))
|