From 704c6c781466bcc0abfc162692938d200a740b18 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Sat, 20 Jun 2026 10:49:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20ACP=20NewSessionResponse=20=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E8=BF=94=E5=9B=9E=20models=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 67fdd4ca 那次合规审计误将 models 从响应里移除,但 SDK 0.19.2 的 schema 实际允许 models?: SessionModelState | null(标注 UNSTABLE 仅表示"未来可能变",并非 "agent 禁止返回")。标准 ACP 客户端(Cursor/Zed/VS Code/RCS)依赖此字段填充 模型选择器 —— 缺失会导致客户端 supportsModelSelection=false,模型切换 UI 不可用。 - createSessionMethod: return 里加回 models - sessionLifecycle: getOrCreateSession 两处 return 透传 models(resume/load 路径) - agent.test: 更新过时的 "models omitted for v1 compliance" 断言 Co-Authored-By: glm-5.2 --- src/services/acp/__tests__/agent.test.ts | 14 +++++++++----- src/services/acp/agent/createSessionMethod.ts | 9 +++++++-- src/services/acp/agent/sessionLifecycle.ts | 5 +++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/services/acp/__tests__/agent.test.ts b/src/services/acp/__tests__/agent.test.ts index 706010d5d..b3c58777b 100644 --- a/src/services/acp/__tests__/agent.test.ts +++ b/src/services/acp/__tests__/agent.test.ts @@ -325,13 +325,17 @@ describe('AcpAgent', () => { expect(res.sessionId.length).toBeGreaterThan(0) }) - test('returns modes and configOptions (models omitted for v1 compliance)', async () => { + test('returns modes, configOptions, and models (clients need models to populate selector)', async () => { const agent = new AcpAgent(makeConn()) const res = await agent.newSession({ cwd: '/tmp' } as any) expect(res.modes).toBeDefined() expect(res.configOptions).toBeDefined() - // Stable v1 NewSessionResponse does not define `models` - expect((res as any).models).toBeUndefined() + // SDK 0.19.2 marks NewSessionResponse.models as UNSTABLE but the schema allows it, and + // standard clients (Cursor/Zed/VS Code) read it to populate the model selector. Omitting + // it forces supportsModelSelection=false on the client. + expect(res.models).toBeDefined() + expect(Array.isArray(res.models!.availableModels)).toBe(true) + expect(typeof res.models!.currentModelId).toBe('string') }) test('each call returns a unique sessionId', async () => { @@ -865,8 +869,8 @@ describe('AcpAgent', () => { } as any) expect(agent.sessions.has(requestedId)).toBe(true) expect(res.modes).toBeDefined() - // models is omitted for v1 compliance - expect((res as any).models).toBeUndefined() + // resume also returns models so clients can render the selector after reconnect. + expect(res.models).toBeDefined() }) test('reuses existing session when sessionId matches and fingerprint unchanged', async () => { diff --git a/src/services/acp/agent/createSessionMethod.ts b/src/services/acp/agent/createSessionMethod.ts index 8e2cc04c2..5e1d0ff74 100644 --- a/src/services/acp/agent/createSessionMethod.ts +++ b/src/services/acp/agent/createSessionMethod.ts @@ -270,11 +270,16 @@ async function createSession( this.sessions.set(sessionId, session) - // Stable v1 NewSessionResponse only defines sessionId/modes/configOptions. - // `models` is a draft/unstable field — omit it for v1 compliance. + // Return models even though SDK 0.19.2 marks it UNSTABLE. The schema does allow the field + // (NewSessionResponse.models?: SessionModelState | null), and standard clients (Cursor/Zed/ + // VS Code ACP) rely on it to populate the model selector — omitting it forces + // supportsModelSelection=false on the client and the user can never switch models. + // The UNSTABLE marker only means "this field may change in a future schema version", not + // "agents MUST NOT return it". The previous "v1 compliance" omission was overzealous. return { sessionId, modes, + models, configOptions, } } finally { diff --git a/src/services/acp/agent/sessionLifecycle.ts b/src/services/acp/agent/sessionLifecycle.ts index 0b6eb7e03..50d23d332 100644 --- a/src/services/acp/agent/sessionLifecycle.ts +++ b/src/services/acp/agent/sessionLifecycle.ts @@ -79,6 +79,9 @@ async function getOrCreateSession( return { sessionId: params.sessionId, modes: existingSession.modes, + // Carry models over on reconnect so the client keeps its model selector + // populated (standard clients gate supportsModelSelection on this field). + models: existingSession.models, configOptions: existingSession.configOptions, } } @@ -150,6 +153,8 @@ async function getOrCreateSession( return { sessionId: response.sessionId, modes: response.modes, + // createSession already returns models; pass it through. Same reason as above. + models: response.models, configOptions: response.configOptions, } }