bevor-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.
Files changed (107) hide show
  1. bevor_sdk/__init__.py +6 -0
  2. bevor_sdk/_async/__init__.py +0 -0
  3. bevor_sdk/_async/client.py +97 -0
  4. bevor_sdk/_async/graph.py +76 -0
  5. bevor_sdk/_async/resources/__init__.py +24 -0
  6. bevor_sdk/_async/resources/_modules/__init__.py +4 -0
  7. bevor_sdk/_async/resources/_modules/events.py +88 -0
  8. bevor_sdk/_async/resources/_modules/pagination.py +50 -0
  9. bevor_sdk/_async/resources/chat/__init__.py +3 -0
  10. bevor_sdk/_async/resources/chat/chats.py +253 -0
  11. bevor_sdk/_async/resources/graph/__init__.py +5 -0
  12. bevor_sdk/_async/resources/graph/codes.py +365 -0
  13. bevor_sdk/_async/resources/graph/files.py +109 -0
  14. bevor_sdk/_async/resources/graph/nodes.py +207 -0
  15. bevor_sdk/_async/resources/manage/__init__.py +4 -0
  16. bevor_sdk/_async/resources/manage/projects.py +34 -0
  17. bevor_sdk/_async/resources/manage/users.py +103 -0
  18. bevor_sdk/_async/resources/security/__init__.py +13 -0
  19. bevor_sdk/_async/resources/security/analyses.py +697 -0
  20. bevor_sdk/_async/resources/security/findings.py +287 -0
  21. bevor_sdk/_async/resources/security/summary.py +107 -0
  22. bevor_sdk/_async/resources/security/transitions.py +104 -0
  23. bevor_sdk/_async/resources/security/versions.py +124 -0
  24. bevor_sdk/_shared/__init__.py +7 -0
  25. bevor_sdk/_shared/call_node.py +360 -0
  26. bevor_sdk/_shared/client.py +119 -0
  27. bevor_sdk/_shared/cursor.py +168 -0
  28. bevor_sdk/_shared/events.py +50 -0
  29. bevor_sdk/_shared/graph.py +335 -0
  30. bevor_sdk/_sync/__init__.py +0 -0
  31. bevor_sdk/_sync/client.py +97 -0
  32. bevor_sdk/_sync/graph.py +75 -0
  33. bevor_sdk/_sync/resources/__init__.py +24 -0
  34. bevor_sdk/_sync/resources/_modules/__init__.py +4 -0
  35. bevor_sdk/_sync/resources/_modules/events.py +88 -0
  36. bevor_sdk/_sync/resources/_modules/pagination.py +50 -0
  37. bevor_sdk/_sync/resources/chat/__init__.py +3 -0
  38. bevor_sdk/_sync/resources/chat/chats.py +253 -0
  39. bevor_sdk/_sync/resources/graph/__init__.py +5 -0
  40. bevor_sdk/_sync/resources/graph/codes.py +365 -0
  41. bevor_sdk/_sync/resources/graph/files.py +109 -0
  42. bevor_sdk/_sync/resources/graph/nodes.py +207 -0
  43. bevor_sdk/_sync/resources/manage/__init__.py +4 -0
  44. bevor_sdk/_sync/resources/manage/me.py +32 -0
  45. bevor_sdk/_sync/resources/manage/projects.py +34 -0
  46. bevor_sdk/_sync/resources/manage/users.py +103 -0
  47. bevor_sdk/_sync/resources/security/__init__.py +13 -0
  48. bevor_sdk/_sync/resources/security/analyses.py +683 -0
  49. bevor_sdk/_sync/resources/security/findings.py +287 -0
  50. bevor_sdk/_sync/resources/security/summary.py +107 -0
  51. bevor_sdk/_sync/resources/security/transitions.py +104 -0
  52. bevor_sdk/_sync/resources/security/versions.py +124 -0
  53. bevor_sdk/exceptions/__init__.py +173 -0
  54. bevor_sdk/helpers/__init__.py +0 -0
  55. bevor_sdk/helpers/call_chain/__init__.py +0 -0
  56. bevor_sdk/helpers/call_chain/enrich_impl.py +108 -0
  57. bevor_sdk/helpers/converted_schema/__init__.py +0 -0
  58. bevor_sdk/helpers/converted_schema/sarif.py +2443 -0
  59. bevor_sdk/helpers/converters/__init__.py +0 -0
  60. bevor_sdk/helpers/converters/sarif.py +303 -0
  61. bevor_sdk/helpers/json_schema/sarif.json +3315 -0
  62. bevor_sdk/helpers/source_fingerprint.py +100 -0
  63. bevor_sdk/helpers/static_analysis.py +119 -0
  64. bevor_sdk/types/__init__.py +3 -0
  65. bevor_sdk/types/_shared.py +14 -0
  66. bevor_sdk/types/dataclasses/__init__.py +17 -0
  67. bevor_sdk/types/dataclasses/graph.py +161 -0
  68. bevor_sdk/types/literals/__init__.py +4 -0
  69. bevor_sdk/types/literals/chat.py +5 -0
  70. bevor_sdk/types/literals/graph.py +61 -0
  71. bevor_sdk/types/literals/manage.py +20 -0
  72. bevor_sdk/types/literals/node_types.py +294 -0
  73. bevor_sdk/types/literals/security.py +38 -0
  74. bevor_sdk/types/literals/static_analysis.py +112 -0
  75. bevor_sdk/types/requests/__init__.py +3 -0
  76. bevor_sdk/types/requests/_shared.py +14 -0
  77. bevor_sdk/types/requests/chat/__init__.py +19 -0
  78. bevor_sdk/types/requests/graph/__init__.py +20 -0
  79. bevor_sdk/types/requests/graph/code.py +42 -0
  80. bevor_sdk/types/requests/graph/snapshot.py +30 -0
  81. bevor_sdk/types/requests/manage/__init__.py +8 -0
  82. bevor_sdk/types/requests/manage/projects.py +28 -0
  83. bevor_sdk/types/requests/security/__init__.py +41 -0
  84. bevor_sdk/types/requests/security/analyses.py +66 -0
  85. bevor_sdk/types/requests/security/findings.py +92 -0
  86. bevor_sdk/types/requests/security/transitions.py +19 -0
  87. bevor_sdk/types/requests/security/versions.py +24 -0
  88. bevor_sdk/types/responses/__init__.py +17 -0
  89. bevor_sdk/types/responses/_shared.py +61 -0
  90. bevor_sdk/types/responses/chat/__init__.py +45 -0
  91. bevor_sdk/types/responses/events.py +26 -0
  92. bevor_sdk/types/responses/graph/__init__.py +29 -0
  93. bevor_sdk/types/responses/graph/code.py +35 -0
  94. bevor_sdk/types/responses/graph/snapshot.py +88 -0
  95. bevor_sdk/types/responses/manage/__init__.py +36 -0
  96. bevor_sdk/types/responses/manage/projects.py +65 -0
  97. bevor_sdk/types/responses/manage/users.py +37 -0
  98. bevor_sdk/types/responses/manage/workspaces.py +73 -0
  99. bevor_sdk/types/responses/security/__init__.py +52 -0
  100. bevor_sdk/types/responses/security/analysis.py +108 -0
  101. bevor_sdk/types/responses/security/base.py +8 -0
  102. bevor_sdk/types/responses/security/findings.py +52 -0
  103. bevor_sdk/types/responses/security/summary.py +44 -0
  104. bevor_sdk/types/responses/security/transitions.py +23 -0
  105. bevor_sdk-0.1.0.dist-info/METADATA +9 -0
  106. bevor_sdk-0.1.0.dist-info/RECORD +107 -0
  107. bevor_sdk-0.1.0.dist-info/WHEEL +4 -0
