agentstack-sdk 0.6.1rc1__tar.gz → 0.6.2__tar.gz

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 (78) hide show
  1. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/PKG-INFO +3 -1
  2. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/pyproject.toml +10 -7
  3. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/oauth/oauth.py +1 -1
  4. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/base.py +1 -8
  5. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/common/form.py +2 -1
  6. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/services/form.py +15 -15
  7. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/services/mcp.py +2 -1
  8. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/tools/call.py +1 -1
  9. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/form_request.py +20 -12
  10. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/types.py +12 -6
  11. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/context.py +2 -1
  12. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/file.py +2 -1
  13. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/model_provider.py +9 -2
  14. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/agent.py +7 -6
  15. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/app.py +3 -2
  16. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/context.py +2 -6
  17. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/middleware/platform_auth_backend.py +1 -1
  18. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/server.py +7 -5
  19. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/store/context_store.py +2 -5
  20. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/telemetry.py +15 -0
  21. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/util/file.py +1 -1
  22. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/util/logging.py +2 -2
  23. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/util/resource_context.py +3 -1
  24. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/util/utils.py +3 -5
  25. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/README.md +0 -0
  26. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/__init__.py +0 -0
  27. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/__init__.py +0 -0
  28. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/__init__.py +0 -0
  29. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/__init__.py +0 -0
  30. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/oauth/__init__.py +0 -0
  31. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py +0 -0
  32. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py +0 -0
  33. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py +0 -0
  34. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/secrets/__init__.py +0 -0
  35. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/auth/secrets/secrets.py +0 -0
  36. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/common/__init__.py +0 -0
  37. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/exceptions.py +0 -0
  38. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/interactions/__init__.py +0 -0
  39. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/interactions/approval.py +0 -0
  40. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/services/__init__.py +0 -0
  41. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/services/embedding.py +0 -0
  42. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/services/llm.py +0 -0
  43. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/services/platform.py +0 -0
  44. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/tools/__init__.py +0 -0
  45. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/tools/exceptions.py +0 -0
  46. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/__init__.py +0 -0
  47. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/agent_detail.py +0 -0
  48. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/canvas.py +0 -0
  49. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/citation.py +0 -0
  50. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/error.py +0 -0
  51. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/settings.py +0 -0
  52. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/a2a/extensions/ui/trajectory.py +0 -0
  53. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/__init__.py +0 -0
  54. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/client.py +0 -0
  55. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/common.py +0 -0
  56. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/configuration.py +0 -0
  57. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/connector.py +0 -0
  58. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/provider.py +0 -0
  59. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/provider_build.py +0 -0
  60. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/provider_discovery.py +0 -0
  61. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/types.py +0 -0
  62. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/user.py +0 -0
  63. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/user_feedback.py +0 -0
  64. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/variables.py +0 -0
  65. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/platform/vector_store.py +0 -0
  66. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/py.typed +0 -0
  67. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/__init__.py +0 -0
  68. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/constants.py +0 -0
  69. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/dependencies.py +0 -0
  70. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/exceptions.py +0 -0
  71. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/middleware/__init__.py +0 -0
  72. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/store/__init__.py +0 -0
  73. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/store/memory_context_store.py +0 -0
  74. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/store/platform_context_store.py +0 -0
  75. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/server/utils.py +0 -0
  76. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/types.py +0 -0
  77. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/util/__init__.py +0 -0
  78. {agentstack_sdk-0.6.1rc1 → agentstack_sdk-0.6.2}/src/agentstack_sdk/util/httpx.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentstack-sdk
3
- Version: 0.6.1rc1
3
+ Version: 0.6.2
4
4
  Summary: Agent Stack SDK
5
5
  Author: IBM Corp.
6
6
  Requires-Dist: a2a-sdk==0.3.21
@@ -22,6 +22,8 @@ Requires-Dist: fastapi>=0.116.1
22
22
  Requires-Dist: authlib>=1.3.0
23
23
  Requires-Dist: async-lru>=2.0.4
24
24
  Requires-Dist: cachetools>=6.2.3
