refactor(packages/coding-agent): restructured acp 0.25 session state
- Removed `userMessageId` from `AcpAgent` prompt state and response payloads. - Removed `models` from new/load/resume/fork session responses and deleted model-state building. - Removed `unstable_setSessionModel` and routed model changes through `setSessionConfigOption`.
This commit is contained in:
@@ -31,14 +31,11 @@ import {
|
||||
type ResumeSessionResponse,
|
||||
type SessionConfigOption,
|
||||
type SessionInfo,
|
||||
type SessionModelState,
|
||||
type SessionModeState,
|
||||
type SessionNotification,
|
||||
type SessionUpdate,
|
||||
type SetSessionConfigOptionRequest,
|
||||
type SetSessionConfigOptionResponse,
|
||||
type SetSessionModelRequest,
|
||||
type SetSessionModelResponse,
|
||||
type SetSessionModeRequest,
|
||||
type SetSessionModeResponse,
|
||||
type Usage,
|
||||
@@ -121,7 +118,6 @@ type PromptQueueState = {
|
||||
type PromptLifecycleError = Error & { readonly code: "ACP_SESSION_CLOSED" };
|
||||
|
||||
type PromptTurnState = {
|
||||
userMessageId: string;
|
||||
cancelRequested: boolean;
|
||||
settled: boolean;
|
||||
/**
|
||||
@@ -465,7 +461,6 @@ export class AcpAgent implements Agent {
|
||||
const response: NewSessionResponse = {
|
||||
sessionId: record.session.sessionId,
|
||||
configOptions: this.#buildConfigOptions(record.session),
|
||||
models: this.#buildModelState(record.session),
|
||||
modes: this.#buildModeState(record.session),
|
||||
};
|
||||
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
||||
@@ -478,7 +473,6 @@ export class AcpAgent implements Agent {
|
||||
await this.#replaySessionHistory(record);
|
||||
const response: LoadSessionResponse = {
|
||||
configOptions: this.#buildConfigOptions(record.session),
|
||||
models: this.#buildModelState(record.session),
|
||||
modes: this.#buildModeState(record.session),
|
||||
};
|
||||
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
||||
@@ -507,7 +501,6 @@ export class AcpAgent implements Agent {
|
||||
const record = await this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []);
|
||||
const response: ResumeSessionResponse = {
|
||||
configOptions: this.#buildConfigOptions(record.session),
|
||||
models: this.#buildModelState(record.session),
|
||||
modes: this.#buildModeState(record.session),
|
||||
};
|
||||
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
||||
@@ -520,7 +513,6 @@ export class AcpAgent implements Agent {
|
||||
const response: ForkSessionResponse = {
|
||||
sessionId: record.session.sessionId,
|
||||
configOptions: this.#buildConfigOptions(record.session),
|
||||
models: this.#buildModelState(record.session),
|
||||
modes: this.#buildModeState(record.session),
|
||||
};
|
||||
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
||||
@@ -588,13 +580,6 @@ export class AcpAgent implements Agent {
|
||||
return { configOptions: this.#buildConfigOptions(record.session) };
|
||||
}
|
||||
|
||||
async unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
|
||||
const record = this.#getSessionRecord(params.sessionId);
|
||||
await this.#setModelById(record.session, params.modelId);
|
||||
await this.#pushConfigOptionUpdate(record);
|
||||
return {};
|
||||
}
|
||||
|
||||
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
||||
const record = this.#getSessionRecord(params.sessionId);
|
||||
const activeTurn = record.promptTurn;
|
||||
@@ -633,7 +618,6 @@ export class AcpAgent implements Agent {
|
||||
const converted = this.#convertPromptBlocks(params.prompt);
|
||||
const pendingPrompt = Promise.withResolvers<PromptResponse>();
|
||||
record.promptTurn = {
|
||||
userMessageId: params.messageId ?? crypto.randomUUID(),
|
||||
cancelRequested: false,
|
||||
settled: false,
|
||||
cleanup: undefined,
|
||||
@@ -766,7 +750,6 @@ export class AcpAgent implements Agent {
|
||||
this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
||||
record.session.sessionManager.getUsageStatistics(),
|
||||
),
|
||||
userMessageId: promptTurn?.userMessageId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -844,7 +827,6 @@ export class AcpAgent implements Agent {
|
||||
this.#finishPrompt(record, {
|
||||
stopReason: "cancelled",
|
||||
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
||||
userMessageId: promptTurn.userMessageId,
|
||||
});
|
||||
return cleanup;
|
||||
}
|
||||
@@ -1162,7 +1144,6 @@ export class AcpAgent implements Agent {
|
||||
this.#finishPrompt(record, {
|
||||
stopReason: this.#resolveStopReason(event, promptTurn.cancelRequested),
|
||||
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
||||
userMessageId: promptTurn.userMessageId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1385,28 +1366,6 @@ export class AcpAgent implements Agent {
|
||||
return configOptions;
|
||||
}
|
||||
|
||||
#buildModelState(session: AgentSession): SessionModelState | undefined {
|
||||
const models = session.getAvailableModels();
|
||||
if (models.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const availableModels = models.map(model => ({
|
||||
modelId: this.#toModelId(model),
|
||||
name: model.name,
|
||||
description: `${model.provider}/${model.id}`,
|
||||
}));
|
||||
const currentModelId = session.model ? this.#toModelId(session.model) : availableModels[0]?.modelId;
|
||||
if (!currentModelId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
availableModels,
|
||||
currentModelId,
|
||||
};
|
||||
}
|
||||
|
||||
#buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
|
||||
return [
|
||||
{ value: THINKING_OFF, name: "Off" },
|
||||
|
||||
@@ -488,13 +488,16 @@ describe("ACP agent", () => {
|
||||
expectAcpStructure(zNewSessionResponse, first);
|
||||
expectAcpStructure(zNewSessionResponse, second);
|
||||
|
||||
expect(first.models?.availableModels.map(model => model.modelId)).toEqual(
|
||||
const modelOption = first.configOptions?.find(opt => opt.id === "model");
|
||||
expect(modelOption?.type).toBe("select");
|
||||
expect((modelOption as any).options?.map((opt: any) => opt.value)).toEqual(
|
||||
TEST_MODELS.map(model => `${model.provider}/${model.id}`),
|
||||
);
|
||||
|
||||
await harness.agent.unstable_setSessionModel({
|
||||
await harness.agent.setSessionConfigOption({
|
||||
sessionId: first.sessionId,
|
||||
modelId: `${TEST_MODELS[1]!.provider}/${TEST_MODELS[1]!.id}`,
|
||||
configId: "model",
|
||||
value: `${TEST_MODELS[1]!.provider}/${TEST_MODELS[1]!.id}`,
|
||||
});
|
||||
await harness.agent.setSessionConfigOption({
|
||||
sessionId: first.sessionId,
|
||||
@@ -914,16 +917,14 @@ describe("ACP agent", () => {
|
||||
const live = await harness.agent.newSession({ cwd: harness.cwdB, mcpServers: [] });
|
||||
const response = await harness.agent.prompt({
|
||||
sessionId: live.sessionId,
|
||||
messageId: "05b17a6f-b310-4be7-b767-6b4f3a84eb63",
|
||||
prompt: [{ type: "text", text: "ping" }],
|
||||
} as PromptRequest);
|
||||
});
|
||||
expectAcpStructure(zPromptResponse, response);
|
||||
expectAcpNotifications(harness.updates);
|
||||
|
||||
const liveChunks = harness.updates.filter(
|
||||
update => update.sessionId === live.sessionId && update.update.sessionUpdate === "agent_message_chunk",
|
||||
);
|
||||
expect(response.userMessageId).toBe("05b17a6f-b310-4be7-b767-6b4f3a84eb63");
|
||||
expect(response.usage).toEqual({
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
@@ -1508,9 +1509,8 @@ describe("ACP agent", () => {
|
||||
|
||||
const firstPrompt = harness.agent.prompt({
|
||||
sessionId: created.sessionId,
|
||||
messageId: "00000000-0000-4000-8000-000000000029",
|
||||
prompt: [{ type: "text", text: "wait for cleanup" }],
|
||||
} as PromptRequest);
|
||||
});
|
||||
await idleBlocked;
|
||||
|
||||
try {
|
||||
@@ -1519,8 +1519,7 @@ describe("ACP agent", () => {
|
||||
expect(session.waitForIdleCalls).toBe(1);
|
||||
|
||||
unblockIdle();
|
||||
const response = await firstPrompt;
|
||||
expect(response.userMessageId).toBe("00000000-0000-4000-8000-000000000029");
|
||||
await firstPrompt;
|
||||
} finally {
|
||||
unblockIdle();
|
||||
harness.abortController.abort();
|
||||
@@ -1548,9 +1547,8 @@ describe("ACP agent", () => {
|
||||
|
||||
const prompt = harness.agent.prompt({
|
||||
sessionId: created.sessionId,
|
||||
messageId: "00000000-0000-4000-8000-000000000047",
|
||||
prompt: [{ type: "text", text: "wait for async delivery" }],
|
||||
} as PromptRequest);
|
||||
});
|
||||
await deliveryBlocked.promise;
|
||||
|
||||
try {
|
||||
@@ -1559,8 +1557,7 @@ describe("ACP agent", () => {
|
||||
expect(session.waitForIdleCalls).toBe(1);
|
||||
|
||||
releaseDelivery();
|
||||
const response = await prompt;
|
||||
expect(response.userMessageId).toBe("00000000-0000-4000-8000-000000000047");
|
||||
await prompt;
|
||||
expect(session.waitForIdleCalls).toBe(2);
|
||||
expect(drainCalls).toBe(2);
|
||||
} finally {
|
||||
@@ -1903,16 +1900,14 @@ describe("ACP agent", () => {
|
||||
const created = await harness.agent.newSession({ cwd: harness.cwdA, mcpServers: [] });
|
||||
const session = harness.findSession(created.sessionId)!;
|
||||
|
||||
const response = await harness.agent.prompt({
|
||||
await harness.agent.prompt({
|
||||
sessionId: created.sessionId,
|
||||
messageId: "00000000-0000-4000-8000-000000000002",
|
||||
prompt: [{ type: "text", text: "/fast status" }],
|
||||
} as PromptRequest);
|
||||
});
|
||||
|
||||
const chunks = harness.updates.filter(
|
||||
update => update.sessionId === created.sessionId && update.update.sessionUpdate === "agent_message_chunk",
|
||||
);
|
||||
expect(response.userMessageId).toBe("00000000-0000-4000-8000-000000000002");
|
||||
expect(session.promptCalls).toEqual([]);
|
||||
expect(
|
||||
chunks.some(
|
||||
|
||||
Reference in New Issue
Block a user