Browse Source

fix: register OTel context manager so AI SDK spans thread into Effect traces (#22645)

Kit Langton 1 day ago
parent
commit
9640d889ba
3 changed files with 18 additions and 2 deletions
  1. 2 0
      bun.lock
  2. 4 2
      packages/opencode/package.json
  3. 12 0
      packages/opencode/src/effect/observability.ts

+ 2 - 0
bun.lock

@@ -359,6 +359,8 @@
         "@opencode-ai/sdk": "workspace:*",
         "@opencode-ai/server": "workspace:*",
         "@openrouter/ai-sdk-provider": "2.5.1",
+        "@opentelemetry/api": "1.9.0",
+        "@opentelemetry/context-async-hooks": "2.6.1",
         "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
         "@opentelemetry/sdk-trace-base": "2.6.1",
         "@opentelemetry/sdk-trace-node": "2.6.1",

+ 4 - 2
packages/opencode/package.json

@@ -113,13 +113,15 @@
     "@octokit/rest": "catalog:",
     "@openauthjs/openauth": "catalog:",
     "@opencode-ai/plugin": "workspace:*",
-    "@opencode-ai/server": "workspace:*",
     "@opencode-ai/script": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",
+    "@opencode-ai/server": "workspace:*",
+    "@openrouter/ai-sdk-provider": "2.5.1",
+    "@opentelemetry/api": "1.9.0",
+    "@opentelemetry/context-async-hooks": "2.6.1",
     "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
     "@opentelemetry/sdk-trace-base": "2.6.1",
     "@opentelemetry/sdk-trace-node": "2.6.1",
-    "@openrouter/ai-sdk-provider": "2.5.1",
     "@opentui/core": "0.1.99",
     "@opentui/solid": "0.1.99",
     "@parcel/watcher": "2.5.1",

+ 12 - 0
packages/opencode/src/effect/observability.ts

@@ -46,6 +46,18 @@ export namespace Observability {
     const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
     const SdkBase = await import("@opentelemetry/sdk-trace-base")
 
+    // @effect/opentelemetry creates a NodeTracerProvider but never calls
+    // register(), so the global @opentelemetry/api context manager stays
+    // as the no-op default. Non-Effect code (like the AI SDK) that calls
+    // tracer.startActiveSpan() relies on context.active() to find the
+    // parent span — without a real context manager every span starts a
+    // new trace. Registering AsyncLocalStorageContextManager fixes this.
+    const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks")
+    const { context } = await import("@opentelemetry/api")
+    const mgr = new AsyncLocalStorageContextManager()
+    mgr.enable()
+    context.setGlobalContextManager(mgr)
+
     return NodeSdk.layer(() => ({
       resource,
       spanProcessor: new SdkBase.BatchSpanProcessor(