fix(acp): restore session options and reasoning boundaries (#48225)

Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
This commit is contained in:
Jacob Wolf 2026-09-11 08:16:41 -05:00 committed by GitHub
parent 0b934e9516
commit 95daf90670
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 495 additions and 46 deletions

View file

@ -61,8 +61,11 @@ export function buildEffortSelectOption(input: {
description: "Available effort levels for this model",
category: "thought_level",
type: "select",
currentValue: selectVariant(input.currentVariant, input.variants),
options: input.variants.map((variant) => ({
currentValue:
input.currentVariant === DEFAULT_VARIANT_VALUE
? DEFAULT_VARIANT_VALUE
: selectVariant(input.currentVariant, input.variants),
options: [...new Set([...input.variants, DEFAULT_VARIANT_VALUE])].map((variant) => ({
value: variant,
name: formatVariantName(variant),
})),

View file

@ -134,7 +134,7 @@ export class Subscription {
sessionId: message.info.sessionID,
update: {
sessionUpdate,
messageId: message.info.id,
messageId: part.type === "reasoning" ? part.id : message.info.id,
...chunk,
},
})
@ -248,7 +248,7 @@ export class Subscription {
sessionId: session.id,
update: {
sessionUpdate: "agent_thought_chunk",
messageId: props.messageID,
messageId: props.partID,
content: {
type: "text",
text: props.delta,

View file

@ -31,10 +31,10 @@ import {
} from "@agentclientprotocol/sdk"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import type { AssistantMessage, Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
import type { AssistantMessage, Message, OpencodeClient, Session, SessionMessageResponse } from "@opencode-ai/sdk/v2"
import { Context, Effect, Layer, ManagedRuntime } from "effect"
import * as ACPError from "./error"
import { buildConfigOptions, parseModelSelection } from "./config-option"
import { buildConfigOptions, DEFAULT_VARIANT_VALUE, parseModelSelection } from "./config-option"
import { promptContentToParts } from "./content"
import { Directory } from "./directory"
import { ACPEvent } from "./event"
@ -210,7 +210,7 @@ export function make(input: {
const loadSession = Effect.fn("ACP.loadSession")(function* (params: LoadSessionRequest) {
const snapshot = yield* directorySnapshot(params.cwd)
yield* request(
const backing = yield* request(
() => input.sdk.session.get({ directory: params.cwd, sessionID: params.sessionId }, { throwOnError: true }),
"session",
)
@ -218,15 +218,18 @@ export function make(input: {
() => input.sdk.session.messages({ directory: params.cwd, sessionID: params.sessionId }, { throwOnError: true }),
"session",
)
const restored = restoreFromMessages(messages.map((item) => item.info))
const model = restored.model ?? selectDefaultModel(snapshot)
const restored = restoreSession(
snapshot,
backing,
messages.map((item) => item.info),
)
const state = yield* session.load({
id: params.sessionId,
cwd: params.cwd,
mcpServers: params.mcpServers,
model,
variant: restored.variant ?? selectVariant(snapshot, model),
modeId: restored.modeId ?? (snapshot.availableModes.length > 0 ? snapshot.defaultModeID : undefined),
model: restored.model,
variant: restored.variant,
modeId: restored.modeId,
})
sessionSnapshots.set(state.id, snapshot)
@ -236,7 +239,7 @@ export function make(input: {
return {
configOptions: configOptions(snapshot, {
model: state.model ?? model,
model: state.model ?? restored.model,
variant: state.variant,
modeId: state.modeId,
}),
@ -291,7 +294,7 @@ export function make(input: {
const resumeSession = Effect.fn("ACP.resumeSession")(function* (params: ResumeSessionRequest) {
const snapshot = yield* directorySnapshot(params.cwd)
yield* request(
const backing = yield* request(
() => input.sdk.session.get({ directory: params.cwd, sessionID: params.sessionId }, { throwOnError: true }),
"session",
)
@ -303,15 +306,18 @@ export function make(input: {
),
"session",
)
const restored = restoreFromMessages(messages.map((item) => item.info))
const model = restored.model ?? selectDefaultModel(snapshot)
const restored = restoreSession(
snapshot,
backing,
messages.map((item) => item.info),
)
const state = yield* session.load({
id: params.sessionId,
cwd: params.cwd,
mcpServers: params.mcpServers ?? [],
model,
variant: restored.variant ?? selectVariant(snapshot, model),
modeId: restored.modeId ?? (snapshot.availableModes.length > 0 ? snapshot.defaultModeID : undefined),
model: restored.model,
variant: restored.variant,
modeId: restored.modeId,
})
sessionSnapshots.set(state.id, snapshot)
@ -320,7 +326,7 @@ export function make(input: {
return {
configOptions: configOptions(snapshot, {
model: state.model ?? model,
model: state.model ?? restored.model,
variant: state.variant,
modeId: state.modeId,
}),
@ -371,15 +377,18 @@ export function make(input: {
input.sdk.session.messages({ directory: params.cwd, sessionID: forked.id, limit: 20 }, { throwOnError: true }),
"session",
)
const restored = restoreFromMessages(messages.map((item) => item.info))
const model = restored.model ?? selectDefaultModel(snapshot)
const restored = restoreSession(
snapshot,
forked,
messages.map((item) => item.info),
)
const state = yield* session.load({
id: forked.id,
cwd: params.cwd,
mcpServers: params.mcpServers ?? [],
model,
variant: restored.variant ?? selectVariant(snapshot, model),
modeId: restored.modeId ?? (snapshot.availableModes.length > 0 ? snapshot.defaultModeID : undefined),
model: restored.model,
variant: restored.variant,
modeId: restored.modeId,
})
sessionSnapshots.set(state.id, snapshot)
@ -390,7 +399,7 @@ export function make(input: {
return {
sessionId: state.id,
configOptions: configOptions(snapshot, {
model: state.model ?? model,
model: state.model ?? restored.model,
variant: state.variant,
modeId: state.modeId,
}),
@ -408,23 +417,25 @@ export function make(input: {
if (params.configId === "model") {
const selected = yield* parseSelectedModel(snapshot, params.value)
const variant = selected.variant ?? selectVariant(snapshot, selected.model)
const variant = selectModelVariant(snapshot, current, selected)
const state = yield* session
.setVariant(params.sessionId, Directory.variants(snapshot, selected.model) ? variant : undefined)
.pipe(Effect.andThen(session.setModel(params.sessionId, selected.model)))
const options = configOptions(snapshot, {
model: state.model ?? selected.model,
variant: state.variant,
modeId: state.modeId,
})
yield* sendConfigOptionUpdate(input.connection, params.sessionId, options)
return {
configOptions: configOptions(snapshot, {
model: state.model ?? selected.model,
variant: state.variant,
modeId: state.modeId,
}),
configOptions: options,
}
}
if (params.configId === "effort") {
const model = current.model ?? selectDefaultModel(snapshot)
const variants = Directory.variants(snapshot, model)
if (!variants || !Object.keys(variants).includes(params.value)) {
if (!variants || !hasVariant(variants, params.value)) {
return yield* new ACPError.InvalidEffortError({ effort: params.value })
}
const state = yield* session.setVariant(params.sessionId, params.value)
@ -468,14 +479,18 @@ export function make(input: {
const current = yield* session.get(params.sessionId)
const snapshot = yield* configSnapshot(current)
const selected = yield* parseSelectedModel(snapshot, params.modelId)
yield* session
.setVariant(
params.sessionId,
Directory.variants(snapshot, selected.model)
? (selected.variant ?? selectVariant(snapshot, selected.model))
: undefined,
)
const state = yield* session
.setVariant(params.sessionId, selectModelVariant(snapshot, current, selected))
.pipe(Effect.andThen(session.setModel(params.sessionId, selected.model)))
yield* sendConfigOptionUpdate(
input.connection,
params.sessionId,
configOptions(snapshot, {
model: state.model ?? selected.model,
variant: state.variant,
modeId: state.modeId,
}),
)
return {}
})
@ -899,6 +914,24 @@ function selectVariant(snapshot: Directory.Snapshot, model: Directory.DefaultMod
return Object.keys(variants)[0]
}
function selectModelVariant(
snapshot: Directory.Snapshot,
current: ACPSession.Info,
selected: { model: Directory.DefaultModel; variant?: string },
) {
const variants = Directory.variants(snapshot, selected.model)
if (!variants) return
if (selected.variant) return selected.variant
if (sameModel(selected.model, current.model) && current.variant && hasVariant(variants, current.variant))
return current.variant
return selectVariant(snapshot, selected.model)
}
function hasVariant(variants: Directory.ModelVariants, variant: string) {
// "default" is also the persisted sentinel for no explicit variant override.
return variant === DEFAULT_VARIANT_VALUE || Object.hasOwn(variants, variant)
}
function configOptions(snapshot: Directory.Snapshot, session: ConfigState) {
return buildConfigOptions({
providers: Object.values(snapshot.providers),
@ -909,6 +942,25 @@ function configOptions(snapshot: Directory.Snapshot, session: ConfigState) {
})
}
function sendConfigOptionUpdate(
connection: ServiceConnection | undefined,
sessionId: string,
options: ReturnType<typeof configOptions>,
) {
if (!connection) return Effect.void
return Effect.tryPromise({
try: () =>
connection.sessionUpdate({
sessionId,
update: {
sessionUpdate: "config_option_update",
configOptions: options,
},
}),
catch: () => undefined,
}).pipe(Effect.ignore)
}
function parseSelectedModel(snapshot: Directory.Snapshot, modelId: string) {
const selected = parseModelSelection(modelId, Object.values(snapshot.providers))
const provider = snapshot.providers[ProviderV2.ID.make(selected.model.providerID)]
@ -1034,6 +1086,75 @@ function stableStringify(value: unknown): string {
.join(",")}}`
}
function restoreSession(
snapshot: Directory.Snapshot,
backing: Pick<Session, "agent" | "model">,
messages: MessageInfo[],
) {
const history = restoreFromMessages(messages)
const durable = restoreDurableModel(backing.model)
const model = restoreModel(snapshot, durable.model, history.model)
return {
model,
variant: restoreVariant(snapshot, model, durable, history),
modeId: restoreMode(snapshot, backing.agent, history.modeId),
}
}
function restoreDurableModel(model: Session["model"] | undefined) {
if (!model) return {}
return {
model: {
providerID: ProviderV2.ID.make(model.providerID),
modelID: ModelV2.ID.make(model.id),
},
variant: model.variant,
}
}
function restoreModel(
snapshot: Directory.Snapshot,
durable: Directory.DefaultModel | undefined,
history: Directory.DefaultModel | undefined,
) {
if (durable && hasModel(snapshot, durable)) return durable
if (history && hasModel(snapshot, history)) return history
return selectDefaultModel(snapshot)
}
function restoreVariant(
snapshot: Directory.Snapshot,
model: Directory.DefaultModel,
durable: { model?: Directory.DefaultModel; variant?: string },
history: { model?: Directory.DefaultModel; variant?: string },
) {
const variants = Directory.variants(snapshot, model)
if (!variants) return
if (sameModel(model, durable.model) && durable.variant && hasVariant(variants, durable.variant))
return durable.variant
if (sameModel(model, history.model) && history.variant && hasVariant(variants, history.variant))
return history.variant
return selectVariant(snapshot, model)
}
function restoreMode(snapshot: Directory.Snapshot, durable: string | undefined, history: string | undefined) {
if (hasMode(snapshot, durable)) return durable
if (hasMode(snapshot, history)) return history
if (snapshot.availableModes.length > 0) return snapshot.defaultModeID
}
function hasModel(snapshot: Directory.Snapshot, model: Directory.DefaultModel) {
return Boolean(snapshot.providers[model.providerID]?.models[model.modelID])
}
function hasMode(snapshot: Directory.Snapshot, modeId: string | undefined) {
return Boolean(modeId && snapshot.availableModes.some((mode) => mode.id === modeId))
}
function sameModel(left: Directory.DefaultModel, right: Directory.DefaultModel | undefined) {
return left.providerID === right?.providerID && left.modelID === right.modelID
}
function restoreFromMessages(messages: readonly MessageInfo[]) {
const user = messages.findLast(
(message) => message.role === "user" && message.model?.providerID && message.model.modelID,

View file

@ -114,6 +114,17 @@ describe("acp config options", () => {
expect(buildEffortSelectOption({ variants: [] })).toBeUndefined()
})
test("exposes an explicit default even when the provider only lists named variants", () => {
expect(buildEffortSelectOption({ variants: ["low", "medium"], currentVariant: "default" })).toMatchObject({
currentValue: "default",
options: [
{ value: "low", name: "Low" },
{ value: "medium", name: "Medium" },
{ value: "default", name: "Default" },
],
})
})
test("builds the mode select option with descriptions when present", () => {
expect(
buildModeSelectOption({

View file

@ -352,6 +352,40 @@ describe("acp event routing", () => {
).toEqual(["agent_thought_chunk", "agent_thought_chunk"])
})
it("uses reasoning part ids as ACP thought message boundaries", async () => {
const harness = createHarness()
await createKnownSession(harness.session, "ses_reasoning", {
messageId: "msg_reasoning",
partId: "part_first",
partType: "reasoning",
})
await Effect.runPromise(
harness.session.recordPartMetadata({
sessionId: "ses_reasoning",
messageId: "msg_reasoning",
partId: "part_second",
partType: "reasoning",
role: "assistant",
}),
)
await harness.subscription.handle(textDelta("ses_reasoning", "msg_reasoning", "part_first", "First"))
await harness.subscription.handle(textDelta("ses_reasoning", "msg_reasoning", "part_second", "Second"))
expect(harness.updates.map((update) => update.update)).toEqual([
{
sessionUpdate: "agent_thought_chunk",
messageId: "part_first",
content: { type: "text", text: "First" },
},
{
sessionUpdate: "agent_thought_chunk",
messageId: "part_second",
content: { type: "text", text: "Second" },
},
])
})
it("does not create extra subscriptions on repeated loadSession", async () => {
const harness = createHarness()
let subscription: ACPEvent.Subscription | undefined

View file

@ -194,6 +194,20 @@ describe("ACP service sessions", () => {
messages: readonly { info: unknown; parts: readonly unknown[] }[] = [],
options?: {
abort?: (input: { sessionID: string }) => Promise<{ data: boolean }>
get?: () => Promise<{
data: {
id: string
agent?: string
model?: { id: string; providerID: string; variant?: string }
}
}>
fork?: (input: { sessionID: string }) => Promise<{
data: {
id: string
agent?: string
model?: { id: string; providerID: string; variant?: string }
}
}>
prompt?: (input: unknown) => Promise<{ data: { info: ReturnType<typeof assistantInfo> } }>
sessionUpdate?: (update: SessionNotification) => Promise<void>
},
@ -243,7 +257,7 @@ describe("ACP service sessions", () => {
},
session: {
create: () => Promise.resolve({ data: { id: "ses_new" } }),
get: () => Promise.resolve({ data: { id: "ses_loaded" } }),
get: options?.get ?? (() => Promise.resolve({ data: { id: "ses_loaded" } })),
list: (input: { directory?: string }) =>
Promise.resolve({
data: input.directory ? sessions.filter((session) => session.directory === input.directory) : sessions,
@ -292,7 +306,7 @@ describe("ACP service sessions", () => {
}),
fork: (input: { sessionID: string }) => {
forks.push(input.sessionID)
return Promise.resolve({ data: { id: `fork_${input.sessionID}` } })
return options?.fork?.(input) ?? Promise.resolve({ data: { id: `fork_${input.sessionID}` } })
},
},
mcp: {
@ -378,6 +392,149 @@ describe("ACP service sessions", () => {
expect(result.configOptions?.find((option) => option.id === "mode")?.currentValue).toBe("plan")
})
it("restores durable model variant and mode before message history", async () => {
const { service } = makeService(
[
{
info: {
role: "assistant",
providerID: "test",
modelID: "second-model",
variant: "medium",
mode: "build",
},
parts: [],
},
],
{
get: () =>
Promise.resolve({
data: {
id: "ses_loaded",
agent: "plan",
model: { providerID: "test", id: "test-model", variant: "high" },
},
}),
},
)
const loaded = await Effect.runPromise(
service.loadSession({ cwd: "/workspace", sessionId: "ses_loaded", mcpServers: [] }),
)
const resumed = await Effect.runPromise(
service.resumeSession({ cwd: "/workspace", sessionId: "ses_loaded", mcpServers: [] }),
)
expect(select(loaded, "model")?.currentValue).toBe("test/test-model")
expect(select(loaded, "effort")?.currentValue).toBe("high")
expect(select(loaded, "mode")?.currentValue).toBe("plan")
expect(select(resumed, "model")?.currentValue).toBe("test/test-model")
expect(select(resumed, "effort")?.currentValue).toBe("high")
expect(select(resumed, "mode")?.currentValue).toBe("plan")
})
it.each(["loadSession", "resumeSession"] as const)("%s preserves default effort", async (method) => {
const { service, prompts, updates } = makeService(
[
{
info: {
role: "user",
model: { providerID: "test", modelID: "second-model", variant: "medium" },
agent: "build",
},
parts: [],
},
],
{
get: () =>
Promise.resolve({
data: {
id: "ses_loaded",
agent: "build",
model: { providerID: "test", id: "second-model", variant: "default" },
},
}),
},
)
const restored = await Effect.runPromise(
service[method]({ cwd: "/workspace", sessionId: "ses_loaded", mcpServers: [] }),
)
expect(select(restored, "effort")?.currentValue).toBe("default")
expect(flattenSelectOptions(select(restored, "effort")).map((option) => option.value)).toContain("default")
await Effect.runPromise(service.setSessionModel({ sessionId: "ses_loaded", modelId: "test/second-model" }))
const update = updates.findLast((item) => item.update.sessionUpdate === "config_option_update")?.update
if (update?.sessionUpdate !== "config_option_update") throw new Error("missing config option update")
expect(select({ configOptions: update.configOptions }, "effort")?.currentValue).toBe("default")
const synchronized = await Effect.runPromise(
service.setSessionConfigOption({ sessionId: "ses_loaded", configId: "model", value: "test/second-model" }),
)
expect(select(synchronized, "effort")?.currentValue).toBe("default")
await Effect.runPromise(service.prompt({ sessionId: "ses_loaded", prompt: [{ type: "text", text: "hello" }] }))
expect(prompts).toEqual([expect.objectContaining({ variant: "default" })])
await Effect.runPromise(
service.setSessionConfigOption({ sessionId: "ses_loaded", configId: "effort", value: "medium" }),
)
const reset = await Effect.runPromise(
service.setSessionConfigOption({ sessionId: "ses_loaded", configId: "effort", value: "default" }),
)
expect(select(reset, "effort")?.currentValue).toBe("default")
})
it("falls back from stale durable state to valid message state", async () => {
const { service } = makeService(
[
{
info: {
role: "assistant",
providerID: "test",
modelID: "second-model",
variant: "medium",
mode: "plan",
},
parts: [],
},
],
{
get: () =>
Promise.resolve({
data: {
id: "ses_loaded",
agent: "missing",
model: { providerID: "missing", id: "missing", variant: "missing" },
},
}),
},
)
const loaded = await Effect.runPromise(
service.loadSession({ cwd: "/workspace", sessionId: "ses_loaded", mcpServers: [] }),
)
expect(select(loaded, "model")?.currentValue).toBe("test/second-model")
expect(select(loaded, "effort")?.currentValue).toBe("medium")
expect(select(loaded, "mode")?.currentValue).toBe("plan")
})
it("restores default effort from history when durable model state is absent", async () => {
const { service } = makeService([
{
info: {
role: "user",
model: { providerID: "test", modelID: "second-model", variant: "default" },
agent: "build",
},
parts: [],
},
])
const resumed = await Effect.runPromise(service.resumeSession({ cwd: "/workspace", sessionId: "ses_loaded" }))
expect(select(resumed, "effort")?.currentValue).toBe("default")
})
it("replays loaded session transcript chunks", async () => {
const { service, updates } = makeService([
{
@ -418,6 +575,47 @@ describe("ACP service sessions", () => {
])
})
it("replays reasoning parts as separate ACP thought messages", async () => {
const { service, updates } = makeService([
{
info: { id: "msg_assistant", sessionID: "ses_loaded", role: "assistant" },
parts: [
{
id: "part_first",
sessionID: "ses_loaded",
messageID: "msg_assistant",
type: "reasoning",
text: "First",
time: { start: 1, end: 2 },
},
{
id: "part_second",
sessionID: "ses_loaded",
messageID: "msg_assistant",
type: "reasoning",
text: "Second",
time: { start: 3, end: 4 },
},
],
},
])
await Effect.runPromise(service.loadSession({ cwd: "/workspace", sessionId: "ses_loaded", mcpServers: [] }))
expect(updates.map((item) => item.update).filter((item) => item.sessionUpdate === "agent_thought_chunk")).toEqual([
{
sessionUpdate: "agent_thought_chunk",
messageId: "part_first",
content: { type: "text", text: "First" },
},
{
sessionUpdate: "agent_thought_chunk",
messageId: "part_second",
content: { type: "text", text: "Second" },
},
])
})
it("lists sessions sorted by updated time with cursor support", async () => {
const { service } = makeService()
const first = await Effect.runPromise(service.listSessions({ cwd: "/workspace" }))
@ -559,6 +757,41 @@ describe("ACP service sessions", () => {
expect(forks).toEqual(["ses_parent"])
})
it("restores fork state from the durable fork before message history", async () => {
const { service } = makeService(
[
{
info: {
role: "assistant",
providerID: "test",
modelID: "test-model",
variant: "default",
mode: "build",
},
parts: [],
},
],
{
fork: (input) =>
Promise.resolve({
data: {
id: `fork_${input.sessionID}`,
agent: "plan",
model: { providerID: "test", id: "second-model", variant: "medium" },
},
}),
},
)
const forked = await Effect.runPromise(
service.forkSession({ cwd: "/workspace", sessionId: "ses_parent", mcpServers: [] }),
)
expect(select(forked, "model")?.currentValue).toBe("test/second-model")
expect(select(forked, "effort")?.currentValue).toBe("medium")
expect(select(forked, "mode")?.currentValue).toBe("plan")
})
it("restores model variant and mode from the latest user message", async () => {
const { service } = makeService([
{
@ -772,7 +1005,7 @@ describe("ACP service sessions", () => {
})
it("switches model and returns updated model and effort options", async () => {
const { service } = makeService()
const { service, updates } = makeService()
const session = await Effect.runPromise(service.newSession({ cwd: "/workspace", mcpServers: [] }))
const updated = await Effect.runPromise(
service.setSessionConfigOption({
@ -784,7 +1017,49 @@ describe("ACP service sessions", () => {
expect(select(updated, "model")?.currentValue).toBe("test/second-model")
expect(select(updated, "effort")?.currentValue).toBe("low")
expect(flattenSelectOptions(select(updated, "effort")).map((option) => option.value)).toEqual(["low", "medium"])
expect(flattenSelectOptions(select(updated, "effort")).map((option) => option.value)).toEqual([
"low",
"medium",
"default",
])
expect(updates.findLast((item) => item.update.sessionUpdate === "config_option_update")?.update).toEqual({
sessionUpdate: "config_option_update",
configOptions: updated.configOptions,
})
})
it("publishes updated model-dependent options for legacy model changes", async () => {
const { service, updates } = makeService()
const session = await Effect.runPromise(service.newSession({ cwd: "/workspace", mcpServers: [] }))
await Effect.runPromise(service.setSessionModel({ sessionId: session.sessionId, modelId: "test/second-model" }))
const update = updates.findLast((item) => item.update.sessionUpdate === "config_option_update")?.update
expect(update?.sessionUpdate).toBe("config_option_update")
if (update?.sessionUpdate !== "config_option_update") throw new Error("missing config option update")
expect(select({ configOptions: update.configOptions }, "model")?.currentValue).toBe("test/second-model")
expect(select({ configOptions: update.configOptions }, "effort")?.currentValue).toBe("low")
})
it("preserves restored effort when legacy clients synchronize the same model", async () => {
const { service, updates } = makeService([], {
get: () =>
Promise.resolve({
data: {
id: "ses_loaded",
agent: "build",
model: { providerID: "test", id: "test-model", variant: "high" },
},
}),
})
await Effect.runPromise(service.resumeSession({ cwd: "/workspace", sessionId: "ses_loaded", mcpServers: [] }))
await Effect.runPromise(service.setSessionModel({ sessionId: "ses_loaded", modelId: "test/test-model" }))
const update = updates.findLast((item) => item.update.sessionUpdate === "config_option_update")?.update
expect(update?.sessionUpdate).toBe("config_option_update")
if (update?.sessionUpdate !== "config_option_update") throw new Error("missing config option update")
expect(select({ configOptions: update.configOptions }, "effort")?.currentValue).toBe("high")
})
it("switches effort and returns the updated effort current value", async () => {
@ -1360,7 +1635,12 @@ function categories(result: NewSessionResponse | LoadSessionResponse) {
}
function select(
result: SetSessionConfigOptionResponse | ResumeSessionResponse | NewSessionResponse | ForkSessionResponse,
result:
| SetSessionConfigOptionResponse
| ResumeSessionResponse
| LoadSessionResponse
| NewSessionResponse
| ForkSessionResponse,
id: string,
) {
return result.configOptions?.find(

View file

@ -71,7 +71,7 @@ describe("opencode acp config option subprocess", () => {
expect(effort.category).toBe("thought_level")
expect(effort.currentValue).toBe("low")
expect(flattenSelectOptions(effort).map((option) => option.value)).toEqual(["low", "high"])
expect(flattenSelectOptions(effort).map((option) => option.value)).toEqual(["low", "high", "default"])
}),
60_000,
)