agents24 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.
agents24/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from .builders import AgentDefinitionBuilder, GraphBuilder
2
+ from .client import Agents24
3
+ from .errors import Agents24SDKError
4
+
5
+ __all__ = [
6
+ "AgentDefinitionBuilder",
7
+ "Agents24",
8
+ "Agents24SDKError",
9
+ "GraphBuilder",
10
+ ]
agents24/artifacts.py ADDED
@@ -0,0 +1,60 @@
1
+ _EXPORTS = []
2
+
3
+
4
+ def _json_schema(value):
5
+ if value is None:
6
+ return {}
7
+ if isinstance(value, dict):
8
+ return value
9
+ if hasattr(value, "model_json_schema"):
10
+ return value.model_json_schema()
11
+ if hasattr(value, "schema"):
12
+ return value.schema()
13
+ raise TypeError("schema must be a JSON Schema dict or a Pydantic model")
14
+
15
+
16
+ def _register(kind, fn=None, **options):
17
+ def decorate(handler):
18
+ name = str(options.get("name") or handler.__name__).strip()
19
+ _EXPORTS.append(
20
+ {
21
+ "name": name,
22
+ "kind": kind,
23
+ "description": options.get("description") or getattr(handler, "__doc__", None),
24
+ "input_schema": _json_schema(options.get("input_schema") or options.get("input")),
25
+ "output_schema": _json_schema(options.get("output_schema") or options.get("output")),
26
+ "config_schema": _json_schema(options.get("config_schema") or options.get("config")),
27
+ "ui_schema": dict(options.get("ui_schema") or options.get("ui") or {}),
28
+ "side_effects": list(options.get("side_effects") or []),
29
+ "execution_mode": options.get("execution_mode") or "interactive",
30
+ "handler_ref": options.get("handler_ref") or handler.__name__,
31
+ }
32
+ )
33
+ return handler
34
+
35
+ if callable(fn):
36
+ return decorate(fn)
37
+ return decorate
38
+
39
+
40
+ def tool(fn=None, **options):
41
+ return _register("tool", fn, **options)
42
+
43
+
44
+ def agent_node(fn=None, **options):
45
+ return _register("agent_node", fn, **options)
46
+
47
+
48
+ def rag_operator(fn=None, **options):
49
+ return _register("rag_operator", fn, **options)
50
+
51
+
52
+ def artifact(*, exports=None):
53
+ for item in exports or []:
54
+ if isinstance(item, dict):
55
+ _EXPORTS.append(item)
56
+ return {"exports": _EXPORTS}
57
+
58
+
59
+ def describe_exports():
60
+ return list(_EXPORTS)
agents24/builders.py ADDED
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping
4
+
5
+
6
+ def _ref_id(ref: str | Mapping[str, Any]) -> str:
7
+ return ref if isinstance(ref, str) else str(ref["id"])
8
+
9
+
10
+ def _normalize_refs(refs: list[str | Mapping[str, Any]] | None) -> list[str]:
11
+ seen: list[str] = []
12
+ for ref in refs or []:
13
+ value = _ref_id(ref)
14
+ if value and value not in seen:
15
+ seen.append(value)
16
+ return seen
17
+
18
+
19
+ class AgentDefinitionBuilder:
20
+ def __init__(self, agents: Any, options: Mapping[str, Any]) -> None:
21
+ self._agents = agents
22
+ self._options = dict(options)
23
+
24
+ def to_graph(self) -> dict[str, Any]:
25
+ model = self._options.get("model")
26
+ config: dict[str, Any] = {
27
+ "instructions": self._options.get("instructions") or "",
28
+ "tools": _normalize_refs(self._options.get("tools")),
29
+ "toolsets": _normalize_refs(self._options.get("toolsets")),
30
+ }
31
+ if model:
32
+ config["model_id"] = _ref_id(model)
33
+ return {
34
+ "spec_version": "4.0",
35
+ "workflow_contract": {"inputs": []},
36
+ "state_contract": {"variables": []},
37
+ "nodes": [
38
+ {"id": "start", "type": "start", "position": {"x": 0, "y": 0}, "config": {}},
39
+ {"id": "agent", "type": "agent", "position": {"x": 260, "y": 0}, "config": config},
40
+ {"id": "end", "type": "end", "position": {"x": 520, "y": 0}, "config": {}},
41
+ ],
42
+ "edges": [
43
+ {"id": "start-agent", "source": "start", "target": "agent", "type": "control"},
44
+ {"id": "agent-end", "source": "agent", "target": "end", "type": "control"},
45
+ ],
46
+ }
47
+
48
+ def to_create_request(self) -> dict[str, Any]:
49
+ knowledge_refs = _normalize_refs(self._options.get("knowledge"))
50
+ memory = dict(self._options.get("memory") or {})
51
+ if knowledge_refs:
52
+ memory.update(
53
+ {
54
+ "long_term_enabled": True,
55
+ "long_term_index_id": knowledge_refs[0],
56
+ "knowledge_refs": knowledge_refs,
57
+ }
58
+ )
59
+ return {
60
+ "name": self._options["name"],
61
+ "description": self._options.get("description"),
62
+ "graph_definition": self.to_graph(),
63
+ "memory_config": memory,
64
+ "execution_constraints": dict(self._options.get("execution") or {}),
65
+ }
66
+
67
+ def create(self, options: dict[str, Any] | None = None) -> dict[str, Any]:
68
+ return self._agents.create(self.to_create_request(), options=options)
69
+
70
+
71
+ class GraphBuilder:
72
+ def __init__(self) -> None:
73
+ self._nodes: dict[str, dict[str, Any]] = {}
74
+ self._edges: list[dict[str, Any]] = []
75
+
76
+ def node(
77
+ self,
78
+ type_: str,
79
+ config: Mapping[str, Any] | None = None,
80
+ *,
81
+ id: str | None = None,
82
+ x: int | float | None = None,
83
+ y: int | float | None = None,
84
+ label: str | None = None,
85
+ ) -> dict[str, Any]:
86
+ node_id = id or f"{type_}_{len(self._nodes) + 1}"
87
+ if node_id in self._nodes:
88
+ raise ValueError(f"Duplicate graph node id: {node_id}")
89
+ node = {
90
+ "id": node_id,
91
+ "type": type_,
92
+ "position": {"x": x if x is not None else len(self._nodes) * 260, "y": y if y is not None else 0},
93
+ "config": dict(config or {}),
94
+ }
95
+ if label:
96
+ node["label"] = label
97
+ self._nodes[node_id] = node
98
+ return node
99
+
100
+ def connect(
101
+ self,
102
+ source: str | Mapping[str, Any],
103
+ target: str | Mapping[str, Any],
104
+ *,
105
+ id: str | None = None,
106
+ source_handle: str | None = None,
107
+ target_handle: str | None = None,
108
+ ) -> "GraphBuilder":
109
+ source_id = source if isinstance(source, str) else str(source["id"])
110
+ target_id = target if isinstance(target, str) else str(target["id"])
111
+ if source_id not in self._nodes:
112
+ raise ValueError(f"Unknown source node id: {source_id}")
113
+ if target_id not in self._nodes:
114
+ raise ValueError(f"Unknown target node id: {target_id}")
115
+ edge = {"id": id or f"{source_id}-{target_id}", "source": source_id, "target": target_id, "type": "control"}
116
+ if source_handle:
117
+ edge["source_handle"] = source_handle
118
+ if target_handle:
119
+ edge["target_handle"] = target_handle
120
+ self._edges.append(edge)
121
+ return self
122
+
123
+ def to_graph(self) -> dict[str, Any]:
124
+ return {
125
+ "spec_version": "4.0",
126
+ "workflow_contract": {"inputs": []},
127
+ "state_contract": {"variables": []},
128
+ "nodes": list(self._nodes.values()),
129
+ "edges": list(self._edges),
130
+ }
agents24/client.py ADDED
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ import requests
7
+
8
+ from .builders import AgentDefinitionBuilder, GraphBuilder
9
+ from .generated.agents import AgentsNamespace
10
+ from .generated.embed import EmbedNamespace
11
+ from .generated.runs import GeneratedRunsNamespace
12
+ from .http import Agents24HttpClient
13
+
14
+
15
+ class RunsNamespace(GeneratedRunsNamespace):
16
+ def get_trace(self, run_id: str) -> dict[str, Any]:
17
+ return {
18
+ "run": self.get(run_id),
19
+ "tree": self.get_tree(run_id),
20
+ "events": self.get_events(run_id),
21
+ "context": self.get_context(run_id),
22
+ }
23
+
24
+
25
+ class Agents24:
26
+ def __init__(
27
+ self,
28
+ *,
29
+ base_url: str,
30
+ api_key: str,
31
+ organization_id: str | None = None,
32
+ project_id: str | None = None,
33
+ timeout: float = 30.0,
34
+ session: requests.Session | None = None,
35
+ ) -> None:
36
+ self._http = Agents24HttpClient(
37
+ base_url=base_url,
38
+ api_key=api_key,
39
+ organization_id=organization_id,
40
+ project_id=project_id,
41
+ timeout=timeout,
42
+ session=session,
43
+ )
44
+ self.agents = AgentsNamespace(self._http)
45
+ self.embed = EmbedNamespace(self._http)
46
+ self.runs = RunsNamespace(self._http)
47
+
48
+ def agent(self, options: Mapping[str, Any]) -> AgentDefinitionBuilder:
49
+ return AgentDefinitionBuilder(self.agents, options)
50
+
51
+ def graph(self) -> GraphBuilder:
52
+ return GraphBuilder()
agents24/errors.py ADDED
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class Agents24SDKError(Exception):
7
+ def __init__(
8
+ self,
9
+ message: str,
10
+ *,
11
+ kind: str,
12
+ status: int | None = None,
13
+ details: Any = None,
14
+ ) -> None:
15
+ super().__init__(message)
16
+ self.message = message
17
+ self.kind = kind
18
+ self.status = status
19
+ self.details = details
@@ -0,0 +1,3 @@
1
+ from .manifest import SDK_OPERATION_IDS
2
+
3
+ __all__ = ["SDK_OPERATION_IDS"]
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Mapping, Sequence
4
+
5
+ from ..errors import Agents24SDKError
6
+ from ..http import Agents24HttpClient
7
+ from ..types import AgentListResponse, AgentResponse, AttachmentUploadResult, RequestOptions, RunCancelResult, RuntimeEvent, StartRunResult, StreamResult
8
+
9
+ class AgentsNamespace:
10
+ def __init__(self, http: Agents24HttpClient) -> None:
11
+ self._http = http
12
+
13
+ def list(self, status: str | None = None, skip: int = 0, limit: int = 20, view: str = "summary") -> AgentListResponse:
14
+ return self._http.request_json("GET", "/agents", params={
15
+ "status": status,
16
+ "skip": skip if skip is not None else 0,
17
+ "limit": limit if limit is not None else 20,
18
+ "view": view if view is not None else "summary",
19
+ }, json_body=None, options=None, mutation=False)
20
+
21
+ def get(self, agent_id: str) -> AgentResponse:
22
+ return self._http.request_json("GET", f"/agents/{agent_id}", params=None, json_body=None, options=None, mutation=False)
23
+
24
+ def create(self, request: Mapping[str, Any], options: RequestOptions | None = None) -> AgentResponse:
25
+ return self._http.request_json("POST", "/agents", params=None, json_body=request, options=options, mutation=True)
26
+
27
+ def update(self, agent_id: str, request: Mapping[str, Any], options: RequestOptions | None = None) -> AgentResponse:
28
+ return self._http.request_json("PATCH", f"/agents/{agent_id}", params=None, json_body=request, options=options, mutation=True)
29
+
30
+ def update_graph(self, agent_id: str, graph: Mapping[str, Any], options: RequestOptions | None = None) -> AgentResponse:
31
+ return self._http.request_json("PUT", f"/agents/{agent_id}/graph", params=None, json_body=graph, options=options, mutation=True)
32
+
33
+ def delete(self, agent_id: str, options: RequestOptions | None = None) -> Any:
34
+ return self._http.request_json("DELETE", f"/agents/{agent_id}", params=None, json_body=None, options=options, mutation=True)
35
+
36
+ def catalog(self) -> Any:
37
+ return self._http.request_json("GET", "/agents/nodes/catalog", params=None, json_body=None, options=None, mutation=False)
38
+
39
+ def schema(self, node_types: Any) -> Any:
40
+ return self._http.request_json("POST", "/agents/nodes/schema", params=None, json_body={
41
+ "node_types": node_types,
42
+ }, options=None, mutation=False)
43
+
44
+ def validate(self, agent_id: str) -> Any:
45
+ return self._http.request_json("POST", f"/agents/{agent_id}/validate", params=None, json_body={}, options=None, mutation=False)
46
+
47
+ def publish(self, agent_id: str, options: RequestOptions | None = None) -> AgentResponse:
48
+ return self._http.request_json("POST", f"/agents/{agent_id}/publish", params=None, json_body={}, options=options, mutation=True)
49
+
50
+ def start_run(self, agent_id: str, payload: Mapping[str, Any], options: RequestOptions | None = None) -> StartRunResult:
51
+ return self._http.request_json("POST", f"/agents/{agent_id}/run", params=None, json_body=payload, options=options, mutation=True)
52
+
53
+ def stream(self, agent_id: str, payload: Mapping[str, Any], on_event: Callable[[RuntimeEvent], None] | None = None, mode: str | None = None) -> StreamResult:
54
+ return self._http.request_stream("POST", f"/agents/{agent_id}/stream", json_body=dict(payload), params={"mode": mode}, on_event=on_event)
55
+
56
+ def resume_run(self, run_id: str, payload: Mapping[str, Any], options: RequestOptions | None = None) -> Any:
57
+ return self._http.request_json("POST", f"/agents/runs/{run_id}/resume", params=None, json_body=payload, options=options, mutation=True)
58
+
59
+ def cancel_run(self, run_id: str, assistant_output_text: str | None = None) -> RunCancelResult:
60
+ return self._http.request_json("POST", f"/agents/runs/{run_id}/cancel", params=None, json_body={
61
+ "assistant_output_text": assistant_output_text,
62
+ }, options=None, mutation=True)
63
+
64
+ def upload_attachments(self, agent_id: str, files: Sequence[Any], thread_id: str | None = None) -> AttachmentUploadResult:
65
+ return self._http.request_multipart("POST", f"/agents/{agent_id}/attachments/upload", data={
66
+ "thread_id": thread_id,
67
+ }, files=files)
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Mapping, Sequence
4
+
5
+ from ..errors import Agents24SDKError
6
+ from ..http import Agents24HttpClient
7
+ from ..types import AttachmentUploadResult, RequestOptions, RunCancelResult, RunContext, RuntimeEvent, StreamResult, ThreadDeleteResult, ThreadDetail, ThreadsResponse
8
+
9
+ class EmbedNamespace:
10
+ def __init__(self, http: Agents24HttpClient) -> None:
11
+ self._http = http
12
+
13
+ def stream_agent(self, agent_id: str, payload: Mapping[str, Any], on_event: Callable[[RuntimeEvent], None] | None = None) -> StreamResult:
14
+ return self._http.request_stream("POST", f"/public/embed/agents/{agent_id}/chat/stream", json_body=dict(payload), params=None, on_event=on_event)
15
+
16
+ def list_agent_threads(self, agent_id: str, external_user_id: Any = None, external_session_id: Any = None, skip: Any = 0, limit: Any = 20) -> ThreadsResponse:
17
+ return self._http.request_json("GET", f"/public/embed/agents/{agent_id}/threads", params={
18
+ "external_user_id": external_user_id,
19
+ "external_session_id": external_session_id,
20
+ "skip": skip if skip is not None else 0,
21
+ "limit": limit if limit is not None else 20,
22
+ }, json_body=None, options=None, mutation=False)
23
+
24
+ def get_agent_thread(self, agent_id: str, thread_id: str, external_user_id: Any = None, external_session_id: Any = None, before_turn_index: Any = None, limit: Any = None, include_subthreads: Any = None, subthread_depth: Any = None, subthread_turn_limit: Any = None, subthread_child_limit: Any = None) -> ThreadDetail:
25
+ return self._http.request_json("GET", f"/public/embed/agents/{agent_id}/threads/{thread_id}", params={
26
+ "external_user_id": external_user_id,
27
+ "external_session_id": external_session_id,
28
+ "before_turn_index": before_turn_index,
29
+ "limit": limit,
30
+ "include_subthreads": include_subthreads,
31
+ "subthread_depth": subthread_depth,
32
+ "subthread_turn_limit": subthread_turn_limit,
33
+ "subthread_child_limit": subthread_child_limit,
34
+ }, json_body=None, options=None, mutation=False)
35
+
36
+ def delete_agent_thread(self, agent_id: str, thread_id: str, external_user_id: Any = None, external_session_id: Any = None, options: RequestOptions | None = None) -> ThreadDeleteResult:
37
+ return self._http.request_json("DELETE", f"/public/embed/agents/{agent_id}/threads/{thread_id}", params={
38
+ "external_user_id": external_user_id,
39
+ "external_session_id": external_session_id,
40
+ }, json_body=None, options=options, mutation=True)
41
+
42
+ def get_agent_run_context(self, agent_id: str, run_id: str, external_user_id: Any = None, external_session_id: Any = None) -> RunContext:
43
+ return self._http.request_json("GET", f"/public/embed/agents/{agent_id}/runs/{run_id}/context", params={
44
+ "external_user_id": external_user_id,
45
+ "external_session_id": external_session_id,
46
+ }, json_body=None, options=None, mutation=False)
47
+
48
+ def cancel_agent_run(self, agent_id: str, run_id: str, external_user_id: Any = None, external_session_id: Any = None, assistant_output_text: str | None = None) -> RunCancelResult:
49
+ return self._http.request_json("POST", f"/public/embed/agents/{agent_id}/runs/{run_id}/cancel", params={
50
+ "external_user_id": external_user_id,
51
+ "external_session_id": external_session_id,
52
+ }, json_body={
53
+ "assistant_output_text": assistant_output_text,
54
+ }, options=None, mutation=True)
55
+
56
+ def upload_agent_attachments(self, agent_id: str, files: Sequence[Any], external_user_id: str | None = None, external_session_id: str | None = None, thread_id: str | None = None) -> AttachmentUploadResult:
57
+ if not external_user_id:
58
+ raise Agents24SDKError("upload_agent_attachments requires external_user_id.", kind="protocol")
59
+ return self._http.request_multipart("POST", f"/public/embed/agents/{agent_id}/attachments/upload", data={
60
+ "external_user_id": external_user_id,
61
+ "external_session_id": external_session_id,
62
+ "thread_id": thread_id,
63
+ }, files=files)
@@ -0,0 +1,28 @@
1
+ SDK_OPERATION_IDS = [
2
+ "agents.cancel_run",
3
+ "agents.catalog",
4
+ "agents.create",
5
+ "agents.delete",
6
+ "agents.get",
7
+ "agents.list",
8
+ "agents.publish",
9
+ "agents.resume_run",
10
+ "agents.schema",
11
+ "agents.start_run",
12
+ "agents.stream",
13
+ "agents.update",
14
+ "agents.update_graph",
15
+ "agents.upload_attachments",
16
+ "agents.validate",
17
+ "embed.cancel_agent_run",
18
+ "embed.delete_agent_thread",
19
+ "embed.get_agent_run_context",
20
+ "embed.get_agent_thread",
21
+ "embed.list_agent_threads",
22
+ "embed.stream_agent",
23
+ "embed.upload_agent_attachments",
24
+ "runs.get",
25
+ "runs.get_context",
26
+ "runs.get_events",
27
+ "runs.get_tree"
28
+ ]
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Mapping, Sequence
4
+
5
+ from ..errors import Agents24SDKError
6
+ from ..http import Agents24HttpClient
7
+ from ..types import RequestOptions, RunContext, RunEventsResponse, RunStatus
8
+
9
+ class GeneratedRunsNamespace:
10
+ def __init__(self, http: Agents24HttpClient) -> None:
11
+ self._http = http
12
+
13
+ def get(self, run_id: str, include_tree: bool | None = None) -> RunStatus:
14
+ return self._http.request_json("GET", f"/agents/runs/{run_id}", params={
15
+ "include_tree": include_tree,
16
+ }, json_body=None, options=None, mutation=False)
17
+
18
+ def get_tree(self, run_id: str) -> Any:
19
+ return self._http.request_json("GET", f"/agents/runs/{run_id}/tree", params=None, json_body=None, options=None, mutation=False)
20
+
21
+ def get_events(self, run_id: str, after_sequence: int | None = None, limit: int | None = None) -> RunEventsResponse:
22
+ return self._http.request_json("GET", f"/agents/runs/{run_id}/events", params={
23
+ "after_sequence": after_sequence,
24
+ "limit": limit,
25
+ }, json_body=None, options=None, mutation=False)
26
+
27
+ def get_context(self, run_id: str) -> RunContext:
28
+ return self._http.request_json("GET", f"/agents/runs/{run_id}/context", params=None, json_body=None, options=None, mutation=False)
agents24/http.py ADDED
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import uuid
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Any, Callable
7
+
8
+ import requests
9
+
10
+ from .errors import Agents24SDKError
11
+ from .sse import consume_event_stream
12
+ from .types import RequestOptions, RuntimeEvent, StreamResult
13
+
14
+
15
+ class Agents24HttpClient:
16
+ def __init__(
17
+ self,
18
+ *,
19
+ base_url: str,
20
+ api_key: str,
21
+ organization_id: str | None = None,
22
+ project_id: str | None = None,
23
+ timeout: float = 30.0,
24
+ session: requests.Session | None = None,
25
+ ) -> None:
26
+ self.base_url = self._normalize_base_url(base_url)
27
+ self.api_key = str(api_key or "").strip()
28
+ if not self.api_key:
29
+ raise Agents24SDKError("Agents24 requires a non-empty api_key.", kind="protocol")
30
+ self.organization_id = str(organization_id) if organization_id else None
31
+ self.project_id = str(project_id) if project_id else None
32
+ self.timeout = timeout
33
+ self.session = session or requests.Session()
34
+
35
+ @staticmethod
36
+ def _normalize_base_url(base_url: str) -> str:
37
+ normalized = str(base_url or "").strip().rstrip("/")
38
+ if not normalized or "://" not in normalized:
39
+ raise Agents24SDKError("Agents24 requires a valid base_url.", kind="protocol")
40
+ return normalized
41
+
42
+ def _headers(
43
+ self,
44
+ *,
45
+ accept: str = "application/json",
46
+ content_type: str | None = "application/json",
47
+ mutation: bool = False,
48
+ options: RequestOptions | None = None,
49
+ ) -> dict[str, str]:
50
+ headers = {
51
+ "Authorization": f"Bearer {self.api_key}",
52
+ "Accept": accept,
53
+ "X-SDK-Contract": "1",
54
+ }
55
+ if content_type:
56
+ headers["Content-Type"] = content_type
57
+ if self.organization_id:
58
+ headers["X-Organization-ID"] = self.organization_id
59
+ if self.project_id:
60
+ headers["X-Project-ID"] = self.project_id
61
+ if mutation:
62
+ headers["X-Idempotency-Key"] = (options or {}).get("idempotency_key") or str(uuid.uuid4())
63
+ metadata = (options or {}).get("request_metadata")
64
+ if metadata:
65
+ headers["X-Request-Metadata"] = json.dumps(metadata, separators=(",", ":"))
66
+ return headers
67
+
68
+ @staticmethod
69
+ def _params(params: Mapping[str, Any] | None, options: RequestOptions | None = None) -> dict[str, Any]:
70
+ merged = {k: v for k, v in dict(params or {}).items() if v not in (None, "")}
71
+ if options and "dry_run" in options:
72
+ merged["dry_run"] = bool(options["dry_run"])
73
+ if options and "validate_only" in options:
74
+ merged["validate_only"] = bool(options["validate_only"])
75
+ return merged
76
+
77
+ def request_json(
78
+ self,
79
+ method: str,
80
+ path: str,
81
+ *,
82
+ params: Mapping[str, Any] | None = None,
83
+ json_body: Any = None,
84
+ options: RequestOptions | None = None,
85
+ mutation: bool = False,
86
+ ) -> Any:
87
+ try:
88
+ response = self.session.request(
89
+ method=method,
90
+ url=f"{self.base_url}{path}",
91
+ params=self._params(params, options),
92
+ json=json_body,
93
+ headers=self._headers(mutation=mutation, options=options),
94
+ timeout=self.timeout,
95
+ )
96
+ except requests.RequestException as exc:
97
+ raise Agents24SDKError("Failed to connect to the Agents24 API.", kind="network") from exc
98
+ self._raise_for_status(response)
99
+ if response.text:
100
+ return response.json()
101
+ return None
102
+
103
+ def request_multipart(
104
+ self,
105
+ method: str,
106
+ path: str,
107
+ *,
108
+ data: Mapping[str, Any],
109
+ files: Sequence[Any],
110
+ ) -> Any:
111
+ form_data = {k: v for k, v in dict(data or {}).items() if v not in (None, "")}
112
+ try:
113
+ response = self.session.request(
114
+ method=method,
115
+ url=f"{self.base_url}{path}",
116
+ data=form_data,
117
+ files={"files": files} if files and not isinstance(files[0], tuple) else list(files),
118
+ headers=self._headers(content_type=None),
119
+ timeout=self.timeout,
120
+ )
121
+ except requests.RequestException as exc:
122
+ raise Agents24SDKError("Failed to connect to the Agents24 attachment endpoint.", kind="network") from exc
123
+ self._raise_for_status(response)
124
+ return response.json()
125
+
126
+ def request_stream(
127
+ self,
128
+ method: str,
129
+ path: str,
130
+ *,
131
+ json_body: Mapping[str, Any],
132
+ params: Mapping[str, Any] | None,
133
+ on_event: Callable[[RuntimeEvent], None] | None,
134
+ ) -> StreamResult:
135
+ try:
136
+ response = self.session.request(
137
+ method=method,
138
+ url=f"{self.base_url}{path}",
139
+ params=self._params(params),
140
+ json=dict(json_body),
141
+ headers=self._headers(accept="text/event-stream"),
142
+ timeout=self.timeout,
143
+ stream=True,
144
+ )
145
+ except requests.RequestException as exc:
146
+ raise Agents24SDKError("Failed to connect to the Agents24 stream endpoint.", kind="network") from exc
147
+ self._raise_for_status(response)
148
+ observed_run_id = response.headers.get("X-Run-ID")
149
+ for event in consume_event_stream(response.iter_lines(decode_unicode=True)):
150
+ observed_run_id = observed_run_id or event["run_id"]
151
+ if on_event:
152
+ on_event(event)
153
+ return {
154
+ "thread_id": response.headers.get("X-Thread-ID"),
155
+ "run_id": observed_run_id,
156
+ }
157
+
158
+ @staticmethod
159
+ def _raise_for_status(response: requests.Response) -> None:
160
+ if response.status_code < 400:
161
+ return
162
+ details: Any
163
+ try:
164
+ details = response.json()
165
+ except Exception:
166
+ details = response.text
167
+ message = response.reason or "Request failed"
168
+ if isinstance(details, dict):
169
+ detail = details.get("detail")
170
+ if isinstance(detail, str):
171
+ message = detail
172
+ elif isinstance(detail, dict) and isinstance(detail.get("message"), str):
173
+ message = detail["message"]
174
+ elif isinstance(details.get("message"), str):
175
+ message = details["message"]
176
+ elif isinstance(details, str) and details:
177
+ message = details
178
+ raise Agents24SDKError(message, kind="http", status=response.status_code, details=details)
agents24/sse.py ADDED
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections.abc import Iterable
5
+
6
+ from .errors import Agents24SDKError
7
+ from .types import RuntimeEvent
8
+
9
+
10
+ def _parse_sse_block(block: str) -> str | None:
11
+ data_lines: list[str] = []
12
+ for raw_line in block.split("\n"):
13
+ line = raw_line.rstrip("\r")
14
+ if not line or line.startswith(":"):
15
+ continue
16
+ field, _, value = line.partition(":")
17
+ if value.startswith(" "):
18
+ value = value[1:]
19
+ if field == "data":
20
+ data_lines.append(value)
21
+ return "\n".join(data_lines) if data_lines else None
22
+
23
+
24
+ def parse_runtime_event(raw_payload: str) -> RuntimeEvent:
25
+ try:
26
+ parsed = json.loads(raw_payload)
27
+ except Exception as exc:
28
+ raise Agents24SDKError("Malformed SSE event: invalid JSON payload.", kind="protocol", details=raw_payload) from exc
29
+ if not isinstance(parsed, dict) or parsed.get("version") != "run-stream.v2":
30
+ raise Agents24SDKError("SSE protocol mismatch: expected run-stream.v2 event envelopes.", kind="protocol", details=parsed)
31
+ for field in ("seq", "ts", "event", "run_id", "stage"):
32
+ if field not in parsed:
33
+ raise Agents24SDKError("Malformed SSE event: missing required envelope fields.", kind="protocol", details=parsed)
34
+ payload = parsed.get("payload") or {}
35
+ diagnostics = parsed.get("diagnostics") or []
36
+ if not isinstance(payload, dict) or not isinstance(diagnostics, list):
37
+ raise Agents24SDKError("Malformed SSE event payload.", kind="protocol", details=parsed)
38
+ parsed["payload"] = payload
39
+ parsed["diagnostics"] = diagnostics
40
+ return parsed # type: ignore[return-value]
41
+
42
+
43
+ def consume_event_stream(lines: Iterable[str | bytes]) -> Iterable[RuntimeEvent]:
44
+ buffer: list[str] = []
45
+ for item in lines:
46
+ line = item.decode("utf-8") if isinstance(item, bytes) else str(item)
47
+ if line == "":
48
+ payload = _parse_sse_block("\n".join(buffer))
49
+ buffer = []
50
+ if payload:
51
+ yield parse_runtime_event(payload)
52
+ else:
53
+ buffer.append(line)
54
+ if buffer:
55
+ payload = _parse_sse_block("\n".join(buffer))
56
+ if payload:
57
+ yield parse_runtime_event(payload)
agents24/types.py ADDED
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, NotRequired, TypeAlias, TypedDict
4
+
5
+ JSONDict: TypeAlias = dict[str, Any]
6
+
7
+
8
+ class RequestOptions(TypedDict, total=False):
9
+ idempotency_key: str
10
+ dry_run: bool
11
+ validate_only: bool
12
+ request_metadata: dict[str, Any]
13
+
14
+
15
+ class RuntimeEvent(TypedDict):
16
+ version: str
17
+ seq: int | float
18
+ ts: str
19
+ event: str
20
+ run_id: str
21
+ stage: str
22
+ payload: dict[str, Any]
23
+ diagnostics: list[dict[str, Any]]
24
+
25
+
26
+ class StreamResult(TypedDict):
27
+ thread_id: str | None
28
+ run_id: str | None
29
+
30
+
31
+ class AttachmentUploadResult(TypedDict):
32
+ items: list[dict[str, Any]]
33
+
34
+
35
+ class AgentCreateRequest(TypedDict):
36
+ name: str
37
+ graph_definition: dict[str, Any]
38
+ description: NotRequired[str | None]
39
+ memory_config: NotRequired[dict[str, Any]]
40
+ execution_constraints: NotRequired[dict[str, Any]]
41
+
42
+
43
+ AgentUpdateRequest: TypeAlias = dict[str, Any]
44
+ AgentResponse: TypeAlias = dict[str, Any]
45
+ AgentListResponse: TypeAlias = dict[str, Any]
46
+ AgentStreamRequest: TypeAlias = dict[str, Any]
47
+ EmbedStreamRequest: TypeAlias = dict[str, Any]
48
+ StartRunResult: TypeAlias = dict[str, Any]
49
+ RunCancelResult: TypeAlias = dict[str, Any]
50
+ RunContext: TypeAlias = dict[str, Any]
51
+ RunEventsResponse: TypeAlias = dict[str, Any]
52
+ RunStatus: TypeAlias = dict[str, Any]
53
+ RunTrace: TypeAlias = dict[str, Any]
54
+ ThreadDeleteResult: TypeAlias = dict[str, Any]
55
+ ThreadDetail: TypeAlias = dict[str, Any]
56
+ ThreadsResponse: TypeAlias = dict[str, Any]
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: agents24
3
+ Version: 0.1.0
4
+ Summary: Unified Python SDK for Agents24 agent runtime and control APIs.
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: requests>=2.31.0
9
+
10
+ # agents24
11
+
12
+ Last Updated: 2026-05-25
13
+
14
+ Unified Python SDK for Agents24.
15
+
16
+ Endpoint methods are generated from `packages/agents24-sdk-contract/agents24.sdk.json`.
17
+ Do not edit files under `agents24/generated/` directly. For SDK maintenance, see `docs/references/agents24_sdk_development_guide.md`.
18
+
19
+ ```python
20
+ from agents24 import Agents24
21
+
22
+ client = Agents24(
23
+ base_url="http://localhost:8000",
24
+ api_key="tpk_...",
25
+ organization_id="org-id",
26
+ project_id="project-id",
27
+ )
28
+
29
+ agent = client.agent({
30
+ "name": "Support Agent",
31
+ "instructions": "Answer briefly.",
32
+ }).create()
33
+
34
+ client.agents.publish(agent["id"])
35
+ ```
36
+
37
+ ## Artifact Authoring
38
+
39
+ Artifact code imports lightweight helpers from `agents24.artifacts`:
40
+
41
+ ```python
42
+ from agents24.artifacts import tool
43
+
44
+
45
+ @tool(
46
+ name="echo",
47
+ input_schema={
48
+ "type": "object",
49
+ "properties": {"text": {"type": "string"}},
50
+ "required": ["text"],
51
+ },
52
+ output_schema={
53
+ "type": "object",
54
+ "properties": {"text": {"type": "string"}},
55
+ "required": ["text"],
56
+ },
57
+ )
58
+ async def echo(input, config, context):
59
+ return {"text": input["text"]}
60
+ ```
61
+
62
+ The `agents24.artifacts` module is transport-free and safe for artifact runtime code.
63
+
64
+ ## Development
65
+
66
+ ```bash
67
+ pnpm run generate:sdk
68
+ pnpm run check:sdk-contract
69
+ pnpm run test:agents24-python
70
+ ```
71
+
72
+ Generated endpoint methods must stay in parity with the TypeScript `@agents24/sdk` package.
@@ -0,0 +1,17 @@
1
+ agents24/__init__.py,sha256=VQohNGpp9E2BzwKSx4iS1TvRHwKFeI6LH2OKgvR6oag,230
2
+ agents24/artifacts.py,sha256=kRz-1QnLeTK6CjFuGrI27mDpMv9JT4KDNInCvRszhIc,1908
3
+ agents24/builders.py,sha256=3Wl1V0jg72OAWhRTIge5TCIlyK5cOj2nKT-5bwfC2Ro,4802
4
+ agents24/client.py,sha256=5vq4yHmddXhfZoqdNFA8ywXq_G5mfkI8k-Ssmi5i3xw,1537
5
+ agents24/errors.py,sha256=ZRjdq0_-NMeqJBzVkMgD1ARCAmxV4xifnhFcr5tOK30,409
6
+ agents24/http.py,sha256=xi_uvR5GCzEdxkGFJQ-wLkWyFQIrexcwcIEzsLEt7rU,6642
7
+ agents24/sse.py,sha256=Omx-aeBgwapiPtyM9FV4hMY5Eg2p0x0pe4EehUHz69U,2260
8
+ agents24/types.py,sha256=5hGgIYPLEMbFzybSiB8ThXPJIkZTehSusy2t7R7hR4k,1479
9
+ agents24/generated/__init__.py,sha256=J35rhjvtYFSMtERrp9OTveWpIBEBGJByBgZ7POBg21I,73
10
+ agents24/generated/agents.py,sha256=rup_FcbVmKWFRMRVbMmod-e2Mw9wmCIFlRLt9jTyxoQ,4245
11
+ agents24/generated/embed.py,sha256=ndWnw-CbL6GFTM0j0kbreug2FDy_7vWOKE9pqiM7a_4,4217
12
+ agents24/generated/manifest.py,sha256=BE4EiUMeylto6wBICXgAs2-kF2Pe2dNETp9SbqY6Kfc,622
13
+ agents24/generated/runs.py,sha256=ponpY4VEl50-aBeByYVUCk-qiVVlS8PWGP40skfrcQI,1333
14
+ agents24-0.1.0.dist-info/METADATA,sha256=k4fSmod7QPCLzYVwb3vSm1na6zyzSPGntbDUEhw9F70,1661
15
+ agents24-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
16
+ agents24-0.1.0.dist-info/top_level.txt,sha256=CVcfR_w_8svOxK5tZP1uayAtxTPL2RaVYpqoPXP4KuY,9
17
+ agents24-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ agents24