feat(observability): add v2 genai tracing

This commit is contained in:
starptech 2026-07-08 18:02:31 +02:00
commit 0271872c09
42 changed files with 3150 additions and 373 deletions

View file

@ -11,6 +11,7 @@ import { Effect, Layer, Schema } from "effect"
import { HttpRouter, HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import { requestRef, type LocationServices } from "../location"
import { ServerObservability } from "../observability"
export class FormLocationMiddleware extends HttpApiMiddleware.Service<
FormLocationMiddleware,
@ -47,28 +48,27 @@ export const formLocationLayer = Layer.effect(
}),
),
)
const row = yield* db
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
.from(SessionTable)
.where(eq(SessionTable.id, sessionID))
.get()
.pipe(Effect.orDie)
if (!row) {
return yield* new SessionNotFoundError({
sessionID,
message: `Session not found: ${sessionID}`,
const location = yield* Effect.gen(function* () {
const row = yield* db
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
.from(SessionTable)
.where(eq(SessionTable.id, sessionID))
.get()
.pipe(Effect.orDie)
if (!row) {
return yield* new SessionNotFoundError({
sessionID,
message: `Session not found: ${sessionID}`,
})
}
return Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
})
}
}).pipe(Effect.tapCause((cause) => ServerObservability.locationFailure(cause, sessionID, "resolve")))
return yield* effect.pipe(
Effect.provide(
locations.get(
Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
}),
),
),
Effect.provide(ServerObservability.locationLayer(locations.get(location), sessionID)),
)
}),
)

View file

@ -10,6 +10,7 @@ import { Effect, Layer, Schema } from "effect"
import { HttpRouter } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
import { ServerObservability } from "../observability"
import type { LocationServices } from "../location"
export class SessionLocationMiddleware extends HttpApiMiddleware.Service<
@ -39,28 +40,27 @@ export const sessionLocationLayer = Layer.effect(
}),
),
)
const row = yield* db
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
.from(SessionTable)
.where(eq(SessionTable.id, sessionID))
.get()
.pipe(Effect.orDie)
if (!row)
return yield* new SessionNotFoundError({
sessionID,
message: `Session not found: ${sessionID}`,
const location = yield* Effect.gen(function* () {
const row = yield* db
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
.from(SessionTable)
.where(eq(SessionTable.id, sessionID))
.get()
.pipe(Effect.orDie)
if (!row)
return yield* new SessionNotFoundError({
sessionID,
message: `Session not found: ${sessionID}`,
})
return Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
})
return yield* effect.pipe(
Effect.provide(
locations.get(
Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
}),
),
),
}).pipe(
Effect.tapCause((cause) => ServerObservability.locationFailure(cause, sessionID, "resolve")),
)
return yield* effect.pipe(Effect.provide(ServerObservability.locationLayer(locations.get(location), sessionID)))
}),
)
}),

View file

@ -0,0 +1,33 @@
export * as ServerObservability from "./observability"
import { Cause, Effect, Layer, Option } from "effect"
import { HttpMiddleware } from "effect/unstable/http"
export const httpTracingDisabled = Layer.succeed(HttpMiddleware.TracerDisabledWhen, () => true)
export function locationLayer<A, E, R>(layer: Layer.Layer<A, E, R>, sessionID: string) {
return layer.pipe(Layer.tapCause((cause) => locationFailure(cause, sessionID, "load")))
}
export function locationFailure(cause: Cause.Cause<unknown>, sessionID: string, phase: "resolve" | "load") {
if (Cause.hasInterruptsOnly(cause)) return Effect.void
const error = Option.getOrUndefined(Cause.findErrorOption(cause))
const log = error && typeof error === "object" && "_tag" in error && error._tag === "SessionNotFoundError"
? Effect.logWarning
: Effect.logError
return log("Failed to resolve Session location", cause).pipe(
Effect.annotateLogs({
operation: "session.location",
phase,
sessionID,
errorType: errorType(cause),
}),
)
}
function errorType(cause: Cause.Cause<unknown>) {
const error = Option.getOrUndefined(Cause.findErrorOption(cause))
if (error && typeof error === "object" && "_tag" in error && typeof error._tag === "string") return error._tag
const failure = Cause.squash(cause)
return failure instanceof Error ? failure.name : "unknown"
}

View file

@ -29,6 +29,7 @@ import { PtyEnvironment } from "./pty-environment"
import { layer } from "./location"
import { formLocationLayer } from "./middleware/form-location"
import { sessionLocationLayer } from "./middleware/session-location"
import { ServerObservability } from "./observability"
const applicationServices = LayerNode.group([
Database.node,
@ -95,6 +96,7 @@ function makeRoutes<AuthError, AuthServices>(
Layer.provide(auth),
Layer.provide(serviceLayer),
Layer.provide(Observability.layer),
Layer.merge(ServerObservability.httpTracingDisabled),
)
}