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,223 @@
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 json
7
+ import logging
8
+ import traceback
9
+ from collections.abc import AsyncIterator
10
+ from contextlib import asynccontextmanager
11
+ from types import NoneType
12
+ from typing import Any, Final
13
+
14
+ import pydantic
15
+
16
+ from agentstack_sdk.a2a.extensions.base import (
17
+ BaseExtensionClient,
18
+ BaseExtensionServer,
19
+ BaseExtensionSpec,
20
+ )
21
+ from agentstack_sdk.a2a.types import AgentMessage, Metadata
22
+ from agentstack_sdk.types import JsonValue
23
+ from agentstack_sdk.util import resource_context
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class Error(pydantic.BaseModel):
29
+ """
30
+ Represents error information for displaying exceptions in the UI.
31
+
32
+ This extension helps display errors in a user-friendly way with:
33
+ - A clear error title (exception type)
34
+ - A descriptive error message
35
+
36
+ Visually, this may appear as an error card in the UI.
37
+
38
+ Properties:
39
+ - title: Title of the error (typically the exception class name).
40
+ - message: The error message describing what went wrong.
41
+ """
42
+
43
+ title: str
44
+ message: str
45
+
46
+
47
+ class ErrorGroup(pydantic.BaseModel):
48
+ """
49
+ Represents a group of errors.
50
+
51
+ Properties:
52
+ - message: A message describing the group of errors.
53
+ - errors: A list of error objects.
54
+ """
55
+
56
+ message: str
57
+ errors: list[Error]
58
+
59
+
60
+ class ErrorMetadata(pydantic.BaseModel):
61
+ """
62
+ Metadata containing an error (or group of errors) and an optional stack trace.
63
+
64
+ Properties:
65
+ - error: The error object or group of errors.
66
+ - stack_trace: Optional formatted stack trace for debugging.
67
+ - context: Optional context dictionary.
68
+ """
69
+
70
+ error: Error | ErrorGroup
71
+ stack_trace: str | None = None
72
+ context: JsonValue | None = None
73
+
74
+
75
+ class ErrorExtensionParams(pydantic.BaseModel):
76
+ """
77
+ Configuration parameters for the error extension.
78
+
79
+ Properties:
80
+ - include_stacktrace: Whether to include stack traces in error messages (default: False).
81
+ """
82
+
83
+ include_stacktrace: bool = False
84
+
85
+
86
+ class ErrorExtensionSpec(BaseExtensionSpec[ErrorExtensionParams]):
87
+ URI: str = "https://a2a-extensions.agentstack.beeai.dev/ui/error/v1"
88
+
89
+
90
+ def _format_stacktrace(exc: BaseException, include_cause: bool = True) -> str:
91
+ """Format exception with full traceback including nested causes."""
92
+ return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__, chain=include_cause))
93
+
94
+
95
+ def _extract_error(exc: BaseException) -> Error | ErrorGroup:
96
+ """
97
+ Extract error information from an exception, handling:
98
+ - BaseExceptionGroup (returns ErrorGroup)
99
+ - FrameworkError from beeai_framework (uses .explain() method)
100
+ """
101
+ # Handle BaseExceptionGroup by recursively extracting errors from each exception
102
+ if isinstance(exc, BaseExceptionGroup):
103
+ errors: list[Error] = []
104
+ for sub_exc in exc.exceptions:
105
+ extracted = _extract_error(sub_exc)
106
+ if isinstance(extracted, ErrorGroup):
107
+ errors.extend(extracted.errors)
108
+ else:
109
+ errors.append(extracted)
110
+ return ErrorGroup(message=str(exc), errors=errors)
111
+
112
+ # Try to handle FrameworkError if beeai_framework is available
113
+ try:
114
+ from beeai_framework.errors import FrameworkError
115
+
116
+ if isinstance(exc, FrameworkError):
117
+ # FrameworkError has special .explain() method
118
+ return Error(title=exc.name(), message=exc.explain())
119
+ except ImportError:
120
+ # beeai_framework not installed, continue with standard handling
121
+ pass
122
+
123
+ return Error(title=type(exc).__name__, message=str(exc))
124
+
125
+
126
+ class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
127
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
128
+ super().__init__(*args, **kwargs)
129
+
130
+ @asynccontextmanager
131
+ async def lifespan(self) -> AsyncIterator[None]:
132
+ """Set up request-scoped error context using ContextVar."""
133
+ with use_error_extension_context(server=self, context={}):
134
+ yield
135
+
136
+ @property
137
+ def context(self) -> JsonValue:
138
+ """Get the current request's error context."""
139
+ try:
140
+ return get_error_extension_context().context
141
+ except LookupError:
142
+ # Fallback for when lifespan hasn't been entered yet
143
+ logger.warning(
144
+ "Attempted to use error context when the error extension is not initialized. Make sure to add the ErrorExtensionServer to the agent dependencies."
145
+ )
146
+ return {}
147
+
148
+ def error_metadata(self, error: BaseException) -> Metadata:
149
+ """
150
+ Create metadata for an error.
151
+
152
+ Args:
153
+ error: The exception to convert to metadata
154
+
155
+ Returns:
156
+ Metadata dictionary with error information
157
+ """
158
+ error_data = _extract_error(error)
159
+ stack_trace = _format_stacktrace(error) if self.spec.params.include_stacktrace else None
160
+ return Metadata(
161
+ {
162
+ self.spec.URI: ErrorMetadata(
163
+ error=error_data,
164
+ stack_trace=stack_trace,
165
+ context=self.context or None,
166
+ ).model_dump(mode="json")
167
+ }
168
+ )
169
+
170
+ def message(
171
+ self,
172
+ error: BaseException,
173
+ ) -> AgentMessage:
174
+ """
175
+ Create an AgentMessage with error metadata and serialized text representation.
176
+
177
+ Args:
178
+ error: The exception to include in the message
179
+
180
+ Returns:
181
+ AgentMessage with error metadata and markdown-formatted text
182
+ """
183
+ try:
184
+ metadata = self.error_metadata(error)
185
+ error_metadata = ErrorMetadata.model_validate(metadata[self.spec.URI])
186
+
187
+ # Serialize to markdown for display
188
+ text_lines: list[str] = []
189
+ if isinstance(error_metadata.error, ErrorGroup):
190
+ text_lines.append(f"## {error_metadata.error.message}\n")
191
+ for err in error_metadata.error.errors:
192
+ text_lines.append(f"### {err.title}\n{err.message}")
193
+ else:
194
+ text_lines.append(f"## {error_metadata.error.title}\n{error_metadata.error.message}")
195
+
196
+ # Add context if present
197
+ if error_metadata.context:
198
+ text_lines.append(f"## Context\n```json\n{json.dumps(error_metadata.context, indent=2)}\n```")
199
+
200
+ if error_metadata.stack_trace:
201
+ text_lines.append(f"## Stack Trace\n```\n{error_metadata.stack_trace}\n```")
202
+
203
+ text = "\n\n".join(text_lines)
204
+
205
+ return AgentMessage(text=text, metadata=metadata)
206
+ except Exception as error_exc:
207
+ return AgentMessage(text=f"Failed to create error message: {error_exc!s}\noriginal exc: {error!s}")
208
+
209
+
210
+ class ErrorExtensionClient(BaseExtensionClient[ErrorExtensionSpec, ErrorMetadata]): ...
211
+
212
+
213
+ DEFAULT_ERROR_EXTENSION: Final = ErrorExtensionServer(ErrorExtensionSpec(ErrorExtensionParams()))
214
+
215
+
216
+ class ErrorContext(pydantic.BaseModel, arbitrary_types_allowed=True):
217
+ server: ErrorExtensionServer = pydantic.Field(default=DEFAULT_ERROR_EXTENSION)
218
+ context: JsonValue = pydantic.Field(default_factory=dict)
219
+
220
+
221
+ get_error_extension_context, use_error_extension_context = resource_context(
222
+ factory=ErrorContext, default_factory=ErrorContext
223
+ )
@@ -0,0 +1,52 @@
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 typing import TYPE_CHECKING, TypeVar, cast
7
+
8
+ from a2a.server.agent_execution.context import RequestContext
9
+ from a2a.types import Message as A2AMessage
10
+ from pydantic import TypeAdapter
11
+ from typing_extensions import override
12
+
13
+ from agentstack_sdk.a2a.extensions.base import (
14
+ BaseExtensionClient,
15
+ BaseExtensionServer,
16
+ NoParamsBaseExtensionSpec,
17
+ )
18
+ from agentstack_sdk.a2a.extensions.common.form import FormRender, FormResponse
19
+ from agentstack_sdk.a2a.types import AgentMessage, InputRequired
20
+
21
+ if TYPE_CHECKING:
22
+ from agentstack_sdk.server.context import RunContext
23
+
24
+ T = TypeVar("T")
25
+
26
+
27
+ class FormRequestExtensionSpec(NoParamsBaseExtensionSpec):
28
+ URI: str = "https://a2a-extensions.agentstack.beeai.dev/ui/form_request/v1"
29
+
30
+
31
+ class FormRequestExtensionServer(BaseExtensionServer[FormRequestExtensionSpec, FormResponse]):
32
+ @override
33
+ def handle_incoming_message(self, message: A2AMessage, run_context: RunContext, request_context: RequestContext):
34
+ super().handle_incoming_message(message, run_context, request_context)
35
+ self.context = run_context
36
+
37
+ async def request_form(self, *, form: FormRender, model: type[T] = FormResponse) -> T | None:
38
+ message = await self.context.yield_async(
39
+ InputRequired(message=AgentMessage(text=form.title, metadata={self.spec.URI: form}))
40
+ )
41
+ return self.parse_form_response(message=message, model=model) if message else None
42
+
43
+ def parse_form_response(self, *, message: A2AMessage, model: type[T] = FormResponse) -> T | None:
44
+ form_response = self.parse_client_metadata(message)
45
+ if form_response is None:
46
+ return None
47
+ if model is FormResponse:
48
+ return cast(T, form_response)
49
+ return TypeAdapter(model).validate_python(dict(form_response))
50
+
51
+
52
+ class FormRequestExtensionClient(BaseExtensionClient[FormRequestExtensionSpec, FormRender]): ...
@@ -0,0 +1,73 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Literal
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from agentstack_sdk.a2a.extensions.base import BaseExtensionClient, BaseExtensionServer, BaseExtensionSpec
12
+
13
+
14
+ class CheckboxField(BaseModel):
15
+ id: str
16
+ label: str
17
+ default_value: bool = False
18
+
19
+
20
+ class CheckboxGroupField(BaseModel):
21
+ id: str
22
+ type: Literal["checkbox_group"] = "checkbox_group"
23
+ fields: list[CheckboxField]
24
+
25
+
26
+ class OptionItem(BaseModel):
27
+ label: str
28
+ value: str
29
+
30
+
31
+ class SingleSelectField(BaseModel):
32
+ type: Literal["single_select"] = "single_select"
33
+ id: str
34
+ label: str
35
+ options: list[OptionItem]
36
+ default_value: str
37
+
38
+
39
+ class SettingsRender(BaseModel):
40
+ fields: list[CheckboxGroupField | SingleSelectField]
41
+
42
+
43
+ class CheckboxFieldValue(BaseModel):
44
+ value: bool | None = None
45
+
46
+
47
+ class CheckboxGroupFieldValue(BaseModel):
48
+ type: Literal["checkbox_group"] = "checkbox_group"
49
+ values: dict[str, CheckboxFieldValue]
50
+
51
+
52
+ class SingleSelectFieldValue(BaseModel):
53
+ type: Literal["single_select"] = "single_select"
54
+ value: str | None = None
55
+
56
+
57
+ SettingsFieldValue = CheckboxGroupFieldValue | SingleSelectFieldValue
58
+
59
+
60
+ class AgentRunSettings(BaseModel):
61
+ values: dict[str, SettingsFieldValue]
62
+
63
+
64
+ class SettingsExtensionSpec(BaseExtensionSpec[SettingsRender | None]):
65
+ URI: str = "https://a2a-extensions.agentstack.beeai.dev/ui/settings/v1"
66
+
67
+
68
+ class SettingsExtensionServer(BaseExtensionServer[SettingsExtensionSpec, AgentRunSettings]):
69
+ def parse_settings_response(self) -> AgentRunSettings:
70
+ return AgentRunSettings.model_validate(self._metadata_from_client)
71
+
72
+
73
+ class SettingsExtensionClient(BaseExtensionClient[SettingsExtensionSpec, SettingsRender]): ...
@@ -0,0 +1,70 @@
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 types import NoneType
7
+
8
+ import pydantic
9
+ from a2a.types import DataPart, FilePart, Part, TextPart
10
+
11
+ from agentstack_sdk.a2a.extensions.base import (
12
+ BaseExtensionClient,
13
+ BaseExtensionServer,
14
+ NoParamsBaseExtensionSpec,
15
+ )
16
+ from agentstack_sdk.a2a.types import AgentMessage, Metadata
17
+
18
+
19
+ class Trajectory(pydantic.BaseModel):
20
+ """
21
+ Represents trajectory information for an agent's reasoning or tool execution
22
+ steps. Helps track the agent's decision-making process and provides
23
+ transparency into how the agent arrived at its response.
24
+
25
+ Trajectory can capture intermediate steps like:
26
+ - A reasoning step with a message
27
+ - A tool execution with tool name, input, and output
28
+
29
+ This information can be used for debugging, audit trails, and providing
30
+ users with insight into the agent's thought process.
31
+
32
+ Visually, this may appear as an accordion component in the UI.
33
+
34
+ Properties:
35
+ - title: Title of the trajectory update.
36
+ - content: Markdown-formatted content of the trajectory update.
37
+ """
38
+
39
+ title: str | None = None
40
+ content: str | None = None
41
+ group_id: str | None = None
42
+
43
+
44
+ class TrajectoryExtensionSpec(NoParamsBaseExtensionSpec):
45
+ URI: str = "https://a2a-extensions.agentstack.beeai.dev/ui/trajectory/v1"
46
+
47
+
48
+ class TrajectoryExtensionServer(BaseExtensionServer[TrajectoryExtensionSpec, NoneType]):
49
+ def trajectory_metadata(
50
+ self, *, title: str | None = None, content: str | None = None, group_id: str | None = None
51
+ ) -> Metadata:
52
+ return Metadata(
53
+ {self.spec.URI: Trajectory(title=title, content=content, group_id=group_id).model_dump(mode="json")}
54
+ )
55
+
56
+ def message(
57
+ self,
58
+ text: str | None = None,
59
+ parts: list[Part | TextPart | FilePart | DataPart] | None = None,
60
+ trajectory_title: str | None = None,
61
+ trajectory_content: str | None = None,
62
+ ) -> AgentMessage:
63
+ return AgentMessage(
64
+ text=text,
65
+ parts=parts or [],
66
+ metadata=self.trajectory_metadata(title=trajectory_title, content=trajectory_content),
67
+ )
68
+
69
+
70
+ class TrajectoryExtensionClient(BaseExtensionClient[TrajectoryExtensionSpec, Trajectory]): ...
@@ -0,0 +1,104 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ import uuid
4
+ from typing import Literal, TypeAlias
5
+
6
+ from a2a.types import (
7
+ Artifact,
8
+ DataPart,
9
+ FilePart,
10
+ FileWithBytes,
11
+ FileWithUri,
12
+ Message,
13
+ Part,
14
+ Role,
15
+ TaskArtifactUpdateEvent,
16
+ TaskState,
17
+ TaskStatus,
18
+ TaskStatusUpdateEvent,
19
+ TextPart,
20
+ )
21
+ from pydantic import Field, model_validator
22
+
23
+ from agentstack_sdk.types import JsonDict, JsonValue
24
+
25
+
26
+ class Metadata(dict[str, JsonValue]): ...
27
+
28
+
29
+ RunYield: TypeAlias = (
30
+ Message # includes AgentMessage (subclass)
31
+ | Part
32
+ | TaskStatus # includes InputRequired and AuthRequired (subclasses)
33
+ | Artifact
34
+ | TextPart
35
+ | FilePart
36
+ | FileWithBytes
37
+ | FileWithUri
38
+ | Metadata
39
+ | DataPart
40
+ | TaskStatusUpdateEvent
41
+ | TaskArtifactUpdateEvent
42
+ | str
43
+ | JsonDict
44
+ | Exception
45
+ )
46
+ RunYieldResume: TypeAlias = Message | None
47
+
48
+
49
+ class AgentArtifact(Artifact):
50
+ artifact_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
51
+ parts: list[Part | TextPart | FilePart | DataPart]
52
+
53
+ @model_validator(mode="after")
54
+ def text_message_validate(self):
55
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts] # pyright: ignore [reportIncompatibleVariableOverride]
56
+ return self
57
+
58
+
59
+ class ArtifactChunk(Artifact):
60
+ last_chunk: bool = False
61
+ parts: list[Part | TextPart | FilePart | DataPart]
62
+
63
+ @model_validator(mode="after")
64
+ def text_message_validate(self):
65
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts] # pyright: ignore [reportIncompatibleVariableOverride]
66
+ return self
67
+
68
+
69
+ class AgentMessage(Message):
70
+ message_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
71
+ role: Literal[Role.agent] = Role.agent # pyright: ignore [reportIncompatibleVariableOverride]
72
+ text: str | None = Field(default=None, exclude=True)
73
+ parts: list[Part | TextPart | FilePart | DataPart] = Field(default_factory=list)
74
+
75
+ @model_validator(mode="after")
76
+ def text_message_validate(self):
77
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts] # pyright: ignore [reportIncompatibleVariableOverride]
78
+ if self.parts and self.text is not None:
79
+ raise ValueError("Message cannot have both parts and text")
80
+ if self.text is not None:
81
+ self.parts.append(Part(root=TextPart(text=self.text)))
82
+ return self
83
+
84
+
85
+ class InputRequired(TaskStatus):
86
+ message: Message | None = None
87
+ state: Literal[TaskState.input_required] = TaskState.input_required # pyright: ignore [reportIncompatibleVariableOverride]
88
+ text: str | None = Field(default=None, exclude=True)
89
+ parts: list[Part | TextPart | DataPart | FilePart] = Field(exclude=True, default_factory=list)
90
+
91
+ @model_validator(mode="after")
92
+ def text_message_validate(self):
93
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts]
94
+ if sum((self.message is not None, self.text is not None, bool(self.parts))) != 1:
95
+ raise ValueError("At most one of message, text, or parts must be provided.")
96
+ if self.text is not None:
97
+ self.message = AgentMessage(text=self.text)
98
+ elif self.parts:
99
+ self.message = AgentMessage(parts=self.parts)
100
+ return self
101
+
102
+
103
+ class AuthRequired(InputRequired):
104
+ state: Literal[TaskState.auth_required] = TaskState.auth_required # pyright: ignore [reportIncompatibleVariableOverride]
@@ -0,0 +1,12 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from .client import *
5
+ from .configuration import *
6
+ from .file import *
7
+ from .model_provider import *
8
+ from .provider import *
9
+ from .provider_build import *
10
+ from .user import *
11
+ from .user_feedback import *
12
+ from .vector_store import *
@@ -0,0 +1,123 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ import asyncio
4
+ import contextlib
5
+ import os
6
+ import ssl
7
+ import typing
8
+ from collections.abc import AsyncIterator, Mapping
9
+ from types import TracebackType
10
+
11
+ import httpx
12
+ from httpx import URL, AsyncBaseTransport
13
+ from httpx._client import EventHook
14
+ from httpx._config import DEFAULT_LIMITS, DEFAULT_MAX_REDIRECTS, Limits
15
+ from httpx._types import AuthTypes, CertTypes, CookieTypes, HeaderTypes, ProxyTypes, QueryParamTypes, TimeoutTypes
16
+ from pydantic import Secret
17
+ from typing_extensions import override
18
+
19
+ from agentstack_sdk.util import resource_context
20
+
21
+ DEFAULT_SDK_TIMEOUT: typing.Final = httpx.Timeout(timeout=30, read=None)
22
+
23
+
24
+ class PlatformClient(httpx.AsyncClient):
25
+ context_id: str | None = None
26
+
27
+ def __init__(
28
+ self,
29
+ context_id: str | None = None, # Enter context scope
30
+ auth_token: str | Secret[str] | None = None,
31
+ *,
32
+ auth: AuthTypes | None = None,
33
+ params: QueryParamTypes | None = None,
34
+ headers: HeaderTypes | None = None,
35
+ cookies: CookieTypes | None = None,
36
+ verify: ssl.SSLContext | str | bool = True,
37
+ cert: CertTypes | None = None,
38
+ http1: bool = True,
39
+ http2: bool = False,
40
+ proxy: ProxyTypes | None = None,
41
+ mounts: None | (Mapping[str, AsyncBaseTransport | None]) = None,
42
+ timeout: TimeoutTypes = DEFAULT_SDK_TIMEOUT,
43
+ follow_redirects: bool = False,
44
+ limits: Limits = DEFAULT_LIMITS,
45
+ max_redirects: int = DEFAULT_MAX_REDIRECTS,
46
+ event_hooks: None | (Mapping[str, list[EventHook]]) = None,
47
+ base_url: URL | str = "",
48
+ transport: AsyncBaseTransport | None = None,
49
+ trust_env: bool = True,
50
+ default_encoding: str | typing.Callable[[bytes], str] = "utf-8",
51
+ ) -> None:
52
+ if not base_url:
53
+ base_url = os.environ.get("PLATFORM_URL", "http://127.0.0.1:8333")
54
+ super().__init__(
55
+ auth=auth,
56
+ params=params,
57
+ headers=headers,
58
+ cookies=cookies,
59
+ verify=verify,
60
+ cert=cert,
61
+ http1=http1,
62
+ http2=http2,
63
+ proxy=proxy,
64
+ mounts=mounts,
65
+ timeout=timeout,
66
+ follow_redirects=follow_redirects,
67
+ limits=limits,
68
+ max_redirects=max_redirects,
69
+ event_hooks=event_hooks,
70
+ base_url=base_url,
71
+ transport=transport,
72
+ trust_env=trust_env,
73
+ default_encoding=default_encoding,
74
+ )
75
+ self.context_id = context_id
76
+ if auth_token:
77
+ self.headers["Authorization"] = f"Bearer {auth_token}"
78
+ self._ref_count: int = 0
79
+ self._context_manager_lock: asyncio.Lock = asyncio.Lock()
80
+
81
+ @override
82
+ async def __aenter__(self) -> typing.Self:
83
+ async with self._context_manager_lock:
84
+ self._ref_count += 1
85
+ if self._ref_count == 1:
86
+ _ = await super().__aenter__()
87
+ return self
88
+
89
+ @override
90
+ async def __aexit__(
91
+ self,
92
+ exc_type: type[BaseException] | None = None,
93
+ exc_value: BaseException | None = None,
94
+ traceback: TracebackType | None = None,
95
+ ) -> None:
96
+ async with self._context_manager_lock:
97
+ self._ref_count -= 1
98
+ if self._ref_count == 0:
99
+ await super().__aexit__(exc_type, exc_value, traceback)
100
+
101
+
102
+ get_platform_client, set_platform_client = resource_context(factory=PlatformClient, default_factory=PlatformClient)
103
+
104
+ P = typing.ParamSpec("P")
105
+ T = typing.TypeVar("T", bound=PlatformClient)
106
+
107
+
108
+ def wrap_context(
109
+ context: typing.Callable[P, contextlib.AbstractContextManager[T]],
110
+ ) -> typing.Callable[P, contextlib.AbstractAsyncContextManager[T]]:
111
+ @contextlib.asynccontextmanager
112
+ async def use_async_resource(*args: P.args, **kwargs: P.kwargs) -> AsyncIterator[T]:
113
+ with context(*args, **kwargs) as resource:
114
+ async with resource:
115
+ yield resource
116
+
117
+ return use_async_resource
118
+
119
+
120
+ use_platform_client = wrap_context(set_platform_client)
121
+
122
+
123
+ __all__ = ["PlatformClient", "get_platform_client", "set_platform_client", "use_platform_client"]
@@ -0,0 +1,37 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from enum import StrEnum
4
+ from typing import Generic, TypeVar
5
+
6
+ from pydantic import BaseModel
7
+
8
+ T = TypeVar("T", bound=BaseModel)
9
+
10
+
11
+ class PaginatedResult(BaseModel, Generic[T]):
12
+ items: list[T]
13
+ total_count: int
14
+ has_more: bool = False
15
+ next_page_token: str | None = None
16
+
17
+
18
+ class GithubVersionType(StrEnum):
19
+ HEAD = "head"
20
+ TAG = "tag"
21
+
22
+
23
+ class ResolvedGithubUrl(BaseModel):
24
+ host: str = "github.com"
25
+ org: str
26
+ repo: str
27
+ version: str
28
+ version_type: GithubVersionType
29
+ commit_hash: str
30
+ path: str | None = None
31
+
32
+
33
+ class ResolvedDockerImageID(BaseModel):
34
+ registry: str
35
+ repository: str
36
+ tag: str
37
+ digest: str