Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 246 additions & 1 deletion apps/workers/coder-acp-copilot/src/acp-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@
// Licensed under the MIT License.

import { describe, it, expect, vi } from "vitest";
import { runACPSession, selectModel, selectReasoningEffort, selectPermissionMode, formatModeError, formatToolArgs, formatToolContent, AUTOPILOT_MODE_ID } from "./acp-client.js";
import {
runACPSession,
selectModel,
resolveRequestedModel,
getCurrentModelId,
hasModelSelectionCapability,
selectFirstAvailableNonDefaultModel,
selectReasoningEffort,
selectPermissionMode,
formatModeError,
formatToolArgs,
formatToolContent,
AUTOPILOT_MODE_ID,
} from "./acp-client.js";
import type * as acp from "@agentclientprotocol/sdk";
import os from "node:os";

Expand Down Expand Up @@ -288,6 +301,238 @@ describe("selectModel", () => {
});
});

describe("selectFirstAvailableNonDefaultModel", () => {
function makeSession(
overrides?: Partial<acp.NewSessionResponse>
): acp.NewSessionResponse {
return {
sessionId: "session-1",
...overrides,
} as acp.NewSessionResponse;
}

it("selects the first advertised model different from the current model", () => {
const session = makeSession({
models: {
currentModelId: "default-model",
availableModels: [
{ modelId: "default-model", name: "Default Model" },
{ modelId: "alternate-model", name: "Alternate Model" },
{ modelId: "another-model", name: "Another Model" },
],
},
});

expect(selectFirstAvailableNonDefaultModel(session)).toBe(
"alternate-model"
);
});

it("returns undefined when the session does not advertise models", () => {
expect(selectFirstAvailableNonDefaultModel(makeSession())).toBeUndefined();
});

it("returns undefined when only the current model is available", () => {
const session = makeSession({
models: {
currentModelId: "default-model",
availableModels: [
{ modelId: "default-model", name: "Default Model" },
],
},
});

expect(selectFirstAvailableNonDefaultModel(session)).toBeUndefined();
});

it("selects a non-default model from a model config option", () => {
const session = makeSession({
configOptions: [
{
id: "model-picker",
category: "model",
name: "Model",
currentValue: "default-model",
options: [
{ value: "default-model", name: "Default Model" },
{ value: "alternate-model", name: "Alternate Model" },
],
type: "select",
},
],
});

expect(selectFirstAvailableNonDefaultModel(session)).toBe(
"alternate-model"
);
});

it("selects a non-default model from a grouped model config option", () => {
const session = makeSession({
configOptions: [
{
id: "model-picker",
category: "model",
name: "Model",
currentValue: "default-model",
options: [
{
group: "recommended",
name: "Recommended",
options: [
{ value: "default-model", name: "Default Model" },
{ value: "alternate-model", name: "Alternate Model" },
],
},
],
type: "select",
},
],
});

expect(selectFirstAvailableNonDefaultModel(session)).toBe(
"alternate-model"
);
});

it("returns undefined when a model config option only lists the current model", () => {
const session = makeSession({
configOptions: [
{
id: "model-picker",
category: "model",
name: "Model",
currentValue: "default-model",
options: [
{ value: "default-model", name: "Default Model" },
],
type: "select",
},
],
});

expect(selectFirstAvailableNonDefaultModel(session)).toBeUndefined();
});

it("prefers the models field when both representations are advertised", () => {
const session = makeSession({
models: {
currentModelId: "models-default",
availableModels: [
{ modelId: "models-default", name: "Models Default" },
{ modelId: "models-alternate", name: "Models Alternate" },
],
},
configOptions: [
{
id: "model-picker",
category: "model",
name: "Model",
currentValue: "config-default",
options: [
{ value: "config-alternate", name: "Config Alternate" },
],
type: "select",
},
],
});

expect(selectFirstAvailableNonDefaultModel(session)).toBe(
"models-alternate"
);
});
});

describe("resolveRequestedModel", () => {
const session = {
sessionId: "session-1",
} as acp.NewSessionResponse;

it("preserves a fixed model id", () => {
expect(resolveRequestedModel("fixed-model", session)).toBe("fixed-model");
});

it("resolves a selector with the new session response", () => {
const selector = vi.fn().mockReturnValue("selected-model");

expect(resolveRequestedModel(selector, session)).toBe("selected-model");
expect(selector).toHaveBeenCalledWith(session);
});
});

describe("model selection session metadata", () => {
function makeSession(
overrides?: Partial<acp.NewSessionResponse>
): acp.NewSessionResponse {
return {
sessionId: "session-1",
...overrides,
} as acp.NewSessionResponse;
}

it("returns the current model advertised by the session", () => {
const session = makeSession({
models: {
currentModelId: "default-model",
availableModels: [
{ modelId: "default-model", name: "Default Model" },
],
},
});

expect(getCurrentModelId(session)).toBe("default-model");
});

it("returns the current model from a model config option", () => {
const session = makeSession({
configOptions: [
{
id: "model-picker",
category: "model",
name: "Model",
currentValue: "default-model",
options: [],
type: "select",
},
],
});

expect(getCurrentModelId(session)).toBe("default-model");
});

it("detects models-field capability", () => {
const session = makeSession({
models: {
currentModelId: "default-model",
availableModels: [],
},
});

expect(hasModelSelectionCapability(session)).toBe(true);
});

it("detects model config-option capability", () => {
const session = makeSession({
configOptions: [
{
id: "model-picker",
category: "model",
name: "Model",
currentValue: "default-model",
options: [],
type: "select",
},
],
});

expect(hasModelSelectionCapability(session)).toBe(true);
});

it("returns false when no model selection mechanism is advertised", () => {
expect(hasModelSelectionCapability(makeSession())).toBe(false);
});
});

describe("selectReasoningEffort", () => {
function makeConnection(overrides?: Partial<acp.ClientSideConnection>): acp.ClientSideConnection {
return {
Expand Down
Loading
Loading