Skip to content

Commit f81e28c

Browse files
authored
feat: add model management to ACP sessions (#3358)
1 parent 61899d4 commit f81e28c

File tree

3 files changed

+105
-5
lines changed

3 files changed

+105
-5
lines changed

packages/opencode/src/acp/agent.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import type {
1212
NewSessionResponse,
1313
PromptRequest,
1414
PromptResponse,
15+
SetSessionModelRequest,
16+
SetSessionModelResponse,
1517
} from "@agentclientprotocol/sdk"
1618
import { Log } from "../util/log"
1719
import { ACPSessionManager } from "./session"
@@ -55,24 +57,81 @@ export class OpenCodeAgent implements Agent {
5557
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
5658
this.log.info("newSession", { cwd: params.cwd, mcpServers: params.mcpServers.length })
5759

58-
const session = await this.sessionManager.create(params.cwd, params.mcpServers)
60+
const model = await this.defaultModel()
61+
const session = await this.sessionManager.create(params.cwd, params.mcpServers, model)
62+
const availableModels = await this.availableModels()
5963

6064
return {
6165
sessionId: session.id,
66+
models: {
67+
currentModelId: `${model.providerID}/${model.modelID}`,
68+
availableModels,
69+
},
6270
_meta: {},
6371
}
6472
}
6573

6674
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
6775
this.log.info("loadSession", { sessionId: params.sessionId, cwd: params.cwd })
6876

69-
await this.sessionManager.load(params.sessionId, params.cwd, params.mcpServers)
77+
const defaultModel = await this.defaultModel()
78+
const session = await this.sessionManager.load(params.sessionId, params.cwd, params.mcpServers, defaultModel)
79+
const availableModels = await this.availableModels()
7080

7181
return {
82+
models: {
83+
currentModelId: `${session.model.providerID}/${session.model.modelID}`,
84+
availableModels,
85+
},
7286
_meta: {},
7387
}
7488
}
7589

90+
async setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
91+
this.log.info("setSessionModel", { sessionId: params.sessionId, modelId: params.modelId })
92+
93+
const session = this.sessionManager.get(params.sessionId)
94+
if (!session) {
95+
throw new Error(`Session not found: ${params.sessionId}`)
96+
}
97+
98+
const parsed = Provider.parseModel(params.modelId)
99+
const model = await Provider.getModel(parsed.providerID, parsed.modelID)
100+
101+
this.sessionManager.setModel(session.id, {
102+
providerID: model.providerID,
103+
modelID: model.modelID,
104+
})
105+
106+
return {
107+
_meta: {},
108+
}
109+
}
110+
111+
private async defaultModel() {
112+
const configured = this.config.defaultModel
113+
if (configured) return configured
114+
return Provider.defaultModel()
115+
}
116+
117+
private async availableModels() {
118+
const providers = await Provider.list()
119+
const entries = Object.entries(providers).sort((a, b) => {
120+
const nameA = a[1].info.name.toLowerCase()
121+
const nameB = b[1].info.name.toLowerCase()
122+
if (nameA < nameB) return -1
123+
if (nameA > nameB) return 1
124+
return 0
125+
})
126+
return entries.flatMap(([providerID, provider]) => {
127+
const models = Provider.sort(Object.values(provider.info.models))
128+
return models.map((model) => ({
129+
modelId: `${providerID}/${model.id}`,
130+
name: `${provider.info.name}/${model.name}`,
131+
}))
132+
})
133+
}
134+
76135
async prompt(params: PromptRequest): Promise<PromptResponse> {
77136
this.log.info("prompt", {
78137
sessionId: params.sessionId,
@@ -84,7 +143,11 @@ export class OpenCodeAgent implements Agent {
84143
throw new Error(`Session not found: ${params.sessionId}`)
85144
}
86145

87-
const model = this.config.defaultModel || (await Provider.defaultModel())
146+
const current = acpSession.model
147+
const model = current ?? (await this.defaultModel())
148+
if (!current) {
149+
this.sessionManager.setModel(acpSession.id, model)
150+
}
88151

89152
const parts = params.prompt.map((content) => {
90153
if (content.type === "text") {

packages/opencode/src/acp/session.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import type { McpServer } from "@agentclientprotocol/sdk"
22
import { Identifier } from "../id/id"
33
import { Session } from "../session"
4+
import { Provider } from "../provider/provider"
45
import type { ACPSessionState } from "./types"
56

67
export class ACPSessionManager {
78
private sessions = new Map<string, ACPSessionState>()
89

9-
async create(cwd: string, mcpServers: McpServer[]): Promise<ACPSessionState> {
10+
async create(
11+
cwd: string,
12+
mcpServers: McpServer[],
13+
model?: ACPSessionState["model"],
14+
): Promise<ACPSessionState> {
1015
const sessionId = `acp_${Identifier.ascending("session")}`
1116
const openCodeSession = await Session.create({ title: `ACP Session ${sessionId}` })
17+
const resolvedModel = model ?? (await Provider.defaultModel())
1218

1319
const state: ACPSessionState = {
1420
id: sessionId,
1521
cwd,
1622
mcpServers,
1723
openCodeSessionId: openCodeSession.id,
1824
createdAt: new Date(),
25+
model: resolvedModel,
1926
}
2027

2128
this.sessions.set(sessionId, state)
@@ -38,23 +45,49 @@ export class ACPSessionManager {
3845
return this.sessions.has(sessionId)
3946
}
4047

41-
async load(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ACPSessionState> {
48+
async load(
49+
sessionId: string,
50+
cwd: string,
51+
mcpServers: McpServer[],
52+
model?: ACPSessionState["model"],
53+
): Promise<ACPSessionState> {
4254
const existing = this.sessions.get(sessionId)
4355
if (existing) {
56+
if (!existing.model) {
57+
const resolved = model ?? (await Provider.defaultModel())
58+
existing.model = resolved
59+
this.sessions.set(sessionId, existing)
60+
}
4461
return existing
4562
}
4663

4764
const openCodeSession = await Session.create({ title: `ACP Session ${sessionId} (loaded)` })
65+
const resolvedModel = model ?? (await Provider.defaultModel())
4866

4967
const state: ACPSessionState = {
5068
id: sessionId,
5169
cwd,
5270
mcpServers,
5371
openCodeSessionId: openCodeSession.id,
5472
createdAt: new Date(),
73+
model: resolvedModel,
5574
}
5675

5776
this.sessions.set(sessionId, state)
5877
return state
5978
}
79+
80+
getModel(sessionId: string) {
81+
const session = this.sessions.get(sessionId)
82+
if (!session) return
83+
return session.model
84+
}
85+
86+
setModel(sessionId: string, model: ACPSessionState["model"]) {
87+
const session = this.sessions.get(sessionId)
88+
if (!session) return
89+
session.model = model
90+
this.sessions.set(sessionId, session)
91+
return session
92+
}
6093
}

packages/opencode/src/acp/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export interface ACPSessionState {
66
mcpServers: McpServer[]
77
openCodeSessionId: string
88
createdAt: Date
9+
model: {
10+
providerID: string
11+
modelID: string
12+
}
913
}
1014

1115
export interface ACPConfig {

0 commit comments

Comments
 (0)