export * as WellKnown from "./wellknown" import { Integration } from "@opencode-ai/schema/integration" import { Context, Effect, Layer, Ref, Schema, Semaphore } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { isDeepStrictEqual } from "node:util" import { makeGlobalNode } from "@opencode-ai/util/effect/app-node" import { httpClient } from "@opencode-ai/util/effect/app-node-platform" import { Bus } from "./bus" import { KV } from "./kv" export interface Auth extends Schema.Schema.Type {} export const Auth = Schema.Struct({ command: Schema.Array(Schema.String), env: Schema.String, }).annotate({ identifier: "WellKnown.Auth" }) export interface RemoteConfig extends Schema.Schema.Type {} export const RemoteConfig = Schema.Struct({ url: Schema.String, headers: Schema.optional(Schema.Record(Schema.String, Schema.String)), }).annotate({ identifier: "WellKnown.RemoteConfig" }) export interface Config extends Schema.Schema.Type {} export const Config = Schema.Record(Schema.String, Schema.Json).annotate({ identifier: "WellKnown.Config" }) export interface Manifest extends Schema.Schema.Type {} export const Manifest = Schema.Struct({ auth: Schema.optional(Auth), config: Schema.optional(Schema.NullOr(Config)), remote_config: Schema.optional(RemoteConfig), }).annotate({ identifier: "WellKnown.Manifest" }) export interface ResolveInput { readonly origin: string readonly variables?: Readonly> } export interface Entry { readonly origin: string readonly integrationID: Integration.ID readonly manifest: Manifest } export interface Interface { readonly entries: () => Effect.Effect readonly snapshot: () => readonly Entry[] readonly refresh: () => Effect.Effect readonly add: (origin: string) => Effect.Effect readonly remove: (origin: string) => Effect.Effect readonly resolve: (entry: Entry, variables: Readonly>) => Effect.Effect } export class Service extends Context.Service()("@opencode/WellKnown") {} export const Event = { Updated: Bus.ephemeral({ type: "wellknown.updated", schema: {} }), } export const inspect = Effect.fn("WellKnown.inspect")(function* (origin: string) { const url = `${origin.replace(/\/+$/, "")}/.well-known/opencode` const http = HttpClient.filterStatusOk(yield* HttpClient.HttpClient) return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.acceptJson)).pipe( Effect.flatMap(HttpClientResponse.schemaBodyJson(Manifest)), Effect.mapError((cause) => new Error(`Failed to load wellknown manifest from ${url}`, { cause })), ) }) export const resolve = Effect.fn("WellKnown.resolve")(function* (input: ResolveInput) { const manifest = yield* inspect(input.origin) return yield* resolveEntry( { origin: input.origin, integrationID: Integration.ID.make(input.origin.replace(/\/+$/, "")), manifest }, input.variables ?? {}, ) }) const resolveEntry = Effect.fnUntraced(function* (entry: Entry, variables: Readonly>) { const configs = entry.manifest.config ? [entry.manifest.config] : [] if (!entry.manifest.remote_config) return configs const substitute = (value: string) => value.replace(/\{env:([^}]+)\}/g, (_, name: string) => variables[name] ?? process.env[name] ?? "") const url = substitute(entry.manifest.remote_config.url) const headers = Object.fromEntries( Object.entries(entry.manifest.remote_config.headers ?? {}).map(([key, value]) => [key, substitute(value)]), ) const http = HttpClient.filterStatusOk(yield* HttpClient.HttpClient) const remote = yield* http .execute(HttpClientRequest.get(url).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setHeaders(headers))) .pipe( Effect.flatMap(HttpClientResponse.schemaBodyJson(Config)), Effect.mapError((cause) => new Error(`Failed to load wellknown remote config from ${url}`, { cause })), ) if (Schema.is(Config)(remote.config)) return [...configs, remote.config] return [...configs, remote] }) const sourcesKey = "wellknown:sources" const Sources = Schema.Array(Schema.String) const layer = Layer.effect( Service, Effect.gen(function* () { const http = yield* HttpClient.HttpClient const kv = yield* KV.Service const bus = yield* Bus.Service const cache = yield* Ref.make(new Map()) const lock = Semaphore.makeUnsafe(1) const load = Effect.fn("WellKnown.load")(function* () { const value = yield* kv.get(sourcesKey) const origins = Schema.is(Sources)(value) ? value : [] const current = yield* Ref.get(cache) const entries = yield* Effect.forEach(origins, (origin) => { const cached = current.get(origin) if (cached) return Effect.succeed(cached) return inspect(origin).pipe( Effect.provideService(HttpClient.HttpClient, http), Effect.map((manifest) => ({ origin, integrationID: Integration.ID.make(origin), manifest })), ) }) yield* Ref.set(cache, new Map(entries.map((entry) => [entry.origin, entry]))) return entries }) const refresh = Effect.fn("WellKnown.refresh")(function* () { return yield* lock.withPermit( Effect.gen(function* () { const value = yield* kv.get(sourcesKey) const origins = Schema.is(Sources)(value) ? value : [] if (!origins.length) return false const entries = yield* Effect.forEach(origins, (origin) => inspect(origin).pipe( Effect.provideService(HttpClient.HttpClient, http), Effect.map((manifest) => ({ origin, integrationID: Integration.ID.make(origin), manifest })), ), ) const next = new Map(entries.map((entry) => [entry.origin, entry])) const changed = !isDeepStrictEqual(Ref.getUnsafe(cache), next) if (!changed) return false yield* Ref.set(cache, next) yield* bus.publish(Event.Updated, {}) return true }), ) }) return Service.of({ entries: load, snapshot: () => Array.from(Ref.getUnsafe(cache).values()), refresh, add: Effect.fn("WellKnown.add")(function* (value) { return yield* lock.withPermit( Effect.gen(function* () { const origin = value.replace(/\/+$/, "") const manifest = yield* inspect(origin).pipe(Effect.provideService(HttpClient.HttpClient, http)) if (!manifest.auth) return yield* Effect.fail(new Error(`No authentication method found at ${origin}`)) const entry = { origin, integrationID: Integration.ID.make(origin), manifest } const sources = yield* kv.get(sourcesKey) const origins = Schema.is(Sources)(sources) ? sources : [] yield* kv.set(sourcesKey, Array.from(new Set([...origins, origin]))) yield* Ref.update(cache, (current) => new Map(current).set(origin, entry)) yield* bus.publish(Event.Updated, {}) return entry }), ) }), remove: Effect.fn("WellKnown.remove")(function* (value) { yield* lock.withPermit( Effect.gen(function* () { const origin = value.replace(/\/+$/, "") const sources = yield* kv.get(sourcesKey) const origins = Schema.is(Sources)(sources) ? sources : [] yield* kv.set( sourcesKey, origins.filter((item) => item !== origin), ) yield* Ref.update(cache, (current) => { const next = new Map(current) next.delete(origin) return next }) yield* bus.publish(Event.Updated, {}) }), ) }), resolve: Effect.fn("WellKnown.resolveEntry")(function* (entry, variables) { return yield* resolveEntry(entry, variables).pipe(Effect.provideService(HttpClient.HttpClient, http)) }), }) }), ) export const node = makeGlobalNode({ service: Service, layer, deps: [httpClient, KV.node, Bus.node] })