Allow ACP providers without model lists

This commit is contained in:
Mohamed Boudra
2026-05-19 20:01:56 +07:00
parent f238cbc20c
commit bc7798af28
8 changed files with 353 additions and 80 deletions
@@ -731,6 +731,31 @@ describe("ACPAgentSession Zed parity", () => {
expect(setSessionConfigOption).not.toHaveBeenCalled();
});
test("does not fail session start when configured model cannot be applied by ACP", async () => {
const logger = createTestLogger();
const childLogger = { trace: vi.fn(), warn: vi.fn() };
vi.spyOn(logger, "child").mockReturnValue(asInternals<typeof logger>(childLogger));
const session = createSessionWithConfig(
{ provider: "deepseek-tui", model: "deepseek/v4" },
logger,
);
const { internals, setSessionConfigOption, unstableSetSessionModel } =
prepareConfiguredOverrideSession(session, {
currentModel: null,
availableModels: null,
configOptions: [],
connection: { unstable_setSessionModel: undefined },
});
await expect(internals.applyConfiguredOverrides()).resolves.toBeUndefined();
expect(unstableSetSessionModel).not.toHaveBeenCalled();
expect(setSessionConfigOption).not.toHaveBeenCalled();
expect(childLogger.warn).toHaveBeenCalledWith(
{ value: "deepseek/v4" },
"deepseek-tui does not expose ACP model selection; using provider default model",
);
});
test("routes config_option_update and refreshes derived mode, model, and thinking state", async () => {
const session = createSession();
const internals = asInternals<ACPSessionInternals>(session);
@@ -1376,7 +1376,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
}
if (typeof this.connection.unstable_setSessionModel !== "function") {
throw new Error(`${this.provider} does not expose ACP model selection`);
throw new Error(this.modelSelectionUnavailableMessage());
}
try {
@@ -1398,7 +1398,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
const modelOption = selection.configOption;
if (!modelOption) {
throw new Error(`${this.provider} does not expose ACP model selection`);
throw new Error(this.modelSelectionUnavailableMessage());
}
if (!selection.configChoice) {
this.warnInvalidSelection(
@@ -1904,7 +1904,17 @@ export class ACPAgentSession implements AgentSession, ACPClient {
availableModels: this.availableModels,
configOptions: this.configOptions,
});
await this.setModelWithSelection({ modelId: configuredModelId, selection });
try {
await this.setModelWithSelection({ modelId: configuredModelId, selection });
} catch (error) {
if (!this.isModelSelectionUnavailableError(error)) {
throw error;
}
this.logger.warn(
{ value: configuredModelId },
`${this.provider} does not expose ACP model selection; using provider default model`,
);
}
}
if (this.config.thinkingOptionId && this.config.thinkingOptionId !== this.thinkingOptionId) {
await this.setThinkingOption(this.config.thinkingOptionId);
@@ -1915,6 +1925,14 @@ export class ACPAgentSession implements AgentSession, ACPClient {
this.logger.warn({ value }, message);
}
private modelSelectionUnavailableMessage(): string {
return `${this.provider} does not expose ACP model selection`;
}
private isModelSelectionUnavailableError(error: unknown): boolean {
return error instanceof Error && error.message === this.modelSelectionUnavailableMessage();
}
private translateSessionUpdate(update: SessionUpdate): AgentStreamEvent[] {
switch (update.sessionUpdate) {
case "user_message_chunk": {