refactor(opencode): fetch remote config with http client (#28661)

This commit is contained in:
Kit Langton
2026-05-21 15:53:44 -04:00
committed by GitHub
parent 562d299a41
commit b99787e95b
6 changed files with 366 additions and 257 deletions

View File

@@ -1,6 +1,7 @@
import { expect } from "bun:test"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import path from "path"
import { pathToFileURL } from "url"
import { Agent } from "../../src/agent/agent"
@@ -29,6 +30,7 @@ const configLayer = Config.layer.pipe(
Layer.provide(AuthTest.empty),
Layer.provide(AccountTest.empty),
Layer.provide(NpmTest.noop),
Layer.provide(FetchHttpClient.layer),
)
const pluginLayer = Plugin.layer.pipe(
Layer.provide(Bus.layer),

View File

@@ -1,5 +1,6 @@
import { test, expect, describe, mock, afterEach, beforeEach } from "bun:test"
import { test, expect, describe, afterEach, beforeEach } from "bun:test"
import { Effect, Exit, Layer, Option } from "effect"
import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http"
import { NodeFileSystem, NodePath } from "@effect/platform-node"
import { Config } from "@/config/config"
import { ConfigManaged } from "@/config/managed"
@@ -36,35 +37,72 @@ import { Global } from "@opencode-ai/core/global"
import { ProjectID } from "../../src/project/schema"
import { Filesystem } from "@/util/filesystem"
import { ConfigPlugin } from "@/config/plugin"
import { Npm } from "@opencode-ai/core/npm"
const emptyAccount = Layer.mock(Account.Service)({
active: () => Effect.succeed(Option.none()),
activeOrg: () => Effect.succeed(Option.none()),
})
const emptyAuth = Layer.mock(Auth.Service)({
all: () => Effect.succeed({}),
})
import { AccountTest } from "../fake/account"
import { AuthTest } from "../fake/auth"
import { NpmTest } from "../fake/npm"
const testFlock = EffectFlock.defaultLayer
const noopNpm = Layer.mock(Npm.Service)({
install: () => Effect.void,
add: () => Effect.die("not implemented"),
which: () => Effect.succeed(Option.none()),
})
const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth),
Layer.provide(emptyAccount),
Layer.provideMerge(infra),
Layer.provide(noopNpm),
const unexpectedHttp = HttpClient.make((request) =>
Effect.die(`unexpected http request: ${request.method} ${request.url}`),
)
const json = (request: Parameters<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) =>
HttpClientResponse.fromWeb(
request,
new Response(JSON.stringify(body), {
status,
headers: { "content-type": "application/json" },
}),
)
const wellKnownAuth = (url: string) =>
Layer.mock(Auth.Service)({
all: () =>
Effect.succeed({
[url]: new Auth.WellKnown({ type: "wellknown", key: "TEST_TOKEN", token: "test-token" }),
}),
})
function remoteConfigClient(input: {
wellKnown: unknown
remote?: unknown
seen: { wellKnown?: string; remote?: string; authorization?: string }
}) {
return HttpClient.make((request) => {
if (request.url.includes(".well-known/opencode")) {
input.seen.wellKnown = request.url
return Effect.succeed(json(request, input.wellKnown))
}
if (input.remote !== undefined && request.url.includes("config.example.com")) {
input.seen.remote = request.url
input.seen.authorization = request.headers.authorization
return Effect.succeed(json(request, input.remote))
}
return Effect.succeed(json(request, {}, 404))
})
}
const configLayer = (
options: {
auth?: Layer.Layer<Auth.Service>
account?: Layer.Layer<Account.Service>
client?: HttpClient.HttpClient
} = {},
) =>
Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(options.auth ?? AuthTest.empty),
Layer.provide(options.account ?? AccountTest.empty),
Layer.provideMerge(infra),
Layer.provide(NpmTest.noop),
Layer.provide(Layer.succeed(HttpClient.HttpClient, options.client ?? unexpectedHttp)),
)
const layer = configLayer()
const it = testEffect(layer)
const provideCurrentInstance = <A, E, R>(effect: Effect.Effect<A, E, R>, ctx: InstanceContext) =>
@@ -95,6 +133,7 @@ const listDirs = (ctx: InstanceContext) =>
)
// Get managed config directory from environment (set in preload.ts)
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
const originalTestToken = process.env.TEST_TOKEN
beforeEach(async () => {
await clear(true)
@@ -102,6 +141,8 @@ beforeEach(async () => {
afterEach(async () => {
await fs.rm(managedConfigDir, { force: true, recursive: true }).catch(() => {})
if (originalTestToken === undefined) delete process.env.TEST_TOKEN
else process.env.TEST_TOKEN = originalTestToken
await clear(true)
})
@@ -528,15 +569,7 @@ test("resolves env templates in account config with account token", async () =>
token: () => Effect.succeed(Option.some(AccessToken.make("st_test_token"))),
})
const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth),
Layer.provide(fakeAccount),
Layer.provideMerge(infra),
Layer.provide(noopNpm),
)
const layer = configLayer({ account: fakeAccount })
try {
await provideTmpdirInstance(() =>
@@ -900,15 +933,7 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
const prev = process.env.OPENCODE_CONFIG_DIR
process.env.OPENCODE_CONFIG_DIR = tmp.extra
const testLayer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth),
Layer.provide(emptyAccount),
Layer.provideMerge(infra),
Layer.provide(noopNpm),
)
const testLayer = configLayer()
try {
await withTestInstance({
@@ -1559,42 +1584,79 @@ it.instance("local .opencode config can override MCP from project config", () =>
)
test("project config overrides remote well-known config", async () => {
const originalFetch = globalThis.fetch
let fetchedUrl: string | undefined
globalThis.fetch = mock((url: string | URL | Request) => {
const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) {
fetchedUrl = urlStr
return Promise.resolve(
new Response(
JSON.stringify({
config: {
mcp: { jira: { type: "remote", url: "https://jira.example.com/mcp", enabled: false } },
},
}),
{ status: 200 },
),
)
}
return originalFetch(url)
}) as unknown as typeof fetch
const fakeAuth = Layer.mock(Auth.Service)({
all: () =>
Effect.succeed({
"https://example.com": new Auth.WellKnown({ type: "wellknown", key: "TEST_TOKEN", token: "test-token" }),
}),
const seen: { wellKnown?: string } = {}
const client = remoteConfigClient({
seen,
wellKnown: {
config: {
mcp: { jira: { type: "remote", url: "https://jira.example.com/mcp", enabled: false } },
},
},
})
const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(fakeAuth),
Layer.provide(emptyAccount),
Layer.provideMerge(infra),
Layer.provide(noopNpm),
await provideTmpdirInstance(
() =>
Config.Service.use((svc) =>
Effect.gen(function* () {
const config = yield* svc.get()
expect(seen.wellKnown).toBe("https://example.com/.well-known/opencode")
expect(config.mcp?.jira?.enabled).toBe(true)
}),
),
{
git: true,
config: { mcp: { jira: { type: "remote", url: "https://jira.example.com/mcp", enabled: true } } },
},
).pipe(
Effect.scoped,
Effect.provide(configLayer({ auth: wellKnownAuth("https://example.com"), client })),
Effect.runPromise,
)
})
test("wellknown URL with trailing slash is normalized", async () => {
const seen: { wellKnown?: string } = {}
const client = remoteConfigClient({
seen,
wellKnown: {
config: {
mcp: { slack: { type: "remote", url: "https://slack.example.com/mcp", enabled: true } },
},
},
})
await provideTmpdirInstance(
() =>
Config.Service.use((svc) =>
Effect.gen(function* () {
yield* svc.get()
expect(seen.wellKnown).toBe("https://example.com/.well-known/opencode")
}),
),
{ git: true },
).pipe(
Effect.scoped,
Effect.provide(configLayer({ auth: wellKnownAuth("https://example.com/"), client })),
Effect.runPromise,
)
})
test("remote well-known config can use FetchHttpClient layer", async () => {
let fetchedUrl: string | undefined
const server = Bun.serve({
port: 0,
fetch: (request) => {
fetchedUrl = request.url
return new Response(
JSON.stringify({
config: {
mcp: { jira: { type: "remote", url: "https://jira.example.com/mcp", enabled: true } },
},
}),
{ status: 200, headers: { "content-type": "application/json" } },
)
},
})
try {
await provideTmpdirInstance(
@@ -1602,151 +1664,168 @@ test("project config overrides remote well-known config", async () => {
Config.Service.use((svc) =>
Effect.gen(function* () {
const config = yield* svc.get()
expect(fetchedUrl).toBe("https://example.com/.well-known/opencode")
expect(fetchedUrl).toBe(`${server.url.origin}/.well-known/opencode`)
expect(config.mcp?.jira?.enabled).toBe(true)
}),
),
{
git: true,
config: { mcp: { jira: { type: "remote", url: "https://jira.example.com/mcp", enabled: true } } },
},
).pipe(Effect.scoped, Effect.provide(layer), Effect.runPromise)
} finally {
globalThis.fetch = originalFetch
}
})
test("wellknown URL with trailing slash is normalized", async () => {
const originalFetch = globalThis.fetch
let fetchedUrl: string | undefined
globalThis.fetch = mock((url: string | URL | Request) => {
const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) {
fetchedUrl = urlStr
return Promise.resolve(
new Response(
JSON.stringify({
config: {
mcp: { slack: { type: "remote", url: "https://slack.example.com/mcp", enabled: true } },
},
}),
{ status: 200 },
),
)
}
return originalFetch(url)
}) as unknown as typeof fetch
const fakeAuth = Layer.mock(Auth.Service)({
all: () =>
Effect.succeed({
"https://example.com/": new Auth.WellKnown({ type: "wellknown", key: "TEST_TOKEN", token: "test-token" }),
}),
})
const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(fakeAuth),
Layer.provide(emptyAccount),
Layer.provideMerge(infra),
Layer.provide(noopNpm),
)
try {
await provideTmpdirInstance(
() =>
Config.Service.use((svc) =>
Effect.gen(function* () {
yield* svc.get()
expect(fetchedUrl).toBe("https://example.com/.well-known/opencode")
}),
),
{ git: true },
).pipe(Effect.scoped, Effect.provide(layer), Effect.runPromise)
).pipe(
Effect.scoped,
Effect.provide(
Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(wellKnownAuth(server.url.origin)),
Layer.provide(AccountTest.empty),
Layer.provideMerge(infra),
Layer.provide(NpmTest.noop),
Layer.provide(FetchHttpClient.layer),
),
),
Effect.runPromise,
)
} finally {
globalThis.fetch = originalFetch
await server.stop(true)
}
})
test("wellknown remote_config supports templated env vars in headers", async () => {
const originalFetch = globalThis.fetch
const originalToken = process.env.TEST_TOKEN
let wellknownFetchedUrl: string | undefined
let remoteFetchedUrl: string | undefined
let remoteHeaders: HeadersInit | undefined
globalThis.fetch = mock((url: string | URL | Request, init?: RequestInit) => {
const urlStr = url instanceof Request ? url.url : url instanceof URL ? url.href : url
if (urlStr.includes(".well-known/opencode")) {
wellknownFetchedUrl = urlStr
return Promise.resolve(
new Response(
JSON.stringify({
remote_config: {
url: "https://config.example.com/opencode.json",
headers: {
Authorization: "Bearer {env:TEST_TOKEN}",
},
},
}),
{ status: 200 },
),
)
}
if (urlStr.includes("config.example.com")) {
remoteFetchedUrl = urlStr
remoteHeaders = init?.headers
return Promise.resolve(
new Response(
JSON.stringify({
mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: true } },
}),
{ status: 200 },
),
)
}
return originalFetch(url, init)
}) as unknown as typeof fetch
const fakeAuth = Layer.mock(Auth.Service)({
all: () =>
Effect.succeed({
"https://example.com": new Auth.WellKnown({ type: "wellknown", key: "TEST_TOKEN", token: "test-token" }),
}),
const seen: { wellKnown?: string; remote?: string; authorization?: string } = {}
const client = remoteConfigClient({
seen,
wellKnown: {
remote_config: {
url: "https://config.example.com/opencode.json",
headers: {
Authorization: "Bearer {env:TEST_TOKEN}",
},
},
},
remote: {
mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: true } },
},
})
const layer = Config.layer.pipe(
Layer.provide(testFlock),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(fakeAuth),
Layer.provide(emptyAccount),
Layer.provideMerge(infra),
Layer.provide(noopNpm),
)
try {
await provideTmpdirInstance(
() =>
Config.Service.use((svc) =>
Effect.gen(function* () {
const config = yield* svc.get()
expect(wellknownFetchedUrl).toBe("https://example.com/.well-known/opencode")
expect(remoteFetchedUrl).toBe("https://config.example.com/opencode.json")
expect(remoteHeaders).toEqual({ Authorization: "Bearer test-token" })
expect(seen.wellKnown).toBe("https://example.com/.well-known/opencode")
expect(seen.remote).toBe("https://config.example.com/opencode.json")
expect(seen.authorization).toBe("Bearer test-token")
expect(config.mcp?.confluence?.enabled).toBe(true)
}),
),
{ git: true },
).pipe(Effect.scoped, Effect.provide(layer), Effect.runPromise)
).pipe(
Effect.scoped,
Effect.provide(configLayer({ auth: wellKnownAuth("https://example.com"), client })),
Effect.runPromise,
)
} finally {
globalThis.fetch = originalFetch
if (originalToken === undefined) delete process.env.TEST_TOKEN
else process.env.TEST_TOKEN = originalToken
}
})
test("wellknown token env substitution does not mutate process env", async () => {
const originalToken = process.env.TEST_TOKEN
process.env.TEST_TOKEN = "preexisting-token"
const seen: { wellKnown?: string; remote?: string; authorization?: string } = {}
const client = remoteConfigClient({
seen,
wellKnown: {
remote_config: {
url: "https://config.example.com/opencode.json",
headers: {
Authorization: "Bearer {env:TEST_TOKEN}",
},
},
},
remote: {
mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: true } },
},
})
try {
const config = await provideTmpdirInstance(() => Config.Service.use((svc) => svc.get()), {
git: true,
config: { username: "{env:TEST_TOKEN}" },
}).pipe(
Effect.scoped,
Effect.provide(configLayer({ auth: wellKnownAuth("https://example.com"), client })),
Effect.runPromise,
)
expect(seen.authorization).toBe("Bearer test-token")
expect(config.username).toBe("test-token")
expect(process.env.TEST_TOKEN).toBe("preexisting-token")
} finally {
if (originalToken === undefined) delete process.env.TEST_TOKEN
else process.env.TEST_TOKEN = originalToken
}
})
test("wellknown config null is treated as absent", async () => {
const seen: { wellKnown?: string; remote?: string; authorization?: string } = {}
const client = remoteConfigClient({
seen,
wellKnown: {
config: null,
remote_config: {
url: "https://config.example.com/opencode.json",
},
},
remote: {
mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: true } },
},
})
await provideTmpdirInstance(
() =>
Config.Service.use((svc) =>
Effect.gen(function* () {
const config = yield* svc.get()
expect(seen.remote).toBe("https://config.example.com/opencode.json")
expect(config.mcp?.confluence?.enabled).toBe(true)
}),
),
{ git: true },
).pipe(
Effect.scoped,
Effect.provide(configLayer({ auth: wellKnownAuth("https://example.com"), client })),
Effect.runPromise,
)
})
test("wellknown remote_config rejects non-object config responses", async () => {
const seen: { wellKnown?: string; remote?: string; authorization?: string } = {}
const client = remoteConfigClient({
seen,
wellKnown: {
remote_config: {
url: "https://config.example.com/opencode.json",
},
},
remote: "not an object",
})
const exit = await provideTmpdirInstance(() => Config.Service.use((svc) => svc.get()).pipe(Effect.exit), {
git: true,
}).pipe(
Effect.scoped,
Effect.provide(configLayer({ auth: wellKnownAuth("https://example.com"), client })),
Effect.runPromise,
)
expect(seen.remote).toBe("https://config.example.com/opencode.json")
expect(Exit.isFailure(exit)).toBe(true)
})
describe("resolvePluginSpec", () => {
test("keeps package specs unchanged", async () => {
await using tmp = await tmpdir()

View File

@@ -1,12 +1,11 @@
import { describe, expect } from "bun:test"
import { Effect, Layer, Option } from "effect"
import { Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
import path from "path"
import { pathToFileURL } from "url"
import { Account } from "../../src/account/account"
import { Auth } from "../../src/auth"
import { Bus } from "../../src/bus"
import { Config } from "../../src/config/config"
import { Env } from "../../src/env"
@@ -15,22 +14,18 @@ import { Plugin } from "../../src/plugin/index"
import { ModelID, ProviderID } from "../../src/provider/schema"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { AccountTest } from "../fake/account"
import { AuthTest } from "../fake/auth"
import { NpmTest } from "../fake/npm"
const emptyAccount = Layer.mock(Account.Service)({
active: () => Effect.succeed(Option.none()),
activeOrg: () => Effect.succeed(Option.none()),
})
const emptyAuth = Layer.mock(Auth.Service)({
all: () => Effect.succeed({}),
})
const configLayer = Config.layer.pipe(
Layer.provide(EffectFlock.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth),
Layer.provide(emptyAccount),
Layer.provide(AuthTest.empty),
Layer.provide(AccountTest.empty),
Layer.provide(NpmTest.noop),
Layer.provide(FetchHttpClient.layer),
)
const it = testEffect(
Layer.mergeAll(

View File

@@ -1,12 +1,11 @@
import { afterEach, describe, expect } from "bun:test"
import { Effect, Layer, Option } from "effect"
import { Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
import path from "path"
import { pathToFileURL } from "url"
import { Account } from "../../src/account/account"
import { Auth } from "../../src/auth"
import { Bus } from "../../src/bus"
import { Config } from "../../src/config/config"
@@ -24,22 +23,18 @@ import { SessionPrompt } from "../../src/session/prompt"
import { SyncEvent } from "../../src/sync"
import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { AccountTest } from "../fake/account"
import { AuthTest } from "../fake/auth"
import { NpmTest } from "../fake/npm"
const emptyAccount = Layer.mock(Account.Service)({
active: () => Effect.succeed(Option.none()),
activeOrg: () => Effect.succeed(Option.none()),
})
const emptyAuth = Layer.mock(Auth.Service)({
all: () => Effect.succeed({}),
})
const configLayer = Config.layer.pipe(
Layer.provide(EffectFlock.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Env.defaultLayer),
Layer.provide(emptyAuth),
Layer.provide(emptyAccount),
Layer.provide(AuthTest.empty),
Layer.provide(AccountTest.empty),
Layer.provide(NpmTest.noop),
Layer.provide(FetchHttpClient.layer),
)
const pluginLayer = Plugin.layer.pipe(
Layer.provide(Bus.layer),