agentstack-sdk 0.5.2rc2__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 (76) hide show
  1. agentstack_sdk/__init__.py +6 -0
  2. agentstack_sdk/a2a/__init__.py +2 -0
  3. agentstack_sdk/a2a/extensions/__init__.py +8 -0
  4. agentstack_sdk/a2a/extensions/auth/__init__.py +5 -0
  5. agentstack_sdk/a2a/extensions/auth/oauth/__init__.py +4 -0
  6. agentstack_sdk/a2a/extensions/auth/oauth/oauth.py +151 -0
  7. agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py +5 -0
  8. agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py +11 -0
  9. agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py +38 -0
  10. agentstack_sdk/a2a/extensions/auth/secrets/__init__.py +4 -0
  11. agentstack_sdk/a2a/extensions/auth/secrets/secrets.py +77 -0
  12. agentstack_sdk/a2a/extensions/base.py +205 -0
  13. agentstack_sdk/a2a/extensions/common/__init__.py +4 -0
  14. agentstack_sdk/a2a/extensions/common/form.py +149 -0
  15. agentstack_sdk/a2a/extensions/exceptions.py +11 -0
  16. agentstack_sdk/a2a/extensions/interactions/__init__.py +4 -0
  17. agentstack_sdk/a2a/extensions/interactions/approval.py +125 -0
  18. agentstack_sdk/a2a/extensions/services/__init__.py +8 -0
  19. agentstack_sdk/a2a/extensions/services/embedding.py +106 -0
  20. agentstack_sdk/a2a/extensions/services/form.py +54 -0
  21. agentstack_sdk/a2a/extensions/services/llm.py +100 -0
  22. agentstack_sdk/a2a/extensions/services/mcp.py +193 -0
  23. agentstack_sdk/a2a/extensions/services/platform.py +141 -0
  24. agentstack_sdk/a2a/extensions/tools/__init__.py +5 -0
  25. agentstack_sdk/a2a/extensions/tools/call.py +114 -0
  26. agentstack_sdk/a2a/extensions/tools/exceptions.py +6 -0
  27. agentstack_sdk/a2a/extensions/ui/__init__.py +10 -0
  28. agentstack_sdk/a2a/extensions/ui/agent_detail.py +54 -0
  29. agentstack_sdk/a2a/extensions/ui/canvas.py +71 -0
  30. agentstack_sdk/a2a/extensions/ui/citation.py +78 -0
  31. agentstack_sdk/a2a/extensions/ui/error.py +223 -0
  32. agentstack_sdk/a2a/extensions/ui/form_request.py +52 -0
  33. agentstack_sdk/a2a/extensions/ui/settings.py +73 -0
  34. agentstack_sdk/a2a/extensions/ui/trajectory.py +70 -0
  35. agentstack_sdk/a2a/types.py +104 -0
  36. agentstack_sdk/platform/__init__.py +12 -0
  37. agentstack_sdk/platform/client.py +123 -0
  38. agentstack_sdk/platform/common.py +37 -0
  39. agentstack_sdk/platform/configuration.py +47 -0
  40. agentstack_sdk/platform/context.py +291 -0
  41. agentstack_sdk/platform/file.py +295 -0
  42. agentstack_sdk/platform/model_provider.py +131 -0
  43. agentstack_sdk/platform/provider.py +219 -0
  44. agentstack_sdk/platform/provider_build.py +190 -0
  45. agentstack_sdk/platform/types.py +45 -0
  46. agentstack_sdk/platform/user.py +70 -0
  47. agentstack_sdk/platform/user_feedback.py +42 -0
  48. agentstack_sdk/platform/variables.py +44 -0
  49. agentstack_sdk/platform/vector_store.py +217 -0
  50. agentstack_sdk/py.typed +0 -0
  51. agentstack_sdk/server/__init__.py +4 -0
  52. agentstack_sdk/server/agent.py +594 -0
  53. agentstack_sdk/server/app.py +87 -0
  54. agentstack_sdk/server/constants.py +9 -0
  55. agentstack_sdk/server/context.py +68 -0
  56. agentstack_sdk/server/dependencies.py +117 -0
  57. agentstack_sdk/server/exceptions.py +3 -0
  58. agentstack_sdk/server/middleware/__init__.py +3 -0
  59. agentstack_sdk/server/middleware/platform_auth_backend.py +131 -0
  60. agentstack_sdk/server/server.py +376 -0
  61. agentstack_sdk/server/store/__init__.py +3 -0
  62. agentstack_sdk/server/store/context_store.py +35 -0
  63. agentstack_sdk/server/store/memory_context_store.py +59 -0
  64. agentstack_sdk/server/store/platform_context_store.py +58 -0
  65. agentstack_sdk/server/telemetry.py +53 -0
  66. agentstack_sdk/server/utils.py +26 -0
  67. agentstack_sdk/types.py +15 -0
  68. agentstack_sdk/util/__init__.py +4 -0
  69. agentstack_sdk/util/file.py +260 -0
  70. agentstack_sdk/util/httpx.py +18 -0
  71. agentstack_sdk/util/logging.py +63 -0
  72. agentstack_sdk/util/resource_context.py +44 -0
  73. agentstack_sdk/util/utils.py +47 -0
  74. agentstack_sdk-0.5.2rc2.dist-info/METADATA +120 -0
  75. agentstack_sdk-0.5.2rc2.dist-info/RECORD +76 -0
  76. agentstack_sdk-0.5.2rc2.dist-info/WHEEL +4 -0
