Files
oh-my-pi/packages/coding-agent/test/modes/internal-url-autocomplete.test.ts
T
can1357 f0b252449b feat(coding-agent): added tiny local model support for memory extraction and consolidation
- Added a new `providers.memoryModel` setting with tiny memory model options and `ONLINE_MEMORY_MODEL_KEY` default in settings.
- Updated Mnemosyne provider resolution so a configured local tiny model overrode remote completion and used new memory extraction and consolidation prompts.
- Expanded the tiny-model CLI registry to download and report all local tiny models (title plus memory) through a unified list.
2026-05-30 18:48:47 +02:00

190 lines
7.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import type { Rule } from "../../src/capability/rule";
import { resetActiveRulesForTests, setActiveRules } from "../../src/capability/rule";
import type { Skill } from "../../src/extensibility/skills";
import { resetActiveSkillsForTests, setActiveSkills } from "../../src/extensibility/skills";
import { InternalUrlRouter } from "../../src/internal-urls/router";
import {
applyInternalUrlCompletion,
extractInternalUrlContext,
getInternalUrlSuggestions,
isInternalUrlPrefix,
} from "../../src/modes/internal-url-autocomplete";
import { PromptActionAutocompleteProvider } from "../../src/modes/prompt-action-autocomplete";
function skill(name: string, description = ""): Skill {
return { name, description, filePath: `/skills/${name}/SKILL.md`, baseDir: `/skills/${name}`, source: "test" };
}
function rule(name: string, description?: string): Rule {
return {
name,
path: `/rules/${name}.md`,
content: `# ${name}`,
...(description ? { description } : {}),
_source: { provider: "test", providerName: "Test", path: `/rules/${name}.md`, level: "project" },
};
}
describe("internal-url-autocomplete", () => {
beforeEach(() => {
setActiveSkills([skill("humanizer", "Remove AI tells"), skill("react", "React UI"), skill("tla", "TLA+ specs")]);
setActiveRules([rule("python", "robomp rules"), rule("style")]);
});
afterEach(() => {
resetActiveSkillsForTests();
resetActiveRulesForTests();
});
describe("extractInternalUrlContext", () => {
it("detects a bare scheme with the full :// typed", () => {
expect(extractInternalUrlContext("local://")).toEqual({ scheme: "local", query: "", token: "local://" });
});
it("treats a single slash as the same in-progress token (preserving exact text)", () => {
expect(extractInternalUrlContext("local:/")).toEqual({ scheme: "local", query: "", token: "local:/" });
});
it("captures the host/path query and the boundary-delimited token", () => {
expect(extractInternalUrlContext("look at skill://hum")).toEqual({
scheme: "skill",
query: "hum",
token: "skill://hum",
});
});
it("keeps nested paths in the query", () => {
expect(extractInternalUrlContext("local://dir/file.json")).toMatchObject({
scheme: "local",
query: "dir/file.json",
});
});
it("ignores schemes with no completion handler (http/https)", () => {
expect(extractInternalUrlContext("https://example.com/x")).toBeNull();
});
it("does not fire on a bare colon in prose", () => {
expect(extractInternalUrlContext("note: hello")).toBeNull();
expect(extractInternalUrlContext("TODO: ship it")).toBeNull();
});
it("requires at least one slash after the colon", () => {
expect(extractInternalUrlContext("local:")).toBeNull();
});
});
describe("getInternalUrlSuggestions", () => {
it("lists every skill for a bare skill:// and prefixes the scheme", async () => {
const result = await getInternalUrlSuggestions("skill://");
expect(result).not.toBeNull();
expect(result!.prefix).toBe("skill://");
expect(result!.items.map(i => i.value).sort()).toEqual(["skill://humanizer", "skill://react", "skill://tla"]);
});
it("fuzzy-filters candidates by the typed query", async () => {
const result = await getInternalUrlSuggestions("skill://hum");
expect(result!.items.map(i => i.value)).toEqual(["skill://humanizer"]);
});
it("ranks an exact/prefix match ahead of a scattered subsequence", async () => {
// "ra" is a prefix of "react" (score 80) and a subsequence of "humanizer" (lower).
const result = await getInternalUrlSuggestions("skill://ra");
expect(result!.items[0]!.value).toBe("skill://react");
});
it("carries the candidate description through", async () => {
const result = await getInternalUrlSuggestions("rule://python");
expect(result!.items[0]).toMatchObject({ value: "rule://python", description: "robomp rules" });
});
it("returns null when no candidate matches", async () => {
expect(await getInternalUrlSuggestions("skill://zzzzz")).toBeNull();
});
it("returns null for schemes without a completion handler", async () => {
expect(await getInternalUrlSuggestions("issue://")).toBeNull();
});
});
describe("router.complete dispatch", () => {
it("returns candidates for a completion-capable scheme", async () => {
const candidates = await InternalUrlRouter.instance().complete("rule", "");
expect(candidates?.map(c => c.value).sort()).toEqual(["python", "style"]);
});
it("returns null for a known scheme that opted out of completion", async () => {
expect(await InternalUrlRouter.instance().complete("issue", "")).toBeNull();
expect(await InternalUrlRouter.instance().complete("pr", "")).toBeNull();
});
it("returns null for an unknown scheme", async () => {
expect(await InternalUrlRouter.instance().complete("bogus", "")).toBeNull();
});
it("exposes the completion-capable schemes", () => {
const schemes = InternalUrlRouter.instance().completionSchemes().sort();
expect(schemes).toEqual(["agent", "artifact", "local", "memory", "omp", "rule", "skill"]);
});
});
describe("applyInternalUrlCompletion", () => {
it("replaces the token in place and appends a trailing space", () => {
const line = "look at skill://hum";
const result = applyInternalUrlCompletion(
[line],
0,
line.length,
{ value: "skill://humanizer", label: "humanizer" },
"skill://hum",
);
expect(result.lines[0]).toBe("look at skill://humanizer ");
expect(result.cursorCol).toBe("look at skill://humanizer ".length);
});
it("preserves text after the cursor", () => {
const line = "skill://hum and more";
const cursorCol = "skill://hum".length;
const result = applyInternalUrlCompletion(
[line],
0,
cursorCol,
{ value: "skill://humanizer", label: "humanizer" },
"skill://hum",
);
expect(result.lines[0]).toBe("skill://humanizer and more");
});
});
describe("isInternalUrlPrefix", () => {
it("recognizes a completion prefix token", () => {
expect(isInternalUrlPrefix("skill://hum")).toBe(true);
expect(isInternalUrlPrefix("local://")).toBe(true);
});
it("rejects non-url prefixes", () => {
expect(isInternalUrlPrefix("@src/foo")).toBe(false);
expect(isInternalUrlPrefix("/model")).toBe(false);
});
});
describe("PromptActionAutocompleteProvider integration", () => {
it("returns url suggestions before falling back to file/emoji completion", async () => {
const provider = new PromptActionAutocompleteProvider([], process.cwd(), []);
const line = "look at skill://hum";
const result = await provider.getSuggestions([line], 0, line.length);
expect(result?.prefix).toBe("skill://hum");
expect(result?.items.map(i => i.value)).toEqual(["skill://humanizer"]);
});
it("applies the selected url candidate in place", async () => {
const provider = new PromptActionAutocompleteProvider([], process.cwd(), []);
const line = "look at skill://hum";
const result = await provider.getSuggestions([line], 0, line.length);
const applied = provider.applyCompletion([line], 0, line.length, result!.items[0]!, result!.prefix);
expect(applied.lines[0]).toBe("look at skill://humanizer ");
});
});
});