feat(core): add provider policy enforcement

This commit is contained in:
Dax Raad 2026-07-16 14:27:31 -04:00
commit 103f764624
12 changed files with 310 additions and 33 deletions

View file

@ -1,10 +1,12 @@
export * as WellKnown from "./wellknown"
import { Integration } from "@opencode-ai/schema/integration"
import { Context, Effect, Layer, PubSub, Ref, Schema, Semaphore, Stream } from "effect"
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 "./effect/app-node"
import { httpClient } from "./effect/app-node-platform"
import { EventV2 } from "./event"
import { KV } from "./kv"
export interface Auth extends Schema.Schema.Type<typeof Auth> {}
@ -43,14 +45,18 @@ export interface Entry {
export interface Interface {
readonly entries: () => Effect.Effect<readonly Entry[], Error>
readonly snapshot: () => readonly Entry[]
readonly refresh: () => Effect.Effect<boolean, Error>
readonly add: (origin: string) => Effect.Effect<Entry, Error>
readonly remove: (origin: string) => Effect.Effect<void>
readonly changes: Stream.Stream<void>
readonly resolve: (entry: Entry, variables: Readonly<Record<string, string>>) => Effect.Effect<Config[], Error>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/WellKnown") {}
export const Event = {
Updated: EventV2.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)
@ -97,8 +103,8 @@ const layer = Layer.effect(
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const kv = yield* KV.Service
const events = yield* EventV2.Service
const cache = yield* Ref.make(new Map<string, Entry>())
const changes = yield* PubSub.unbounded<void>()
const lock = Semaphore.makeUnsafe(1)
const load = Effect.fn("WellKnown.load")(function* () {
@ -117,9 +123,41 @@ const layer = Layer.effect(
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) yield* Ref.set(cache, next)
yield* events.publish(Event.Updated, {})
return changed
}),
)
})
yield* Effect.sleep("10 minutes").pipe(
Effect.andThen(
refresh().pipe(
Effect.catch((error) => Effect.logWarning("failed to refresh wellknown manifests", { error })),
),
),
Effect.forever,
Effect.forkScoped({ startImmediately: 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* () {
@ -131,7 +169,7 @@ const layer = Layer.effect(
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* PubSub.publish(changes, undefined)
yield* events.publish(Event.Updated, {})
return entry
}),
)
@ -151,11 +189,10 @@ const layer = Layer.effect(
next.delete(origin)
return next
})
yield* PubSub.publish(changes, undefined)
yield* events.publish(Event.Updated, {})
}),
)
}),
changes: Stream.fromPubSub(changes),
resolve: Effect.fn("WellKnown.resolveEntry")(function* (entry, variables) {
return yield* resolveEntry(entry, variables).pipe(Effect.provideService(HttpClient.HttpClient, http))
}),
@ -163,4 +200,4 @@ const layer = Layer.effect(
}),
)
export const node = makeGlobalNode({ service: Service, layer, deps: [httpClient, KV.node] })
export const node = makeGlobalNode({ service: Service, layer, deps: [httpClient, KV.node, EventV2.node] })