From 6ad2967db900e04634c59112bc3c988a817281a2 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 29 May 2026 23:42:20 -0400 Subject: [PATCH] refactor(opencode): defer legacy provider policy integration --- packages/opencode/src/provider/provider.ts | 15 +-- .../test/provider/header-timeout.test.ts | 115 ++++++++---------- 2 files changed, 56 insertions(+), 74 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 73d21d6dd1..49c582e0a9 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -29,9 +29,6 @@ import { ModelID, ProviderID } from "./schema" import { ModelStatus } from "./model-status" import { RuntimeFlags } from "@/effect/runtime-flags" import { ProviderError } from "./error" -import { LocationServiceMap } from "@opencode-ai/core/location-layer" -import { Policy } from "@opencode-ai/core/policy" -import { AbsolutePath } from "@opencode-ai/core/schema" const log = Log.create({ service: "provider" }) const OPENAI_HEADER_TIMEOUT_DEFAULT = 10_000 @@ -1210,11 +1207,9 @@ export const layer = Layer.effect( const plugin = yield* Plugin.Service const modelsDevSvc = yield* ModelsDev.Service const runtimeFlags = yield* RuntimeFlags.Service - const locations = yield* LocationServiceMap - const state = yield* InstanceState.make((ctx) => + const state = yield* InstanceState.make(() => Effect.gen(function* () { - const policy = yield* Policy.Service using _ = log.time("state") const bridge = yield* EffectBridge.make() const cfg = yield* config.get() @@ -1485,10 +1480,7 @@ export const layer = Layer.effect( for (const [id, provider] of Object.entries(providers)) { const providerID = ProviderID.make(id) - if ( - !isProviderAllowed(providerID) || - (yield* policy.evaluate("provider.use", providerID, "allow")) === "deny" - ) { + if (!isProviderAllowed(providerID)) { delete providers[providerID] continue } @@ -1545,7 +1537,7 @@ export const layer = Layer.effect( modelLoaders, varsLoaders, } - }).pipe(Effect.provide(locations.get({ directory: AbsolutePath.make(ctx.directory) }))), + }), ) const list = Effect.fn("Provider.list")(() => InstanceState.use(state, (s) => s.providers)) @@ -1881,7 +1873,6 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Plugin.defaultLayer), Layer.provide(ModelsDev.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer), - Layer.provide(LocationServiceMap.layer), ), ) diff --git a/packages/opencode/test/provider/header-timeout.test.ts b/packages/opencode/test/provider/header-timeout.test.ts index 8860c39c32..543bb5f30d 100644 --- a/packages/opencode/test/provider/header-timeout.test.ts +++ b/packages/opencode/test/provider/header-timeout.test.ts @@ -3,8 +3,9 @@ import { createServer, type Server } from "node:http" import { streamText } from "ai" import { Effect, Layer } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" +import { disposeAllInstances, provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" import { testEffect } from "../lib/effect" +import { reply, TestLLMServer } from "../lib/llm-server" import { testProviderConfig } from "../lib/test-provider" import { Env } from "@/env" import { Plugin } from "@/plugin" @@ -21,66 +22,70 @@ const it = testEffect( Provider.defaultLayer, Env.defaultLayer, Plugin.defaultLayer, + TestLLMServer.layer, CrossSpawnSpawner.defaultLayer, ), ) it.live("headerTimeout does not abort delayed SSE body after headers arrive", () => - Effect.gen(function* () { - const server = yield* Effect.acquireRelease( - Effect.promise(() => delayedBodyServer(250)), - (server) => Effect.sync(() => server.server.close()), - ) + provideTmpdirServer( + ({ llm }) => + Effect.gen(function* () { + yield* llm.push(reply().wait(Bun.sleep(250)).text("late").stop()) - yield* provideTmpdirInstance( - () => - Effect.gen(function* () { - const provider = yield* Provider.Service - const model = yield* provider.getModel(ProviderID.make("test"), ModelID.make("test-model")) - const result = streamText({ - model: yield* provider.getLanguage(model), - messages: [{ role: "user", content: "hello" }], - }) + const provider = yield* Provider.Service + const model = yield* provider.getModel(ProviderID.make("test"), ModelID.make("test-model")) + const result = streamText({ + model: yield* provider.getLanguage(model), + messages: [{ role: "user", content: "hello" }], + }) - expect(yield* Effect.promise(() => result.text)).toBe("late") - }), - { config: providerConfig(server.url, { headerTimeout: 50 }) }, - ) - }), + expect(yield* Effect.promise(() => result.text)).toBe("late") + }), + { + config: (url) => { + const config = testProviderConfig(url) + return { + ...config, + provider: { + test: { + ...config.provider.test, + options: { ...config.provider.test.options, headerTimeout: 50 }, + }, + }, + } + }, + }, + ), ) it.live("chunkTimeout raises a response stream error when SSE body stalls", () => - Effect.gen(function* () { - const server = yield* Effect.acquireRelease( - Effect.promise(() => delayedBodyServer(250)), - (server) => Effect.sync(() => server.server.close()), - ) + provideTmpdirServer( + ({ llm }) => + Effect.gen(function* () { + yield* llm.push(reply().wait(Bun.sleep(250)).text("late").stop()) - yield* provideTmpdirInstance( - () => - Effect.gen(function* () { - const provider = yield* Provider.Service - const model = yield* provider.getModel(ProviderID.make("test"), ModelID.make("test-model")) - const result = streamText({ - model: yield* provider.getLanguage(model), - onError() {}, - messages: [{ role: "user", content: "hello" }], - }) + const provider = yield* Provider.Service + const model = yield* provider.getModel(ProviderID.make("test"), ModelID.make("test-model")) + const result = streamText({ + model: yield* provider.getLanguage(model), + onError() {}, + messages: [{ role: "user", content: "hello" }], + }) - const error = yield* Effect.promise(async () => { - try { - for await (const part of result.fullStream) { - if (part.type === "error") return part.error - } - } catch (error) { - return error + const error = yield* Effect.promise(async () => { + try { + for await (const part of result.fullStream) { + if (part.type === "error") return part.error } - }) - expect(error).toBeInstanceOf(ProviderError.ResponseStreamError) - }), - { config: providerConfig(server.url, { chunkTimeout: 50 }) }, - ) - }), + } catch (error) { + return error + } + }) + expect(error).toBeInstanceOf(ProviderError.ResponseStreamError) + }), + { config: (url) => providerConfig(url, { chunkTimeout: 50 }) }, + ), ) it.live("headerTimeout aborts when response headers do not arrive", () => @@ -200,20 +205,6 @@ async function delayedHeaderServer(delay: number): Promise<{ server: Server; url return { server, url: `http://127.0.0.1:${address.port}` } } -async function delayedBodyServer(delay: number): Promise<{ server: Server; url: string }> { - const server = createServer((_, res) => { - res.writeHead(200, { "content-type": "text/event-stream" }) - res.flushHeaders() - setTimeout(() => { - res.end('data: {"choices":[{"delta":{"content":"late"}}]}\n\ndata: [DONE]\n\n') - }, delay) - }) - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) - const address = server.address() - if (!address || typeof address === "string") throw new Error("server did not bind to a TCP port") - return { server, url: `http://127.0.0.1:${address.port}` } -} - function withAuthContent(self: Effect.Effect, value: Record = defaultAuthContent()) { return Effect.acquireUseRelease( Effect.sync(() => {