mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
fix: preserve LM Studio quant model refs (#71486)
This commit is contained in:
@@ -70,6 +70,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/Fly.io: seed Control UI allowed origins from the actual runtime
|
||||
bind and port so CLI-driven non-loopback starts do not crash before config
|
||||
exists. Fixes #71823.
|
||||
- Models/LM Studio: preserve `@iq*` quant suffixes in model refs and provider
|
||||
matching so `/model lmstudio/...@iq3_xxs` keeps the exact LM Studio variant.
|
||||
Fixes #71474. (#71486) Thanks @Bartok9, @XinwuC, and @Sanjays2402.
|
||||
- Feishu: accept Schema 2.0 card action callbacks that report
|
||||
`context.open_chat_id` instead of legacy `context.chat_id`, so button
|
||||
callbacks no longer drop as malformed. Fixes #71670. Thanks @eddy1068.
|
||||
|
||||
@@ -29,8 +29,8 @@ 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.
|
||||
// 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 lmstudio/foo@iq3_xxs@work
|
||||
|
||||
@@ -894,6 +894,30 @@ 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([
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1321,6 +1321,52 @@ describe("resolvePluginProviders", () => {
|
||||
expectModelOwningPluginIds("gpt-5.4", ["workspace-openai"]);
|
||||
});
|
||||
|
||||
it("preserves LM Studio @iq* quant suffixes when resolving model-owned provider plugins", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "lmstudio",
|
||||
providerIds: ["lmstudio"],
|
||||
modelSupport: {
|
||||
modelPatterns: ["^qwen3\\.6-27b@iq3_xxs$"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const provider: ProviderPlugin = {
|
||||
id: "lmstudio",
|
||||
label: "LM Studio",
|
||||
auth: [],
|
||||
};
|
||||
const registry = createEmptyPluginRegistry();
|
||||
registry.providers.push({ pluginId: "lmstudio", provider, source: "bundled" });
|
||||
resolveRuntimePluginRegistryMock.mockReturnValue(registry);
|
||||
|
||||
expectModelOwningPluginIds("qwen3.6-27b@iq3_xxs", ["lmstudio"]);
|
||||
expectModelOwningPluginIds("qwen3.6-27b", undefined);
|
||||
|
||||
const providers = resolvePluginProviders({
|
||||
config: {},
|
||||
modelRefs: ["qwen3.6-27b@iq3_xxs"],
|
||||
bundledProviderAllowlistCompat: true,
|
||||
});
|
||||
|
||||
expectResolvedProviders(providers, [
|
||||
{ id: "lmstudio", label: "LM Studio", auth: [], pluginId: "lmstudio" },
|
||||
]);
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["lmstudio"],
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["lmstudio"],
|
||||
entries: {
|
||||
lmstudio: { enabled: true },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("auto-loads a model-owned provider plugin from shorthand model refs", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
|
||||
Reference in New Issue
Block a user