diff --git a/packages/paperclip-runner/src/drivers/acpx/model-verification.test.ts b/packages/paperclip-runner/src/drivers/acpx/model-verification.test.ts index cd4cf1317a..5fee5bc04e 100644 --- a/packages/paperclip-runner/src/drivers/acpx/model-verification.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/model-verification.test.ts @@ -24,7 +24,7 @@ describe("ACPX qualified model verification", () => { expect(setModel).not.toHaveBeenCalled(); }); - it("selects Claude's canonical model and normalizes its ACP selector", async () => { + it("accepts and normalizes Claude's qualified ACP selector", async () => { const setModel = vi.fn(async () => undefined); const getStatus = vi.fn(async () => ({ models: { @@ -33,6 +33,34 @@ describe("ACPX qualified model verification", () => { }, })); + await expect( + requireVerifiedAcpxModel( + { getStatus, setModel }, + resolveQualifiedAcpxProfile("claude", "claude-sonnet-5"), + ), + ).resolves.toMatchObject({ + models: { + currentModelId: "claude-sonnet-5", + availableModelIds: ["default", "claude-sonnet-5", "opus"], + }, + }); + expect(setModel).not.toHaveBeenCalled(); + expect(getStatus).toHaveBeenCalledTimes(1); + }); + + it("selects Claude's profile-pinned ACP selector from a stale default", async () => { + let selected = false; + const setModel = vi.fn(async (model: string) => { + expect(model).toBe("sonnet"); + selected = true; + }); + const getStatus = vi.fn(async () => ({ + models: { + currentModelId: selected ? "sonnet" : "default", + availableModelIds: ["default", "sonnet", "opus"], + }, + })); + await expect( requireVerifiedAcpxModel( { getStatus, setModel }, @@ -45,7 +73,7 @@ describe("ACPX qualified model verification", () => { }, }); expect(setModel).toHaveBeenCalledTimes(1); - expect(setModel).toHaveBeenCalledWith("claude-sonnet-5"); + expect(setModel).toHaveBeenCalledWith("sonnet"); expect(getStatus).toHaveBeenCalledTimes(2); }); diff --git a/packages/paperclip-runner/src/drivers/acpx/model-verification.ts b/packages/paperclip-runner/src/drivers/acpx/model-verification.ts index 6a875c94ac..e178dcabe1 100644 --- a/packages/paperclip-runner/src/drivers/acpx/model-verification.ts +++ b/packages/paperclip-runner/src/drivers/acpx/model-verification.ts @@ -28,24 +28,26 @@ export async function requireVerifiedAcpxModel( ); } const requestedModel = profile.qualificationModel; + const providerModel = profile.reportedModelId; let status = await control.getStatus(); - const mustSelectCanonical = - profile.reportedModelId !== requestedModel || - status.models?.currentModelId !== requestedModel; - if (mustSelectCanonical) { + if (status.models?.currentModelId !== providerModel) { if (!control.setModel) { throw acpxModelVerificationError( "ACPX_MODEL_SELECTION_UNAVAILABLE", - "ACPX agent cannot verify its canonical model through ACP config options", + "ACPX agent cannot verify its qualified model through ACP config options", ); } - await control.setModel(requestedModel); + // The caller-facing model is already pinned by resolveQualifiedAcpxProfile. + // Select the immutable ACP-facing identifier from that same profile: some + // providers expose a stable selector (for example Claude's `sonnet`) while + // Paperclip publishes the canonical model name after verification. + await control.setModel(providerModel); status = await control.getStatus(); } - if (status.models?.currentModelId !== profile.reportedModelId) { + if (status.models?.currentModelId !== providerModel) { throw acpxModelVerificationError( "ACPX_EFFECTIVE_MODEL_MISMATCH", - `ACPX effective model mismatch: requested ${requestedModel}, expected ACP selector ${profile.reportedModelId}, received ${status.models?.currentModelId ?? "unverified"}`, + `ACPX effective model mismatch: requested ${requestedModel}, expected ACP selector ${providerModel}, received ${status.models?.currentModelId ?? "unverified"}`, ); } return normalizeQualifiedModelStatus(status, profile);