bevor_sdk/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from bevor_sdk._async.client import BevorClient as AsyncBevorClient
2
+ from bevor_sdk._async.graph import Graph as AsyncGraph
3
+ from bevor_sdk._sync.client import BevorClient
4
+ from bevor_sdk._sync.graph import Graph
5
+
6
+ __all__ = ["BevorClient", "AsyncBevorClient", "Graph", "AsyncGraph"]
File without changes
@@ -0,0 +1,97 @@
1
+ from typing import Any, Dict, Mapping, Self
2
+
3
+ import httpx
4
+
5
+ from bevor_sdk._shared import _BaseClient
6
+
7
+ from .resources import (
8
+ AnalysisResources,
9
+ AnalysisSummaryResources,
10
+ AnalysisVersionsResources,
11
+ ChatResources,
12
+ CodesResources,
13
+ FindingResources,
14
+ ProjectResources,
15
+ TransitionResources,
16
+ )
17
+
18
+ DEFAULT_TIMEOUT = httpx.Timeout(120.0, connect=30.0)
19
+
20
+
21
+ class BevorClient(_BaseClient):
22
+ def __init__(
23
+ self,
24
+ api_key: str,
25
+ *,
26
+ base_url: str | None = None,
27
+ default_headers: Mapping[str, str] | None = None,
28
+ timeout: httpx.Timeout | None = None,
29
+ ):
30
+ super().__init__(
31
+ api_key=api_key, base_url=base_url, default_headers=default_headers, timeout=timeout
32
+ )
33
+ self._http: httpx.AsyncClient | None = None
34
+
35
+ self.codes = CodesResources(self)
36
+ self.chats = ChatResources(self)
37
+ self.analyses = AnalysisResources(self)
38
+ self.transitions = TransitionResources(self)
39
+ self.analysis_versions = AnalysisVersionsResources(self)
40
+ self.summary = AnalysisSummaryResources(self)
41
+ self.findings = FindingResources(self)
42
+ self.projects = ProjectResources(self)
43
+
44
+ def _transport(self, client: httpx.AsyncClient | None) -> httpx.AsyncClient:
45
+ if client is not None:
46
+ return client
47
+ if self._http is None:
48
+ self._http = httpx.AsyncClient(timeout=self._timeout)
49
+ return self._http
50
+
51
+ async def _aclose(self) -> None:
52
+ if self._closed:
53
+ return
54
+ self._closed = True
55
+ if self._http is not None:
56
+ await self._http.aclose()
57
+ self._http = None
58
+
59
+ async def __aenter__(self) -> Self:
60
+ return self
61
+
62
+ async def __aexit__(self, *args: Any) -> None:
63
+ await self._aclose()
64
+
65
+ async def request_json(
66
+ self,
67
+ method: str,
68
+ path: str,
69
+ *,
70
+ params: Mapping[str, Any] | None = None,
71
+ json: Any = None,
72
+ content: httpx._types.RequestContent | None = None,
73
+ data: httpx._types.RequestData | None = None,
74
+ files: httpx._types.RequestFiles | None = None,
75
+ client: httpx.AsyncClient | None = None,
76
+ headers: Dict[str, str] | None = None,
77
+ ) -> Any:
78
+ self._ensure_open()
79
+ url = self._url(path)
80
+ http = self._transport(client)
81
+ payload: Any = json
82
+ if isinstance(json, dict):
83
+ payload = self._prune_json_body(json)
84
+ resp = await http.request(
85
+ method,
86
+ url,
87
+ params=self._prune_query_params(params),
88
+ json=payload,
89
+ content=content,
90
+ data=data,
91
+ files=files,
92
+ headers=self._merge_headers(headers),
93
+ )
94
+ has_content = self._handle_http_error(resp)
95
+ if not has_content:
96
+ return None
97
+ return resp.json()
@@ -0,0 +1,76 @@
1
+ import asyncio
2
+ from typing import List
3
+
4
+ from bevor_sdk import AsyncBevorClient
5
+ from bevor_sdk._shared import _BaseGraph
6
+ from bevor_sdk.types import ID
7
+ from bevor_sdk.types.responses.graph import GraphSnapshotEdge, GraphSnapshotFile, GraphSnapshotNode
8
+
9
+
10
+ class Graph(_BaseGraph):
11
+ """
12
+ Language-agnostic graph view for one code version, backed by HTTP only.
13
+
14
+ Call :meth:`build` after construction (or use :meth:`from_snapshot` for offline snapshots).
15
+ """
16
+
17
+ def __init__(self, client: AsyncBevorClient, code_id: ID) -> None:
18
+ super().__init__(code_id=code_id)
19
+ self._client: AsyncBevorClient = client
20
+
21
+ @classmethod
22
+ def from_snapshot(
23
+ cls,
24
+ client: AsyncBevorClient,
25
+ code_id: ID,
26
+ nodes: List[GraphSnapshotNode],
27
+ edges: List[GraphSnapshotEdge],
28
+ files: List[GraphSnapshotFile],
29
+ ) -> "Graph":
30
+ core = cls(client=client, code_id=code_id)
31
+ core._ingest_snapshot(nodes=nodes, edges=edges, files=files)
32
+ return core
33
+
34
+ async def build(self) -> None:
35
+ """Fetch snapshot via client (nodes + edges + files)."""
36
+ if self._is_built:
37
+ return
38
+ node_res, edge_res, file_res = await asyncio.gather(
39
+ self._client.codes.nodes.list(self.code_id),
40
+ self._client.codes.edges(self.code_id),
41
+ self._client.codes.files.list(self.code_id),
42
+ )
43
+ self._ingest_snapshot(nodes=node_res.results, edges=edge_res.results, files=file_res.results)
44
+ self._is_built = True
45
+
46
+ async def get_file_content(self, file_id: ID) -> str:
47
+ if file_id in self._lazy_file_text:
48
+ return self._lazy_file_text[file_id]
49
+
50
+ text = await self._client.codes.files.content(file_id=file_id, code_id=self.code_id)
51
+ self._lazy_file_text[file_id] = text
52
+ return text
53
+
54
+ async def reconstruct_chunk(self, node_id: ID, *, with_docstring: bool = False) -> str:
55
+ self._ensure_built()
56
+ node = self.get_node(node_id)
57
+ text = await self.get_file_content(node.file_id)
58
+ source_bytes = text.encode("utf-8")
59
+
60
+ docstring_use = None
61
+ if with_docstring and node.docstring:
62
+ if "@inheritdoc" not in node.docstring:
63
+ docstring_use = node.docstring
64
+ else:
65
+ natspecs = self.get_edges_for_node(
66
+ node_id, direction="out", edge_type="has_ref_comment"
67
+ )
68
+ if natspecs:
69
+ natspec = natspecs[0]
70
+ assert natspec.dst_id
71
+ natspec_node = self.get_node(natspec.dst_id)
72
+ docstring_use = natspec_node.docstring
73
+ else:
74
+ docstring_use = node.docstring
75
+
76
+ return Graph.reconstruct(node=node, source=source_bytes, docstring_use=docstring_use)
@@ -0,0 +1,24 @@
1
+ from .chat import ChatResources
2
+ from .graph import CodesResources, FileResources, NodeResources
3
+ from .manage import ProjectResources, ProjectUserResources
4
+ from .security import (
5
+ AnalysisResources,
6
+ AnalysisSummaryResources,
7
+ AnalysisVersionsResources,
8
+ FindingResources,
9
+ TransitionResources,
10
+ )
11
+
12
+ __all__ = [
13
+ "ProjectResources",
14
+ "AnalysisResources",
15
+ "FindingResources",
16
+ "ChatResources",
17
+ "CodesResources",
18
+ "AnalysisVersionsResources",
19
+ "FileResources",
20
+ "NodeResources",
21
+ "AnalysisSummaryResources",
22
+ "TransitionResources",
23
+ "ProjectUserResources",
24
+ ]
@@ -0,0 +1,4 @@
1
+ from .events import EventResource
2
+ from .pagination import PaginatedResource
3
+
4
+ __all__ = ["PaginatedResource", "EventResource"]
@@ -0,0 +1,88 @@
1
+ from typing import TYPE_CHECKING, AsyncIterator, Awaitable, Callable, Generic, TypeVar
2
+
3
+ import httpx
4
+ from pydantic import BaseModel
5
+
6
+ from bevor_sdk._shared import SSEEvent, _BaseSSEParser
7
+ from bevor_sdk.exceptions import ServerException
8
+
9
+ if TYPE_CHECKING:
10
+ from bevor_sdk._async.client import BevorClient
11
+
12
+ T = TypeVar("T", bound=BaseModel)
13
+ EventT = TypeVar("EventT", bound=BaseModel)
14
+
15
+
16
+ class SSEParser(_BaseSSEParser):
17
+ async def events(self, lines: AsyncIterator[str]) -> AsyncIterator[SSEEvent]:
18
+ async for line in lines:
19
+ event = self.feed(line)
20
+ if event is not None:
21
+ yield event
22
+
23
+
24
+ class EventResource(Generic[T, EventT]):
25
+ """generic wrapper for streaming async responses"""
26
+
27
+ def __init__(
28
+ self,
29
+ data: T,
30
+ refresh_data: Callable[[], Awaitable[T]],
31
+ is_completed: Callable[[T], Awaitable[bool]],
32
+ handle_event: Callable[[SSEEvent], Awaitable[EventT | None]],
33
+ *,
34
+ _client: "BevorClient",
35
+ _timeout: httpx.Timeout | None = None,
36
+ ) -> None:
37
+ self._data = data
38
+ self._refresh_data = refresh_data
39
+ self._is_completed = is_completed
40
+ self._handle_event = handle_event
41
+ self._timeout = _timeout or httpx.Timeout(300.0, connect=30.0)
42
+ self._client = _client
43
+
44
+ @property
45
+ def data(self) -> T:
46
+ return self._data
47
+
48
+ async def _get_token(self, client: httpx.AsyncClient | None = None) -> str:
49
+ data = await self._client.request_json(
50
+ "POST",
51
+ "/v1/events/auth",
52
+ client=client,
53
+ )
54
+ token = data.get("token")
55
+ if not token:
56
+ raise ServerException()
57
+ return token
58
+
59
+ async def stream_completion(
60
+ self, client: httpx.AsyncClient | None = None
61
+ ) -> AsyncIterator[EventT]:
62
+ if await self._is_completed(self._data):
63
+ return
64
+ self._client._ensure_open()
65
+ token = await self._get_token(client)
66
+
67
+ url = self._client._url("/v1/events/handler")
68
+ http = self._client._transport(client)
69
+
70
+ async with http.stream(
71
+ "GET",
72
+ url,
73
+ params={"token": token},
74
+ headers=self._client._default_headers,
75
+ timeout=self._timeout,
76
+ ) as response:
77
+ response.raise_for_status()
78
+ parser = SSEParser()
79
+ async for event in parser.events(response.aiter_lines()):
80
+ if relevant_event := await self._handle_event(event):
81
+ yield relevant_event
82
+ self._data = await self._refresh_data()
83
+ if await self._is_completed(self._data):
84
+ await response.aclose()
85
+ break
86
+
87
+
88
+ __all__ = ["EventResource"]
@@ -0,0 +1,50 @@
1
+ from typing import Awaitable, Callable, Generic, List, TypeVar
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from bevor_sdk.types.responses._shared import Pagination
6
+
7
+ T = TypeVar("T", bound=BaseModel)
8
+
9
+
10
+ class PaginatedResource(Generic[T]):
11
+ """generic wrapper for incrementally building paginated data"""
12
+
13
+ def __init__(
14
+ self,
15
+ data: Pagination[T],
16
+ *,
17
+ fetch_page: Callable[[int], Awaitable[Pagination[T]]],
18
+ ) -> None:
19
+ self._results = list(data.results)
20
+ self._current = data
21
+ self._fetch_page = fetch_page
22
+
23
+ @property
24
+ def results(self) -> List[T]:
25
+ return self._results
26
+
27
+ @property
28
+ def current(self) -> Pagination[T]:
29
+ return self._current
30
+
31
+ @property
32
+ def has_more(self) -> bool:
33
+ return self._current.more
34
+
35
+ async def fetch_next(self) -> None:
36
+ if not self.has_more:
37
+ raise ValueError("No more pages to fetch. check .has_more")
38
+ nxt = await self._fetch_page(self._current.page + 1)
39
+ self._results.extend(nxt.results)
40
+ self._current = nxt
41
+
42
+
43
+ """
44
+ ## Expanded Types
45
+
46
+ Certain resources support a `with_pagination` or `with_streaming` argument. If `True` is provided, then the responses extend the standard
47
+ API responses for added functionality.
48
+
49
+ `with_pagination` will return a `PaginatedResource` type
50
+ """
@@ -0,0 +1,3 @@
1
+ from .chats import ChatResources
2
+
3
+ __all__ = ["ChatResources"]
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, AsyncGenerator, Dict, Literal, overload
4
+
5
+ import httpx
6
+
7
+ from bevor_sdk._async.resources._modules import PaginatedResource
8
+ from bevor_sdk.types import ID
9
+ from bevor_sdk.types.requests.chat import ChatMessageBodyDict, ChatsQueryParams
10
+ from bevor_sdk.types.responses import Pagination, ResultsResponse
11
+ from bevor_sdk.types.responses.chat import (
12
+ ChatMessageSchema,
13
+ ChatSchema,
14
+ StreamedMessageType,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from bevor_sdk._async.client import BevorClient
19
+
20
+
21
+ class ChatResources:
22
+ def __init__(self, client: BevorClient) -> None:
23
+ self._client = client
24
+
25
+ def __call__(self, chat_id: ID) -> BoundChatResources:
26
+ return BoundChatResources(self, chat_id)
27
+
28
+ async def create_for_analysis(
29
+ self,
30
+ *,
31
+ version_id: ID,
32
+ client: httpx.AsyncClient | None = None,
33
+ headers: Dict[str, str] | None = None,
34
+ ) -> ChatSchema:
35
+ """POST ``/v1/chats``."""
36
+ data = await self._client.request_json(
37
+ "POST",
38
+ "/v1/chats",
39
+ json={"version_id": version_id, "scope": "analysis"},
40
+ client=client,
41
+ headers=headers,
42
+ )
43
+ return ChatSchema.model_validate(data)
44
+
45
+ async def create_for_code(
46
+ self,
47
+ *,
48
+ code_id: ID,
49
+ client: httpx.AsyncClient | None = None,
50
+ headers: Dict[str, str] | None = None,
51
+ ) -> ChatSchema:
52
+ """POST ``/v1/chats``."""
53
+ data = await self._client.request_json(
54
+ "POST",
55
+ "/v1/chats",
56
+ json={"code_id": code_id, "scope": "code"},
57
+ client=client,
58
+ headers=headers,
59
+ )
60
+ return ChatSchema.model_validate(data)
61
+
62
+ @overload
63
+ async def list(
64
+ self,
65
+ options: ChatsQueryParams | None = None,
66
+ *,
67
+ with_pagination: Literal[True],
68
+ client: httpx.AsyncClient | None = None,
69
+ headers: Dict[str, str] | None = None,
70
+ ) -> PaginatedResource[ChatSchema]: ...
71
+ @overload
72
+ async def list(
73
+ self,
74
+ options: ChatsQueryParams | None = None,
75
+ *,
76
+ with_pagination: Literal[False] = False,
77
+ client: httpx.AsyncClient | None = None,
78
+ headers: Dict[str, str] | None = None,
79
+ ) -> Pagination[ChatSchema]: ...
80
+ async def list(
81
+ self,
82
+ options: ChatsQueryParams | None = None,
83
+ *,
84
+ with_pagination: bool = False,
85
+ client: httpx.AsyncClient | None = None,
86
+ headers: Dict[str, str] | None = None,
87
+ ) -> PaginatedResource[ChatSchema] | Pagination[ChatSchema]:
88
+ """GET ``/v1/chats``."""
89
+ opts = options or ChatsQueryParams()
90
+
91
+ async def fetch_page(p: int) -> Pagination[ChatSchema]:
92
+ opts["page"] = p
93
+ data = await self._client.request_json(
94
+ "GET",
95
+ "/v1/chats",
96
+ params=opts,
97
+ client=client,
98
+ headers=headers,
99
+ )
100
+ return Pagination[ChatSchema].model_validate(data)
101
+
102
+ first = await fetch_page(opts["page"] or 0 if "page" in opts else 0)
103
+ if not with_pagination:
104
+ return first
105
+
106
+ return PaginatedResource[ChatSchema](first, fetch_page=fetch_page)
107
+
108
+ async def get(
109
+ self,
110
+ chat_id: ID,
111
+ *,
112
+ client: httpx.AsyncClient | None = None,
113
+ headers: Dict[str, str] | None = None,
114
+ ) -> ChatSchema:
115
+ """GET ``/v1/chats/{chat_id}``."""
116
+ data = await self._client.request_json(
117
+ "GET", f"/v1/chats/{chat_id}", client=client, headers=headers
118
+ )
119
+ return ChatSchema.model_validate(data)
120
+
121
+ async def messages(
122
+ self,
123
+ chat_id: ID,
124
+ *,
125
+ client: httpx.AsyncClient | None = None,
126
+ headers: Dict[str, str] | None = None,
127
+ ) -> ResultsResponse[ChatMessageSchema]:
128
+ """GET ``/v1/chats/{chat_id}/messages``."""
129
+ data = await self._client.request_json(
130
+ "GET", f"/v1/chats/{chat_id}/messages", client=client, headers=headers
131
+ )
132
+ return ResultsResponse[ChatMessageSchema].model_validate(data)
133
+
134
+ async def sync(
135
+ self,
136
+ chat_id: ID,
137
+ body: ChatMessageBodyDict,
138
+ *,
139
+ client: httpx.AsyncClient | None = None,
140
+ headers: Dict[str, str] | None = None,
141
+ ) -> ChatMessageSchema:
142
+ """POST ``/v1/chats/{chat_id}/sync``."""
143
+ data = await self._client.request_json(
144
+ "POST",
145
+ f"/v1/chats/{chat_id}/sync",
146
+ json=body,
147
+ client=client,
148
+ headers=headers,
149
+ )
150
+ return ChatMessageSchema.model_validate(data)
151
+
152
+ async def session_token(
153
+ self,
154
+ chat_id: ID,
155
+ *,
156
+ client: httpx.AsyncClient | None = None,
157
+ headers: Dict[str, str] | None = None,
158
+ ) -> str:
159
+ data = await self._client.request_json(
160
+ "POST",
161
+ f"/v1/chats/{chat_id}/session-token",
162
+ client=client,
163
+ headers=headers,
164
+ )
165
+ assert isinstance(data, dict) and "token" in data
166
+ return str(data["token"])
167
+
168
+ async def stream(
169
+ self,
170
+ chat_id: ID,
171
+ session_token: str,
172
+ body: ChatMessageBodyDict,
173
+ *,
174
+ client: httpx.AsyncClient | None = None,
175
+ headers: Dict[str, str] | None = None,
176
+ ) -> AsyncGenerator[StreamedMessageType]:
177
+ """POST ``/v1/chats/{chat_id}/stream``. Use session_token() for initial request"""
178
+ self._client._ensure_open()
179
+
180
+ url = self._client._url(f"/v1/chats/{chat_id}/stream")
181
+ http = self._client._transport(client)
182
+ async with http.stream(
183
+ "POST",
184
+ url,
185
+ json=self._client._prune_json_body({**body, "token": session_token}),
186
+ headers=self._client._merge_headers(headers),
187
+ ) as resp:
188
+ resp.raise_for_status()
189
+ async for line in resp.aiter_lines():
190
+ if not line:
191
+ continue
192
+ try:
193
+ yield StreamedMessageType.model_validate_json(line)
194
+ except Exception:
195
+ pass
196
+
197
+
198
+ class BoundChatResources:
199
+ __slots__ = ("_parent", "_chat_id")
200
+
201
+ def __init__(self, parent: ChatResources, chat_id: ID) -> None:
202
+ self._parent = parent
203
+ self._chat_id = chat_id
204
+
205
+ async def get(
206
+ self,
207
+ *,
208
+ client: httpx.AsyncClient | None = None,
209
+ headers: Dict[str, str] | None = None,
210
+ ) -> ChatSchema:
211
+ """GET ``/v1/chats/{chat_id}``."""
212
+ return await self._parent.get(self._chat_id, client=client, headers=headers)
213
+
214
+ async def messages(
215
+ self,
216
+ *,
217
+ client: httpx.AsyncClient | None = None,
218
+ headers: Dict[str, str] | None = None,
219
+ ) -> ResultsResponse[ChatMessageSchema]:
220
+ """GET ``/v1/chats/{chat_id}/messages``."""
221
+ return await self._parent.messages(self._chat_id, client=client, headers=headers)
222
+
223
+ async def sync(
224
+ self,
225
+ body: ChatMessageBodyDict,
226
+ *,
227
+ client: httpx.AsyncClient | None = None,
228
+ headers: Dict[str, str] | None = None,
229
+ ) -> ChatMessageSchema:
230
+ """POST ``/v1/chats/{chat_id}/sync``."""
231
+ return await self._parent.sync(
232
+ self._chat_id,
233
+ body,
234
+ client=client,
235
+ headers=headers,
236
+ )
237
+
238
+ async def stream(
239
+ self,
240
+ session_token: str,
241
+ body: ChatMessageBodyDict,
242
+ *,
243
+ client: httpx.AsyncClient | None = None,
244
+ headers: Dict[str, str] | None = None,
245
+ ) -> AsyncGenerator[StreamedMessageType]:
246
+ """POST ``/v1/chats/{chat_id}/stream``. Use session_token() for initial request"""
247
+ return self._parent.stream(
248
+ self._chat_id,
249
+ session_token,
250
+ body,
251
+ client=client,
252
+ headers=headers,
253
+ )
@@ -0,0 +1,5 @@
1
+ from .codes import CodesResources
2
+ from .files import FileResources
3
+ from .nodes import NodeResources
4
+
5
+ __all__ = ["CodesResources", "FileResources", "NodeResources"]