test(acp-next): add config option helpers (#29234)

This commit is contained in:
Shoubhit Dash 2026-05-25 20:54:57 +05:30 committed by GitHub
parent 2fce3c1370
commit fe482fe3dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 432 additions and 0 deletions

View file

@ -0,0 +1,203 @@
import type { SessionConfigOption } from "@agentclientprotocol/sdk"
export const DEFAULT_VARIANT_VALUE = "default"
export type ConfigOptionModel = {
id: string
name: string
variants?: Record<string, Record<string, unknown>>
}
export type ConfigOptionProvider = {
id: string
name: string
models: Record<string, ConfigOptionModel>
}
export type ConfigOptionMode = {
id: string
name: string
description?: string
}
export type ModelSelection = {
model: {
providerID: string
modelID: string
}
variant?: string
}
export function buildModelSelectOption(input: {
providers: readonly ConfigOptionProvider[]
currentModel: ModelSelection["model"]
currentVariant?: string
includeVariants?: boolean
}): SessionConfigOption {
return {
id: "model",
name: "Model",
category: "model",
type: "select",
currentValue: formatCurrentModelId({
model: input.currentModel,
variant: input.currentVariant,
variants: variantsForModel(input.providers, input.currentModel),
includeVariant: input.includeVariants ?? false,
}),
options: buildModelSelectOptions(input.providers, { includeVariants: input.includeVariants ?? false }),
}
}
export function buildEffortSelectOption(input: {
variants: readonly string[]
currentVariant?: string
}): SessionConfigOption | undefined {
if (input.variants.length === 0) return undefined
return {
id: "effort",
name: "Effort",
description: "Available effort levels for this model",
category: "thought_level",
type: "select",
currentValue: selectVariant(input.currentVariant, input.variants),
options: input.variants.map((variant) => ({
value: variant,
name: formatVariantName(variant),
})),
}
}
export function buildModeSelectOption(input: {
modes: readonly ConfigOptionMode[]
currentModeId: string
}): SessionConfigOption {
return {
id: "mode",
name: "Session Mode",
category: "mode",
type: "select",
currentValue: input.currentModeId,
options: input.modes.map((mode) => ({
value: mode.id,
name: mode.name,
...(mode.description ? { description: mode.description } : {}),
})),
}
}
export function buildConfigOptions(input: {
providers: readonly ConfigOptionProvider[]
currentModel: ModelSelection["model"]
currentVariant?: string
includeModelVariants?: boolean
modes?: readonly ConfigOptionMode[]
currentModeId?: string
}): SessionConfigOption[] {
const variants = variantsForModel(input.providers, input.currentModel)
const effort = buildEffortSelectOption({ variants, currentVariant: input.currentVariant })
return [
buildModelSelectOption({
providers: input.providers,
currentModel: input.currentModel,
currentVariant: input.currentVariant,
includeVariants: input.includeModelVariants ?? false,
}),
...(effort ? [effort] : []),
...(input.modes && input.currentModeId
? [buildModeSelectOption({ modes: input.modes, currentModeId: input.currentModeId })]
: []),
]
}
export function parseModelSelection(modelId: string, providers: readonly ConfigOptionProvider[]): ModelSelection {
const provider = providers.find((item) => modelId.startsWith(`${item.id}/`))
if (provider) {
const modelID = modelId.slice(provider.id.length + 1)
if (provider.models[modelID]) {
return { model: { providerID: provider.id, modelID } }
}
const separator = modelID.lastIndexOf("/")
if (separator > -1) {
const baseModelID = modelID.slice(0, separator)
const variant = modelID.slice(separator + 1)
if (provider.models[baseModelID]?.variants?.[variant]) {
return { model: { providerID: provider.id, modelID: baseModelID }, variant }
}
}
return { model: { providerID: provider.id, modelID } }
}
const separator = modelId.indexOf("/")
if (separator === -1) {
return { model: { providerID: modelId, modelID: "" } }
}
return {
model: {
providerID: modelId.slice(0, separator),
modelID: modelId.slice(separator + 1),
},
}
}
export function formatCurrentModelId(input: {
model: ModelSelection["model"]
variant?: string
variants?: readonly string[]
includeVariant?: boolean
}) {
const base = `${input.model.providerID}/${input.model.modelID}`
if (!input.includeVariant || !input.variants?.length) return base
return `${base}/${selectVariant(input.variant, input.variants)}`
}
export function formatVariantName(variant: string) {
return variant
.split(/[_-]/)
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
.join(" ")
}
function buildModelSelectOptions(
providers: readonly ConfigOptionProvider[],
options: { includeVariants: boolean },
): Array<{ value: string; name: string }> {
return providers.flatMap((provider) =>
Object.values(provider.models)
.sort((a, b) => a.name.localeCompare(b.name))
.flatMap((model) => {
const base = {
value: `${provider.id}/${model.id}`,
name: `${provider.name}/${model.name}`,
}
if (!options.includeVariants || !model.variants) return [base]
return [
base,
...Object.keys(model.variants)
.filter((variant) => variant !== DEFAULT_VARIANT_VALUE)
.map((variant) => ({
value: `${provider.id}/${model.id}/${variant}`,
name: `${provider.name}/${model.name} (${formatVariantName(variant)})`,
})),
]
}),
)
}
function variantsForModel(providers: readonly ConfigOptionProvider[], model: ModelSelection["model"]) {
return Object.keys(
providers.find((provider) => provider.id === model.providerID)?.models[model.modelID]?.variants ?? {},
)
}
function selectVariant(variant: string | undefined, variants: readonly string[]) {
if (variant && variants.includes(variant)) return variant
if (variants.includes(DEFAULT_VARIANT_VALUE)) return DEFAULT_VARIANT_VALUE
return variants[0]
}

View file

@ -0,0 +1,229 @@
import { describe, expect, test } from "bun:test"
import {
buildConfigOptions,
buildEffortSelectOption,
buildModeSelectOption,
buildModelSelectOption,
formatCurrentModelId,
formatVariantName,
parseModelSelection,
type ConfigOptionProvider,
} from "@/acp-next/config-option"
const providers: ConfigOptionProvider[] = [
{
id: "anthropic",
name: "Anthropic",
models: {
"claude/sonnet-4": {
id: "claude/sonnet-4",
name: "Claude Sonnet 4",
variants: {
default: {},
high: {},
"very-high": {},
},
},
"claude-haiku": {
id: "claude-haiku",
name: "Claude Haiku",
},
},
},
{
id: "openai",
name: "OpenAI",
models: {
"gpt-5": {
id: "gpt-5",
name: "GPT-5",
variants: {
minimal: {},
low: {},
},
},
},
},
]
describe("acp-next config options", () => {
test("builds the model select option with ACP verifier category", () => {
expect(
buildModelSelectOption({
providers,
currentModel: { providerID: "anthropic", modelID: "claude/sonnet-4" },
currentVariant: "high",
}),
).toEqual({
id: "model",
name: "Model",
category: "model",
type: "select",
currentValue: "anthropic/claude/sonnet-4",
options: [
{ value: "anthropic/claude-haiku", name: "Anthropic/Claude Haiku" },
{ value: "anthropic/claude/sonnet-4", name: "Anthropic/Claude Sonnet 4" },
{ value: "openai/gpt-5", name: "OpenAI/GPT-5" },
],
})
})
test("includes variant ids in the model option only when requested", () => {
const option = buildModelSelectOption({
providers,
currentModel: { providerID: "anthropic", modelID: "claude/sonnet-4" },
currentVariant: "high",
includeVariants: true,
})
expect(option.currentValue).toBe("anthropic/claude/sonnet-4/high")
if (option.type !== "select") throw new Error("expected select option")
expect(option.options).toContainEqual({
value: "anthropic/claude/sonnet-4/high",
name: "Anthropic/Claude Sonnet 4 (High)",
})
expect(option.options).not.toContainEqual({
value: "anthropic/claude/sonnet-4/default",
name: "Anthropic/Claude Sonnet 4 (Default)",
})
})
test("builds effort option from variants and falls back to default when current variant is invalid", () => {
expect(buildEffortSelectOption({ variants: ["low", "default", "high"], currentVariant: "missing" })).toEqual({
id: "effort",
name: "Effort",
description: "Available effort levels for this model",
category: "thought_level",
type: "select",
currentValue: "default",
options: [
{ value: "low", name: "Low" },
{ value: "default", name: "Default" },
{ value: "high", name: "High" },
],
})
})
test("effort fallback uses the first variant when default is absent", () => {
expect(buildEffortSelectOption({ variants: ["minimal", "low"], currentVariant: "missing" })?.currentValue).toBe(
"minimal",
)
})
test("omits effort option when there are no variants", () => {
expect(buildEffortSelectOption({ variants: [] })).toBeUndefined()
})
test("builds the mode select option with descriptions when present", () => {
expect(
buildModeSelectOption({
currentModeId: "build",
modes: [
{ id: "build", name: "Build", description: "Make code changes" },
{ id: "plan", name: "Plan" },
],
}),
).toEqual({
id: "mode",
name: "Session Mode",
category: "mode",
type: "select",
currentValue: "build",
options: [
{ value: "build", name: "Build", description: "Make code changes" },
{ value: "plan", name: "Plan" },
],
})
})
test("builds full config options with model, effort, and mode in stable order", () => {
const options = buildConfigOptions({
providers,
currentModel: { providerID: "anthropic", modelID: "claude/sonnet-4" },
currentVariant: "very-high",
modes: [
{ id: "build", name: "Build" },
{ id: "plan", name: "Plan" },
],
currentModeId: "plan",
})
expect(options.map((option) => option.id)).toEqual(["model", "effort", "mode"])
expect(options.map((option) => option.category)).toEqual(["model", "thought_level", "mode"])
expect(options[1]?.currentValue).toBe("very-high")
})
test("full config options omit effort for models without variants", () => {
expect(
buildConfigOptions({
providers,
currentModel: { providerID: "anthropic", modelID: "claude-haiku" },
}).map((option) => option.id),
).toEqual(["model"])
})
test("parses provider/model selections", () => {
expect(parseModelSelection("openai/gpt-5", providers)).toEqual({
model: { providerID: "openai", modelID: "gpt-5" },
})
})
test("parses provider/model/variant selections when the base model exposes that variant", () => {
expect(parseModelSelection("openai/gpt-5/low", providers)).toEqual({
model: { providerID: "openai", modelID: "gpt-5" },
variant: "low",
})
})
test("prefers exact slash-containing model ids before treating the tail as a variant", () => {
expect(parseModelSelection("anthropic/claude/sonnet-4", providers)).toEqual({
model: { providerID: "anthropic", modelID: "claude/sonnet-4" },
})
})
test("parses trailing variants for slash-containing model ids", () => {
expect(parseModelSelection("anthropic/claude/sonnet-4/high", providers)).toEqual({
model: { providerID: "anthropic", modelID: "claude/sonnet-4" },
variant: "high",
})
})
test("keeps unknown trailing segments in the model id when they are not valid variants", () => {
expect(parseModelSelection("anthropic/claude/sonnet-4/missing", providers)).toEqual({
model: { providerID: "anthropic", modelID: "claude/sonnet-4/missing" },
})
})
test("formats current model ids with and without selected variants", () => {
expect(
formatCurrentModelId({
model: { providerID: "openai", modelID: "gpt-5" },
variant: "low",
variants: ["minimal", "low"],
}),
).toBe("openai/gpt-5")
expect(
formatCurrentModelId({
model: { providerID: "openai", modelID: "gpt-5" },
variant: "low",
variants: ["minimal", "low"],
includeVariant: true,
}),
).toBe("openai/gpt-5/low")
})
test("formats current model ids with variant fallback", () => {
expect(
formatCurrentModelId({
model: { providerID: "anthropic", modelID: "claude/sonnet-4" },
variant: "missing",
variants: ["default", "high"],
includeVariant: true,
}),
).toBe("anthropic/claude/sonnet-4/default")
})
test("formats variant names for display", () => {
expect(formatVariantName("very_high-effort")).toBe("Very High Effort")
})
})