fix: treat /model status as model list
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
- LM Studio responses API: tools payloads no longer include `strict: null`, and LM Studio no longer gets forced `<think>/<final>` tags.
|
- LM Studio responses API: tools payloads no longer include `strict: null`, and LM Studio no longer gets forced `<think>/<final>` tags.
|
||||||
- Identity emoji no longer auto-prefixes replies (set `messages.responsePrefix` explicitly if desired).
|
- Identity emoji no longer auto-prefixes replies (set `messages.responsePrefix` explicitly if desired).
|
||||||
- Model switches now enqueue a system event so the next run knows the active model.
|
- Model switches now enqueue a system event so the next run knows the active model.
|
||||||
|
- `/model status` now lists available models (same as `/model`).
|
||||||
- `process log` pagination is now line-based (omit `offset` to grab the last N lines).
|
- `process log` pagination is now line-based (omit `offset` to grab the last N lines).
|
||||||
- macOS WebChat: assistant bubbles now update correctly when toggling light/dark mode.
|
- macOS WebChat: assistant bubbles now update correctly when toggling light/dark mode.
|
||||||
- macOS: avoid spawning a duplicate gateway process when an external listener already exists.
|
- macOS: avoid spawning a duplicate gateway process when an external listener already exists.
|
||||||
|
|||||||
@@ -366,6 +366,32 @@ describe("directive parsing", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("lists allowlisted models on /model status", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
|
const storePath = path.join(home, "sessions.json");
|
||||||
|
|
||||||
|
const res = await getReplyFromConfig(
|
||||||
|
{ Body: "/model status", From: "+1222", To: "+1222" },
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
agent: {
|
||||||
|
model: "anthropic/claude-opus-4-5",
|
||||||
|
workspace: path.join(home, "clawd"),
|
||||||
|
allowedModels: ["anthropic/claude-opus-4-5", "openai/gpt-4.1-mini"],
|
||||||
|
},
|
||||||
|
session: { store: storePath },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||||
|
expect(text).toContain("anthropic/claude-opus-4-5");
|
||||||
|
expect(text).toContain("openai/gpt-4.1-mini");
|
||||||
|
expect(text).not.toContain("claude-sonnet-4-1");
|
||||||
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("sets model override on /model directive", async () => {
|
it("sets model override on /model directive", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
|
|||||||
@@ -554,6 +554,11 @@ export async function getReplyFromConfig(
|
|||||||
alias
|
alias
|
||||||
? `Model switched to ${alias} (${label}).`
|
? `Model switched to ${alias} (${label}).`
|
||||||
: `Model switched to ${label}.`;
|
: `Model switched to ${label}.`;
|
||||||
|
const isModelListAlias =
|
||||||
|
hasModelDirective && rawModelDirective?.trim().toLowerCase() === "status";
|
||||||
|
const effectiveModelDirective = isModelListAlias
|
||||||
|
? undefined
|
||||||
|
: rawModelDirective;
|
||||||
|
|
||||||
const directiveOnly = (() => {
|
const directiveOnly = (() => {
|
||||||
if (
|
if (
|
||||||
@@ -569,7 +574,7 @@ export async function getReplyFromConfig(
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
if (directiveOnly) {
|
if (directiveOnly) {
|
||||||
if (hasModelDirective && !rawModelDirective) {
|
if (hasModelDirective && (!rawModelDirective || isModelListAlias)) {
|
||||||
if (allowedModelCatalog.length === 0) {
|
if (allowedModelCatalog.length === 0) {
|
||||||
cleanupTyping();
|
cleanupTyping();
|
||||||
return { text: "No models available." };
|
return { text: "No models available." };
|
||||||
@@ -620,16 +625,16 @@ export async function getReplyFromConfig(
|
|||||||
let modelSelection:
|
let modelSelection:
|
||||||
| { provider: string; model: string; isDefault: boolean; alias?: string }
|
| { provider: string; model: string; isDefault: boolean; alias?: string }
|
||||||
| undefined;
|
| undefined;
|
||||||
if (hasModelDirective && rawModelDirective) {
|
if (hasModelDirective && effectiveModelDirective) {
|
||||||
const resolved = resolveModelRefFromString({
|
const resolved = resolveModelRefFromString({
|
||||||
raw: rawModelDirective,
|
raw: effectiveModelDirective,
|
||||||
defaultProvider,
|
defaultProvider,
|
||||||
aliasIndex,
|
aliasIndex,
|
||||||
});
|
});
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
cleanupTyping();
|
cleanupTyping();
|
||||||
return {
|
return {
|
||||||
text: `Unrecognized model "${rawModelDirective}". Use /model to list available models.`,
|
text: `Unrecognized model "${effectiveModelDirective}". Use /model to list available models.`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const key = modelKey(resolved.ref.provider, resolved.ref.model);
|
const key = modelKey(resolved.ref.provider, resolved.ref.model);
|
||||||
@@ -742,9 +747,9 @@ export async function getReplyFromConfig(
|
|||||||
}
|
}
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
if (hasModelDirective && rawModelDirective) {
|
if (hasModelDirective && effectiveModelDirective) {
|
||||||
const resolved = resolveModelRefFromString({
|
const resolved = resolveModelRefFromString({
|
||||||
raw: rawModelDirective,
|
raw: effectiveModelDirective,
|
||||||
defaultProvider,
|
defaultProvider,
|
||||||
aliasIndex,
|
aliasIndex,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user