diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8178d6cc..e9fcbded 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: - name: Install dependencies working-directory: sdk - run: poetry install + run: poetry install --all-extras - name: Run ruff linter working-directory: sdk @@ -180,7 +180,7 @@ jobs: - name: Install dependencies working-directory: sdk - run: poetry install + run: poetry install --all-extras - name: Run tests with coverage working-directory: sdk diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index c23e0d63..1685114e 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- `setup_tracing` type errors: exporter selection is typed, `endpoint` is + passed explicitly, and the unused provider reassignment is gone. +- A2A adapter type errors: `A2AConfig` imports `SecurityScheme` + unconditionally, and the executor no longer falls back to enqueuing a raw + dict the event queue cannot model. +- CI lint and test jobs install all extras, so the tracing, a2a and mcp + modules are type-checked and tested. + ## [0.5.0] - 2026-09-10 **Migration note for DLQ consumers.** The *shape of DLQ entries* changes in diff --git a/sdk/eggai/adapters/a2a/config.py b/sdk/eggai/adapters/a2a/config.py index f2fa6fef..62e0f7b4 100644 --- a/sdk/eggai/adapters/a2a/config.py +++ b/sdk/eggai/adapters/a2a/config.py @@ -1,13 +1,8 @@ """A2A configuration for EggAI agents.""" +from a2a.types import SecurityScheme from pydantic import BaseModel, ConfigDict, Field -try: - from a2a.types import SecurityScheme -except ImportError: - # Fallback if a2a-sdk not installed - SecurityScheme = dict - class A2AConfig(BaseModel): """Simple A2A configuration for EggAI agents.""" diff --git a/sdk/eggai/adapters/a2a/executor.py b/sdk/eggai/adapters/a2a/executor.py index 1193baa8..d090c814 100644 --- a/sdk/eggai/adapters/a2a/executor.py +++ b/sdk/eggai/adapters/a2a/executor.py @@ -189,30 +189,14 @@ def _extract_message_data(self, request_context: RequestContext) -> dict[str, An async def _send_agent_response(self, event_queue: EventQueue, data: dict): """Send agent response through event queue.""" - try: - from uuid import uuid4 - - from a2a.types import DataPart, Message, Part, Role + from uuid import uuid4 - # Create A2A message with response data - response_message = Message( - message_id=str(uuid4()), - role=Role.agent, - parts=[Part(root=DataPart(data=data))], - ) + from a2a.types import DataPart, Message, Part, Role - # Enqueue the message as an event - await event_queue.enqueue_event(response_message) - logger.debug(f"Sent agent response: {data}") - - except ImportError as e: - logger.error(f"A2A types not available: {e}") - raise - except (ValueError, TypeError) as e: - logger.exception(f"Failed to create A2A message from data: {e}") - # Fallback: try to send as simple event - try: - await event_queue.enqueue_event({"text": json.dumps(data)}) - except (TypeError, ValueError) as json_err: - logger.error(f"Failed to serialize response data: {json_err}") - raise + response_message = Message( + message_id=str(uuid4()), + role=Role.agent, + parts=[Part(root=DataPart(data=data))], + ) + await event_queue.enqueue_event(response_message) + logger.debug(f"Sent agent response: {data}") diff --git a/sdk/eggai/tracing.py b/sdk/eggai/tracing.py index a7a49c7d..a18ee566 100644 --- a/sdk/eggai/tracing.py +++ b/sdk/eggai/tracing.py @@ -3,7 +3,10 @@ import os from collections.abc import Callable from contextlib import contextmanager -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from opentelemetry.sdk.trace.export import SpanExporter class _NoOpSpan: @@ -101,7 +104,7 @@ def get_backend(): def setup_tracing( - exporter="otlp", + exporter: str | SpanExporter = "otlp", *, service_name: str | None = None, endpoint: str | None = None, @@ -110,29 +113,34 @@ def setup_tracing( from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider - from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter + from opentelemetry.sdk.trace.export import ( + BatchSpanProcessor, + ConsoleSpanExporter, + SpanExporter, + ) resource = Resource.create( - {"service.name": service_name or os.getenv("OTEL_SERVICE_NAME", "eggai")} + {"service.name": service_name or os.getenv("OTEL_SERVICE_NAME") or "eggai"} ) provider = TracerProvider(resource=resource) - if exporter == "console": + span_exporter: SpanExporter + if isinstance(exporter, SpanExporter): + span_exporter = exporter + elif exporter == "console": span_exporter = ConsoleSpanExporter() elif exporter == "otlp-http": from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( - OTLPSpanExporter, + OTLPSpanExporter as HttpSpanExporter, ) - span_exporter = OTLPSpanExporter(**({"endpoint": endpoint} if endpoint else {})) - elif hasattr(exporter, "export"): - span_exporter = exporter # accept pre-built exporter object (for testing) + span_exporter = HttpSpanExporter(endpoint=endpoint) else: # "otlp" / "otlp-grpc" (default) from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( - OTLPSpanExporter, + OTLPSpanExporter as GrpcSpanExporter, ) - span_exporter = OTLPSpanExporter(**({"endpoint": endpoint} if endpoint else {})) + span_exporter = GrpcSpanExporter(endpoint=endpoint) provider.add_span_processor(BatchSpanProcessor(span_exporter)) @@ -140,9 +148,6 @@ def setup_tracing( if isinstance(trace.get_tracer_provider(), ProxyTracerProvider): trace.set_tracer_provider(provider) - else: - # Adopt the already-configured provider (API base type, not the SDK one). - provider = trace.get_tracer_provider() from importlib.metadata import version diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 14cce52c..0ac340e8 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -119,7 +119,6 @@ line-ending = "auto" [tool.mypy] python_version = "3.10" -# The lint job installs no extras, so the a2a adapter and tracing are not type-checked yet. files = ["eggai"] ignore_missing_imports = true warn_unused_ignores = true diff --git a/sdk/tests/test_tracing.py b/sdk/tests/test_tracing.py index 00fc23b5..397f0161 100644 --- a/sdk/tests/test_tracing.py +++ b/sdk/tests/test_tracing.py @@ -55,7 +55,7 @@ async def handler(msg): # OTEL does not allow overriding the global TracerProvider, so a single shared # provider + exporter is configured once at module level and cleared between tests. -otel = pytest.importorskip("opentelemetry") +otel = pytest.importorskip("opentelemetry.sdk") from opentelemetry import trace as _otel_trace # noqa: E402 from opentelemetry.sdk.trace import TracerProvider as _TracerProvider # noqa: E402