Skip to content
Open
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
16 changes: 16 additions & 0 deletions src/agents/model-ref-profile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ describe("splitTrailingAuthProfile", () => {
});
});

it("keeps @iq* importance-quantization suffixes in model ids", () => {
expect(splitTrailingAuthProfile("lmstudio/qwen3.6-27b@iq3_xxs")).toEqual({
model: "lmstudio/qwen3.6-27b@iq3_xxs",
});
expect(splitTrailingAuthProfile("lmstudio/qwen3.6-27b@iq4_xs")).toEqual({
model: "lmstudio/qwen3.6-27b@iq4_xs",
});
});

it("supports auth profiles after @iq* quant suffixes", () => {
expect(splitTrailingAuthProfile("lmstudio/qwen3.6-27b@iq3_xxs@work")).toEqual({
model: "lmstudio/qwen3.6-27b@iq3_xxs",
profile: "work",
});
});

it("keeps @4bit/@8bit quant suffixes in model ids", () => {
expect(splitTrailingAuthProfile("lmstudio-mb-pro/gemma-4-31b@4bit")).toEqual({
model: "lmstudio-mb-pro/gemma-4-31b@4bit",
Expand Down
7 changes: 5 additions & 2 deletions src/agents/model-ref-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ export function splitTrailingAuthProfile(raw: string): {
// of the model id. These often use '@' (ex: gemma-4-31b-it@q8_0) which would
// otherwise be misinterpreted as an auth profile delimiter.
//
// Covers standard GGUF quant tags (q4_0, q8_0, q4_k_xl, …) and importance-
// quantization variants (iq3_xxs, iq4_xs, …) used by llama.cpp / LM Studio.
//
// If an auth profile is needed, it can still be specified as a second suffix:
// lmstudio/foo@q8_0@work
if (/^(?:q\d+(?:_[a-z0-9]+)*|\d+bit)(?:@|$)/i.test(suffixAfterDelimiter())) {
// lmstudio/foo@q8_0@work lmstudio/foo@iq3_xxs@work
if (/^(?:i?q\d+(?:_[a-z0-9]+)*|\d+bit)(?:@|$)/i.test(suffixAfterDelimiter())) {
const nextDelimiter = trimmed.indexOf("@", profileDelimiter + 1);
if (nextDelimiter < 0) {
return { model: trimmed };
Expand Down
22 changes: 22 additions & 0 deletions src/agents/model-selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,28 @@ describe("model-selection", () => {
});
});

it("preserves LM Studio @iq* quant suffixes", () => {
const resolved = resolveModelRefFromString({
raw: "lmstudio/qwen3.6-27b@iq3_xxs",
defaultProvider: "anthropic",
});
expect(resolved?.ref).toEqual({
provider: "lmstudio",
model: "qwen3.6-27b@iq3_xxs",
});
});

it("splits trailing profile suffix after LM Studio @iq* quant suffixes", () => {
const resolved = resolveModelRefFromString({
raw: "lmstudio/qwen3.6-27b@iq3_xxs@work",
defaultProvider: "anthropic",
});
expect(resolved?.ref).toEqual({
provider: "lmstudio",
model: "qwen3.6-27b@iq3_xxs",
});
});

it("strips profile suffix before alias resolution", () => {
const index = {
byAlias: new Map([
Expand Down
14 changes: 14 additions & 0 deletions src/auto-reply/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ describe("extractModelDirective", () => {
expect(result.rawProfile).toBe("cf:default");
});

it("keeps LM Studio @iq* quant suffixes inside model ids", () => {
const result = extractModelDirective("/model lmstudio/qwen3.6-27b@iq3_xxs");
expect(result.hasDirective).toBe(true);
expect(result.rawModel).toBe("lmstudio/qwen3.6-27b@iq3_xxs");
expect(result.rawProfile).toBeUndefined();
});

it("allows profile overrides after LM Studio @iq* quant suffixes", () => {
const result = extractModelDirective("/model lmstudio/qwen3.6-27b@iq3_xxs@work");
expect(result.hasDirective).toBe(true);
expect(result.rawModel).toBe("lmstudio/qwen3.6-27b@iq3_xxs");
expect(result.rawProfile).toBe("work");
});

it("returns no directive for plain text", () => {
const result = extractModelDirective("hello world");
expect(result.hasDirective).toBe(false);
Expand Down
5 changes: 2 additions & 3 deletions src/plugins/providers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { splitTrailingAuthProfile } from "../agents/model-ref-profile.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import { withBundledPluginVitestCompat } from "./bundled-compat.js";
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
Expand Down Expand Up @@ -290,9 +291,7 @@ function resolveManifestRegistry(params: {
}

function stripModelProfileSuffix(value: string): string {
const trimmed = value.trim();
const at = trimmed.indexOf("@");
return at <= 0 ? trimmed : trimmed.slice(0, at).trim();
return splitTrailingAuthProfile(value).model;
}

function splitExplicitModelRef(rawModel: string): { provider?: string; modelId: string } | null {
Expand Down
Loading