@@ -0,0 +1,47 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import pydantic
7
+
8
+ from agentstack_sdk.platform.client import PlatformClient, get_platform_client
9
+
10
+
11
+ class SystemConfiguration(pydantic.BaseModel):
12
+ id: str
13
+ default_llm_model: str | None = None
14
+ default_embedding_model: str | None = None
15
+ updated_at: pydantic.AwareDatetime
16
+ created_by: str
17
+
18
+ @staticmethod
19
+ async def get(*, client: PlatformClient | None = None) -> SystemConfiguration:
20
+ """Get the current system configuration."""
21
+ async with client or get_platform_client() as client:
22
+ return pydantic.TypeAdapter(SystemConfiguration).validate_python(
23
+ (await client.get(url="/api/v1/configurations/system")).raise_for_status().json()
24
+ )
25
+
26
+ @staticmethod
27
+ async def update(
28
+ *,
29
+ default_llm_model: str | None = None,
30
+ default_embedding_model: str | None = None,
31
+ client: PlatformClient | None = None,
32
+ ) -> SystemConfiguration:
33
+ """Update the system configuration."""
34
+ async with client or get_platform_client() as client:
35
+ return pydantic.TypeAdapter(SystemConfiguration).validate_python(
36
+ (
37
+ await client.put(
38
+ url="/api/v1/configurations/system",
39
+ json={
40
+ "default_llm_model": default_llm_model,
41
+ "default_embedding_model": default_embedding_model,
42
+ },
43
+ )
44
+ )
45
+ .raise_for_status()
46
+ .json()
47
+ )
@@ -0,0 +1,291 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ from collections.abc import AsyncIterator
7
+ from typing import Literal
8
+ from uuid import UUID, uuid4
9
+
10
+ import pydantic
11
+ from a2a.types import Artifact, Message
12
+ from pydantic import AwareDatetime, BaseModel, Field, SerializeAsAny, computed_field
13
+
14
+ from agentstack_sdk.platform.client import PlatformClient, get_platform_client
15
+ from agentstack_sdk.platform.common import PaginatedResult
16
+ from agentstack_sdk.platform.provider import Provider
17
+ from agentstack_sdk.platform.types import Metadata, MetadataPatch
18
+ from agentstack_sdk.util.utils import filter_dict, utc_now
19
+
20
+
21
+ class ContextHistoryItem(BaseModel):
22
+ id: UUID = Field(default_factory=uuid4)
23
+ data: Artifact | Message
24
+ created_at: AwareDatetime = Field(default_factory=utc_now)
25
+ context_id: str
26
+
27
+ @computed_field
28
+ @property
29
+ def kind(self) -> Literal["message", "artifact"]:
30
+ return getattr(self.data, "kind", "artifact")
31
+
32
+
33
+ class ContextToken(pydantic.BaseModel):
34
+ context_id: str
35
+ token: pydantic.Secret[str]
36
+ expires_at: pydantic.AwareDatetime | None = None
37
+
38
+
39
+ class ContextPermissions(pydantic.BaseModel):
40
+ files: set[Literal["read", "write", "extract", "*"]] = set()
41
+ vector_stores: set[Literal["read", "write", "*"]] = set()
42
+ context_data: set[Literal["read", "write", "*"]] = set()
43
+
44
+
45
+ class Permissions(ContextPermissions):
46
+ llm: set[Literal["*"] | str] = set()
47
+ embeddings: set[Literal["*"] | str] = set()
48
+ a2a_proxy: set[Literal["*"] | str] = set()
49
+ model_providers: set[Literal["read", "write", "*"]] = set()
50
+ variables: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()
51
+
52
+ providers: set[Literal["read", "write", "*"]] = set() # write includes "show logs" permission
53
+ provider_variables: set[Literal["read", "write", "*"]] = set()
54
+
55
+ contexts: set[Literal["read", "write", "*"]] = set()
56
+
57
+ connectors: set[Literal["read", "write", "proxy", "*"]] = set()
58
+
59
+
60
+ class Context(pydantic.BaseModel):
61
+ id: str
62
+ created_at: pydantic.AwareDatetime
63
+ updated_at: pydantic.AwareDatetime
64
+ last_active_at: pydantic.AwareDatetime
65
+ created_by: str
66
+ provider_id: str | None = None
67
+ metadata: Metadata | None = None
68
+
69
+ @staticmethod
70
+ async def create(
71
+ *,
72
+ metadata: Metadata | None = None,
73
+ provider_id: str | None = None,
74
+ client: PlatformClient | None = None,
75
+ ) -> Context:
76
+ async with client or get_platform_client() as client:
77
+ return pydantic.TypeAdapter(Context).validate_python(
78
+ (
79
+ await client.post(
80
+ url="/api/v1/contexts",
81
+ json=filter_dict({"metadata": metadata, "provider_id": provider_id}),
82
+ )
83
+ )
84
+ .raise_for_status()
85
+ .json()
86
+ )
87
+
88
+ @staticmethod
89
+ async def list(
90
+ *,
91
+ client: PlatformClient | None = None,
92
+ page_token: str | None = None,
93
+ limit: int | None = None,
94
+ order: Literal["asc"] | Literal["desc"] | None = None,
95
+ order_by: Literal["created_at"] | Literal["updated_at"] | None = None,
96
+ include_empty: bool = True,
97
+ provider_id: str | None = None,
98
+ ) -> PaginatedResult[Context]:
99
+ # `self` has a weird type so that you can call both `instance.get()` to update an instance, or `File.get("123")` to obtain a new instance
100
+ async with client or get_platform_client() as client:
101
+ return pydantic.TypeAdapter(PaginatedResult[Context]).validate_python(
102
+ (
103
+ await client.get(
104
+ url="/api/v1/contexts",
105
+ params=filter_dict(
106
+ {
107
+ "page_token": page_token,
108
+ "limit": limit,
109
+ "order": order,
110
+ "order_by": order_by,
111
+ "include_empty": include_empty,
112
+ "provider_id": provider_id,
113
+ }
114
+ ),
115
+ )
116
+ )
117
+ .raise_for_status()
118
+ .json()
119
+ )
120
+
121
+ async def get(
122
+ self: Context | str,
123
+ *,
124
+ client: PlatformClient | None = None,
125
+ ) -> Context:
126
+ # `self` has a weird type so that you can call both `instance.get()` to update an instance, or `File.get("123")` to obtain a new instance
127
+ context_id = self if isinstance(self, str) else self.id
128
+ async with client or get_platform_client() as client:
129
+ return pydantic.TypeAdapter(Context).validate_python(
130
+ (await client.get(url=f"/api/v1/contexts/{context_id}")).raise_for_status().json()
131
+ )
132
+
133
+ async def update(
134
+ self: Context | str,
135
+ *,
136
+ metadata: Metadata | None,
137
+ client: PlatformClient | None = None,
138
+ ) -> Context:
139
+ # `self` has a weird type so that you can call both `instance.get()` to update an instance, or `File.get("123")` to obtain a new instance
140
+ context_id = self if isinstance(self, str) else self.id
141
+ async with client or get_platform_client() as client:
142
+ result = pydantic.TypeAdapter(Context).validate_python(
143
+ (await client.put(url=f"/api/v1/contexts/{context_id}", json={"metadata": metadata}))
144
+ .raise_for_status()
145
+ .json()
146
+ )
147
+ if isinstance(self, Context):
148
+ self.__dict__.update(result.__dict__)
149
+ return self
150
+ return result
151
+
152
+ async def patch_metadata(
153
+ self: Context | str,
154
+ *,
155
+ metadata: MetadataPatch | None,
156
+ client: PlatformClient | None = None,
157
+ ) -> Context:
158
+ # `self` has a weird type so that you can call both `instance.get()` to update an instance, or `File.get("123")` to obtain a new instance
159
+ context_id = self if isinstance(self, str) else self.id
160
+ async with client or get_platform_client() as client:
161
+ result = pydantic.TypeAdapter(Context).validate_python(
162
+ (await client.patch(url=f"/api/v1/contexts/{context_id}/metadata", json={"metadata": metadata}))
163
+ .raise_for_status()
164
+ .json()
165
+ )
166
+ if isinstance(self, Context):
167
+ self.__dict__.update(result.__dict__)
168
+ return self
169
+ return result
170
+
171
+ async def delete(
172
+ self: Context | str,
173
+ *,
174
+ client: PlatformClient | None = None,
175
+ ) -> None:
176
+ # `self` has a weird type so that you can call both `instance.delete()` or `File.delete("123")`
177
+ context_id = self if isinstance(self, str) else self.id
178
+ async with client or get_platform_client() as client:
179
+ _ = (await client.delete(url=f"/api/v1/contexts/{context_id}")).raise_for_status()
180
+
181
+ async def generate_token(
182
+ self: Context | str,
183
+ *,
184
+ providers: list[str] | list[Provider] | None = None,
185
+ client: PlatformClient | None = None,
186
+ grant_global_permissions: Permissions | None = None,
187
+ grant_context_permissions: ContextPermissions | None = None,
188
+ ) -> ContextToken:
189
+ """
190
+ Generate token for agent authentication
191
+
192
+ @param grant_global_permissions: Global permissions granted by the token. Must be subset of the users permissions
193
+ @param grant_context_permissions: Context permissions granted by the token. Must be subset of the users permissions
194
+ """
195
+ # `self` has a weird type so that you can call both `instance.content()` to get content of an instance, or `File.content("123")`
196
+ context_id = self if isinstance(self, str) else self.id
197
+ grant_global_permissions = grant_global_permissions or Permissions()
198
+ grant_context_permissions = grant_context_permissions or Permissions()
199
+
200
+ if isinstance(self, Context) and self.metadata and (provider_id := self.metadata.get("provider_id", None)):
201
+ providers = providers or [provider_id]
202
+
203
+ if "*" not in grant_global_permissions.a2a_proxy and not grant_global_permissions.a2a_proxy:
204
+ if not providers:
205
+ raise ValueError(
206
+ "Invalid audience: You must specify providers or use '*' in grant_global_permissions.a2a_proxy."
207
+ )
208
+
209
+ grant_global_permissions.a2a_proxy |= {p.id if isinstance(p, Provider) else p for p in providers}
210
+
211
+ async with client or get_platform_client() as client:
212
+ token_response = (
213
+ (
214
+ await client.post(
215
+ url=f"/api/v1/contexts/{context_id}/token",
216
+ json={
217
+ "grant_global_permissions": grant_global_permissions.model_dump(mode="json"),
218
+ "grant_context_permissions": grant_context_permissions.model_dump(mode="json"),
219
+ },
220
+ )
221
+ )
222
+ .raise_for_status()
223
+ .json()
224
+ )
225
+ return pydantic.TypeAdapter(ContextToken).validate_python({**token_response, "context_id": context_id})
226
+
227
+ async def add_history_item(
228
+ self: Context | str,
229
+ *,
230
+ data: Message | Artifact,
231
+ client: PlatformClient | None = None,
232
+ ) -> None:
233
+ """Add a Message or Artifact to the context history (append-only)"""
234
+ target_context_id = self if isinstance(self, str) else self.id
235
+ async with client or get_platform_client() as platform_client:
236
+ _ = (
237
+ await platform_client.post(
238
+ url=f"/api/v1/contexts/{target_context_id}/history", json=data.model_dump(mode="json")
239
+ )
240
+ ).raise_for_status()
241
+
242
+ async def delete_history_from_id(
243
+ self: Context | str,
244
+ *,
245
+ from_id: UUID | str,
246
+ client: PlatformClient | None = None,
247
+ ) -> None:
248
+ """Delete all history items from a specific item onwards (inclusive)"""
249
+ target_context_id = self if isinstance(self, str) else self.id
250
+ async with client or get_platform_client() as platform_client:
251
+ _ = (
252
+ await platform_client.delete(
253
+ url=f"/api/v1/contexts/{target_context_id}/history", params={"from_id": str(from_id)}
254
+ )
255
+ ).raise_for_status()
256
+
257
+ async def list_history(
258
+ self: Context | str,
259
+ *,
260
+ page_token: str | None = None,
261
+ limit: int | None = None,
262
+ order: Literal["asc"] | Literal["desc"] | None = "asc",
263
+ order_by: Literal["created_at"] | Literal["updated_at"] | None = None,
264
+ client: PlatformClient | None = None,
265
+ ) -> PaginatedResult[ContextHistoryItem]:
266
+ """List all history items for this context in chronological order"""
267
+ target_context_id = self if isinstance(self, str) else self.id
268
+ async with client or get_platform_client() as platform_client:
269
+ return pydantic.TypeAdapter(PaginatedResult[ContextHistoryItem]).validate_python(
270
+ (
271
+ await platform_client.get(
272
+ url=f"/api/v1/contexts/{target_context_id}/history",
273
+ params=filter_dict(
274
+ {"page_token": page_token, "limit": limit, "order": order, "order_by": order_by}
275
+ ),
276
+ )
277
+ )
278
+ .raise_for_status()
279
+ .json()
280
+ )
281
+
282
+ async def list_all_history(
283
+ self: Context | str, client: PlatformClient | None = None
284
+ ) -> AsyncIterator[ContextHistoryItem]:
285
+ result = await Context.list_history(self, client=client)
286
+ for item in result.items:
287
+ yield item
288
+ while result.has_more:
289
+ result = await Context.list_history(self, page_token=result.next_page_token, client=client)
290
+ for item in result.items:
291
+ yield item
@@ -0,0 +1,295 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import typing
7
+ from collections.abc import AsyncIterator
8
+ from contextlib import asynccontextmanager
9
+ from typing import Literal
10
+
11
+ import pydantic
12
+ from a2a.types import FilePart, FileWithUri
13
+
14
+ from agentstack_sdk.platform.client import PlatformClient, get_platform_client
15
+ from agentstack_sdk.platform.common import PaginatedResult
16
+ from agentstack_sdk.util.file import LoadedFile, LoadedFileWithUri, PlatformFileUrl
17
+ from agentstack_sdk.util.utils import filter_dict
18
+
19
+ ExtractionFormatLiteral = typing.Literal["markdown", "vendor_specific_json"]
20
+
21
+
22
+ class ExtractedFileInfo(pydantic.BaseModel):
23
+ """Information about an extracted file."""
24
+
25
+ file_id: str
26
+ format: ExtractionFormatLiteral | None
27
+
28
+
29
+ class Extraction(pydantic.BaseModel):
30
+ id: str
31
+ file_id: str
32
+ extracted_files: list[ExtractedFileInfo] = pydantic.Field(default_factory=list)
33
+ status: typing.Literal["pending", "in_progress", "completed", "failed", "cancelled"] = "pending"
34
+ job_id: str | None = None
35
+ error_message: str | None = None
36
+ extraction_metadata: dict[str, typing.Any] | None = None
37
+ started_at: pydantic.AwareDatetime | None = None
38
+ finished_at: pydantic.AwareDatetime | None = None
39
+ created_at: pydantic.AwareDatetime
40
+
41
+
42
+ class File(pydantic.BaseModel):
43
+ id: str
44
+ filename: str
45
+ content_type: str
46
+ file_size_bytes: int
47
+ created_at: pydantic.AwareDatetime
48
+ created_by: str
49
+ file_type: typing.Literal["user_upload", "extracted_text"]
50
+ parent_file_id: str | None = None
51
+
52
+ @property
53
+ def url(self) -> PlatformFileUrl:
54
+ return PlatformFileUrl(f"agentstack://{self.id}")
55
+
56
+ @staticmethod
57
+ async def create(
58
+ *,
59
+ filename: str,
60
+ content: typing.BinaryIO | bytes,
61
+ content_type: str = "application/octet-stream",
62
+ client: PlatformClient | None = None,
63
+ context_id: str | None | Literal["auto"] = "auto",
64
+ ) -> File:
65
+ async with client or get_platform_client() as platform_client:
66
+ context_id = platform_client.context_id if context_id == "auto" else context_id
67
+ return pydantic.TypeAdapter(File).validate_python(
68
+ (
69
+ await platform_client.post(
70
+ url="/api/v1/files",
71
+ files={"file": (filename, content, content_type)},
72
+ params=context_id and {"context_id": context_id},
73
+ )
74
+ )
75
+ .raise_for_status()
76
+ .json()
77
+ )
78
+
79
+ async def get(
80
+ self: File | str,
81
+ *,
82
+ client: PlatformClient | None = None,
83
+ context_id: str | None | Literal["auto"] = "auto",
84
+ ) -> File:
85
+ # `self` has a weird type so that you can call both `instance.get()` to update an instance, or `File.get("123")` to obtain a new instance
86
+ file_id = self if isinstance(self, str) else self.id
87
+ async with client or get_platform_client() as platform_client:
88
+ context_id = platform_client.context_id if context_id == "auto" else context_id
89
+ return pydantic.TypeAdapter(File).validate_python(
90
+ (
91
+ await platform_client.get(
92
+ url=f"/api/v1/files/{file_id}",
93
+ params=context_id and {"context_id": context_id},
94
+ )
95
+ )
96
+ .raise_for_status()
97
+ .json()
98
+ )
99
+
100
+ async def delete(
101
+ self: File | str,
102
+ *,
103
+ client: PlatformClient | None = None,
104
+ context_id: str | None | Literal["auto"] = "auto",
105
+ ) -> None:
106
+ # `self` has a weird type so that you can call both `instance.delete()` or `File.delete("123")`
107
+ file_id = self if isinstance(self, str) else self.id
108
+ async with client or get_platform_client() as platform_client:
109
+ context_id = platform_client.context_id if context_id == "auto" else context_id
110
+ _ = (
111
+ await platform_client.delete(
112
+ url=f"/api/v1/files/{file_id}", params=context_id and {"context_id": context_id}
113
+ )
114
+ ).raise_for_status()
115
+
116
+ @asynccontextmanager
117
+ async def load_content(
118
+ self: File | str,
119
+ *,
120
+ stream: bool = False,
121
+ client: PlatformClient | None = None,
122
+ context_id: str | None | Literal["auto"] = "auto",
123
+ ) -> AsyncIterator[LoadedFile]:
124
+ # `self` has a weird type so that you can call both `instance.load_content()` to create an extraction for an instance, or `File.load_content("123")`
125
+ file_id = self if isinstance(self, str) else self.id
126
+ async with client or get_platform_client() as platform_client:
127
+ context_id = platform_client.context_id if context_id == "auto" else context_id
128
+
129
+ file = await File.get(file_id, client=client, context_id=context_id) if isinstance(self, str) else self
130
+
131
+ async with platform_client.stream(
132
+ "GET", url=f"/api/v1/files/{file_id}/content", params=context_id and {"context_id": context_id}
133
+ ) as response:
134
+ response.raise_for_status()
135
+ if not stream:
136
+ await response.aread()
137
+ yield LoadedFileWithUri(response=response, content_type=file.content_type, filename=file.filename)
138
+
139
+ @asynccontextmanager
140
+ async def load_text_content(
141
+ self: File | str,
142
+ *,
143
+ stream: bool = False,
144
+ client: PlatformClient | None = None,
145
+ context_id: str | None | Literal["auto"] = "auto",
146
+ ) -> AsyncIterator[LoadedFile]:
147
+ # `self` has a weird type so that you can call both `instance.load_text_content()` to create an extraction for an instance, or `File.load_text_content("123")`
148
+ file_id = self if isinstance(self, str) else self.id
149
+ async with client or get_platform_client() as platform_client:
150
+ context_id = platform_client.context_id if context_id == "auto" else context_id
151
+
152
+ file = await File.get(file_id, client=client, context_id=context_id) if isinstance(self, str) else self
153
+
154
+ async with platform_client.stream(
155
+ "GET",
156
+ url=f"/api/v1/files/{file_id}/text_content",
157
+ params=context_id and {"context_id": context_id},
158
+ ) as response:
159
+ response.raise_for_status()
160
+ if not stream:
161
+ await response.aread()
162
+ yield LoadedFileWithUri(response=response, content_type=file.content_type, filename=file.filename)
163
+
164
+ @asynccontextmanager
165
+ async def load_json_content(
166
+ self: File | str,
167
+ *,
168
+ stream: bool = False,
169
+ client: PlatformClient | None = None,
170
+ context_id: str | None | Literal["auto"] = "auto",
171
+ ) -> AsyncIterator[LoadedFile]:
172
+ # `self` has a weird type so that you can call both `instance.load_json_content()` to create an extraction for an instance, or `File.load_json_content("123")`
173
+ file_id = self if isinstance(self, str) else self.id
174
+ async with client or get_platform_client() as platform_client:
175
+ context_id = platform_client.context_id if context_id == "auto" else context_id
176
+
177
+ file = await File.get(file_id, client=client, context_id=context_id) if isinstance(self, str) else self
178
+ extraction = await file.get_extraction(client=client, context_id=context_id)
179
+
180
+ for extracted_file_info in extraction.extracted_files:
181
+ if extracted_file_info.format != "vendor_specific_json":
182
+ continue
183
+ extracted_json_file_id = extracted_file_info.file_id
184
+ async with platform_client.stream(
185
+ "GET",
186
+ url=f"/api/v1/files/{extracted_json_file_id}/content",
187
+ params=context_id and {"context_id": context_id},
188
+ ) as response:
189
+ response.raise_for_status()
190
+ if not stream:
191
+ await response.aread()
192
+ yield LoadedFileWithUri(response=response, content_type=file.content_type, filename=file.filename)
193
+ return
194
+
195
+ raise ValueError("No extracted JSON content available for this file.")
196
+
197
+ async def create_extraction(
198
+ self: File | str,
199
+ *,
200
+ formats: list[ExtractionFormatLiteral] | None = None,
201
+ client: PlatformClient | None = None,
202
+ context_id: str | None | Literal["auto"] = "auto",
203
+ ) -> Extraction:
204
+ # `self` has a weird type so that you can call both `instance.create_extraction()` to create an extraction for an instance, or `File.create_extraction("123")`
205
+ file_id = self if isinstance(self, str) else self.id
206
+ async with client or get_platform_client() as platform_client:
207
+ context_id = platform_client.context_id if context_id == "auto" else context_id
208
+ return pydantic.TypeAdapter(Extraction).validate_python(
209
+ (
210
+ await platform_client.post(
211
+ url=f"/api/v1/files/{file_id}/extraction",
212
+ params=context_id and {"context_id": context_id},
213
+ json={"settings": {"formats": formats}} if formats else None,
214
+ )
215
+ )
216
+ .raise_for_status()
217
+ .json()
218
+ )
219
+
220
+ async def get_extraction(
221
+ self: File | str,
222
+ *,
223
+ client: PlatformClient | None = None,
224
+ context_id: str | None | Literal["auto"] = "auto",
225
+ ) -> Extraction:
226
+ # `self` has a weird type so that you can call both `instance.get_extraction()` to get an extraction of an instance, or `File.get_extraction("123", "456")`
227
+ file_id = self if isinstance(self, str) else self.id
228
+ async with client or get_platform_client() as platform_client:
229
+ context_id = platform_client.context_id if context_id == "auto" else context_id
230
+ return pydantic.TypeAdapter(Extraction).validate_python(
231
+ (
232
+ await platform_client.get(
233
+ url=f"/api/v1/files/{file_id}/extraction",
234
+ params=context_id and {"context_id": context_id},
235
+ )
236
+ )
237
+ .raise_for_status()
238
+ .json()
239
+ )
240
+
241
+ async def delete_extraction(
242
+ self: File | str,
243
+ *,
244
+ client: PlatformClient | None = None,
245
+ context_id: str | None | Literal["auto"] = "auto",
246
+ ) -> None:
247
+ # `self` has a weird type so that you can call both `instance.delete_extraction()` or `File.delete_extraction("123", "456")`
248
+ file_id = self if isinstance(self, str) else self.id
249
+ async with client or get_platform_client() as platform_client:
250
+ context_id = platform_client.context_id if context_id == "auto" else context_id
251
+ _ = (
252
+ await platform_client.delete(
253
+ url=f"/api/v1/files/{file_id}/extraction",
254
+ params=context_id and {"context_id": context_id},
255
+ )
256
+ ).raise_for_status()
257
+
258
+ def to_file_part(self: File) -> FilePart:
259
+ return FilePart(file=FileWithUri(name=self.filename, uri=f"agentstack://{self.id}"))
260
+
261
+ @staticmethod
262
+ async def list(
263
+ *,
264
+ content_type: str | None = None,
265
+ filename_search: str | None = None,
266
+ page_token: str | None = None,
267
+ limit: int | None = None,
268
+ order: Literal["asc"] | Literal["desc"] | None = "asc",
269
+ order_by: Literal["created_at"] | Literal["filename"] | Literal["file_size_bytes"] | None = None,
270
+ client: PlatformClient | None = None,
271
+ context_id: str | None | Literal["auto"] = "auto",
272
+ ) -> PaginatedResult[File]:
273
+ # `self` has a weird type so that you can call both `instance.list_history()` or `ProviderBuild.list_history("123")`
274
+ async with client or get_platform_client() as platform_client:
275
+ context_id = platform_client.context_id if context_id == "auto" else context_id
276
+ return pydantic.TypeAdapter(PaginatedResult[File]).validate_python(
277
+ (
278
+ await platform_client.get(
279
+ url="/api/v1/files",
280
+ params=filter_dict(
281
+ {
282
+ "context_id": context_id,
283
+ "content_type": content_type,
284
+ "filename_search": filename_search,
285
+ "page_token": page_token,
286
+ "limit": limit,
287
+ "order": order,
288
+ "order_by": order_by,
289
+ }
290
+ ),
291
+ )
292
+ )
293
+ .raise_for_status()
294
+ .json()
295
+ )