Skip to content

feat(opencode): bridge global OTel tracer for AI SDK telemetry#21799

Draft
kitlangton wants to merge 1 commit intodevfrom
worktree-feat+otel-bridge
Draft

feat(opencode): bridge global OTel tracer for AI SDK telemetry#21799
kitlangton wants to merge 1 commit intodevfrom
worktree-feat+otel-bridge

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

  • Adds a lightweight BasicTracerProvider bridge (src/otel-bridge.ts) that registers a global OTel tracer when OTEL_EXPORTER_OTLP_ENDPOINT is set
  • Enables AI SDK's experimental_telemetry spans to be exported alongside Effect's own OTLP spans (added in feat(opencode): add OTLP observability support #21387)
  • Turns on recordInputs/recordOutputs on both streamText (llm.ts) and generateObject (agent.ts) call sites so full prompt/response content is captured as span attributes
  • Telemetry auto-enables when the OTLP endpoint env var is set — no separate config flag needed
  • Zero runtime cost when OTEL_EXPORTER_OTLP_ENDPOINT is unset (dynamic imports, conditional init)

How it works

Effect's Otlp.layerJson instruments Effect services but uses an internal fiber-scoped tracer — it does NOT register a global TracerProvider. The AI SDK's experimental_telemetry reads from @opentelemetry/api's global tracer, so without a global provider its spans are silently dropped.

This bridge fills the gap with BasicTracerProvider from @opentelemetry/sdk-trace-base (much lighter than @opentelemetry/sdk-node) exporting via OTLPTraceExporter (HTTP/JSON) to the same endpoint. Both trace streams land in the same collector.

What you get

With OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 opencode:

  • Effect spans: Session.create, Tool.run, Provider.chat, etc.
  • AI SDK spans: ai.streamText, ai.streamText.doStream, ai.toolCall
  • Message content (via recordInputs/recordOutputs): ai.prompt, ai.response.text, ai.toolCall.args, ai.toolCall.result, ai.usage.*
  • Metadata: ai.telemetry.metadata.userId, ai.telemetry.metadata.sessionId

Works with any OTLP/HTTP collector — Jaeger, Aspire, SigNoz, Grafana Tempo, or a local dev tool like leto.

New deps

  • @opentelemetry/sdk-trace-baseBasicTracerProvider + BatchSpanProcessor
  • @opentelemetry/exporter-trace-otlp-http — OTLP/HTTP JSON exporter
  • @opentelemetry/api — global tracer registration
  • @opentelemetry/resources + @opentelemetry/semantic-conventions — service resource attributes

Related

Test plan

  • Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318, run Jaeger/Aspire/leto, verify AI SDK spans appear with message content
  • Verify Effect spans still export correctly
  • Verify zero overhead when env var is unset (no OTel packages loaded)
  • bun run typecheck passes across all 13 packages

The existing Effect Otlp.layerJson (merged via #21387) instruments
Effect services but does not register a global OTel tracer provider.
The AI SDK's experimental_telemetry reads from the global provider,
so its spans were silently dropped.

This adds a lightweight BasicTracerProvider bridge that registers
globally when OTEL_EXPORTER_OTLP_ENDPOINT is set, using the same
endpoint and headers the Effect layer already reads. AI SDK spans
(ai.streamText, ai.toolCall, prompt/response content, token counts)
now export alongside Effect service spans to any OTLP/HTTP collector.

Also enables recordInputs/recordOutputs on both streamText and
generateObject call sites so full message content is captured as
span attributes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant