summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-14 22:30:50 -0400
committerGitHub <[email protected]>2026-04-14 22:30:50 -0400
commitf73ff781e797aac8beaaaab9a49218ec5d9e0f14 (patch)
treef969bcad378f8c591d8d97af7abad90dfefd2fec /packages
parent68a9a47976fc0388370d9e3e760181ea67e85e9d (diff)
downloadopencode-f73ff781e797aac8beaaaab9a49218ec5d9e0f14.tar.gz
opencode-f73ff781e797aac8beaaaab9a49218ec5d9e0f14.zip
fix(opencode): export AI SDK telemetry spans (#22526)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/package.json4
-rw-r--r--packages/opencode/src/agent/agent.ts6
-rw-r--r--packages/opencode/src/effect/app-runtime.ts2
-rw-r--r--packages/opencode/src/effect/bootstrap-runtime.ts2
-rw-r--r--packages/opencode/src/effect/observability.ts68
-rw-r--r--packages/opencode/src/effect/oltp.ts41
-rw-r--r--packages/opencode/src/effect/run-service.ts2
-rw-r--r--packages/opencode/src/session/llm.ts8
8 files changed, 89 insertions, 44 deletions
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index b957c695b..abfcb52e7 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -98,6 +98,7 @@
"@ai-sdk/xai": "3.0.75",
"@aws-sdk/credential-providers": "3.993.0",
"@clack/prompts": "1.0.0-alpha.1",
+ "@effect/opentelemetry": "catalog:",
"@effect/platform-node": "catalog:",
"@gitlab/opencode-gitlab-auth": "1.3.3",
"@hono/node-server": "1.19.11",
@@ -115,6 +116,9 @@
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
+ "@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",
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 562c90cc0..ce49218b7 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -21,6 +21,8 @@ import { Plugin } from "@/plugin"
import { Skill } from "../skill"
import { Effect, Context, Layer } from "effect"
import { InstanceState } from "@/effect/instance-state"
+import * as Option from "effect/Option"
+import * as OtelTracer from "@effect/opentelemetry/Tracer"
export namespace Agent {
export const Info = z
@@ -334,6 +336,9 @@ export namespace Agent {
const model = input.model ?? (yield* provider.defaultModel())
const resolved = yield* provider.getModel(model.providerID, model.modelID)
const language = yield* provider.getLanguage(resolved)
+ const tracer = cfg.experimental?.openTelemetry
+ ? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer))
+ : undefined
const system = [PROMPT_GENERATE]
yield* plugin.trigger("experimental.chat.system.transform", { model: resolved }, { system })
@@ -346,6 +351,7 @@ export namespace Agent {
const params = {
experimental_telemetry: {
isEnabled: cfg.experimental?.openTelemetry,
+ tracer,
metadata: {
userId: cfg.username ?? "unknown",
},
diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts
index 0d32bce08..b13396615 100644
--- a/packages/opencode/src/effect/app-runtime.ts
+++ b/packages/opencode/src/effect/app-runtime.ts
@@ -1,6 +1,6 @@
import { Layer, ManagedRuntime } from "effect"
import { attach, memoMap } from "./run-service"
-import { Observability } from "./oltp"
+import { Observability } from "./observability"
import { AppFileSystem } from "@/filesystem"
import { Bus } from "@/bus"
diff --git a/packages/opencode/src/effect/bootstrap-runtime.ts b/packages/opencode/src/effect/bootstrap-runtime.ts
index 0db46fe3e..d8400c52a 100644
--- a/packages/opencode/src/effect/bootstrap-runtime.ts
+++ b/packages/opencode/src/effect/bootstrap-runtime.ts
@@ -10,7 +10,7 @@ import { File } from "@/file"
import { Vcs } from "@/project/vcs"
import { Snapshot } from "@/snapshot"
import { Bus } from "@/bus"
-import { Observability } from "./oltp"
+import { Observability } from "./observability"
export const BootstrapLayer = Layer.mergeAll(
Plugin.defaultLayer,
diff --git a/packages/opencode/src/effect/observability.ts b/packages/opencode/src/effect/observability.ts
new file mode 100644
index 000000000..5486eafbf
--- /dev/null
+++ b/packages/opencode/src/effect/observability.ts
@@ -0,0 +1,68 @@
+import { Effect, Layer, Logger } from "effect"
+import { FetchHttpClient } from "effect/unstable/http"
+import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability"
+import { EffectLogger } from "@/effect/logger"
+import { Flag } from "@/flag/flag"
+import { CHANNEL, VERSION } from "@/installation/meta"
+
+export namespace Observability {
+ const base = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
+ export const enabled = !!base
+
+ const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
+ ? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
+ (acc, x) => {
+ const [key, value] = x.split("=")
+ acc[key] = value
+ return acc
+ },
+ {} as Record<string, string>,
+ )
+ : undefined
+
+ const resource = {
+ serviceName: "opencode",
+ serviceVersion: VERSION,
+ attributes: {
+ "deployment.environment.name": CHANNEL === "local" ? "local" : CHANNEL,
+ "opencode.client": Flag.OPENCODE_CLIENT,
+ },
+ }
+
+ const logs = Logger.layer(
+ [
+ EffectLogger.logger,
+ OtlpLogger.make({
+ url: `${base}/v1/logs`,
+ resource,
+ headers,
+ }),
+ ],
+ { mergeWithExisting: false },
+ ).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer))
+
+ const traces = async () => {
+ const NodeSdk = await import("@effect/opentelemetry/NodeSdk")
+ const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
+ const SdkBase = await import("@opentelemetry/sdk-trace-base")
+
+ return NodeSdk.layer(() => ({
+ resource,
+ spanProcessor: new SdkBase.BatchSpanProcessor(
+ new OTLP.OTLPTraceExporter({
+ url: `${base}/v1/traces`,
+ headers,
+ }),
+ ),
+ }))
+ }
+
+ export const layer = !base
+ ? EffectLogger.layer
+ : Layer.unwrap(
+ Effect.gen(function* () {
+ const trace = yield* Effect.promise(traces)
+ return Layer.mergeAll(trace, logs)
+ }),
+ )
+}
diff --git a/packages/opencode/src/effect/oltp.ts b/packages/opencode/src/effect/oltp.ts
deleted file mode 100644
index 33b67151a..000000000
--- a/packages/opencode/src/effect/oltp.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Duration, Layer } from "effect"
-import { FetchHttpClient } from "effect/unstable/http"
-import { Otlp } from "effect/unstable/observability"
-import { EffectLogger } from "@/effect/logger"
-import { Flag } from "@/flag/flag"
-import { CHANNEL, VERSION } from "@/installation/meta"
-
-export namespace Observability {
- const base = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
- export const enabled = !!base
-
- const resource = {
- serviceName: "opencode",
- serviceVersion: VERSION,
- attributes: {
- "deployment.environment.name": CHANNEL === "local" ? "local" : CHANNEL,
- "opencode.client": Flag.OPENCODE_CLIENT,
- },
- }
-
- const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
- ? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
- (acc, x) => {
- const [key, value] = x.split("=")
- acc[key] = value
- return acc
- },
- {} as Record<string, string>,
- )
- : undefined
-
- export const layer = !base
- ? EffectLogger.layer
- : Otlp.layerJson({
- baseUrl: base,
- loggerExportInterval: Duration.seconds(1),
- loggerMergeWithExisting: true,
- resource,
- headers,
- }).pipe(Layer.provide(EffectLogger.layer), Layer.provide(FetchHttpClient.layer))
-}
diff --git a/packages/opencode/src/effect/run-service.ts b/packages/opencode/src/effect/run-service.ts
index 532278612..bb4307b57 100644
--- a/packages/opencode/src/effect/run-service.ts
+++ b/packages/opencode/src/effect/run-service.ts
@@ -3,7 +3,7 @@ import * as Context from "effect/Context"
import { Instance } from "@/project/instance"
import { LocalContext } from "@/util/local-context"
import { InstanceRef, WorkspaceRef } from "./instance-ref"
-import { Observability } from "./oltp"
+import { Observability } from "./observability"
import { WorkspaceContext } from "@/control-plane/workspace-context"
export const memoMap = Layer.makeMemoMapUnsafe()
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 732ad7a9f..5a4c04119 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -21,6 +21,8 @@ import { SessionID } from "@/session/schema"
import { Auth } from "@/auth"
import { Installation } from "@/installation"
import { makeRuntime } from "@/effect/run-service"
+import * as Option from "effect/Option"
+import * as OtelTracer from "@effect/opentelemetry/Tracer"
export namespace LLM {
const log = Log.create({ service: "llm" })
@@ -312,6 +314,10 @@ export namespace LLM {
})
}
+ const tracer = cfg.experimental?.openTelemetry
+ ? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer))
+ : undefined
+
return streamText({
onError(error) {
l.error("stream error", {
@@ -383,6 +389,8 @@ export namespace LLM {
}),
experimental_telemetry: {
isEnabled: cfg.experimental?.openTelemetry,
+ functionId: "session.llm",
+ tracer,
metadata: {
userId: cfg.username ?? "unknown",
sessionId: input.sessionID,