feat: add fast mode toggle for OpenAI models
This commit is contained in:
@@ -378,4 +378,42 @@ describe("executeSlashCommand directives", () => {
|
||||
expect(result.content).toBe("Current verbose level: full.\nOptions: on, full, off.");
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
|
||||
});
|
||||
|
||||
it("reports the current fast mode for bare /fast", async () => {
|
||||
const request = vi.fn(async (method: string, _payload?: unknown) => {
|
||||
if (method === "sessions.list") {
|
||||
return {
|
||||
sessions: [row("agent:main:main", { fastMode: true })],
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected method: ${method}`);
|
||||
});
|
||||
|
||||
const result = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"fast",
|
||||
"",
|
||||
);
|
||||
|
||||
expect(result.content).toBe("Current fast mode: on.\nOptions: status, on, off.");
|
||||
expect(request).toHaveBeenNthCalledWith(1, "sessions.list", {});
|
||||
});
|
||||
|
||||
it("patches fast mode for /fast on", async () => {
|
||||
const request = vi.fn().mockResolvedValue({ ok: true });
|
||||
|
||||
const result = await executeSlashCommand(
|
||||
{ request } as unknown as GatewayBrowserClient,
|
||||
"agent:main:main",
|
||||
"fast",
|
||||
"on",
|
||||
);
|
||||
|
||||
expect(result.content).toBe("Fast mode enabled.");
|
||||
expect(request).toHaveBeenCalledWith("sessions.patch", {
|
||||
key: "agent:main:main",
|
||||
fastMode: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,6 +63,8 @@ export async function executeSlashCommand(
|
||||
return await executeModel(client, sessionKey, args);
|
||||
case "think":
|
||||
return await executeThink(client, sessionKey, args);
|
||||
case "fast":
|
||||
return await executeFast(client, sessionKey, args);
|
||||
case "verbose":
|
||||
return await executeVerbose(client, sessionKey, args);
|
||||
case "export":
|
||||
@@ -252,6 +254,44 @@ async function executeVerbose(
|
||||
}
|
||||
}
|
||||
|
||||
async function executeFast(
|
||||
client: GatewayBrowserClient,
|
||||
sessionKey: string,
|
||||
args: string,
|
||||
): Promise<SlashCommandResult> {
|
||||
const rawMode = args.trim().toLowerCase();
|
||||
|
||||
if (!rawMode || rawMode === "status") {
|
||||
try {
|
||||
const session = await loadCurrentSession(client, sessionKey);
|
||||
return {
|
||||
content: formatDirectiveOptions(
|
||||
`Current fast mode: ${resolveCurrentFastMode(session)}.`,
|
||||
"status, on, off",
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
return { content: `Failed to get fast mode: ${String(err)}` };
|
||||
}
|
||||
}
|
||||
|
||||
if (rawMode !== "on" && rawMode !== "off") {
|
||||
return {
|
||||
content: `Unrecognized fast mode "${args.trim()}". Valid levels: status, on, off.`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await client.request("sessions.patch", { key: sessionKey, fastMode: rawMode === "on" });
|
||||
return {
|
||||
content: `Fast mode ${rawMode === "on" ? "enabled" : "disabled"}.`,
|
||||
action: "refresh",
|
||||
};
|
||||
} catch (err) {
|
||||
return { content: `Failed to set fast mode: ${String(err)}` };
|
||||
}
|
||||
}
|
||||
|
||||
async function executeUsage(
|
||||
client: GatewayBrowserClient,
|
||||
sessionKey: string,
|
||||
@@ -534,6 +574,10 @@ function resolveCurrentThinkingLevel(
|
||||
});
|
||||
}
|
||||
|
||||
function resolveCurrentFastMode(session: GatewaySessionRow | undefined): "on" | "off" {
|
||||
return session?.fastMode === true ? "on" : "off";
|
||||
}
|
||||
|
||||
function fmtTokens(n: number): string {
|
||||
if (n >= 1_000_000) {
|
||||
return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`;
|
||||
|
||||
@@ -23,4 +23,11 @@ describe("parseSlashCommand", () => {
|
||||
args: "full",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses fast commands", () => {
|
||||
expect(parseSlashCommand("/fast:on")).toMatchObject({
|
||||
command: { name: "fast" },
|
||||
args: "on",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,6 +88,15 @@ export const SLASH_COMMANDS: SlashCommandDef[] = [
|
||||
executeLocal: true,
|
||||
argOptions: ["on", "off", "full"],
|
||||
},
|
||||
{
|
||||
name: "fast",
|
||||
description: "Toggle fast mode",
|
||||
args: "<status|on|off>",
|
||||
icon: "zap",
|
||||
category: "model",
|
||||
executeLocal: true,
|
||||
argOptions: ["status", "on", "off"],
|
||||
},
|
||||
|
||||
// ── Tools ──
|
||||
{
|
||||
|
||||
@@ -63,6 +63,7 @@ export async function patchSession(
|
||||
patch: {
|
||||
label?: string | null;
|
||||
thinkingLevel?: string | null;
|
||||
fastMode?: boolean | null;
|
||||
verboseLevel?: string | null;
|
||||
reasoningLevel?: string | null;
|
||||
},
|
||||
@@ -77,6 +78,9 @@ export async function patchSession(
|
||||
if ("thinkingLevel" in patch) {
|
||||
params.thinkingLevel = patch.thinkingLevel;
|
||||
}
|
||||
if ("fastMode" in patch) {
|
||||
params.fastMode = patch.fastMode;
|
||||
}
|
||||
if ("verboseLevel" in patch) {
|
||||
params.verboseLevel = patch.verboseLevel;
|
||||
}
|
||||
|
||||
@@ -379,6 +379,7 @@ export type GatewaySessionRow = {
|
||||
systemSent?: boolean;
|
||||
abortedLastRun?: boolean;
|
||||
thinkingLevel?: string;
|
||||
fastMode?: boolean;
|
||||
verboseLevel?: string;
|
||||
reasoningLevel?: string;
|
||||
elevatedLevel?: string;
|
||||
@@ -396,6 +397,7 @@ export type SessionsPatchResult = SessionsPatchResultBase<{
|
||||
sessionId: string;
|
||||
updatedAt?: number;
|
||||
thinkingLevel?: string;
|
||||
fastMode?: boolean;
|
||||
verboseLevel?: string;
|
||||
reasoningLevel?: string;
|
||||
elevatedLevel?: string;
|
||||
|
||||
@@ -60,7 +60,7 @@ describe("sessions view", () => {
|
||||
await Promise.resolve();
|
||||
|
||||
const selects = container.querySelectorAll("select");
|
||||
const verbose = selects[1] as HTMLSelectElement | undefined;
|
||||
const verbose = selects[2] as HTMLSelectElement | undefined;
|
||||
expect(verbose?.value).toBe("full");
|
||||
expect(Array.from(verbose?.options ?? []).some((option) => option.value === "full")).toBe(true);
|
||||
});
|
||||
@@ -83,10 +83,32 @@ describe("sessions view", () => {
|
||||
await Promise.resolve();
|
||||
|
||||
const selects = container.querySelectorAll("select");
|
||||
const reasoning = selects[2] as HTMLSelectElement | undefined;
|
||||
const reasoning = selects[3] as HTMLSelectElement | undefined;
|
||||
expect(reasoning?.value).toBe("custom-mode");
|
||||
expect(
|
||||
Array.from(reasoning?.options ?? []).some((option) => option.value === "custom-mode"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("renders explicit fast mode without falling back to inherit", async () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderSessions(
|
||||
buildProps(
|
||||
buildResult({
|
||||
key: "agent:main:main",
|
||||
kind: "direct",
|
||||
updatedAt: Date.now(),
|
||||
fastMode: true,
|
||||
}),
|
||||
),
|
||||
),
|
||||
container,
|
||||
);
|
||||
await Promise.resolve();
|
||||
|
||||
const selects = container.querySelectorAll("select");
|
||||
const fast = selects[1] as HTMLSelectElement | undefined;
|
||||
expect(fast?.value).toBe("on");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ export type SessionsProps = {
|
||||
patch: {
|
||||
label?: string | null;
|
||||
thinkingLevel?: string | null;
|
||||
fastMode?: boolean | null;
|
||||
verboseLevel?: string | null;
|
||||
reasoningLevel?: string | null;
|
||||
},
|
||||
@@ -52,6 +53,11 @@ const VERBOSE_LEVELS = [
|
||||
{ value: "on", label: "on" },
|
||||
{ value: "full", label: "full" },
|
||||
] as const;
|
||||
const FAST_LEVELS = [
|
||||
{ value: "", label: "inherit" },
|
||||
{ value: "on", label: "on" },
|
||||
{ value: "off", label: "off" },
|
||||
] as const;
|
||||
const REASONING_LEVELS = ["", "off", "on", "stream"] as const;
|
||||
const PAGE_SIZES = [10, 25, 50, 100] as const;
|
||||
|
||||
@@ -306,6 +312,7 @@ export function renderSessions(props: SessionsProps) {
|
||||
${sortHeader("updated", "Updated")}
|
||||
${sortHeader("tokens", "Tokens")}
|
||||
<th>Thinking</th>
|
||||
<th>Fast</th>
|
||||
<th>Verbose</th>
|
||||
<th>Reasoning</th>
|
||||
<th style="width: 60px;"></th>
|
||||
@@ -316,7 +323,7 @@ export function renderSessions(props: SessionsProps) {
|
||||
paginated.length === 0
|
||||
? html`
|
||||
<tr>
|
||||
<td colspan="9" style="text-align: center; padding: 48px 16px; color: var(--muted)">
|
||||
<td colspan="10" style="text-align: center; padding: 48px 16px; color: var(--muted)">
|
||||
No sessions found.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -390,6 +397,8 @@ function renderRow(
|
||||
const isBinaryThinking = isBinaryThinkingProvider(row.modelProvider);
|
||||
const thinking = resolveThinkLevelDisplay(rawThinking, isBinaryThinking);
|
||||
const thinkLevels = withCurrentOption(resolveThinkLevelOptions(row.modelProvider), thinking);
|
||||
const fastMode = row.fastMode === true ? "on" : row.fastMode === false ? "off" : "";
|
||||
const fastLevels = withCurrentLabeledOption(FAST_LEVELS, fastMode);
|
||||
const verbose = row.verboseLevel ?? "";
|
||||
const verboseLevels = withCurrentLabeledOption(VERBOSE_LEVELS, verbose);
|
||||
const reasoning = row.reasoningLevel ?? "";
|
||||
@@ -465,6 +474,23 @@ function renderRow(
|
||||
)}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
?disabled=${disabled}
|
||||
style="padding: 6px 10px; font-size: 13px; border: 1px solid var(--border); border-radius: var(--radius-sm); min-width: 90px;"
|
||||
@change=${(e: Event) => {
|
||||
const value = (e.target as HTMLSelectElement).value;
|
||||
onPatch(row.key, { fastMode: value === "" ? null : value === "on" });
|
||||
}}
|
||||
>
|
||||
${fastLevels.map(
|
||||
(level) =>
|
||||
html`<option value=${level.value} ?selected=${fastMode === level.value}>
|
||||
${level.label}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
?disabled=${disabled}
|
||||
|
||||
Reference in New Issue
Block a user