25
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1
26
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.52.3
25
27
  Requires-Python: >=3.11, <3.14
26
28
  Description-Content-Type: text/markdown
27
29
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentstack-sdk"
3
- version = "0.6.1-rc1"
3
+ version = "0.6.2"
4
4
  description = "Agent Stack SDK"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "IBM Corp." }]
@@ -25,12 +25,14 @@ dependencies = [
25
25
  "authlib>=1.3.0",
26
26
  "async-lru>=2.0.4",
27
27
  "cachetools>=6.2.3",
28
+ "opentelemetry-instrumentation-httpx>=0.60b1",
29
+ "opentelemetry-instrumentation-openai>=0.52.3",
28
30
  ]
29
31
 
30
32
  [dependency-groups]
31
33
  dev = [
32
34
  "beeai-framework[duckduckgo,wikipedia]>=0.1.76",
33
- "pyright>=1.1.403",
35
+ "pyrefly>=0.52.0",
34
36
  "pytest>=8.4.1",
35
37
  "pytest-asyncio>=1.1.0",
36
38
  "pytest-httpx>=0.35.0",
@@ -64,7 +66,7 @@ lint.select = [
64
66
  lint.ignore = [
65
67
  "E501", # line lenght (annyoing)
66
68
  "N999", # invalid module name agentstack-server (yeah, we use a dash, deal with it)
67
- "F403", # redundant with Pyright
69
+ "F403", # redundant with type checking
68
70
  ]
69
71
  force-exclude = true
70
72
 
@@ -74,7 +76,8 @@ asyncio_mode = "auto"
74
76
  asyncio_default_fixture_loop_scope = "function"
75
77
  addopts = "-v"
76
78
 
77
- [tool.pyright]
78
- ignore = ["tests/**", "examples/cli.py"]
79
- venvPath = "."
80
- venv = ".venv"
79
+ [tool.pyrefly]
80
+ project-includes = [
81
+ "**/*.py*",
82
+ "**/*.ipynb",
83
+ ]
@@ -145,7 +145,7 @@ class OAuthExtensionClient(BaseExtensionClient[OAuthExtensionSpec, NoneType]):
145
145
  return A2AMessage(
146
146
  message_id=str(uuid.uuid4()),
147
147
  role=Role.user,
148
- parts=[TextPart(text="Authorization completed")], # type: ignore
148
+ parts=[TextPart(text="Authorization completed")],
149
149
  task_id=task_id,
150
150
  metadata={self.spec.URI: data.model_dump(mode="json")},
151
151
  )
@@ -51,11 +51,6 @@ class BaseExtensionSpec(abc.ABC, typing.Generic[ParamsT]):
51
51
  Description to be attached with the extension spec.
52
52
  """
53
53
 
54
- Params: type[ParamsT]
55
- """
56
- Type of the extension params, attached to the agent card.
57
- """
58
-
59
54
  def __init_subclass__(cls, **kwargs):
60
55
  super().__init_subclass__(**kwargs)
61
56
  cls.Params = _get_generic_args(cls, BaseExtensionSpec)[0]
@@ -72,7 +67,7 @@ class BaseExtensionSpec(abc.ABC, typing.Generic[ParamsT]):
72
67
  self.params = params
73
68
 
74
69
  @classmethod
75
- def from_agent_card(cls, agent: AgentCard) -> typing.Self | None:
70
+ def from_agent_card(cls: type[BaseExtensionSpec], agent: AgentCard) -> typing.Self | None:
76
71
  """
77
72
  Client should construct an extension instance using this classmethod.
78
73
  """
@@ -120,7 +115,6 @@ ExtensionSpecT = typing.TypeVar("ExtensionSpecT", bound=BaseExtensionSpec[typing
120
115
 
121
116
 
122
117
  class BaseExtensionServer(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromClientT]):
123
- MetadataFromClient: type[MetadataFromClientT]
124
118
  """
125
119
  Type of the extension metadata, attached to messages.
126
120
  """
@@ -182,7 +176,6 @@ class BaseExtensionServer(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromCl
182
176
 
183
177
 
184
178
  class BaseExtensionClient(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromServerT]):
185
- MetadataFromServer: type[MetadataFromServerT]
186
179
  """
187
180
  Type of the extension metadata, attached to messages.
188
181
  """
@@ -1,6 +1,7 @@
1
1
  # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ from collections.abc import Generator
4
5
  from typing import Literal
5
6
 
6
7
  from pydantic import BaseModel, Field, model_validator
@@ -137,7 +138,7 @@ FormFieldValue = (
137
138
  class FormResponse(BaseModel):
138
139
  values: dict[str, FormFieldValue]
139
140
 
140
- def __iter__(self):
141
+ def __iter__(self) -> Generator[tuple[str, list[dict[str, str | None]] | list[str] | str | bool | None]]:
141
142
  for key, value in self.values.items():
142
143
  match value:
143
144
  case FileFieldValue():
@@ -4,7 +4,7 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import Self, TypeVar, cast
7
+ from typing import Any, Self, TypeVar
8
8
 
9
9
  from pydantic import BaseModel, TypeAdapter
10
10
  from typing_extensions import TypedDict
@@ -38,17 +38,17 @@ T = TypeVar("T")
38
38
 
39
39
 
40
40
  class FormServiceExtensionServer(BaseExtensionServer[FormServiceExtensionSpec, FormServiceExtensionMetadata]):
41
- def parse_initial_form(self, *, model: type[T] = FormResponse) -> T | None:
42
- if self.data is None:
43
- return None
44
-
45
- initial_form = self.data.form_fulfillments.get("initial_form")
46
-
47
- if initial_form is None:
48
- return None
49
- if model is FormResponse:
50
- return cast(T, initial_form)
51
- return TypeAdapter(model).validate_python(dict(initial_form))
52
-
53
-
54
- class FormServiceExtensionClient(BaseExtensionClient[FormServiceExtensionSpec, FormRender]): ...
41
+ def parse_initial_form(self, *, model: type[T] | None = None) -> T | None:
42
+ initial_form = getattr(self.data, "form_fulfillments", {}).get("initial_form")
43
+ return (
44
+ TypeAdapter(model).validate_python(dict(initial_form))
45
+ if initial_form is not None and model is not None
46
+ else initial_form
47
+ )
48
+
49
+
50
+ class FormServiceExtensionClient(BaseExtensionClient[FormServiceExtensionSpec, FormRender]):
51
+ def fulfillment_metadata(self, *, form_fulfillments: dict[str, FormResponse]) -> dict[str, Any]:
52
+ return {
53
+ self.spec.URI: FormServiceExtensionMetadata(form_fulfillments=form_fulfillments).model_dump(mode="json")
54
+ }
@@ -12,7 +12,7 @@ import pydantic
12
12
  from a2a.server.agent_execution.context import RequestContext
13
13
  from a2a.types import Message as A2AMessage
14
14
  from mcp.client.stdio import StdioServerParameters, stdio_client
15
- from mcp.client.streamable_http import streamablehttp_client
15
+ from mcp.client.streamable_http import streamablehttp_client # pyrefly: ignore [deprecated] -- TODO: upgrade
16
16
  from typing_extensions import override
17
17
 
18
18
  from agentstack_sdk.a2a.extensions.auth.oauth.oauth import OAuthExtensionServer
@@ -160,6 +160,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
160
160
  ):
161
161
  yield (read, write)
162
162
  elif isinstance(transport, StreamableHTTPTransport):
163
+ # pyrefly: ignore [deprecated] -- TODO: upgrade
163
164
  async with streamablehttp_client(
164
165
  url=transport.url,
165
166
  headers=transport.headers,
@@ -88,7 +88,7 @@ class ToolCallExtensionServer(BaseExtensionServer[ToolCallExtensionSpec, ToolCal
88
88
  match result.action:
89
89
  case "accept":
90
90
  return result
91
- case "reject":
91
+ case _:
92
92
  raise ToolCallRejectionError("User has rejected the tool call")
93
93
 
94
94
  else:
@@ -1,9 +1,9 @@
1
- # Copyright 2025 © BeeAI a Series of LF Projects, LLC
1
+ # Copyright 2026 © BeeAI a Series of LF Projects, LLC
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from typing import TYPE_CHECKING, TypeVar, cast
6
+ from typing import TYPE_CHECKING, TypeVar, overload
7
7
 
8
8
  from a2a.server.agent_execution.context import RequestContext
9
9
  from a2a.types import Message as A2AMessage
@@ -34,19 +34,27 @@ class FormRequestExtensionServer(BaseExtensionServer[FormRequestExtensionSpec, F
34
34
  super().handle_incoming_message(message, run_context, request_context)
35
35
  self.context = run_context
36
36
 
37
- async def request_form(self, *, form: FormRender, model: type[T] = FormResponse) -> T | None:
37
+ @overload
38
+ async def request_form(self, *, form: FormRender, model: None = None) -> FormResponse | None: ...
39
+ @overload
40
+ async def request_form(self, *, form: FormRender, model: type[T]) -> T | None: ...
41
+ async def request_form(self, *, form: FormRender, model: type[T] | None = None) -> T | FormResponse | None:
38
42
  message = await self.context.yield_async(
39
43
  InputRequired(message=AgentMessage(text=form.title, metadata={self.spec.URI: form}))
40
44
  )
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))
45
+ return self.parse_form_response(message=message, model=model or FormResponse) if message else None
46
+
47
+ @overload
48
+ def parse_form_response(self, *, message: A2AMessage, model: None = None) -> FormResponse | None: ...
49
+ @overload
50
+ def parse_form_response(self, *, message: A2AMessage, model: type[T]) -> T | None: ...
51
+ def parse_form_response(self, *, message: A2AMessage, model: type[T] | None = None) -> T | FormResponse | None:
52
+ form_response: FormResponse | None = self.parse_client_metadata(message)
53
+ return (
54
+ TypeAdapter(model).validate_python(dict(iter(form_response)))
55
+ if form_response is not None and model is not None
56
+ else form_response
57
+ )
50
58
 
51
59
 
52
60
  class FormRequestExtensionClient(BaseExtensionClient[FormRequestExtensionSpec, FormRender]): ...
@@ -48,33 +48,37 @@ RunYieldResume: TypeAlias = Message | None
48
48
 
49
49
  class AgentArtifact(Artifact):
50
50
  artifact_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
51
+ # pyrefly: ignore [bad-override] -- we intentionally broaden the base type
51
52
  parts: list[Part | TextPart | FilePart | DataPart]
52
53
 
53
54
  @model_validator(mode="after")
54
55
  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
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts]
56
57
  return self
57
58
 
58
59
 
59
60
  class ArtifactChunk(Artifact):
60
61
  last_chunk: bool = False
62
+ # pyrefly: ignore [bad-override] -- we intentionally broaden the base type
61
63
  parts: list[Part | TextPart | FilePart | DataPart]
62
64
 
63
65
  @model_validator(mode="after")
64
66
  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]
67
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts]
66
68
  return self
67
69
 
68
70
 
69
71
  class AgentMessage(Message):
70
72
  message_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
71
- role: Literal[Role.agent] = Role.agent # pyright: ignore [reportIncompatibleVariableOverride]
73
+ # pyrefly: ignore [bad-override] -- we treat this as read-only
74
+ role: Literal[Role.agent] = Role.agent
72
75
  text: str | None = Field(default=None, exclude=True)
76
+ # pyrefly: ignore [bad-override] -- we intentionally broaden the base type
73
77
  parts: list[Part | TextPart | FilePart | DataPart] = Field(default_factory=list)
74
78
 
75
79
  @model_validator(mode="after")
76
80
  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]
81
+ self.parts = [part if isinstance(part, Part) else Part(root=part) for part in self.parts]
78
82
  if self.parts and self.text is not None:
79
83
  raise ValueError("Message cannot have both parts and text")
80
84
  if self.text is not None:
@@ -84,7 +88,8 @@ class AgentMessage(Message):
84
88
 
85
89
  class InputRequired(TaskStatus):
86
90
  message: Message | None = None
87
- state: Literal[TaskState.input_required] = TaskState.input_required # pyright: ignore [reportIncompatibleVariableOverride]
91
+ # pyrefly: ignore [bad-override] -- we treat this as read-only
92
+ state: Literal[TaskState.input_required] = TaskState.input_required
88
93
  text: str | None = Field(default=None, exclude=True)
89
94
  parts: list[Part | TextPart | DataPart | FilePart] = Field(exclude=True, default_factory=list)
90
95
 
@@ -101,4 +106,5 @@ class InputRequired(TaskStatus):
101
106
 
102
107
 
103
108
  class AuthRequired(InputRequired):
104
- state: Literal[TaskState.auth_required] = TaskState.auth_required # pyright: ignore [reportIncompatibleVariableOverride]
109
+ # pyrefly: ignore [bad-override] -- we treat this as read-only
110
+ state: Literal[TaskState.auth_required] = TaskState.auth_required
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import builtins
6
7
  from collections.abc import AsyncIterator
7
8
  from typing import Literal
8
9
  from uuid import UUID, uuid4
@@ -181,7 +182,7 @@ class Context(pydantic.BaseModel):
181
182
  async def generate_token(
182
183
  self: Context | str,
183
184
  *,
184
- providers: list[str] | list[Provider] | None = None,
185
+ providers: builtins.list[str] | builtins.list[Provider] | None = None,
185
186
  client: PlatformClient | None = None,
186
187
  grant_global_permissions: Permissions | None = None,
187
188
  grant_context_permissions: ContextPermissions | None = None,
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import builtins
6
7
  import typing
7
8
  from collections.abc import AsyncIterator
8
9
  from contextlib import asynccontextmanager
@@ -197,7 +198,7 @@ class File(pydantic.BaseModel):
197
198
  async def create_extraction(
198
199
  self: File | str,
199
200
  *,
200
- formats: list[ExtractionFormatLiteral] | None = None,
201
+ formats: builtins.list[ExtractionFormatLiteral] | None = None,
201
202
  client: PlatformClient | None = None,
202
203
  context_id: str | None | Literal["auto"] = "auto",
203
204
  ) -> Extraction:
@@ -3,6 +3,7 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import builtins
6
7
  from enum import StrEnum
7
8
 
8
9
  import pydantic
@@ -44,6 +45,11 @@ class ModelCapability(StrEnum):
44
45
  EMBEDDING = "embedding"
45
46
 
46
47
 
48
+ class ModelProviderState(StrEnum):
49
+ ONLINE = "online"
50
+ OFFLINE = "offline"
51
+
52
+
47
53
  class ModelProvider(pydantic.BaseModel):
48
54
  id: str
49
55
  name: str | None = None
@@ -54,6 +60,7 @@ class ModelProvider(pydantic.BaseModel):
54
60
  watsonx_space_id: str | None = None
55
61
  created_at: pydantic.AwareDatetime
56
62
  capabilities: set[ModelCapability]
63
+ state: ModelProviderState
57
64
 
58
65
  @staticmethod
59
66
  async def create(
@@ -110,7 +117,7 @@ class ModelProvider(pydantic.BaseModel):
110
117
  capability: ModelCapability = ModelCapability.LLM,
111
118
  suggested_models: tuple[str, ...] | None = None,
112
119
  client: PlatformClient | None = None,
113
- ) -> list[ModelWithScore]:
120
+ ) -> builtins.list[ModelWithScore]:
114
121
  async with client or get_platform_client() as client:
115
122
  return pydantic.TypeAdapter(list[ModelWithScore]).validate_python(
116
123
  (
@@ -124,7 +131,7 @@ class ModelProvider(pydantic.BaseModel):
124
131
  )
125
132
 
126
133
  @staticmethod
127
- async def list(*, client: PlatformClient | None = None) -> list[ModelProvider]:
134
+ async def list(*, client: PlatformClient | None = None) -> builtins.list[ModelProvider]:
128
135
  async with client or get_platform_client() as client:
129
136
  return pydantic.TypeAdapter(list[ModelProvider]).validate_python(
130
137
  (await client.get(url="/api/v1/model_providers")).raise_for_status().json()["items"]
@@ -54,7 +54,7 @@ from agentstack_sdk.util.logging import logger
54
54
  AgentFunction: TypeAlias = Callable[[], AsyncGenerator[RunYield, RunYieldResume]]
55
55
  AgentFunctionFactory: TypeAlias = Callable[[RequestContext, ContextStore], AbstractAsyncContextManager[AgentFunction]]
56
56
 
57
- OriginalFnType = TypeVar("OriginalFnType", bound=Callable[..., Any]) # pyright: ignore[reportExplicitAny]
57
+ OriginalFnType = TypeVar("OriginalFnType", bound=Callable[..., Any])
58
58
 
59
59
 
60
60
  class AgentExecuteFn(typing.Protocol):
@@ -121,7 +121,7 @@ def agent(
121
121
  """
122
122
 
123
123
  capabilities = capabilities.model_copy(deep=True) if capabilities else AgentCapabilities(streaming=True)
124
- detail = detail or AgentDetail() # pyright: ignore [reportCallIssue]
124
+ detail = detail or AgentDetail()
125
125
 
126
126
  def decorator(fn: OriginalFnType) -> AgentFactory:
127
127
  def agent_factory(modify_dependencies: Callable[[dict[str, Depends]], None]):
@@ -339,7 +339,7 @@ class AgentRun:
339
339
  else initialize_deps_exceptions[0]
340
340
  )
341
341
 
342
- self.run_context._store = await self._context_store.create( # pyright: ignore[reportPrivateUsage]
342
+ self.run_context._store = await self._context_store.create(
343
343
  context_id=self.run_context.context_id,
344
344
  initialized_dependencies=list(dependency_args.values()),
345
345
  )
@@ -360,8 +360,8 @@ class AgentRun:
360
360
  )
361
361
 
362
362
  async def _run_agent_function(self, initial_message: Message) -> None:
363
- yield_queue = self.run_context._yield_queue # pyright: ignore[reportPrivateUsage]
364
- yield_resume_queue = self.run_context._yield_resume_queue # pyright: ignore[reportPrivateUsage]
363
+ yield_queue = self.run_context._yield_queue
364
+ yield_resume_queue = self.run_context._yield_resume_queue
365
365
 
366
366
  try:
367
367
  async with self._dependencies_lifespan(initial_message) as dependency_args:
@@ -565,8 +565,9 @@ class Executor(AgentExecutor):
565
565
  assert task_id and context_id
566
566
 
567
567
  async def cleanup_fn():
568
+ nonlocal task_id
568
569
  await asyncio.sleep(self._task_timeout.total_seconds())
569
- if not (run := self._running_tasks.get(task_id)):
570
+ if task_id is None or not (run := self._running_tasks.get(task_id)):
570
571
  return
571
572
  try:
572
573
  while not run.done:
@@ -16,8 +16,9 @@ from a2a.server.tasks import (
16
16
  TaskStore,
17
17
  )
18
18
  from a2a.types import AgentInterface, TransportProtocol
19
- from fastapi import APIRouter, Depends, FastAPI
19
+ from fastapi import APIRouter, FastAPI
20
20
  from fastapi.applications import AppType
21
+ from fastapi.params import Depends
21
22
  from starlette.types import Lifespan
22
23
 
23
24
  from agentstack_sdk.server.agent import Agent, Executor
@@ -34,7 +35,7 @@ def create_app(
34
35
  push_sender: PushNotificationSender | None = None,
35
36
  request_context_builder: RequestContextBuilder | None = None,
36
37
  lifespan: Lifespan[AppType] | None = None,
37
- dependencies: list[Depends] | None = None, # pyright: ignore [reportGeneralTypeIssues]
38
+ dependencies: list[Depends] | None = None,
38
39
  override_interfaces: bool = True,
39
40
  task_timeout: timedelta = timedelta(minutes=10),
40
41
  **kwargs,
@@ -33,14 +33,10 @@ class RunContext(BaseModel, arbitrary_types_allowed=True):
33
33
  await self._store.store(data)
34
34
 
35
35
  @overload
36
- async def load_history(
37
- self, load_history_items: Literal[False] = False
38
- ) -> AsyncGenerator[Message | Artifact, None]:
39
- yield ... # type: ignore
36
+ def load_history(self, load_history_items: Literal[False] = False) -> AsyncGenerator[Message | Artifact, None]: ...
40
37
 
41
38
  @overload
42
- async def load_history(self, load_history_items: Literal[True]) -> AsyncGenerator[ContextHistoryItem, None]:
43
- yield ... # type: ignore
39
+ def load_history(self, load_history_items: Literal[True]) -> AsyncGenerator[ContextHistoryItem, None]: ...
44
40
 
45
41
  async def load_history(
46
42
  self, load_history_items: bool = False
@@ -63,7 +63,7 @@ async def discover_jwks() -> KeySet:
63
63
  try:
64
64
  async with use_platform_client() as client:
65
65
  response = await client.get("/.well-known/jwks")
66
- return JsonWebKey.import_key_set(response.raise_for_status().json()) # pyright: ignore[reportAny]
66
+ return JsonWebKey.import_key_set(response.raise_for_status().json())
67
67
  except Exception as e:
68
68
  url = "{platform_url}/.well-known/jwks"
69
69
  logger.warning(f"JWKS discovery failed for url {url}: {e}")
@@ -56,13 +56,12 @@ class Server:
56
56
  self._all_configured_variables: set[str] = set()
57
57
 
58
58
  @functools.wraps(agent_decorator)
59
- def agent(*args, **kwargs) -> Callable:
60
- self, other_args = args[0], args[1:] # Must hide self due to pyright issues
59
+ def agent(self, *args, **kwargs) -> Callable:
61
60
  if self._agent_factory:
62
61
  raise ValueError("Server can have only one agent.")
63
62
 
64
63
  def decorator(fn: Callable) -> Callable:
65
- self._agent_factory = agent_decorator(*other_args, **kwargs)(fn) # pyright: ignore [reportArgumentType]
64
+ self._agent_factory = agent_decorator(*args, **kwargs)(fn)
66
65
  return fn
67
66
 
68
67
  return decorator
@@ -158,6 +157,8 @@ class Server:
158
157
 
159
158
  from agentstack_sdk.server.app import create_app
160
159
 
160
+ self_registration = False if self._production_mode else self_registration
161
+
161
162
  @asynccontextmanager
162
163
  async def _lifespan_fn(app: FastAPI) -> AsyncGenerator[None, None]:
163
164
  async with self._self_registration_client or nullcontext():
@@ -165,7 +166,8 @@ class Server:
165
166
  reload_task = asyncio.create_task(self._reload_variables_periodically()) if self_registration else None
166
167
 
167
168
  try:
168
- async with lifespan_fn(app) if lifespan_fn else nullcontext(): # pyright: ignore [reportArgumentType]
169
+ # pyrefly: ignore [bad-argument-type] -- probably bug in Pyrefly
170
+ async with lifespan_fn(app) if lifespan_fn else nullcontext():
169
171
  yield
170
172
  finally:
171
173
  if register_task:
@@ -275,7 +277,7 @@ class Server:
275
277
 
276
278
  @functools.wraps(serve)
277
279
  def run(*args, **kwargs) -> None:
278
- self = args[0] # Must hide self due to pyright issues
280
+ self = args[0] # TODO(typing): resolve without hiding `self`
279
281
  asyncio.run(self.serve(**kwargs))
280
282
 
281
283
  @property
@@ -17,13 +17,10 @@ if TYPE_CHECKING:
17
17
 
18
18
 
19
19
  class ContextStoreInstance(Protocol):
20
- async def load_history(
20
+ def load_history(
21
21
  self, load_history_items: bool = False
22
- ) -> AsyncIterator[ContextHistoryItem | Message | Artifact]:
23
- yield ... # type: ignore
24
-
22
+ ) -> AsyncIterator[ContextHistoryItem | Message | Artifact]: ...
25
23
  async def store(self, data: Message | Artifact) -> None: ...
26
-
27
24
  async def delete_history_from_id(self, from_id: UUID) -> None: ...
28
25
 
29
26
 
@@ -9,6 +9,7 @@ from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
9
9
  from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
10
10
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11
11
  from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
12
+ from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
12
13
  from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
13
14
  from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
14
15
  from opentelemetry.sdk.metrics import MeterProvider
@@ -31,6 +32,20 @@ def configure_telemetry(app: FastAPI) -> None:
31
32
 
32
33
  FastAPIInstrumentor.instrument_app(app)
33
34
 
35
+ httpxclient_instrumentor = HTTPXClientInstrumentor()
36
+ if httpxclient_instrumentor:
37
+ httpxclient_instrumentor.instrument()
38
+
39
+ try:
40
+ import openai # noqa: F401
41
+ from opentelemetry.instrumentation.openai import OpenAIInstrumentor
42
+
43
+ openai_instrumentor = OpenAIInstrumentor()
44
+ if openai_instrumentor:
45
+ openai_instrumentor.instrument()
46
+ except ModuleNotFoundError:
47
+ pass
48
+
34
49
  resource = Resource(attributes={SERVICE_NAME: "agentstack-sdk-a2a-server", SERVICE_VERSION: __version__})
35
50
 
36
51
  # Traces
@@ -90,7 +90,7 @@ class LoadedFileWithBytes(LoadedFile):
90
90
  return self._content
91
91
 
92
92
  @cached_property
93
- def text(self) -> str: # pyright: ignore [reportIncompatibleMethodOverride]
93
+ def text(self) -> str:
94
94
  return self._content.decode(self._encoding)
95
95
 
96
96
  def read(self) -> bytes:
@@ -54,8 +54,8 @@ def configure_logger(level: int | str | None = None) -> None:
54
54
  if level is not None:
55
55
  level = level if isinstance(level, int) else logging.getLevelNamesMapping()[level.upper()]
56
56
  logging.config.dictConfig(
57
- {
58
- **LOGGING_CONFIG,
57
+ LOGGING_CONFIG
58
+ | {
59
59
  "loggers": {"root": {"level": getLevelName(level), "handlers": ["console"]}},
60
60
  }
61
61
  )
@@ -18,7 +18,9 @@ def resource_context(
18
18
  factory: typing.Callable[P, T],
19
19
  default_factory: typing.Callable[[], T],
20
20
  ) -> tuple[typing.Callable[[], T], typing.Callable[P, contextlib.AbstractContextManager[T]]]:
21
- contextvar: contextvars.ContextVar[T] = contextvars.ContextVar(f"resource_context({factory.__name__})")
21
+ contextvar: contextvars.ContextVar[T] = contextvars.ContextVar(
22
+ f"resource_context({getattr(factory, '__name__', '<unknown>')})"
23
+ )
22
24
 
23
25
  def use_resource(*args: P.args, **kwargs: P.kwargs):
24
26
  @contextlib.contextmanager
@@ -4,18 +4,16 @@ import json
4
4
  import re
5
5
  from collections.abc import AsyncIterator
6
6
  from datetime import UTC, datetime
7
- from typing import Any, TypeVar, cast
7
+ from typing import Any, TypeVar
8
8
 
9
9
  import httpx
10
10
  from httpx import HTTPStatusError
11
11
 
12
12
  T = TypeVar("T")
13
- V = TypeVar("V")
14
13
 
15
14
 
16
- def filter_dict(map: dict[str, T | V], value_to_exclude: V = None) -> dict[str, T]:
17
- """Remove entries with unwanted values (None by default) from dictionary."""
18
- return {key: cast(T, value) for key, value in map.items() if value is not value_to_exclude}
15
+ def filter_dict(d: dict):
16
+ return {k: v for k, v in d.items() if v is not None}
19
17
 
20
18
 
21
19
  async def parse_stream(response: httpx.Response) -> AsyncIterator[dict[str, Any]]: