chore: merge v2 into service channel config

This commit is contained in:
Dax Raad 2026-07-21 10:28:58 -04:00
commit e76b29c0b4
1174 changed files with 21121 additions and 336917 deletions

View file

@ -5,7 +5,7 @@ import { Observability } from "../observability"
export function makeRuntime<I, S, E>(service: Context.Service<I, S>, layer: Layer.Layer<I, E>) {
let rt: ManagedRuntime.ManagedRuntime<I, E> | undefined
const getRuntime = () =>
(rt ??= ManagedRuntime.make(Layer.provideMerge(layer, Observability.layer) as Layer.Layer<I, E>, {
(rt ??= ManagedRuntime.make(Layer.provideMerge(layer, Observability.layer()) as Layer.Layer<I, E>, {
memoMap,
}))

View file

@ -1,73 +0,0 @@
import { Config } from "effect"
export function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
return value === "true" || value === "1"
}
const copy = process.env["OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"]
const fff = process.env["OPENCODE_DISABLE_FFF"]
function enabledByExperimental(key: string) {
return process.env[key] === undefined ? truthy("OPENCODE_EXPERIMENTAL") : truthy(key)
}
export const Flag = {
OTEL_EXPORTER_OTLP_ENDPOINT: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"],
OTEL_EXPORTER_OTLP_HEADERS: process.env["OTEL_EXPORTER_OTLP_HEADERS"],
OPENCODE_AUTO_HEAP_SNAPSHOT: truthy("OPENCODE_AUTO_HEAP_SNAPSHOT"),
OPENCODE_GIT_BASH_PATH: process.env["OPENCODE_GIT_BASH_PATH"],
OPENCODE_CONFIG: process.env["OPENCODE_CONFIG"],
OPENCODE_CONFIG_CONTENT: process.env["OPENCODE_CONFIG_CONTENT"],
OPENCODE_DISABLE_AUTOUPDATE: truthy("OPENCODE_DISABLE_AUTOUPDATE"),
OPENCODE_ALWAYS_NOTIFY_UPDATE: truthy("OPENCODE_ALWAYS_NOTIFY_UPDATE"),
OPENCODE_DISABLE_PRUNE: truthy("OPENCODE_DISABLE_PRUNE"),
OPENCODE_DISABLE_TERMINAL_TITLE: truthy("OPENCODE_DISABLE_TERMINAL_TITLE"),
OPENCODE_SHOW_TTFD: truthy("OPENCODE_SHOW_TTFD"),
OPENCODE_DISABLE_AUTOCOMPACT: truthy("OPENCODE_DISABLE_AUTOCOMPACT"),
OPENCODE_DISABLE_MODELS_FETCH: truthy("OPENCODE_DISABLE_MODELS_FETCH"),
OPENCODE_DISABLE_MOUSE: truthy("OPENCODE_DISABLE_MOUSE"),
OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"],
OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"],
OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"],
OPENCODE_DISABLE_FFF: fff === undefined ? process.platform === "win32" : truthy("OPENCODE_DISABLE_FFF"),
OPENCODE_DISABLE_FILEWATCHER: truthy("OPENCODE_DISABLE_FILEWATCHER"),
// Experimental
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT:
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
OPENCODE_DB: process.env["OPENCODE_DB"],
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
OPENCODE_EXPERIMENTAL_WORKSPACES: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"),
// Evaluated at access time (not module load) because tests, the CLI, and
// external tooling set these env vars at runtime.
get OPENCODE_DISABLE_PROJECT_CONFIG() {
return truthy("OPENCODE_DISABLE_PROJECT_CONFIG")
},
get OPENCODE_EXPERIMENTAL_REFERENCES() {
return enabledByExperimental("OPENCODE_EXPERIMENTAL_REFERENCES")
},
get OPENCODE_TUI_CONFIG() {
return process.env["OPENCODE_TUI_CONFIG"]
},
get OPENCODE_CONFIG_DIR() {
return process.env["OPENCODE_CONFIG_DIR"]
},
get OPENCODE_PURE() {
return truthy("OPENCODE_PURE")
},
get OPENCODE_PERMISSION() {
return process.env["OPENCODE_PERMISSION"]
},
get OPENCODE_PLUGIN_META_FILE() {
return process.env["OPENCODE_PLUGIN_META_FILE"]
},
get OPENCODE_CLIENT() {
return process.env["OPENCODE_CLIENT"] ?? "cli"
},
}

View file

@ -4,7 +4,6 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir"
import os from "os"
import { Context, Effect, Layer } from "effect"
import { Flock } from "./flock"
import { Flag } from "./flag"
import { makeGlobalNode } from "./effect/app-node"
const app = "opencode"
@ -61,7 +60,7 @@ export function make(input: Partial<Interface> = {}): Interface {
home: Path.home,
data: Path.data,
cache: Path.cache,
config: Flag.OPENCODE_CONFIG_DIR ?? Path.config,
config: Path.config,
state: Path.state,
tmp: Path.tmp,
bin: Path.bin,
@ -73,7 +72,7 @@ export function make(input: Partial<Interface> = {}): Interface {
const layer = Layer.effect(
Service,
Effect.sync(() => Service.of(make())),
Effect.sync(() => Service.of(make({ config: process.env.OPENCODE_CONFIG_DIR ?? Path.config }))),
)
export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] })

View file

@ -2,29 +2,43 @@ export * as Observability from "./observability"
import { NodeFileSystem } from "@effect/platform-node"
import { LayerNode } from "./effect/layer-node"
import { Effect, Layer, Logger, References } from "effect"
import { Effect, Layer, Logger, References, Schema } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { OtlpSerialization } from "effect/unstable/observability"
import { Logging } from "./observability/logging"
import { Otlp } from "./observability/otlp"
const local = Logger.layer(Logging.loggers(), { mergeWithExisting: false }).pipe(
Layer.provide(NodeFileSystem.layer),
Layer.orDie,
Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
)
export const Options = Schema.Struct({
endpoint: Schema.optional(Schema.String),
headers: Schema.optional(Schema.String),
client: Schema.optional(Schema.String),
})
export type Options = typeof Options.Type
export const layer = Layer.unwrap(
Effect.gen(function* () {
const logs = Logger.layer([...Logging.loggers(), ...Otlp.loggers()], { mergeWithExisting: false }).pipe(
Layer.provide(NodeFileSystem.layer),
Layer.provide(OtlpSerialization.layerJson),
Layer.provide(FetchHttpClient.layer),
Layer.orDie,
Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
)
return Layer.merge(logs, yield* Effect.promise(Otlp.tracingLayer))
}),
).pipe(Layer.catchCause(() => local))
export function layer(
options: Options = {
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
headers: process.env.OTEL_EXPORTER_OTLP_HEADERS,
client: process.env.OPENCODE_CLIENT ?? "cli",
},
) {
const local = Logger.layer(Logging.loggers(), { mergeWithExisting: false }).pipe(
Layer.provide(NodeFileSystem.layer),
Layer.orDie,
Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
)
return Layer.unwrap(
Effect.gen(function* () {
const logs = Logger.layer([...Logging.loggers(), ...Otlp.loggers(options)], { mergeWithExisting: false }).pipe(
Layer.provide(NodeFileSystem.layer),
Layer.provide(OtlpSerialization.layerJson),
Layer.provide(FetchHttpClient.layer),
Layer.orDie,
Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
)
return Layer.merge(logs, yield* Effect.promise(() => Otlp.tracingLayer(options)))
}),
).pipe(Layer.catchCause(() => local))
}
export const node = LayerNode.make({ name: "observability", layer, deps: [] })
export const node = LayerNode.make({ name: "observability", layer: layer(), deps: [] })

View file

@ -1,13 +1,17 @@
import { Layer } from "effect"
import { OtlpLogger } from "effect/unstable/observability"
import { Flag } from "../flag"
import { InstallationChannel, InstallationVersion } from "../installation/version"
import { runID } from "./shared"
const endpoint = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
export interface Options {
readonly endpoint?: string
readonly headers?: string
readonly client?: string
}
const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
function parseHeaders(value?: string) {
return value
? value.split(",").reduce(
(acc, entry) => {
const [key, ...value] = entry.split("=")
acc[key] = value.join("=")
@ -15,7 +19,8 @@ const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
},
{} as Record<string, string>,
)
: undefined
: undefined
}
function resourceAttributes() {
const value = process.env.OTEL_RESOURCE_ATTRIBUTES
@ -33,27 +38,33 @@ function resourceAttributes() {
}
}
export function resource(): { serviceName: string; serviceVersion: string; attributes: Record<string, string> } {
export function resource(client = "cli"): { serviceName: string; serviceVersion: string; attributes: Record<string, string> } {
return {
serviceName: "opencode",
serviceVersion: InstallationVersion,
attributes: {
...resourceAttributes(),
"deployment.environment.name": InstallationChannel,
"opencode.client": Flag.OPENCODE_CLIENT,
"opencode.client": client,
"opencode.run": runID,
"service.instance.id": runID,
},
}
}
export function loggers() {
if (!endpoint) return []
return [OtlpLogger.make({ url: `${endpoint}/v1/logs`, resource: resource(), headers })]
export function loggers(options?: Options) {
if (!options?.endpoint) return []
return [
OtlpLogger.make({
url: `${options.endpoint}/v1/logs`,
resource: resource(options.client),
headers: parseHeaders(options.headers),
}),
]
}
export async function tracingLayer() {
if (!endpoint) return Layer.empty
export async function tracingLayer(options?: Options) {
if (!options?.endpoint) return Layer.empty
const NodeSdk = await import("@effect/opentelemetry/NodeSdk")
const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
const SdkBase = await import("@opentelemetry/sdk-trace-base")
@ -66,11 +77,11 @@ export async function tracingLayer() {
context.setGlobalContextManager(manager)
return NodeSdk.layer(() => ({
resource: resource(),
resource: resource(options.client),
spanProcessor: new SdkBase.BatchSpanProcessor(
new OTLP.OTLPTraceExporter({
url: `${endpoint}/v1/traces`,
headers,
url: `${options.endpoint}/v1/traces`,
headers: parseHeaders(options.headers),
}),
),
}))