Skip to content

Commit f73ff78

Browse files
authored
fix(opencode): export AI SDK telemetry spans (#22526)
1 parent 68a9a47 commit f73ff78

File tree

10 files changed

+489
-402
lines changed

10 files changed

+489
-402
lines changed

bun.lock

Lines changed: 397 additions & 356 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"packages/slack"
2727
],
2828
"catalog": {
29-
"@effect/platform-node": "4.0.0-beta.46",
29+
"@effect/opentelemetry": "4.0.0-beta.48",
30+
"@effect/platform-node": "4.0.0-beta.48",
3031
"@types/bun": "1.3.11",
3132
"@types/cross-spawn": "6.0.6",
3233
"@octokit/rest": "22.0.0",
@@ -47,7 +48,7 @@
4748
"dompurify": "3.3.1",
4849
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
4950
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
50-
"effect": "4.0.0-beta.46",
51+
"effect": "4.0.0-beta.48",
5152
"ai": "6.0.158",
5253
"cross-spawn": "7.0.6",
5354
"hono": "4.10.7",

packages/opencode/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"@ai-sdk/xai": "3.0.75",
9999
"@aws-sdk/credential-providers": "3.993.0",
100100
"@clack/prompts": "1.0.0-alpha.1",
101+
"@effect/opentelemetry": "catalog:",
101102
"@effect/platform-node": "catalog:",
102103
"@gitlab/opencode-gitlab-auth": "1.3.3",
103104
"@hono/node-server": "1.19.11",
@@ -115,6 +116,9 @@
115116
"@opencode-ai/script": "workspace:*",
116117
"@opencode-ai/sdk": "workspace:*",
117118
"@opencode-ai/util": "workspace:*",
119+
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
120+
"@opentelemetry/sdk-trace-base": "2.6.1",
121+
"@opentelemetry/sdk-trace-node": "2.6.1",
118122
"@openrouter/ai-sdk-provider": "2.5.1",
119123
"@opentui/core": "0.1.99",
120124
"@opentui/solid": "0.1.99",

packages/opencode/src/agent/agent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { Plugin } from "@/plugin"
2121
import { Skill } from "../skill"
2222
import { Effect, Context, Layer } from "effect"
2323
import { InstanceState } from "@/effect/instance-state"
24+
import * as Option from "effect/Option"
25+
import * as OtelTracer from "@effect/opentelemetry/Tracer"
2426

2527
export namespace Agent {
2628
export const Info = z
@@ -334,6 +336,9 @@ export namespace Agent {
334336
const model = input.model ?? (yield* provider.defaultModel())
335337
const resolved = yield* provider.getModel(model.providerID, model.modelID)
336338
const language = yield* provider.getLanguage(resolved)
339+
const tracer = cfg.experimental?.openTelemetry
340+
? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer))
341+
: undefined
337342

338343
const system = [PROMPT_GENERATE]
339344
yield* plugin.trigger("experimental.chat.system.transform", { model: resolved }, { system })
@@ -346,6 +351,7 @@ export namespace Agent {
346351
const params = {
347352
experimental_telemetry: {
348353
isEnabled: cfg.experimental?.openTelemetry,
354+
tracer,
349355
metadata: {
350356
userId: cfg.username ?? "unknown",
351357
},

packages/opencode/src/effect/app-runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Layer, ManagedRuntime } from "effect"
22
import { attach, memoMap } from "./run-service"
3-
import { Observability } from "./oltp"
3+
import { Observability } from "./observability"
44

55
import { AppFileSystem } from "@/filesystem"
66
import { Bus } from "@/bus"

packages/opencode/src/effect/bootstrap-runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { File } from "@/file"
1010
import { Vcs } from "@/project/vcs"
1111
import { Snapshot } from "@/snapshot"
1212
import { Bus } from "@/bus"
13-
import { Observability } from "./oltp"
13+
import { Observability } from "./observability"
1414

1515
export const BootstrapLayer = Layer.mergeAll(
1616
Plugin.defaultLayer,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Effect, Layer, Logger } from "effect"
2+
import { FetchHttpClient } from "effect/unstable/http"
3+
import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability"
4+
import { EffectLogger } from "@/effect/logger"
5+
import { Flag } from "@/flag/flag"
6+
import { CHANNEL, VERSION } from "@/installation/meta"
7+
8+
export namespace Observability {
9+
const base = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
10+
export const enabled = !!base
11+
12+
const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
13+
? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
14+
(acc, x) => {
15+
const [key, value] = x.split("=")
16+
acc[key] = value
17+
return acc
18+
},
19+
{} as Record<string, string>,
20+
)
21+
: undefined
22+
23+
const resource = {
24+
serviceName: "opencode",
25+
serviceVersion: VERSION,
26+
attributes: {
27+
"deployment.environment.name": CHANNEL === "local" ? "local" : CHANNEL,
28+
"opencode.client": Flag.OPENCODE_CLIENT,
29+
},
30+
}
31+
32+
const logs = Logger.layer(
33+
[
34+
EffectLogger.logger,
35+
OtlpLogger.make({
36+
url: `${base}/v1/logs`,
37+
resource,
38+
headers,
39+
}),
40+
],
41+
{ mergeWithExisting: false },
42+
).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer))
43+
44+
const traces = async () => {
45+
const NodeSdk = await import("@effect/opentelemetry/NodeSdk")
46+
const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
47+
const SdkBase = await import("@opentelemetry/sdk-trace-base")
48+
49+
return NodeSdk.layer(() => ({
50+
resource,
51+
spanProcessor: new SdkBase.BatchSpanProcessor(
52+
new OTLP.OTLPTraceExporter({
53+
url: `${base}/v1/traces`,
54+
headers,
55+
}),
56+
),
57+
}))
58+
}
59+
60+
export const layer = !base
61+
? EffectLogger.layer
62+
: Layer.unwrap(
63+
Effect.gen(function* () {
64+
const trace = yield* Effect.promise(traces)
65+
return Layer.mergeAll(trace, logs)
66+
}),
67+
)
68+
}

packages/opencode/src/effect/oltp.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

packages/opencode/src/effect/run-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as Context from "effect/Context"
33
import { Instance } from "@/project/instance"
44
import { LocalContext } from "@/util/local-context"
55
import { InstanceRef, WorkspaceRef } from "./instance-ref"
6-
import { Observability } from "./oltp"
6+
import { Observability } from "./observability"
77
import { WorkspaceContext } from "@/control-plane/workspace-context"
88

99
export const memoMap = Layer.makeMemoMapUnsafe()

packages/opencode/src/session/llm.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { SessionID } from "@/session/schema"
2121
import { Auth } from "@/auth"
2222
import { Installation } from "@/installation"
2323
import { makeRuntime } from "@/effect/run-service"
24+
import * as Option from "effect/Option"
25+
import * as OtelTracer from "@effect/opentelemetry/Tracer"
2426

2527
export namespace LLM {
2628
const log = Log.create({ service: "llm" })
@@ -312,6 +314,10 @@ export namespace LLM {
312314
})
313315
}
314316

317+
const tracer = cfg.experimental?.openTelemetry
318+
? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer))
319+
: undefined
320+
315321
return streamText({
316322
onError(error) {
317323
l.error("stream error", {
@@ -383,6 +389,8 @@ export namespace LLM {
383389
}),
384390
experimental_telemetry: {
385391
isEnabled: cfg.experimental?.openTelemetry,
392+
functionId: "session.llm",
393+
tracer,
386394
metadata: {
387395
userId: cfg.username ?? "unknown",
388396
sessionId: input.sessionID,

0 commit comments

Comments
 (0)