Skip to content

Commit 23684b4

Browse files
acp: move reasoning level to a separate switcher
1 parent b7a06e1 commit 23684b4

File tree

3 files changed

+396
-25
lines changed

3 files changed

+396
-25
lines changed

packages/opencode/src/acp/agent.ts

Lines changed: 225 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ import {
1212
type ListSessionsRequest,
1313
type ListSessionsResponse,
1414
type LoadSessionRequest,
15+
type LoadSessionResponse,
1516
type NewSessionRequest,
17+
type NewSessionResponse,
1618
type PermissionOption,
1719
type PlanEntry,
1820
type PromptRequest,
1921
type ResumeSessionRequest,
2022
type ResumeSessionResponse,
23+
type SessionConfigOption,
2124
type Role,
2225
type SessionInfo,
26+
type SetSessionConfigOptionRequest,
27+
type SetSessionConfigOptionResponse,
2328
type SetSessionModelRequest,
29+
type SetSessionModelResponse,
2430
type SetSessionModeRequest,
2531
type SetSessionModeResponse,
2632
type ToolCallContent,
@@ -48,8 +54,13 @@ import { applyPatch } from "diff"
4854

4955
type ModeOption = { id: string; name: string; description?: string }
5056
type ModelOption = { modelId: string; name: string }
57+
type ProviderEntry = { id: string; name: string; models: Record<string, any> }
58+
type VariantEntry = { id: string; models: Record<string, { variants?: Record<string, any> }> }
5159

5260
const DEFAULT_VARIANT_VALUE = "default"
61+
const MODE_CONFIG_ID = "mode"
62+
const MODEL_CONFIG_ID = "model"
63+
const REASONING_CONFIG_ID = "reasoning_effort"
5364

5465
export namespace ACP {
5566
const log = Log.create({ service: "acp-agent" })
@@ -567,7 +578,7 @@ export namespace ACP {
567578
throw new Error("Authentication not implemented")
568579
}
569580

570-
async newSession(params: NewSessionRequest) {
581+
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
571582
const directory = params.cwd
572583
try {
573584
const model = await defaultModel(this.config, directory)
@@ -586,6 +597,7 @@ export namespace ACP {
586597

587598
return {
588599
sessionId,
600+
configOptions: load.configOptions,
589601
models: load.models,
590602
modes: load.modes,
591603
_meta: load._meta,
@@ -601,7 +613,7 @@ export namespace ACP {
601613
}
602614
}
603615

604-
async loadSession(params: LoadSessionRequest) {
616+
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
605617
const directory = params.cwd
606618
const sessionId = params.sessionId
607619

@@ -635,12 +647,29 @@ export namespace ACP {
635647
})
636648

637649
const lastUser = messages?.findLast((m) => m.info.role === "user")?.info
638-
if (lastUser?.role === "user") {
650+
if (lastUser?.role === "user" && result.models) {
639651
result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`
640652
this.sessionManager.setModel(sessionId, {
641653
providerID: ProviderID.make(lastUser.model.providerID),
642654
modelID: ModelID.make(lastUser.model.modelID),
643655
})
656+
this.sessionManager.setVariant(sessionId, lastUser.variant)
657+
result.configOptions = buildConfigOptions({
658+
providers: await this.sdk.config
659+
.providers({ directory }, { throwOnError: true })
660+
.then((x) => sortProvidersByName(x.data!.providers)),
661+
modes: (result.modes?.availableModes ?? []).map((mode) => ({
662+
id: mode.id,
663+
name: mode.name,
664+
description: mode.description ?? undefined,
665+
})),
666+
currentModeId: result.modes?.currentModeId,
667+
model: {
668+
providerID: ProviderID.make(lastUser.model.providerID),
669+
modelID: ModelID.make(lastUser.model.modelID),
670+
},
671+
variant: lastUser.variant,
672+
})
644673
if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
645674
result.modes.currentModeId = lastUser.agent
646675
this.sessionManager.setMode(sessionId, lastUser.agent)
@@ -1147,7 +1176,7 @@ export namespace ACP {
11471176
return { availableModes, currentModeId }
11481177
}
11491178

1150-
private async loadSessionMode(params: LoadSessionRequest) {
1179+
private async loadSessionMode(params: LoadSessionRequest): Promise<LoadSessionResponse & { sessionId: string }> {
11511180
const directory = params.cwd
11521181
const model = await defaultModel(this.config, directory)
11531182
const sessionId = params.sessionId
@@ -1156,10 +1185,11 @@ export namespace ACP {
11561185
const entries = sortProvidersByName(providers)
11571186
const availableVariants = modelVariantsFromProviders(entries, model)
11581187
const currentVariant = this.sessionManager.getVariant(sessionId)
1159-
if (currentVariant && !availableVariants.includes(currentVariant)) {
1188+
const variant = currentVariant && availableVariants.includes(currentVariant) ? currentVariant : undefined
1189+
if (currentVariant && !variant) {
11601190
this.sessionManager.setVariant(sessionId, undefined)
11611191
}
1162-
const availableModels = buildAvailableModels(entries, { includeVariants: true })
1192+
const availableModels = buildAvailableModels(entries)
11631193
const modeState = await this.resolveModeState(directory, sessionId)
11641194
const currentModeId = modeState.currentModeId
11651195
const modes = currentModeId
@@ -1168,6 +1198,13 @@ export namespace ACP {
11681198
currentModeId,
11691199
}
11701200
: undefined
1201+
const configOptions = buildConfigOptions({
1202+
providers: entries,
1203+
modes: modeState.availableModes,
1204+
currentModeId,
1205+
model,
1206+
variant,
1207+
})
11711208

11721209
const commands = await this.config.sdk.command
11731210
.list(
@@ -1241,20 +1278,21 @@ export namespace ACP {
12411278

12421279
return {
12431280
sessionId,
1281+
configOptions,
12441282
models: {
1245-
currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true),
1283+
currentModelId: formatModelIdWithVariant(model, undefined, availableVariants, false),
12461284
availableModels,
12471285
},
12481286
modes,
12491287
_meta: buildVariantMeta({
12501288
model,
1251-
variant: this.sessionManager.getVariant(sessionId),
1289+
variant,
12521290
availableVariants,
12531291
}),
12541292
}
12551293
}
12561294

1257-
async unstable_setSessionModel(params: SetSessionModelRequest) {
1295+
async unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
12581296
const session = this.sessionManager.get(params.sessionId)
12591297
const providers = await this.sdk.config
12601298
.providers({ directory: session.cwd }, { throwOnError: true })
@@ -1266,16 +1304,110 @@ export namespace ACP {
12661304

12671305
const entries = sortProvidersByName(providers)
12681306
const availableVariants = modelVariantsFromProviders(entries, selection.model)
1307+
const variant = selection.variant && availableVariants.includes(selection.variant) ? selection.variant : undefined
1308+
1309+
if (selection.variant && !variant) {
1310+
this.sessionManager.setVariant(session.id, undefined)
1311+
}
1312+
1313+
setTimeout(() => {
1314+
this.connection.sessionUpdate({
1315+
sessionId: session.id,
1316+
update: {
1317+
sessionUpdate: "config_option_update",
1318+
configOptions: buildConfigOptions({
1319+
providers: entries,
1320+
modes: [],
1321+
currentModeId: this.sessionManager.get(session.id).modeId,
1322+
model: selection.model,
1323+
variant,
1324+
}),
1325+
},
1326+
})
1327+
}, 0)
12691328

12701329
return {
12711330
_meta: buildVariantMeta({
12721331
model: selection.model,
1273-
variant: selection.variant,
1332+
variant,
12741333
availableVariants,
12751334
}),
12761335
}
12771336
}
12781337

1338+
async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
1339+
const session = this.sessionManager.get(params.sessionId)
1340+
const current = session.model ?? (await defaultModel(this.config, session.cwd))
1341+
if (!session.model) {
1342+
this.sessionManager.setModel(session.id, current)
1343+
}
1344+
1345+
const providers = await this.sdk.config
1346+
.providers({ directory: session.cwd }, { throwOnError: true })
1347+
.then((x) => x.data!.providers)
1348+
const entries = sortProvidersByName(providers)
1349+
const modes = await this.loadAvailableModes(session.cwd)
1350+
const modeId = session.modeId ?? (await AgentModule.defaultAgent())
1351+
1352+
if (params.configId === MODE_CONFIG_ID) {
1353+
if (!modes.some((mode) => mode.id === params.value)) {
1354+
throw RequestError.invalidParams(JSON.stringify({ error: `Invalid config value: ${params.value}` }))
1355+
}
1356+
this.sessionManager.setMode(session.id, params.value)
1357+
return {
1358+
configOptions: buildConfigOptions({
1359+
providers: entries,
1360+
modes,
1361+
currentModeId: params.value,
1362+
model: current,
1363+
variant: this.sessionManager.getVariant(session.id),
1364+
}),
1365+
}
1366+
}
1367+
1368+
if (params.configId === MODEL_CONFIG_ID) {
1369+
const selection = parseModelSelection(params.value, entries)
1370+
const variants = modelVariantsFromProviders(entries, selection.model)
1371+
const variant = this.sessionManager.getVariant(session.id)
1372+
const next = variant && variants.includes(variant) ? variant : undefined
1373+
this.sessionManager.setModel(session.id, selection.model)
1374+
this.sessionManager.setVariant(session.id, next)
1375+
return {
1376+
configOptions: buildConfigOptions({
1377+
providers: entries,
1378+
modes,
1379+
currentModeId: session.modeId ?? modeId,
1380+
model: selection.model,
1381+
variant: next,
1382+
}),
1383+
}
1384+
}
1385+
1386+
if (params.configId !== REASONING_CONFIG_ID) {
1387+
throw RequestError.invalidParams(JSON.stringify({ error: `Unknown config option: ${params.configId}` }))
1388+
}
1389+
1390+
const variants = [
1391+
DEFAULT_VARIANT_VALUE,
1392+
...modelVariantsFromProviders(entries, current).filter((variant) => variant !== DEFAULT_VARIANT_VALUE),
1393+
]
1394+
if (!variants.includes(params.value)) {
1395+
throw RequestError.invalidParams(JSON.stringify({ error: `Invalid config value: ${params.value}` }))
1396+
}
1397+
1398+
this.sessionManager.setVariant(session.id, params.value === DEFAULT_VARIANT_VALUE ? undefined : params.value)
1399+
1400+
return {
1401+
configOptions: buildConfigOptions({
1402+
providers: entries,
1403+
modes,
1404+
currentModeId: session.modeId ?? modeId,
1405+
model: current,
1406+
variant: this.sessionManager.getVariant(session.id),
1407+
}),
1408+
}
1409+
}
1410+
12791411
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
12801412
const session = this.sessionManager.get(params.sessionId)
12811413
const availableModes = await this.loadAvailableModes(session.cwd)
@@ -1648,7 +1780,7 @@ export namespace ACP {
16481780
}
16491781

16501782
function modelVariantsFromProviders(
1651-
providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
1783+
providers: VariantEntry[],
16521784
model: { providerID: ProviderID; modelID: ModelID },
16531785
): string[] {
16541786
const provider = providers.find((entry) => entry.id === model.providerID)
@@ -1658,11 +1790,7 @@ export namespace ACP {
16581790
return Object.keys(modelInfo.variants)
16591791
}
16601792

1661-
function buildAvailableModels(
1662-
providers: Array<{ id: string; name: string; models: Record<string, any> }>,
1663-
options: { includeVariants?: boolean } = {},
1664-
): ModelOption[] {
1665-
const includeVariants = options.includeVariants ?? false
1793+
function buildAvailableModels(providers: ProviderEntry[]): ModelOption[] {
16661794
return providers.flatMap((provider) => {
16671795
const unsorted: Array<{ id: string; name: string; variants?: Record<string, any> }> = Object.values(
16681796
provider.models,
@@ -1673,17 +1801,86 @@ export namespace ACP {
16731801
modelId: `${provider.id}/${model.id}`,
16741802
name: `${provider.name}/${model.name}`,
16751803
}
1676-
if (!includeVariants || !model.variants) return [base]
1677-
const variants = Object.keys(model.variants).filter((variant) => variant !== DEFAULT_VARIANT_VALUE)
1678-
const variantOptions = variants.map((variant) => ({
1679-
modelId: `${provider.id}/${model.id}/${variant}`,
1680-
name: `${provider.name}/${model.name} (${variant})`,
1681-
}))
1682-
return [base, ...variantOptions]
1804+
return [base]
16831805
})
16841806
})
16851807
}
16861808

1809+
function buildReasoning(input: {
1810+
providers: VariantEntry[]
1811+
model: { providerID: ProviderID; modelID: ModelID }
1812+
variant?: string
1813+
}): SessionConfigOption[] {
1814+
const base = modelVariantsFromProviders(input.providers, input.model)
1815+
if (!base.length) return []
1816+
const variants = [DEFAULT_VARIANT_VALUE, ...base.filter((variant) => variant !== DEFAULT_VARIANT_VALUE)]
1817+
const current = input.variant && variants.includes(input.variant) ? input.variant : DEFAULT_VARIANT_VALUE
1818+
return [
1819+
{
1820+
type: "select",
1821+
id: REASONING_CONFIG_ID,
1822+
name: "Reasoning effort",
1823+
category: "thought_level",
1824+
currentValue: current,
1825+
options: variants.map((variant) => ({
1826+
value: variant,
1827+
name: variant === DEFAULT_VARIANT_VALUE ? "Default" : variant,
1828+
})),
1829+
},
1830+
]
1831+
}
1832+
1833+
function buildModeConfig(input: { modes: ModeOption[]; currentModeId?: string }): SessionConfigOption[] {
1834+
if (!input.modes.length || !input.currentModeId) return []
1835+
return [
1836+
{
1837+
type: "select",
1838+
id: MODE_CONFIG_ID,
1839+
name: "Mode",
1840+
category: "mode",
1841+
currentValue: input.currentModeId,
1842+
options: input.modes.map((mode) => ({
1843+
value: mode.id,
1844+
name: mode.name,
1845+
description: mode.description,
1846+
})),
1847+
},
1848+
]
1849+
}
1850+
1851+
function buildModelConfig(input: {
1852+
providers: ProviderEntry[]
1853+
model: { providerID: ProviderID; modelID: ModelID }
1854+
}): SessionConfigOption[] {
1855+
return [
1856+
{
1857+
type: "select",
1858+
id: MODEL_CONFIG_ID,
1859+
name: "Model",
1860+
category: "model",
1861+
currentValue: `${input.model.providerID}/${input.model.modelID}`,
1862+
options: buildAvailableModels(input.providers).map((model) => ({
1863+
value: model.modelId,
1864+
name: model.name,
1865+
})),
1866+
},
1867+
]
1868+
}
1869+
1870+
function buildConfigOptions(input: {
1871+
providers: ProviderEntry[]
1872+
modes: ModeOption[]
1873+
currentModeId?: string
1874+
model: { providerID: ProviderID; modelID: ModelID }
1875+
variant?: string
1876+
}) {
1877+
return [
1878+
...buildModeConfig({ modes: input.modes, currentModeId: input.currentModeId }),
1879+
...buildModelConfig({ providers: input.providers, model: input.model }),
1880+
...buildReasoning({ providers: input.providers, model: input.model, variant: input.variant }),
1881+
]
1882+
}
1883+
16871884
function formatModelIdWithVariant(
16881885
model: { providerID: ProviderID; modelID: ModelID },
16891886
variant: string | undefined,
@@ -1711,8 +1908,11 @@ export namespace ACP {
17111908

17121909
function parseModelSelection(
17131910
modelId: string,
1714-
providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
1715-
): { model: { providerID: ProviderID; modelID: ModelID }; variant?: string } {
1911+
providers: VariantEntry[],
1912+
): {
1913+
model: { providerID: ProviderID; modelID: ModelID }
1914+
variant?: string
1915+
} {
17161916
const parsed = Provider.parseModel(modelId)
17171917
const provider = providers.find((p) => p.id === parsed.providerID)
17181918
if (!provider) {

0 commit comments

Comments
 (0)