Compare commits

...
4 changed files with 52 additions and 21 deletions
+1 -1
View File
@@ -162,6 +162,6 @@ describe("MCP OAuth", () => {
})
test("rejects an invalid redirect URL", async () => {
await expect(authorize("not a URL")).rejects.toThrow("cannot be parsed as a URL")
await expect(authorize("not a URL")).rejects.toThrow(TypeError)
})
})
+2 -2
View File
@@ -66,7 +66,7 @@ async function createRegistryFixture(directory: string) {
exports: "./index.js",
})
await Bun.write(path.join(root, "package", "index.js"), `export const version = "${version}"\n`)
await Bun.$`tar -czf ${path.join(root, "package.tgz")} -C ${root} package`
await Bun.$`tar -czf package.tgz package`.cwd(root)
tarballs.set(version, await Bun.file(path.join(root, "package.tgz")).bytes())
}
const state = { latest: "1.0.0" }
@@ -252,7 +252,7 @@ describe("Npm.add", () => {
}
}).pipe(Effect.scoped, Effect.provide(npmLayer(cache)), Effect.runPromise)
expect(entries.added.entrypoint).toEndWith("/index.js")
expect(entries.added.entrypoint).toBe(pathToFileURL(path.join(entries.added.directory, "index.js")).href)
expect(entries.added.version).toBe(fixture.commit)
expect(entries.cached).toEqual(entries.added)
expect(entries.resolved).toEqual(entries.added)
+47 -17
View File
@@ -3,7 +3,9 @@ import { createServer } from "node:http"
import { makeMemoryDriver } from "@opencode-ai/core/environment/index"
import { Workspace } from "@opencode-ai/core/workspace"
import { WorkspaceDriver } from "@opencode-ai/core/workspace/driver"
import { Effect } from "effect"
import { Agent } from "@opencode-ai/schema/agent"
import { Integration } from "@opencode-ai/schema/integration"
import { Effect, Schedule, Schema } from "effect"
import { tmpdir } from "../../core/test/fixture/tmpdir"
import { it } from "../../core/test/lib/effect"
import { ServerFetch } from "../src/fetch"
@@ -58,19 +60,35 @@ function occupy(port: number, cancel = false) {
})
}
const ready = (handler: Handler) =>
Effect.promise(() => handler(new Request("http://opencode.local/api/model/default")))
const connectOpenAI = (handler: Handler) =>
Effect.promise(() =>
handler(
new Request("http://opencode.local/api/integration/openai/connect/oauth", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ methodID: "chatgpt-browser" }),
}),
),
)
Effect.gen(function* () {
// Catalog endpoints no longer wait for plugin initialization.
yield* Effect.gen(function* () {
const response = yield* Effect.promise(() => handler(new Request("http://opencode.local/api/integration")))
expect(response.status).toBe(200)
const body = Schema.decodeUnknownSync(Schema.Struct({ data: Schema.Array(Integration.Info) }))(
yield* Effect.promise(() => response.json()),
)
return body.data.some(
(integration) =>
integration.id === "openai" &&
integration.methods.some((method) => method.type === "oauth" && method.id === "chatgpt-browser"),
)
}).pipe(
Effect.filterOrFail((ready) => ready),
Effect.retry(Schedule.spaced("10 millis")),
Effect.timeout("2 seconds"),
)
return yield* Effect.promise(() =>
handler(
new Request("http://opencode.local/api/integration/openai/connect/oauth", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ methodID: "chatgpt-browser" }),
}),
),
)
})
const workspaceDriver = WorkspaceDriver.make({
create: ({ workspaceID }) => Effect.succeed({ binding: { workspaceID } }),
@@ -189,7 +207,6 @@ it.live("cancels a stale OpenAI OAuth callback server before falling back", () =
Effect.gen(function* () {
const requests = yield* occupy(1455, true)
const handler = yield* ServerFetch.make(options)
yield* ready(handler)
const response = yield* connectOpenAI(handler)
expect(response.status).toBe(200)
@@ -203,7 +220,6 @@ it.live("falls back to port 1457 when OpenAI OAuth port 1455 remains busy", () =
Effect.gen(function* () {
const requests = yield* occupy(1455)
const handler = yield* ServerFetch.make(options)
yield* ready(handler)
const response = yield* connectOpenAI(handler)
expect(response.status).toBe(200)
@@ -220,7 +236,6 @@ it.live(
yield* occupy(1455)
yield* occupy(1457)
const handler = yield* ServerFetch.make(options)
yield* ready(handler)
const response = yield* connectOpenAI(handler)
expect(response.status).toBe(400)
@@ -439,7 +454,22 @@ it.live("routes pending requests by Session without loading an instance", () =>
expect(yield* Effect.promise(() => globalForms.json())).toMatchObject({ data: [{ title: "Global form" }] })
// Agent permission policy is installed by plugin activation.
expect((yield* ready(handler)).status).toBe(200)
yield* Effect.gen(function* () {
const response = yield* Effect.promise(() => handler(new Request("http://opencode.local/api/agent")))
expect(response.status).toBe(200)
const body = Schema.decodeUnknownSync(Schema.Struct({ data: Schema.Array(Agent.Info) }))(
yield* Effect.promise(() => response.json()),
)
return body.data.some(
(agent) =>
agent.id === "build" &&
agent.permissions.some((rule) => rule.action === "shell" && rule.resource === "*" && rule.effect === "ask"),
)
}).pipe(
Effect.filterOrFail((ready) => ready),
Effect.retry(Schedule.spaced("10 millis")),
Effect.timeout("2 seconds"),
)
const createdPermission = yield* Effect.promise(() =>
handler(
new Request(`http://opencode.local/api/session/${created.data.id}/permission`, {
+2 -1
View File
@@ -293,7 +293,8 @@ const layer = Layer.effect(
installedNameValue,
installed?.path ?? path.join(staging, "node_modules", installedNameValue),
target,
subpaths,
// Resolve installed entrypoints after rename so Bun cannot hold the staging directory open on Windows.
installed ? [] : subpaths,
)
if (!installed && !result.entrypoint) return yield* new InstallFailedError({ add: [pkg], dir: staging })
return { name: installedNameValue, result }