diff options
| author | Kit Langton <[email protected]> | 2026-04-14 22:30:50 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-14 22:30:50 -0400 |
| commit | f73ff781e797aac8beaaaab9a49218ec5d9e0f14 (patch) | |
| tree | f969bcad378f8c591d8d97af7abad90dfefd2fec /packages | |
| parent | 68a9a47976fc0388370d9e3e760181ea67e85e9d (diff) | |
| download | opencode-f73ff781e797aac8beaaaab9a49218ec5d9e0f14.tar.gz opencode-f73ff781e797aac8beaaaab9a49218ec5d9e0f14.zip | |
fix(opencode): export AI SDK telemetry spans (#22526)
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/opencode/package.json | 4 | ||||
| -rw-r--r-- | packages/opencode/src/agent/agent.ts | 6 | ||||
| -rw-r--r-- | packages/opencode/src/effect/app-runtime.ts | 2 | ||||
| -rw-r--r-- | packages/opencode/src/effect/bootstrap-runtime.ts | 2 | ||||
| -rw-r--r-- | packages/opencode/src/effect/observability.ts | 68 | ||||
| -rw-r--r-- | packages/opencode/src/effect/oltp.ts | 41 | ||||
| -rw-r--r-- | packages/opencode/src/effect/run-service.ts | 2 | ||||
| -rw-r--r-- | packages/opencode/src/session/llm.ts | 8 |
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, |
