agentstack-sdk 0.4.3rc3__py3-none-any.whl → 0.5.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.
- agentstack_sdk/a2a/extensions/base.py +1 -1
- agentstack_sdk/a2a/extensions/services/form.py +2 -1
- agentstack_sdk/a2a/extensions/services/mcp.py +8 -13
- agentstack_sdk/a2a/extensions/ui/canvas.py +4 -1
- agentstack_sdk/a2a/extensions/ui/citation.py +1 -6
- agentstack_sdk/a2a/extensions/ui/error.py +39 -30
- agentstack_sdk/a2a/extensions/ui/trajectory.py +1 -2
- agentstack_sdk/a2a/types.py +15 -14
- agentstack_sdk/platform/__init__.py +2 -0
- agentstack_sdk/platform/context.py +2 -6
- agentstack_sdk/platform/file.py +45 -1
- agentstack_sdk/platform/user.py +49 -4
- agentstack_sdk/platform/user_feedback.py +42 -0
- agentstack_sdk/server/agent.py +289 -278
- agentstack_sdk/server/app.py +13 -2
- agentstack_sdk/server/constants.py +0 -3
- agentstack_sdk/server/context.py +0 -9
- agentstack_sdk/server/dependencies.py +5 -11
- agentstack_sdk/server/server.py +3 -1
- agentstack_sdk/util/utils.py +5 -1
- agentstack_sdk-0.5.0.dist-info/METADATA +118 -0
- {agentstack_sdk-0.4.3rc3.dist-info → agentstack_sdk-0.5.0.dist-info}/RECORD +23 -22
- agentstack_sdk-0.4.3rc3.dist-info/METADATA +0 -69
- {agentstack_sdk-0.4.3rc3.dist-info → agentstack_sdk-0.5.0.dist-info}/WHEEL +0 -0
|
@@ -111,7 +111,7 @@ class NoParamsBaseExtensionSpec(BaseExtensionSpec[NoneType]):
|
|
|
111
111
|
return None
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
ExtensionSpecT = typing.TypeVar("ExtensionSpecT", bound=BaseExtensionSpec)
|
|
114
|
+
ExtensionSpecT = typing.TypeVar("ExtensionSpecT", bound=BaseExtensionSpec[typing.Any])
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
class BaseExtensionServer(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromClientT]):
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from typing import Self,
|
|
7
|
+
from typing import Self, TypeVar, cast
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, TypeAdapter
|
|
10
|
+
from typing_extensions import TypedDict
|
|
10
11
|
|
|
11
12
|
from agentstack_sdk.a2a.extensions.base import BaseExtensionClient, BaseExtensionServer, BaseExtensionSpec
|
|
12
13
|
from agentstack_sdk.a2a.extensions.common.form import FormRender, FormResponse
|
|
@@ -39,7 +39,7 @@ class StdioTransport(pydantic.BaseModel):
|
|
|
39
39
|
class StreamableHTTPTransport(pydantic.BaseModel):
|
|
40
40
|
type: Literal["streamable_http"] = "streamable_http"
|
|
41
41
|
|
|
42
|
-
url:
|
|
42
|
+
url: str
|
|
43
43
|
headers: dict[str, str] | None = None
|
|
44
44
|
|
|
45
45
|
|
|
@@ -111,13 +111,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
|
|
|
111
111
|
for fullfilment in self.data.mcp_fulfillments.values():
|
|
112
112
|
if fullfilment.transport.type == "streamable_http":
|
|
113
113
|
try:
|
|
114
|
-
fullfilment.transport.url =
|
|
115
|
-
re.sub(
|
|
116
|
-
r"^http[s]?://{platform_url}",
|
|
117
|
-
platform_url,
|
|
118
|
-
str(fullfilment.transport.url),
|
|
119
|
-
)
|
|
120
|
-
)
|
|
114
|
+
fullfilment.transport.url = re.sub("^{platform_url}", platform_url, str(fullfilment.transport.url))
|
|
121
115
|
except Exception:
|
|
122
116
|
logger.warning("Platform URL substitution failed", exc_info=True)
|
|
123
117
|
|
|
@@ -126,7 +120,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
|
|
|
126
120
|
if metadata:
|
|
127
121
|
for name, demand in self.spec.params.mcp_demands.items():
|
|
128
122
|
if not (fulfillment := metadata.mcp_fulfillments.get(name)):
|
|
129
|
-
|
|
123
|
+
continue
|
|
130
124
|
if fulfillment.transport.type not in demand.allowed_transports:
|
|
131
125
|
raise ValueError(f'Transport "{fulfillment.transport.type}" not allowed for demand "{name}"')
|
|
132
126
|
return metadata
|
|
@@ -148,7 +142,8 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
|
|
|
148
142
|
fulfillment = self.data.mcp_fulfillments.get(demand) if self.data else None
|
|
149
143
|
|
|
150
144
|
if not fulfillment:
|
|
151
|
-
|
|
145
|
+
yield None
|
|
146
|
+
return
|
|
152
147
|
|
|
153
148
|
transport = fulfillment.transport
|
|
154
149
|
|
|
@@ -162,7 +157,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
|
|
|
162
157
|
yield (read, write)
|
|
163
158
|
elif isinstance(transport, StreamableHTTPTransport):
|
|
164
159
|
async with streamablehttp_client(
|
|
165
|
-
url=
|
|
160
|
+
url=transport.url,
|
|
166
161
|
headers=transport.headers,
|
|
167
162
|
auth=await self._create_auth(transport),
|
|
168
163
|
) as (
|
|
@@ -180,12 +175,12 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
|
|
|
180
175
|
platform
|
|
181
176
|
and platform.data
|
|
182
177
|
and platform.data.base_url
|
|
183
|
-
and
|
|
178
|
+
and transport.url.startswith(str(platform.data.base_url))
|
|
184
179
|
):
|
|
185
180
|
return await platform.create_httpx_auth()
|
|
186
181
|
oauth = self._get_oauth_server()
|
|
187
182
|
if oauth:
|
|
188
|
-
return await oauth.create_httpx_auth(resource_url=transport.url)
|
|
183
|
+
return await oauth.create_httpx_auth(resource_url=pydantic.AnyUrl(transport.url))
|
|
189
184
|
return None
|
|
190
185
|
|
|
191
186
|
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
import pydantic
|
|
9
|
-
from a2a.types import Artifact
|
|
9
|
+
from a2a.types import Artifact, TextPart
|
|
10
10
|
from a2a.types import Message as A2AMessage
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
@@ -38,6 +38,9 @@ class CanvasExtensionSpec(NoParamsBaseExtensionSpec):
|
|
|
38
38
|
|
|
39
39
|
class CanvasExtensionServer(BaseExtensionServer[CanvasExtensionSpec, CanvasEditRequestMetadata]):
|
|
40
40
|
def handle_incoming_message(self, message: A2AMessage, context: RunContext):
|
|
41
|
+
if message.metadata and self.spec.URI in message.metadata and message.parts:
|
|
42
|
+
message.parts = [part for part in message.parts if not isinstance(part.root, TextPart)]
|
|
43
|
+
|
|
41
44
|
super().handle_incoming_message(message, context)
|
|
42
45
|
self.context = context
|
|
43
46
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from types import NoneType
|
|
7
|
-
from typing import Any
|
|
8
7
|
|
|
9
8
|
import pydantic
|
|
10
9
|
from a2a.types import DataPart, FilePart, Part, TextPart
|
|
@@ -59,11 +58,7 @@ class CitationExtensionSpec(NoParamsBaseExtensionSpec):
|
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
class CitationExtensionServer(BaseExtensionServer[CitationExtensionSpec, NoneType]):
|
|
62
|
-
def citation_metadata(
|
|
63
|
-
self,
|
|
64
|
-
*,
|
|
65
|
-
citations: list[Citation],
|
|
66
|
-
) -> Metadata[str, Any]:
|
|
61
|
+
def citation_metadata(self, *, citations: list[Citation]) -> Metadata:
|
|
67
62
|
return Metadata({self.spec.URI: CitationMetadata(citations=citations).model_dump(mode="json")})
|
|
68
63
|
|
|
69
64
|
def message(
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
import contextvars
|
|
7
6
|
import json
|
|
8
7
|
import logging
|
|
9
8
|
import traceback
|
|
10
9
|
from collections.abc import AsyncIterator
|
|
11
10
|
from contextlib import asynccontextmanager
|
|
12
11
|
from types import NoneType
|
|
13
|
-
from typing import Any
|
|
12
|
+
from typing import Any, Final
|
|
14
13
|
|
|
15
14
|
import pydantic
|
|
16
15
|
|
|
@@ -20,6 +19,7 @@ from agentstack_sdk.a2a.extensions.base import (
|
|
|
20
19
|
BaseExtensionSpec,
|
|
21
20
|
)
|
|
22
21
|
from agentstack_sdk.a2a.types import AgentMessage, JsonDict, Metadata
|
|
22
|
+
from agentstack_sdk.util import resource_context
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
@@ -125,25 +125,18 @@ def _extract_error(exc: BaseException) -> Error | ErrorGroup:
|
|
|
125
125
|
class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
|
|
126
126
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
127
127
|
super().__init__(*args, **kwargs)
|
|
128
|
-
# Server-scoped ContextVar for request-scoped error context
|
|
129
|
-
self._error_context_var: contextvars.ContextVar[JsonDict] = contextvars.ContextVar("error_context")
|
|
130
128
|
|
|
131
129
|
@asynccontextmanager
|
|
132
130
|
async def lifespan(self) -> AsyncIterator[None]:
|
|
133
131
|
"""Set up request-scoped error context using ContextVar."""
|
|
134
|
-
|
|
135
|
-
token = self._error_context_var.set({})
|
|
136
|
-
|
|
137
|
-
try:
|
|
132
|
+
with use_error_extension_context(server=self, context={}):
|
|
138
133
|
yield
|
|
139
|
-
finally:
|
|
140
|
-
self._error_context_var.reset(token)
|
|
141
134
|
|
|
142
135
|
@property
|
|
143
136
|
def context(self) -> JsonDict:
|
|
144
137
|
"""Get the current request's error context."""
|
|
145
138
|
try:
|
|
146
|
-
return
|
|
139
|
+
return get_error_extension_context().context
|
|
147
140
|
except LookupError:
|
|
148
141
|
# Fallback for when lifespan hasn't been entered yet
|
|
149
142
|
logger.warning(
|
|
@@ -151,7 +144,7 @@ class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
|
|
|
151
144
|
)
|
|
152
145
|
return {}
|
|
153
146
|
|
|
154
|
-
def error_metadata(self, error: BaseException) -> Metadata
|
|
147
|
+
def error_metadata(self, error: BaseException) -> Metadata:
|
|
155
148
|
"""
|
|
156
149
|
Create metadata for an error.
|
|
157
150
|
|
|
@@ -186,28 +179,44 @@ class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
|
|
|
186
179
|
Returns:
|
|
187
180
|
AgentMessage with error metadata and markdown-formatted text
|
|
188
181
|
"""
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
182
|
+
try:
|
|
183
|
+
metadata = self.error_metadata(error)
|
|
184
|
+
error_metadata = ErrorMetadata.model_validate(metadata[self.spec.URI])
|
|
185
|
+
|
|
186
|
+
# Serialize to markdown for display
|
|
187
|
+
text_lines: list[str] = []
|
|
188
|
+
if isinstance(error_metadata.error, ErrorGroup):
|
|
189
|
+
text_lines.append(f"## {error_metadata.error.message}\n")
|
|
190
|
+
for err in error_metadata.error.errors:
|
|
191
|
+
text_lines.append(f"### {err.title}\n{err.message}")
|
|
192
|
+
else:
|
|
193
|
+
text_lines.append(f"## {error_metadata.error.title}\n{error_metadata.error.message}")
|
|
200
194
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
195
|
+
# Add context if present
|
|
196
|
+
if error_metadata.context:
|
|
197
|
+
text_lines.append(f"## Context\n```json\n{json.dumps(error_metadata.context, indent=2)}\n```")
|
|
204
198
|
|
|
205
|
-
|
|
206
|
-
|
|
199
|
+
if error_metadata.stack_trace:
|
|
200
|
+
text_lines.append(f"## Stack Trace\n```\n{error_metadata.stack_trace}\n```")
|
|
207
201
|
|
|
208
|
-
|
|
202
|
+
text = "\n\n".join(text_lines)
|
|
209
203
|
|
|
210
|
-
|
|
204
|
+
return AgentMessage(text=text, metadata=metadata)
|
|
205
|
+
except Exception as error_exc:
|
|
206
|
+
return AgentMessage(text=f"Failed to create error message: {error_exc!s}\noriginal exc: {error!s}")
|
|
211
207
|
|
|
212
208
|
|
|
213
209
|
class ErrorExtensionClient(BaseExtensionClient[ErrorExtensionSpec, ErrorMetadata]): ...
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
DEFAULT_ERROR_EXTENSION: Final = ErrorExtensionServer(ErrorExtensionSpec(ErrorExtensionParams()))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class ErrorContext(pydantic.BaseModel, arbitrary_types_allowed=True):
|
|
216
|
+
server: ErrorExtensionServer = pydantic.Field(default=DEFAULT_ERROR_EXTENSION)
|
|
217
|
+
context: JsonDict = pydantic.Field(default_factory=dict)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
get_error_extension_context, use_error_extension_context = resource_context(
|
|
221
|
+
factory=ErrorContext, default_factory=ErrorContext
|
|
222
|
+
)
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from types import NoneType
|
|
7
|
-
from typing import Any
|
|
8
7
|
|
|
9
8
|
import pydantic
|
|
10
9
|
from a2a.types import DataPart, FilePart, Part, TextPart
|
|
@@ -49,7 +48,7 @@ class TrajectoryExtensionSpec(NoParamsBaseExtensionSpec):
|
|
|
49
48
|
class TrajectoryExtensionServer(BaseExtensionServer[TrajectoryExtensionSpec, NoneType]):
|
|
50
49
|
def trajectory_metadata(
|
|
51
50
|
self, *, title: str | None = None, content: str | None = None, group_id: str | None = None
|
|
52
|
-
) -> Metadata
|
|
51
|
+
) -> Metadata:
|
|
53
52
|
return Metadata(
|
|
54
53
|
{self.spec.URI: Trajectory(title=title, content=content, group_id=group_id).model_dump(mode="json")}
|
|
55
54
|
)
|
agentstack_sdk/a2a/types.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
import typing
|
|
4
3
|
import uuid
|
|
5
|
-
from typing import
|
|
4
|
+
from typing import TYPE_CHECKING, Literal, TypeAlias
|
|
6
5
|
|
|
7
6
|
from a2a.types import (
|
|
8
7
|
Artifact,
|
|
@@ -21,11 +20,19 @@ from a2a.types import (
|
|
|
21
20
|
)
|
|
22
21
|
from pydantic import Field, model_validator
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
JsonValue: TypeAlias = list["JsonValue"] | dict[str, "JsonValue"] | str | bool | int | float | None
|
|
25
|
+
JsonDict: TypeAlias = dict[str, JsonValue]
|
|
26
|
+
else:
|
|
27
|
+
from typing import Union
|
|
26
28
|
|
|
29
|
+
from typing_extensions import TypeAliasType
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
JsonValue = TypeAliasType("JsonValue", "Union[dict[str, JsonValue], list[JsonValue], str, int, float, bool, None]") # noqa: UP007
|
|
32
|
+
JsonDict = TypeAliasType("JsonDict", "dict[str, JsonValue]")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Metadata(dict[str, JsonValue]): ...
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
RunYield: TypeAlias = (
|
|
@@ -42,7 +49,7 @@ RunYield: TypeAlias = (
|
|
|
42
49
|
| TaskStatusUpdateEvent
|
|
43
50
|
| TaskArtifactUpdateEvent
|
|
44
51
|
| str
|
|
45
|
-
|
|
|
52
|
+
| JsonDict
|
|
46
53
|
| Exception
|
|
47
54
|
)
|
|
48
55
|
RunYieldResume: TypeAlias = Message | None
|
|
@@ -50,7 +57,7 @@ RunYieldResume: TypeAlias = Message | None
|
|
|
50
57
|
|
|
51
58
|
class AgentArtifact(Artifact):
|
|
52
59
|
artifact_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
53
|
-
parts: list[Part | TextPart | FilePart | DataPart]
|
|
60
|
+
parts: list[Part | TextPart | FilePart | DataPart]
|
|
54
61
|
|
|
55
62
|
@model_validator(mode="after")
|
|
56
63
|
def text_message_validate(self):
|
|
@@ -60,7 +67,7 @@ class AgentArtifact(Artifact):
|
|
|
60
67
|
|
|
61
68
|
class ArtifactChunk(Artifact):
|
|
62
69
|
last_chunk: bool = False
|
|
63
|
-
parts: list[Part | TextPart | FilePart | DataPart]
|
|
70
|
+
parts: list[Part | TextPart | FilePart | DataPart]
|
|
64
71
|
|
|
65
72
|
@model_validator(mode="after")
|
|
66
73
|
def text_message_validate(self):
|
|
@@ -104,9 +111,3 @@ class InputRequired(TaskStatus):
|
|
|
104
111
|
|
|
105
112
|
class AuthRequired(InputRequired):
|
|
106
113
|
state: Literal[TaskState.auth_required] = TaskState.auth_required # pyright: ignore [reportIncompatibleVariableOverride]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
JsonDict = TypeAliasType(
|
|
110
|
-
"JsonDict",
|
|
111
|
-
"Union[dict[str, JsonDict], list[JsonDict], str, int, float, bool, None]", # pyright: ignore[reportDeprecated] # noqa: UP007
|
|
112
|
-
)
|
|
@@ -31,10 +31,6 @@ class ContextToken(pydantic.BaseModel):
|
|
|
31
31
|
expires_at: pydantic.AwareDatetime | None = None
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
class ResourceIdPermission(pydantic.BaseModel):
|
|
35
|
-
id: str
|
|
36
|
-
|
|
37
|
-
|
|
38
34
|
class ContextPermissions(pydantic.BaseModel):
|
|
39
35
|
files: set[Literal["read", "write", "extract", "*"]] = set()
|
|
40
36
|
vector_stores: set[Literal["read", "write", "*"]] = set()
|
|
@@ -42,8 +38,8 @@ class ContextPermissions(pydantic.BaseModel):
|
|
|
42
38
|
|
|
43
39
|
|
|
44
40
|
class Permissions(ContextPermissions):
|
|
45
|
-
llm: set[Literal["*"] |
|
|
46
|
-
embeddings: set[Literal["*"] |
|
|
41
|
+
llm: set[Literal["*"] | str] = set()
|
|
42
|
+
embeddings: set[Literal["*"] | str] = set()
|
|
47
43
|
a2a_proxy: set[Literal["*"]] = set()
|
|
48
44
|
model_providers: set[Literal["read", "write", "*"]] = set()
|
|
49
45
|
variables: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()
|
agentstack_sdk/platform/file.py
CHANGED
|
@@ -16,11 +16,20 @@ from agentstack_sdk.platform.common import PaginatedResult
|
|
|
16
16
|
from agentstack_sdk.util.file import LoadedFile, LoadedFileWithUri, PlatformFileUrl
|
|
17
17
|
from agentstack_sdk.util.utils import filter_dict
|
|
18
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
|
+
|
|
19
28
|
|
|
20
29
|
class Extraction(pydantic.BaseModel):
|
|
21
30
|
id: str
|
|
22
31
|
file_id: str
|
|
23
|
-
|
|
32
|
+
extracted_files: list[ExtractedFileInfo] = pydantic.Field(default_factory=list)
|
|
24
33
|
status: typing.Literal["pending", "in_progress", "completed", "failed", "cancelled"] = "pending"
|
|
25
34
|
job_id: str | None = None
|
|
26
35
|
error_message: str | None = None
|
|
@@ -152,9 +161,43 @@ class File(pydantic.BaseModel):
|
|
|
152
161
|
await response.aread()
|
|
153
162
|
yield LoadedFileWithUri(response=response, content_type=file.content_type, filename=file.filename)
|
|
154
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
|
+
|
|
155
197
|
async def create_extraction(
|
|
156
198
|
self: File | str,
|
|
157
199
|
*,
|
|
200
|
+
formats: list[ExtractionFormatLiteral] | None = None,
|
|
158
201
|
client: PlatformClient | None = None,
|
|
159
202
|
context_id: str | None | Literal["auto"] = "auto",
|
|
160
203
|
) -> Extraction:
|
|
@@ -167,6 +210,7 @@ class File(pydantic.BaseModel):
|
|
|
167
210
|
await platform_client.post(
|
|
168
211
|
url=f"/api/v1/files/{file_id}/extraction",
|
|
169
212
|
params=context_id and {"context_id": context_id},
|
|
213
|
+
json={"settings": {"formats": formats}} if formats else None,
|
|
170
214
|
)
|
|
171
215
|
)
|
|
172
216
|
.raise_for_status()
|
agentstack_sdk/platform/user.py
CHANGED
|
@@ -3,23 +3,68 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from enum import StrEnum
|
|
7
7
|
|
|
8
8
|
import pydantic
|
|
9
9
|
|
|
10
10
|
from agentstack_sdk.platform.client import PlatformClient, get_platform_client
|
|
11
|
+
from agentstack_sdk.platform.common import PaginatedResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UserRole(StrEnum):
|
|
15
|
+
ADMIN = "admin"
|
|
16
|
+
DEVELOPER = "developer"
|
|
17
|
+
USER = "user"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ChangeRoleResponse(pydantic.BaseModel):
|
|
21
|
+
user_id: str
|
|
22
|
+
new_role: UserRole
|
|
11
23
|
|
|
12
24
|
|
|
13
25
|
class User(pydantic.BaseModel):
|
|
14
26
|
id: str
|
|
15
|
-
role:
|
|
16
|
-
email:
|
|
27
|
+
role: UserRole
|
|
28
|
+
email: str
|
|
17
29
|
created_at: pydantic.AwareDatetime
|
|
30
|
+
role_updated_at: pydantic.AwareDatetime | None = None
|
|
18
31
|
|
|
19
32
|
@staticmethod
|
|
20
33
|
async def get(*, client: PlatformClient | None = None) -> User:
|
|
21
|
-
"""Get the current user information."""
|
|
22
34
|
async with client or get_platform_client() as client:
|
|
23
35
|
return pydantic.TypeAdapter(User).validate_python(
|
|
24
36
|
(await client.get(url="/api/v1/user")).raise_for_status().json()
|
|
25
37
|
)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
async def list(
|
|
41
|
+
*,
|
|
42
|
+
email: str | None = None,
|
|
43
|
+
limit: int = 40,
|
|
44
|
+
page_token: str | None = None,
|
|
45
|
+
client: PlatformClient | None = None,
|
|
46
|
+
) -> PaginatedResult[User]:
|
|
47
|
+
async with client or get_platform_client() as client:
|
|
48
|
+
params: dict[str, int | str] = {"limit": limit}
|
|
49
|
+
if email:
|
|
50
|
+
params["email"] = email
|
|
51
|
+
if page_token:
|
|
52
|
+
params["page_token"] = page_token
|
|
53
|
+
|
|
54
|
+
return pydantic.TypeAdapter(PaginatedResult[User]).validate_python(
|
|
55
|
+
(await client.get(url="/api/v1/users", params=params)).raise_for_status().json()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
async def set_role(
|
|
60
|
+
user_id: str,
|
|
61
|
+
new_role: UserRole,
|
|
62
|
+
*,
|
|
63
|
+
client: PlatformClient | None = None,
|
|
64
|
+
) -> ChangeRoleResponse:
|
|
65
|
+
async with client or get_platform_client() as client:
|
|
66
|
+
return pydantic.TypeAdapter(ChangeRoleResponse).validate_python(
|
|
67
|
+
(await client.put(url=f"/api/v1/users/{user_id}/role", json={"new_role": new_role}))
|
|
68
|
+
.raise_for_status()
|
|
69
|
+
.json()
|
|
70
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
import pydantic
|
|
8
|
+
|
|
9
|
+
from agentstack_sdk.platform.client import PlatformClient, get_platform_client
|
|
10
|
+
from agentstack_sdk.platform.common import PaginatedResult
|
|
11
|
+
from agentstack_sdk.util.utils import filter_dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UserFeedback(pydantic.BaseModel):
|
|
15
|
+
id: UUID
|
|
16
|
+
provider_id: UUID
|
|
17
|
+
task_id: UUID
|
|
18
|
+
context_id: UUID
|
|
19
|
+
rating: int
|
|
20
|
+
message: str
|
|
21
|
+
comment: str | None = None
|
|
22
|
+
comment_tags: list[str] | None = None
|
|
23
|
+
created_at: datetime
|
|
24
|
+
agent_name: str
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
async def list(
|
|
28
|
+
*,
|
|
29
|
+
provider_id: str | None = None,
|
|
30
|
+
limit: int = 50,
|
|
31
|
+
after_cursor: str | None = None,
|
|
32
|
+
client: PlatformClient | None = None,
|
|
33
|
+
) -> "ListUserFeedbackResponse":
|
|
34
|
+
async with client or get_platform_client() as client:
|
|
35
|
+
params = filter_dict({"provider_id": provider_id, "limit": limit, "after_cursor": after_cursor})
|
|
36
|
+
return pydantic.TypeAdapter(ListUserFeedbackResponse).validate_python(
|
|
37
|
+
(await client.get(url="/api/v1/user_feedback", params=params)).raise_for_status().json()
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ListUserFeedbackResponse(PaginatedResult[UserFeedback]):
|
|
42
|
+
pass
|