fix(core): refresh Console auth before catalog load
This commit is contained in:
@@ -89,14 +89,24 @@ export const OpencodePlugin = define<HttpClient.HttpClient | Bus.Service | Scope
|
||||
const http = yield* HttpClient.HttpClient
|
||||
const loading = Semaphore.makeUnsafe(1)
|
||||
let connected = false
|
||||
let savedConnection = false
|
||||
let providers: typeof ConfigV1.Info.Type.provider | undefined
|
||||
|
||||
const load = Effect.fn("OpencodePlugin.load")(function* () {
|
||||
const connection = yield* ctx.integration.connection.active("opencode")
|
||||
const credential = connection
|
||||
const resolved = connection
|
||||
? yield* ctx.integration.connection.resolve(connection).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
: undefined
|
||||
// Plugin activation batches transforms, so the first resolve can precede this plugin's OAuth registration.
|
||||
const credential =
|
||||
connection && resolved?.type === "oauth" && resolved.expires <= Date.now() + Duration.toMillis(Duration.minutes(5))
|
||||
? yield* ctx.integration.reload().pipe(
|
||||
Effect.andThen(ctx.integration.connection.resolve(connection)),
|
||||
Effect.catch(() => Effect.succeed(undefined)),
|
||||
)
|
||||
: resolved
|
||||
connected = connection !== undefined
|
||||
savedConnection = connection?.type === "credential"
|
||||
providers = credential
|
||||
? yield* fetchProviders(http, credential).pipe(
|
||||
Effect.catch((cause) =>
|
||||
@@ -116,6 +126,7 @@ export const OpencodePlugin = define<HttpClient.HttpClient | Bus.Service | Scope
|
||||
|
||||
yield* load()
|
||||
yield* ctx.catalog.transform((catalog) => {
|
||||
if (savedConnection && providers === undefined) catalog.provider.remove(Provider.ID.opencode)
|
||||
for (const [providerID, item] of Object.entries(providers ?? {})) {
|
||||
catalog.provider.update(providerID, (provider) => {
|
||||
provider.integrationID = Integration.ID.make("opencode")
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Plugin } from "@opencode-ai/core/plugin"
|
||||
import { PluginHost } from "@opencode-ai/core/plugin/host"
|
||||
import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
|
||||
import { Provider } from "@opencode-ai/core/provider"
|
||||
import { State } from "@opencode-ai/core/state"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PluginTestLayer } from "./fixture"
|
||||
|
||||
@@ -283,6 +284,96 @@ describe("OpencodePlugin", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("refreshes saved OAuth before loading the Console catalog on cold startup", () =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
const requests: string[] = []
|
||||
return {
|
||||
requests,
|
||||
server: Bun.serve({
|
||||
port: 0,
|
||||
fetch: (request) => {
|
||||
const url = new URL(request.url)
|
||||
if (url.pathname === "/auth/device/token") {
|
||||
requests.push("refresh")
|
||||
return Response.json({ access_token: "fresh", refresh_token: "next", expires_in: 600 })
|
||||
}
|
||||
if (url.pathname === "/api/config") {
|
||||
requests.push(`config:${request.headers.get("authorization")}`)
|
||||
return Response.json({
|
||||
config: {
|
||||
provider: {
|
||||
console: {
|
||||
name: "Console",
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
models: { current: { name: "Current" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return new Response("Not found", { status: 404 })
|
||||
},
|
||||
}),
|
||||
}
|
||||
}),
|
||||
({ requests, server }) =>
|
||||
Effect.gen(function* () {
|
||||
yield* (yield* Credential.Service).create({
|
||||
integrationID: Integration.ID.make("opencode"),
|
||||
value: Credential.OAuth.make({
|
||||
type: "oauth",
|
||||
methodID: Integration.MethodID.make("device"),
|
||||
access: "expired",
|
||||
refresh: "refresh",
|
||||
expires: 1,
|
||||
metadata: { server: server.url.origin },
|
||||
}),
|
||||
})
|
||||
|
||||
yield* State.batch(addPlugin())
|
||||
|
||||
expect(requests).toEqual(["refresh", "config:Bearer fresh"])
|
||||
expect(
|
||||
yield* (yield* Catalog.Service).model.get(Provider.ID.make("console"), Model.ID.make("current")),
|
||||
).toBeDefined()
|
||||
}),
|
||||
({ server }) => Effect.promise(() => server.stop(true)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("hides legacy fallback models when Console configuration fails", () =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.sync(() =>
|
||||
Bun.serve({
|
||||
port: 0,
|
||||
fetch: () => new Response("Unauthorized", { status: 401 }),
|
||||
}),
|
||||
),
|
||||
(server) =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
yield* catalog.transform((draft) => {
|
||||
draft.provider.update(Provider.ID.opencode, () => {})
|
||||
draft.model.update(Provider.ID.opencode, Model.ID.make("legacy"), () => {})
|
||||
})
|
||||
yield* (yield* Credential.Service).create({
|
||||
integrationID: Integration.ID.make("opencode"),
|
||||
value: Credential.Key.make({
|
||||
type: "key",
|
||||
key: "console-key",
|
||||
metadata: { server: server.url.origin },
|
||||
}),
|
||||
})
|
||||
|
||||
yield* State.batch(addPlugin())
|
||||
|
||||
expect(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("legacy"))).toBeUndefined()
|
||||
}),
|
||||
(server) => Effect.promise(() => server.stop(true)),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("uses a public key and disables paid models without credentials", () =>
|
||||
withEnv({ OPENCODE_API_KEY: undefined }, () =>
|
||||
Effect.gen(function* () {
|
||||
|
||||
Reference in New Issue
Block a user