fix: filter reserved commands from model aliases + add tests
This commit is contained in:
115
src/auto-reply/model.test.ts
Normal file
115
src/auto-reply/model.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { extractModelDirective } from "./model.js";
|
||||
|
||||
describe("extractModelDirective", () => {
|
||||
describe("basic /model command", () => {
|
||||
it("extracts /model with argument", () => {
|
||||
const result = extractModelDirective("/model gpt-5");
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("gpt-5");
|
||||
expect(result.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("extracts /model with provider/model format", () => {
|
||||
const result = extractModelDirective("/model anthropic/claude-opus-4-5");
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("anthropic/claude-opus-4-5");
|
||||
});
|
||||
|
||||
it("extracts /model with profile override", () => {
|
||||
const result = extractModelDirective("/model gpt-5@myprofile");
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("gpt-5");
|
||||
expect(result.rawProfile).toBe("myprofile");
|
||||
});
|
||||
|
||||
it("returns no directive for plain text", () => {
|
||||
const result = extractModelDirective("hello world");
|
||||
expect(result.hasDirective).toBe(false);
|
||||
expect(result.cleaned).toBe("hello world");
|
||||
});
|
||||
});
|
||||
|
||||
describe("alias shortcuts", () => {
|
||||
it("recognizes /gpt as model directive when alias is configured", () => {
|
||||
const result = extractModelDirective("/gpt", { aliases: ["gpt", "sonnet", "opus"] });
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("gpt");
|
||||
expect(result.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("recognizes /sonnet as model directive", () => {
|
||||
const result = extractModelDirective("/sonnet", { aliases: ["gpt", "sonnet", "opus"] });
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("sonnet");
|
||||
});
|
||||
|
||||
it("recognizes alias mid-message", () => {
|
||||
const result = extractModelDirective("switch to /opus please", {
|
||||
aliases: ["opus"],
|
||||
});
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("opus");
|
||||
expect(result.cleaned).toBe("switch to please");
|
||||
});
|
||||
|
||||
it("is case-insensitive for aliases", () => {
|
||||
const result = extractModelDirective("/GPT", { aliases: ["gpt"] });
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("GPT");
|
||||
});
|
||||
|
||||
it("does not match alias without leading slash", () => {
|
||||
const result = extractModelDirective("gpt is great", { aliases: ["gpt"] });
|
||||
expect(result.hasDirective).toBe(false);
|
||||
});
|
||||
|
||||
it("does not match unknown aliases", () => {
|
||||
const result = extractModelDirective("/unknown", { aliases: ["gpt", "sonnet"] });
|
||||
expect(result.hasDirective).toBe(false);
|
||||
expect(result.cleaned).toBe("/unknown");
|
||||
});
|
||||
|
||||
it("prefers /model over alias when both present", () => {
|
||||
const result = extractModelDirective("/model haiku", { aliases: ["gpt"] });
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("haiku");
|
||||
});
|
||||
|
||||
it("handles empty aliases array", () => {
|
||||
const result = extractModelDirective("/gpt", { aliases: [] });
|
||||
expect(result.hasDirective).toBe(false);
|
||||
});
|
||||
|
||||
it("handles undefined aliases", () => {
|
||||
const result = extractModelDirective("/gpt");
|
||||
expect(result.hasDirective).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("handles alias with special regex characters", () => {
|
||||
const result = extractModelDirective("/test.alias", {
|
||||
aliases: ["test.alias"],
|
||||
});
|
||||
expect(result.hasDirective).toBe(true);
|
||||
expect(result.rawModel).toBe("test.alias");
|
||||
});
|
||||
|
||||
it("does not match partial alias", () => {
|
||||
const result = extractModelDirective("/gpt-turbo", { aliases: ["gpt"] });
|
||||
expect(result.hasDirective).toBe(false);
|
||||
});
|
||||
|
||||
it("handles empty body", () => {
|
||||
const result = extractModelDirective("", { aliases: ["gpt"] });
|
||||
expect(result.hasDirective).toBe(false);
|
||||
expect(result.cleaned).toBe("");
|
||||
});
|
||||
|
||||
it("handles undefined body", () => {
|
||||
const result = extractModelDirective(undefined, { aliases: ["gpt"] });
|
||||
expect(result.hasDirective).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -31,7 +31,10 @@ import { clearCommandLane, getQueueSize } from "../process/command-queue.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveCommandAuthorization } from "./command-auth.js";
|
||||
import { hasControlCommand } from "./command-detection.js";
|
||||
import { shouldHandleTextCommands } from "./commands-registry.js";
|
||||
import {
|
||||
listChatCommands,
|
||||
shouldHandleTextCommands,
|
||||
} from "./commands-registry.js";
|
||||
import { getAbortMemory } from "./reply/abort.js";
|
||||
import { runReplyAgent } from "./reply/agent-runner.js";
|
||||
import { resolveBlockStreamingChunking } from "./reply/block-streaming.js";
|
||||
@@ -312,9 +315,15 @@ export async function getReplyFromConfig(
|
||||
rawDrop: undefined,
|
||||
hasQueueOptions: false,
|
||||
});
|
||||
const reservedCommands = new Set(
|
||||
listChatCommands().flatMap((cmd) =>
|
||||
cmd.textAliases.map((a) => a.replace(/^\//, "").toLowerCase()),
|
||||
),
|
||||
);
|
||||
const configuredAliases = Object.values(cfg.agent?.models ?? {})
|
||||
.map((entry) => entry.alias)
|
||||
.filter((alias): alias is string => Boolean(alias));
|
||||
.filter((alias): alias is string => Boolean(alias))
|
||||
.filter((alias) => !reservedCommands.has(alias.toLowerCase()));
|
||||
let parsedDirectives = parseInlineDirectives(rawBody, {
|
||||
modelAliases: configuredAliases,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user