mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-07 10:28:48 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a8da2c985 |
@@ -94,11 +94,9 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: End
|
||||
prepare: async (next) => {
|
||||
const selected =
|
||||
next.model ??
|
||||
(options.variant
|
||||
? await client.model
|
||||
.default({ location: { directory: next.location.directory, workspace: next.location.workspaceID } })
|
||||
.then((result) => result.data)
|
||||
: undefined)
|
||||
(await client.model
|
||||
.default({ location: { directory: next.location.directory, workspace: next.location.workspaceID } })
|
||||
.then((result) => result.data))
|
||||
const model = selected
|
||||
? {
|
||||
providerID: selected.providerID,
|
||||
@@ -108,7 +106,12 @@ async function execute(input: RunCommandInput, prepared: Prepared, endpoint: End
|
||||
: undefined
|
||||
if ((options.variant ?? explicit?.variant) && !model)
|
||||
throw new RunTargetError("Cannot select a variant before selecting a model", next.session?.id)
|
||||
return { model, agent: next.agent }
|
||||
const agent =
|
||||
next.agent ??
|
||||
(await client.agent
|
||||
.list({ location: { directory: next.location.directory, workspace: next.location.workspaceID } })
|
||||
.then((result) => result.data.find((item) => item.mode !== "subagent" && !item.hidden)?.id))
|
||||
return { model, agent }
|
||||
},
|
||||
}).catch((error) => {
|
||||
if (!(error instanceof RunTargetError)) throw error
|
||||
|
||||
@@ -56,13 +56,16 @@ export async function resolveSessionTarget(input: {
|
||||
agent: input.agent ?? selected?.agent,
|
||||
signal: input.signal,
|
||||
})
|
||||
if (!selected && (!prepared.agent || !prepared.model)) {
|
||||
throw new SessionTargetMutationError(new Error("Creating a session requires an agent and model"))
|
||||
}
|
||||
const session =
|
||||
selected ??
|
||||
(await input.client.session
|
||||
.create(
|
||||
{
|
||||
agent: prepared.agent,
|
||||
model: prepared.model,
|
||||
agent: prepared.agent!,
|
||||
model: prepared.model!,
|
||||
location: { directory: location.directory, workspaceID: location.workspaceID },
|
||||
},
|
||||
...requestOptions(input.signal),
|
||||
|
||||
@@ -61,7 +61,11 @@ describe("session target resolver", () => {
|
||||
spyOn(client.location, "get").mockResolvedValue(location("/server", "work_1"))
|
||||
const create = spyOn(client.session, "create").mockImplementation(async (input) => {
|
||||
order.push("create")
|
||||
expect(input).toMatchObject({ agent: "prepared", location: { directory: "/server", workspaceID: "work_1" } })
|
||||
expect(input).toMatchObject({
|
||||
agent: "prepared",
|
||||
model: { providerID: "openai", id: "gpt-5" },
|
||||
location: { directory: "/server", workspaceID: "work_1" },
|
||||
})
|
||||
return session("ses_fresh", "/server", "work_1")
|
||||
})
|
||||
|
||||
@@ -71,20 +75,22 @@ describe("session target resolver", () => {
|
||||
prepare: async (input) => {
|
||||
order.push("prepare")
|
||||
expect(input.location.workspaceID).toBe("work_1")
|
||||
return { model: input.model, agent: "prepared" }
|
||||
return { model: { providerID: "openai", id: "gpt-5" }, agent: "prepared" }
|
||||
},
|
||||
})
|
||||
expect(create).toHaveBeenCalledTimes(1)
|
||||
expect(order).toEqual(["prepare", "create"])
|
||||
})
|
||||
|
||||
test("uses the agent resolved by the server for a fresh Session", async () => {
|
||||
test("requires an explicit agent and model for a fresh Session", async () => {
|
||||
const client = OpenCode.make({ baseUrl: "https://opencode.test" })
|
||||
spyOn(client.location, "get").mockResolvedValue(location("/project"))
|
||||
spyOn(client.session, "create").mockResolvedValue({ ...session("ses_fresh", "/project"), agent: "review" })
|
||||
const create = spyOn(client.session, "create")
|
||||
|
||||
const target = await resolveSessionTarget({ client, prepare })
|
||||
expect(target.agent).toBe("review")
|
||||
await expect(resolveSessionTarget({ client, prepare })).rejects.toThrow(
|
||||
"Creating a session requires an agent and model",
|
||||
)
|
||||
expect(create).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("does not retry an ambiguous Session creation", async () => {
|
||||
|
||||
@@ -120,12 +120,12 @@ export type SessionListOperation<E = never> = (input?: Endpoint5_0Input) => Effe
|
||||
export type Endpoint5_1Input = {
|
||||
readonly id?: Session.ID | undefined
|
||||
readonly title?: string | undefined
|
||||
readonly agent?: Agent.ID | undefined
|
||||
readonly model?: Model.Ref | undefined
|
||||
readonly agent: Agent.ID
|
||||
readonly model: Model.Ref
|
||||
readonly location?: Location.Ref | undefined
|
||||
}
|
||||
export type Endpoint5_1Output = Session.Info
|
||||
export type SessionCreateOperation<E = never> = (input?: Endpoint5_1Input) => Effect.Effect<Endpoint5_1Output, E>
|
||||
export type SessionCreateOperation<E = never> = (input: Endpoint5_1Input) => Effect.Effect<Endpoint5_1Output, E>
|
||||
|
||||
export type Endpoint5_2Input = {
|
||||
readonly info: Session.Info
|
||||
|
||||
@@ -305,15 +305,15 @@ const Endpoint5_0 = (raw: RawClient["server.session"]) => (input?: Endpoint5_0In
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_1 = (raw: RawClient["server.session"]) => (input?: Endpoint5_1Input) =>
|
||||
const Endpoint5_1 = (raw: RawClient["server.session"]) => (input: Endpoint5_1Input) =>
|
||||
preserveEffect<Endpoint5_1Output>()(
|
||||
raw["session.create"]({
|
||||
payload: {
|
||||
id: input?.["id"],
|
||||
title: input?.["title"],
|
||||
agent: input?.["agent"],
|
||||
model: input?.["model"],
|
||||
location: input?.["location"],
|
||||
id: input["id"],
|
||||
title: input["title"],
|
||||
agent: input["agent"],
|
||||
model: input["model"],
|
||||
location: input["location"],
|
||||
},
|
||||
}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
|
||||
@@ -464,17 +464,17 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
create: (input?: SessionCreateInput, requestOptions?: RequestOptions) =>
|
||||
create: (input: SessionCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionCreateOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session`,
|
||||
body: {
|
||||
id: input?.["id"],
|
||||
title: input?.["title"],
|
||||
agent: input?.["agent"],
|
||||
model: input?.["model"],
|
||||
location: input?.["location"],
|
||||
id: input["id"],
|
||||
title: input["title"],
|
||||
agent: input["agent"],
|
||||
model: input["model"],
|
||||
location: input["location"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
|
||||
@@ -2436,36 +2436,36 @@ export type SessionCreateInput = {
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
|
||||
}["id"]
|
||||
readonly title?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
|
||||
}["title"]
|
||||
readonly agent?: {
|
||||
readonly agent: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
|
||||
}["agent"]
|
||||
readonly model?: {
|
||||
readonly model: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
|
||||
}["model"]
|
||||
readonly location?: {
|
||||
readonly id?: string | null
|
||||
readonly title?: string | null
|
||||
readonly agent?: string | null
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
|
||||
}["location"]
|
||||
}
|
||||
|
||||
@@ -181,6 +181,8 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
|
||||
const page = yield* client.session.list({ limit: 10 })
|
||||
const active = yield* client.session.active()
|
||||
const created = yield* client.session.create({
|
||||
agent: Agent.ID.make("build"),
|
||||
model: Model.Ref.make({ id: "claude", providerID: "anthropic" }),
|
||||
location: Location.Ref.make({ directory: AbsolutePath.make("/tmp/project") }),
|
||||
})
|
||||
yield* client.session.switchAgent({ sessionID: Session.ID.make("ses_test"), agent: Agent.ID.make("build") })
|
||||
|
||||
@@ -454,7 +454,11 @@ test("session methods use the public HTTP contract", async () => {
|
||||
|
||||
const page = await client.session.list({ limit: 10, order: "desc", parentID: null })
|
||||
const active = await client.session.active()
|
||||
const created = await client.session.create({ location: { directory: "/tmp/project" } })
|
||||
const created = await client.session.create({
|
||||
agent: "build",
|
||||
model: { id: "claude", providerID: "anthropic" },
|
||||
location: { directory: "/tmp/project" },
|
||||
})
|
||||
await client.session.switchAgent({ sessionID: "ses_test", agent: "build" })
|
||||
await client.session.switchModel({
|
||||
sessionID: "ses_test",
|
||||
@@ -528,7 +532,7 @@ test("middleware errors remain declared client errors", async () => {
|
||||
})
|
||||
|
||||
try {
|
||||
await client.session.create({})
|
||||
await client.session.create({ agent: "build", model: { id: "claude", providerID: "anthropic" } })
|
||||
throw new Error("Expected request to fail")
|
||||
} catch (error) {
|
||||
expect(isUnauthorizedError(error)).toBe(true)
|
||||
|
||||
@@ -340,12 +340,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../p
|
||||
hook: (name, callback) => hooks.register("session", name, callback),
|
||||
create: (input) =>
|
||||
runtime.session.create({
|
||||
id: input?.id,
|
||||
title: input?.title,
|
||||
agent: input?.agent,
|
||||
model: input?.model,
|
||||
id: input.id,
|
||||
title: input.title,
|
||||
agent: input.agent,
|
||||
model: input.model,
|
||||
location:
|
||||
input?.location ?? Location.Ref.make({ directory: location.directory, workspaceID: location.workspaceID }),
|
||||
input.location ?? Location.Ref.make({ directory: location.directory, workspaceID: location.workspaceID }),
|
||||
}),
|
||||
get: (input) => runtime.session.get(input.sessionID),
|
||||
prompt: runtime.session.prompt,
|
||||
|
||||
@@ -269,25 +269,21 @@ export function fromPromise(plugin: Plugin) {
|
||||
register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
|
||||
create: (input) =>
|
||||
run(
|
||||
host.session.create(
|
||||
input === undefined
|
||||
? undefined
|
||||
: {
|
||||
id: input.id == null ? undefined : Session.ID.make(input.id),
|
||||
agent: input.agent == null ? undefined : Agent.ID.make(input.agent),
|
||||
model: input.model == null ? undefined : model(input.model),
|
||||
location:
|
||||
input.location == null
|
||||
? undefined
|
||||
: Location.Ref.make({
|
||||
directory: AbsolutePath.make(input.location.directory),
|
||||
workspaceID:
|
||||
input.location.workspaceID === undefined
|
||||
? undefined
|
||||
: Workspace.ID.make(input.location.workspaceID),
|
||||
}),
|
||||
},
|
||||
),
|
||||
host.session.create({
|
||||
id: input.id == null ? undefined : Session.ID.make(input.id),
|
||||
agent: Agent.ID.make(input.agent),
|
||||
model: model(input.model),
|
||||
location:
|
||||
input.location == null
|
||||
? undefined
|
||||
: Location.Ref.make({
|
||||
directory: AbsolutePath.make(input.location.directory),
|
||||
workspaceID:
|
||||
input.location.workspaceID === undefined
|
||||
? undefined
|
||||
: Workspace.ID.make(input.location.workspaceID),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
get: (input) => run(host.session.get({ sessionID: Session.ID.make(input.sessionID) })),
|
||||
prompt: (input) =>
|
||||
|
||||
@@ -151,8 +151,8 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||
payload: Schema.Struct({
|
||||
id: Session.ID.pipe(Schema.optional),
|
||||
title: Schema.String.pipe(Schema.optional),
|
||||
agent: Agent.ID.pipe(Schema.optional),
|
||||
model: Model.Ref.pipe(Schema.optional),
|
||||
agent: Agent.ID,
|
||||
model: Model.Ref,
|
||||
location: Location.Ref.pipe(Schema.optional),
|
||||
}),
|
||||
success: Schema.Struct({ data: Session.Info }),
|
||||
@@ -160,7 +160,7 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.create",
|
||||
summary: "Create session",
|
||||
description: "Create a session at the requested location.",
|
||||
description: "Create a session with an explicit agent and model at the requested location.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user