Sort subagent model descriptions deterministically

This commit is contained in:
musistudio 2026-07-02 11:44:13 +08:00
parent 2cbaaded8f
commit 1adfbbf006
2 changed files with 107 additions and 8 deletions

View file

@ -421,8 +421,7 @@ function claudeCodeAgentToolInstructions(config: AppConfig): { prompt: string; t
}
function configuredSubagentModelDescriptionRows(config: AppConfig): string[] {
const rows: string[] = [];
const seen = new Set<string>();
const candidates: Array<{ key: string; row: string; selector: string }> = [];
for (const provider of config.Providers) {
const providerName = provider.name?.trim();
if (!providerName || !Array.isArray(provider.models)) {
@ -436,18 +435,42 @@ function configuredSubagentModelDescriptionRows(config: AppConfig): string[] {
}
const selector = `${providerName}/${model}`;
const key = selector.toLowerCase();
if (seen.has(key)) {
continue;
}
seen.add(key);
const displayName = provider.modelDisplayNames?.[model]?.trim();
const label = displayName && displayName !== model ? `${selector} (${displayName})` : selector;
rows.push(`- ${label}: ${singleLineText(description, 320)}`);
candidates.push({
key,
row: `- ${label}: ${singleLineText(description, 320)}`,
selector
});
}
}
candidates.sort(compareSubagentModelDescriptionRows);
const rows: string[] = [];
const seen = new Set<string>();
for (const candidate of candidates) {
if (seen.has(candidate.key)) {
continue;
}
seen.add(candidate.key);
rows.push(candidate.row);
}
return rows;
}
function compareSubagentModelDescriptionRows(
left: { key: string; row: string; selector: string },
right: { key: string; row: string; selector: string }
): number {
return compareCodeUnitStrings(left.key, right.key) ||
compareCodeUnitStrings(left.selector, right.selector) ||
compareCodeUnitStrings(left.row, right.row);
}
function compareCodeUnitStrings(left: string, right: string): number {
return left < right ? -1 : left > right ? 1 : 0;
}
function removeClaudeCodeBillingSystemHeader(body: Record<string, unknown>): void {
const system = body.system;
if (!Array.isArray(system) || system.length === 0) {

View file

@ -6,7 +6,7 @@ function createRouterPlugin(options = {}) {
const agent = options.agent ?? "claude-code";
return new ClaudeCodeRouterPlugin({
CUSTOM_ROUTER_PATH: "",
Providers: [
Providers: options.providers ?? [
{
modelDescriptions: options.modelDescriptions,
modelDisplayNames: options.modelDisplayNames,
@ -158,6 +158,82 @@ test("built-in Claude Code route injects subagent model instructions into Agent
}
});
test("built-in Claude Code route injects subagent model descriptions in stable order", async () => {
const providersA = [
{
modelDescriptions: {
"z-model": "Zeta model.",
"a-model": "Alpha model."
},
models: ["z-model", "a-model"],
name: "Zeta",
type: "anthropic_messages"
},
{
modelDescriptions: {
"z-model": "Z model.",
"a-model": "A model."
},
models: ["z-model", "a-model"],
name: "Alpha",
type: "anthropic_messages"
}
];
const providersB = [
{
modelDescriptions: {
"a-model": "A model.",
"z-model": "Z model."
},
models: ["a-model", "z-model"],
name: "Alpha",
type: "anthropic_messages"
},
{
modelDescriptions: {
"a-model": "Alpha model.",
"z-model": "Zeta model."
},
models: ["a-model", "z-model"],
name: "Zeta",
type: "anthropic_messages"
}
];
const routeRequest = (plugin) => plugin.routeRequest({
body: {
messages: [],
model: "claude-default",
tools: [
{
description: "Start a subagent.",
input_schema: {
properties: {
prompt: { description: "Task prompt.", type: "string" }
},
type: "object"
},
name: "Agent"
}
]
},
headers: {
"user-agent": "Claude Code"
},
method: "POST",
url: "/v1/messages"
});
const pluginA = createRouterPlugin({ profileModel: "Alpha/a-model", providers: providersA });
const pluginB = createRouterPlugin({ profileModel: "Alpha/a-model", providers: providersB });
const [resultA, resultB] = await Promise.all([routeRequest(pluginA), routeRequest(pluginB)]);
const description = resultA.body.tools[0].description;
assert.equal(description, resultB.body.tools[0].description);
assert.ok(description.indexOf("- Alpha/a-model: A model.") < description.indexOf("- Alpha/z-model: Z model."));
assert.ok(description.indexOf("- Alpha/z-model: Z model.") < description.indexOf("- Zeta/a-model: Alpha model."));
assert.ok(description.indexOf("- Zeta/a-model: Alpha model.") < description.indexOf("- Zeta/z-model: Zeta model."));
});
test("built-in Claude Code route injects workflow subagent model instructions into the Workflow tool", async () => {
const plugin = createRouterPlugin({
modelDescriptions: {