mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
fix(server): let agent and provider lists wait for plugins (#59082)
Co-authored-by: Kit Langton <kit.langton@gmail.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
f6991636a5
commit
61163cb134
+1
-1
@@ -492,7 +492,7 @@ export function make(options: ClientOptions) {
|
||||
path: `/api/agent`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 404],
|
||||
declaredStatuses: [400, 401, 404, 503],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
|
||||
Generated
+10
@@ -359,6 +359,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "ServiceUnavailableError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Retrieve currently registered agents.",
|
||||
|
||||
@@ -3,13 +3,14 @@ import { Location } from "@opencode/schema/location"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/http-api"
|
||||
import { LocationQuery, locationQueryOpenApi } from "./location.js"
|
||||
import { AgentNotFoundError } from "../errors.js"
|
||||
import { AgentNotFoundError, ServiceUnavailableError } from "../errors.js"
|
||||
|
||||
export const AgentGroup = HttpApiGroup.make("server.agent")
|
||||
.add(
|
||||
HttpApiEndpoint.get("agent.list", "/api/agent", {
|
||||
query: LocationQuery,
|
||||
success: Location.response(Schema.Array(Agent.Info)),
|
||||
error: ServiceUnavailableError,
|
||||
})
|
||||
.annotateMerge(locationQueryOpenApi)
|
||||
.annotateMerge(
|
||||
|
||||
@@ -2,13 +2,14 @@ import { Agent } from "@opencode/core/agent"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/http-api"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../location"
|
||||
import { awaitPlugins, response } from "../location"
|
||||
import { AgentNotFoundError } from "@opencode/protocol/errors"
|
||||
|
||||
export const AgentHandler = HttpApiBuilder.group(Api, "server.agent", (handlers) =>
|
||||
handlers
|
||||
.handle("agent.list", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* awaitPlugins
|
||||
return yield* response(Agent.Service.use((agent) => agent.list()))
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ProviderNotFoundError } from "@opencode/protocol/errors"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/http-api"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../location"
|
||||
import { awaitPlugins, response } from "../location"
|
||||
|
||||
export const ProviderHandler = HttpApiBuilder.group(Api, "server.provider", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -11,6 +11,7 @@ export const ProviderHandler = HttpApiBuilder.group(Api, "server.provider", (han
|
||||
.handle(
|
||||
"provider.list",
|
||||
Effect.fn(function* () {
|
||||
yield* awaitPlugins
|
||||
const providers = yield* Provider.Service
|
||||
return yield* response(providers.available())
|
||||
}),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { it } from "../../core/test/lib/effect"
|
||||
import { createRoutes } from "../src/routes"
|
||||
|
||||
it.live(
|
||||
"provider reads stay nonblocking while authentication discovery waits for plugins",
|
||||
"agent and provider lists wait for plugins while single provider reads stay nonblocking",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const tmp = yield* tmpdirScoped("opencode-provider-endpoints-")
|
||||
@@ -32,6 +32,7 @@ it.live(
|
||||
models: { chat: {} },
|
||||
},
|
||||
},
|
||||
agents: { reviewer: { description: "Reviews changes" } },
|
||||
}),
|
||||
},
|
||||
}).pipe(Layer.provide(HttpServer.layerServices)),
|
||||
@@ -62,22 +63,35 @@ it.live(
|
||||
}),
|
||||
)
|
||||
})
|
||||
// Config providers activate after SDK plugins; reads must return the current snapshot without waiting.
|
||||
const list = yield* request("GET", "/api/provider").pipe(Effect.timeout("2 seconds"))
|
||||
// Config providers and agents activate after SDK plugins, so the lists wait rather than answer with a partial
|
||||
// catalog that a client would cache as loaded.
|
||||
const providers = yield* request("GET", "/api/provider").pipe(Effect.forkScoped)
|
||||
const agents = yield* request("GET", "/api/agent").pipe(Effect.forkScoped)
|
||||
yield* Deferred.await(started)
|
||||
const integrations = yield* request("GET", "/api/integration").pipe(Effect.forkScoped)
|
||||
expect(list.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => list.json())).toMatchObject({
|
||||
location: { directory: tmp.path },
|
||||
data: expect.not.arrayContaining([expect.objectContaining({ id: "custom" })]),
|
||||
})
|
||||
const get = yield* request("GET", "/api/provider/custom").pipe(Effect.timeout("2 seconds"))
|
||||
expect(get.status).toBe(404)
|
||||
expect(yield* Effect.promise(() => get.json())).toMatchObject({
|
||||
_tag: "ProviderNotFoundError",
|
||||
providerID: "custom",
|
||||
})
|
||||
expect(providers.pollUnsafe()).toBeUndefined()
|
||||
expect(agents.pollUnsafe()).toBeUndefined()
|
||||
yield* Deferred.succeed(release, undefined)
|
||||
const list = yield* Fiber.join(providers)
|
||||
expect(list.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => list.json())).toMatchObject({
|
||||
location: { directory: tmp.path },
|
||||
data: expect.arrayContaining([expect.objectContaining({ id: "custom" })]),
|
||||
})
|
||||
const agentList = yield* Fiber.join(agents)
|
||||
expect(agentList.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => agentList.json())).toMatchObject({
|
||||
data: expect.arrayContaining([
|
||||
expect.objectContaining({ id: "build" }),
|
||||
expect.objectContaining({ id: "reviewer" }),
|
||||
]),
|
||||
})
|
||||
const discovered = yield* Fiber.join(integrations)
|
||||
expect(discovered.status).toBe(200)
|
||||
expect(yield* Effect.promise(() => discovered.json())).toMatchObject({
|
||||
|
||||
Reference in New Issue
Block a user