mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Add rewrite selection runtime settings
This commit is contained in:
parent
084cdd4573
commit
2dd6043340
11 changed files with 132 additions and 84 deletions
|
|
@ -34,7 +34,6 @@ export function registerRewriteSelectionCommand(plugin: RewriteSelectionCommandH
|
|||
},
|
||||
originalText,
|
||||
noteText: editor.getValue(),
|
||||
contextMode: "note",
|
||||
instruction: "",
|
||||
status: "editing-prompt",
|
||||
streamText: "",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Modal, Notice, type App, type Editor } from "obsidian";
|
|||
|
||||
import { renderUnifiedDiff } from "../ui/turn-diff";
|
||||
import { buildSelectionUnifiedDiff } from "./diff";
|
||||
import { canApplyRewrite, type RewriteContextMode, type RewriteSession } from "./model";
|
||||
import { canApplyRewrite, type RewriteSession } from "./model";
|
||||
import { buildRewritePrompt } from "./prompt";
|
||||
import { runRewriteSelection } from "./runner";
|
||||
|
||||
|
|
@ -15,7 +15,6 @@ export interface RewriteSelectionModalOptions {
|
|||
|
||||
export class RewriteSelectionModal extends Modal {
|
||||
private instructionEl: HTMLTextAreaElement | null = null;
|
||||
private contextEl: HTMLSelectElement | null = null;
|
||||
private generateButton: HTMLButtonElement | null = null;
|
||||
private applyButton: HTMLButtonElement | null = null;
|
||||
private statusEl: HTMLElement | null = null;
|
||||
|
|
@ -43,16 +42,6 @@ export class RewriteSelectionModal extends Modal {
|
|||
this.instructionEl.oninput = () => this.syncControls();
|
||||
|
||||
const controls = contentEl.createDiv({ cls: "codex-panel-rewrite__controls" });
|
||||
const contextLabel = controls.createEl("label", { cls: "codex-panel-rewrite__context" });
|
||||
contextLabel.createSpan({ text: "Context" });
|
||||
this.contextEl = contextLabel.createEl("select");
|
||||
this.addContextOption("note", "Selection + note context");
|
||||
this.addContextOption("selection", "Selection only");
|
||||
this.contextEl.value = this.options.session.contextMode === "selection" ? "selection" : "note";
|
||||
this.contextEl.onchange = () => {
|
||||
this.options.session.contextMode = this.contextEl?.value === "selection" ? "selection" : "note";
|
||||
};
|
||||
|
||||
const actions = controls.createDiv({ cls: "codex-panel-rewrite__actions" });
|
||||
this.generateButton = actions.createEl("button", { text: "Generate", attr: { type: "button" } });
|
||||
this.generateButton.onclick = () => void this.generate();
|
||||
|
|
@ -83,10 +72,6 @@ export class RewriteSelectionModal extends Modal {
|
|||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
private addContextOption(value: Exclude<RewriteContextMode, "note-and-thread">, label: string): void {
|
||||
this.contextEl?.createEl("option", { text: label, attr: { value } });
|
||||
}
|
||||
|
||||
private async generate(): Promise<void> {
|
||||
const instruction = this.instructionEl?.value.trim() ?? "";
|
||||
if (!instruction) {
|
||||
|
|
@ -96,7 +81,6 @@ export class RewriteSelectionModal extends Modal {
|
|||
}
|
||||
|
||||
this.options.session.instruction = instruction;
|
||||
this.options.session.contextMode = this.contextEl?.value === "selection" ? "selection" : "note";
|
||||
this.options.session.status = "generating";
|
||||
this.options.session.streamText = "";
|
||||
this.options.session.replacementText = null;
|
||||
|
|
@ -163,7 +147,6 @@ export class RewriteSelectionModal extends Modal {
|
|||
const generating = this.options.session.status === "generating";
|
||||
const hasInstruction = Boolean(this.instructionEl?.value.trim());
|
||||
if (this.instructionEl) this.instructionEl.disabled = generating;
|
||||
if (this.contextEl) this.contextEl.disabled = generating;
|
||||
if (this.generateButton) this.generateButton.disabled = generating || !hasInstruction;
|
||||
if (this.applyButton) this.applyButton.disabled = generating || this.options.session.replacementText === null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import type { EditorPosition } from "obsidian";
|
||||
|
||||
export type RewriteContextMode = "selection" | "note" | "note-and-thread";
|
||||
|
||||
export type RewriteStatus = "editing-prompt" | "generating" | "preview" | "applied" | "cancelled" | "failed";
|
||||
|
||||
export interface RewriteSession {
|
||||
|
|
@ -12,7 +10,6 @@ export interface RewriteSession {
|
|||
};
|
||||
originalText: string;
|
||||
noteText: string;
|
||||
contextMode: RewriteContextMode;
|
||||
instruction: string;
|
||||
status: RewriteStatus;
|
||||
streamText: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { RewriteContextMode, RewriteSession } from "./model";
|
||||
import type { RewriteSession } from "./model";
|
||||
|
||||
const MAX_NOTE_CONTEXT_CHARS = 20_000;
|
||||
|
||||
|
|
@ -25,32 +25,19 @@ export function buildRewritePrompt(session: RewriteSession): string {
|
|||
"Target:",
|
||||
`- File: ${session.filePath}`,
|
||||
`- Selection: ${positionLabel(session.targetRange.from)} to ${positionLabel(session.targetRange.to)}`,
|
||||
`- Context mode: ${contextModeLabel(session.contextMode)}`,
|
||||
"- Context mode: Selection + note context",
|
||||
"",
|
||||
"User instruction:",
|
||||
session.instruction,
|
||||
"",
|
||||
"Selected text:",
|
||||
fenced(session.originalText),
|
||||
...noteContextLines(session),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function noteContextLines(session: RewriteSession): string[] {
|
||||
if (session.contextMode === "selection") return [];
|
||||
return [
|
||||
"",
|
||||
"Current note context:",
|
||||
fenced(truncateNoteContext(session.noteText)),
|
||||
"",
|
||||
"Reminder: use the note context only to make the selected-text replacement coherent.",
|
||||
];
|
||||
}
|
||||
|
||||
function contextModeLabel(mode: RewriteContextMode): string {
|
||||
if (mode === "selection") return "Selection only";
|
||||
if (mode === "note") return "Selection + note context";
|
||||
return "Selection + note context + active thread";
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function positionLabel(position: { line: number; ch: number }): string {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { SortDirection } from "../generated/app-server/v2/SortDirection";
|
|||
import type { ThreadItem } from "../generated/app-server/v2/ThreadItem";
|
||||
import type { Turn } from "../generated/app-server/v2/Turn";
|
||||
import { inputToText, truncate } from "../utils";
|
||||
import { findModelByIdOrName, supportedEffortsForModel } from "../runtime/model";
|
||||
import { runtimeOverride, validatedRuntimeOverride } from "../runtime/model";
|
||||
|
||||
const NAMING_SERVICE_NAME = "codex-panel-naming";
|
||||
const NAMING_TIMEOUT_MS = 60_000;
|
||||
|
|
@ -214,21 +214,11 @@ export interface NamingRuntime {
|
|||
}
|
||||
|
||||
export function namingRuntime(settings: ThreadNamingRuntimeSettings): NamingRuntime {
|
||||
return {
|
||||
...(settings.threadNamingModel ? { model: settings.threadNamingModel } : {}),
|
||||
...(settings.threadNamingEffort ? { effort: settings.threadNamingEffort } : {}),
|
||||
};
|
||||
return runtimeOverride({ model: settings.threadNamingModel, effort: settings.threadNamingEffort });
|
||||
}
|
||||
|
||||
export function validatedNamingRuntime(settings: ThreadNamingRuntimeSettings, models: Model[]): NamingRuntime {
|
||||
const runtime = namingRuntime(settings);
|
||||
if (!runtime.model || !runtime.effort) return runtime;
|
||||
|
||||
const model = findModelByIdOrName(models, runtime.model);
|
||||
if (!model) return runtime;
|
||||
|
||||
const supportedEfforts = new Set(supportedEffortsForModel(model));
|
||||
return supportedEfforts.has(runtime.effort) ? runtime : { model: runtime.model };
|
||||
return validatedRuntimeOverride({ model: settings.threadNamingModel, effort: settings.threadNamingEffort }, models);
|
||||
}
|
||||
|
||||
function turnWithCollectedItems(turn: Turn, items: ThreadItem[]): Turn {
|
||||
|
|
|
|||
|
|
@ -26,3 +26,31 @@ export function supportedEffortsForModel(model: Model | null): ReasoningEffort[]
|
|||
const efforts = model?.supportedReasoningEfforts.map((option) => option.reasoningEffort).filter(isReasoningEffort) ?? [];
|
||||
return efforts.length > 0 ? efforts : REASONING_EFFORTS;
|
||||
}
|
||||
|
||||
export interface RuntimeOverrideSettings {
|
||||
model: string | null;
|
||||
effort: ReasoningEffort | null;
|
||||
}
|
||||
|
||||
export interface RuntimeOverride {
|
||||
model?: string;
|
||||
effort?: ReasoningEffort;
|
||||
}
|
||||
|
||||
export function runtimeOverride(settings: RuntimeOverrideSettings): RuntimeOverride {
|
||||
return {
|
||||
...(settings.model ? { model: settings.model } : {}),
|
||||
...(settings.effort ? { effort: settings.effort } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function validatedRuntimeOverride(settings: RuntimeOverrideSettings, models: Model[]): RuntimeOverride {
|
||||
const runtime = runtimeOverride(settings);
|
||||
if (!runtime.model || !runtime.effort) return runtime;
|
||||
|
||||
const model = findModelByIdOrName(models, runtime.model);
|
||||
if (!model) return runtime;
|
||||
|
||||
const supportedEfforts = new Set(supportedEffortsForModel(model));
|
||||
return supportedEfforts.has(runtime.effort) ? runtime : { model: runtime.model };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ export interface CodexPanelSettings {
|
|||
codexPath: string;
|
||||
threadNamingModel: string | null;
|
||||
threadNamingEffort: ReasoningEffort | null;
|
||||
rewriteSelectionModel: string | null;
|
||||
rewriteSelectionEffort: ReasoningEffort | null;
|
||||
sendShortcut: SendShortcut;
|
||||
}
|
||||
|
||||
|
|
@ -17,6 +19,8 @@ export const DEFAULT_SETTINGS: CodexPanelSettings = {
|
|||
codexPath: DEFAULT_CODEX_PATH,
|
||||
threadNamingModel: null,
|
||||
threadNamingEffort: null,
|
||||
rewriteSelectionModel: null,
|
||||
rewriteSelectionEffort: null,
|
||||
sendShortcut: "enter",
|
||||
};
|
||||
|
||||
|
|
@ -26,6 +30,8 @@ export function normalizeSettings(data: unknown): CodexPanelSettings {
|
|||
codexPath: stringOrDefault(record.codexPath, DEFAULT_CODEX_PATH).trim() || DEFAULT_CODEX_PATH,
|
||||
threadNamingModel: threadNamingModelOrDefault(record.threadNamingModel),
|
||||
threadNamingEffort: reasoningEffortOrDefault(record.threadNamingEffort),
|
||||
rewriteSelectionModel: modelOrDefault(record.rewriteSelectionModel),
|
||||
rewriteSelectionEffort: reasoningEffortOrDefault(record.rewriteSelectionEffort),
|
||||
sendShortcut: sendShortcutOrDefault(record.sendShortcut),
|
||||
};
|
||||
}
|
||||
|
|
@ -33,10 +39,12 @@ export function normalizeSettings(data: unknown): CodexPanelSettings {
|
|||
export function settingsMatchNormalizedData(data: unknown, settings: CodexPanelSettings): boolean {
|
||||
const record = asRecord(data);
|
||||
return (
|
||||
Object.keys(record).length === 4 &&
|
||||
Object.keys(record).length === 6 &&
|
||||
record.codexPath === settings.codexPath &&
|
||||
record.threadNamingModel === settings.threadNamingModel &&
|
||||
record.threadNamingEffort === settings.threadNamingEffort &&
|
||||
record.rewriteSelectionModel === settings.rewriteSelectionModel &&
|
||||
record.rewriteSelectionEffort === settings.rewriteSelectionEffort &&
|
||||
record.sendShortcut === settings.sendShortcut
|
||||
);
|
||||
}
|
||||
|
|
@ -50,6 +58,10 @@ function stringOrDefault(value: unknown, fallback: string): string {
|
|||
}
|
||||
|
||||
function threadNamingModelOrDefault(value: unknown): string | null {
|
||||
return modelOrDefault(value);
|
||||
}
|
||||
|
||||
function modelOrDefault(value: unknown): string | null {
|
||||
if (typeof value !== "string") return DEFAULT_SETTINGS.threadNamingModel;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : null;
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
private hooksLoaded = false;
|
||||
private hooksLoading = false;
|
||||
private hooksStatus = "";
|
||||
private namingModels: Model[] = [];
|
||||
private namingModelsLoading = false;
|
||||
private namingModelsStatus = "";
|
||||
private models: Model[] = [];
|
||||
private modelsLoading = false;
|
||||
private modelsStatus = "";
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
|
|
@ -102,7 +102,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
.setDesc("Model and reasoning effort used only for automatic thread titles.")
|
||||
.addDropdown((dropdown) => {
|
||||
const current = this.plugin.settings.threadNamingModel;
|
||||
const options = this.namingModelOptions();
|
||||
const options = this.modelOptions();
|
||||
dropdown.selectEl.ariaLabel = "Thread naming model";
|
||||
dropdown.addOption(CODEX_DEFAULT_VALUE, "Codex default");
|
||||
if (current && !options.some((model) => model.model === current || model.id === current)) {
|
||||
|
|
@ -122,7 +122,7 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
})
|
||||
.addDropdown((dropdown) => {
|
||||
const current = this.plugin.settings.threadNamingEffort;
|
||||
const options = this.namingEffortOptions();
|
||||
const options = this.effortOptions(this.plugin.settings.threadNamingModel);
|
||||
dropdown.selectEl.ariaLabel = "Thread naming effort";
|
||||
dropdown.addOption(CODEX_DEFAULT_VALUE, "Codex default");
|
||||
for (const effort of options) {
|
||||
|
|
@ -133,10 +133,47 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
if (this.namingModelsLoading || (this.namingModelsStatus && !this.namingModelsStatus.startsWith("Loaded "))) {
|
||||
|
||||
new Setting(configSection)
|
||||
.setName("Rewrite selection model")
|
||||
.setDesc("Model and reasoning effort used only for selection rewrites.")
|
||||
.addDropdown((dropdown) => {
|
||||
const current = this.plugin.settings.rewriteSelectionModel;
|
||||
const options = this.modelOptions();
|
||||
dropdown.selectEl.ariaLabel = "Rewrite selection model";
|
||||
dropdown.addOption(CODEX_DEFAULT_VALUE, "Codex default");
|
||||
if (current && !options.some((model) => model.model === current || model.id === current)) {
|
||||
dropdown.addOption(current, `${current} (saved)`);
|
||||
}
|
||||
for (const model of options) {
|
||||
dropdown.addOption(model.model, model.model);
|
||||
}
|
||||
dropdown.setValue(current ?? CODEX_DEFAULT_VALUE).onChange(async (value) => {
|
||||
this.plugin.settings.rewriteSelectionModel = value === CODEX_DEFAULT_VALUE ? null : value;
|
||||
if (!this.rewriteSelectionEffortSupported(this.plugin.settings.rewriteSelectionEffort)) {
|
||||
this.plugin.settings.rewriteSelectionEffort = null;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
})
|
||||
.addDropdown((dropdown) => {
|
||||
const current = this.plugin.settings.rewriteSelectionEffort;
|
||||
const options = this.effortOptions(this.plugin.settings.rewriteSelectionModel);
|
||||
dropdown.selectEl.ariaLabel = "Rewrite selection effort";
|
||||
dropdown.addOption(CODEX_DEFAULT_VALUE, "Codex default");
|
||||
for (const effort of options) {
|
||||
dropdown.addOption(effort, effort);
|
||||
}
|
||||
dropdown.setValue(current && options.includes(current) ? current : CODEX_DEFAULT_VALUE).onChange(async (value) => {
|
||||
this.plugin.settings.rewriteSelectionEffort = value === CODEX_DEFAULT_VALUE ? null : (value as ReasoningEffort);
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
if (this.modelsLoading || (this.modelsStatus && !this.modelsStatus.startsWith("Loaded "))) {
|
||||
configSection.createEl("p", {
|
||||
cls: "setting-item-description codex-panel-settings__section-status",
|
||||
text: this.namingModelsStatus || "Loading models...",
|
||||
text: this.modelsStatus || "Loading models...",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -170,10 +207,10 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
|
||||
private async refreshSettingsData(): Promise<void> {
|
||||
this.settingsDataLoading = true;
|
||||
this.namingModelsLoading = true;
|
||||
this.modelsLoading = true;
|
||||
this.archivedThreadsLoading = true;
|
||||
this.hooksLoading = true;
|
||||
this.namingModelsStatus = "Loading models...";
|
||||
this.modelsStatus = "Loading models...";
|
||||
this.archivedThreadsStatus = "Loading archived threads...";
|
||||
this.hooksStatus = "Loading hooks...";
|
||||
this.display();
|
||||
|
|
@ -183,11 +220,11 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
const result = await this.withSettingsSession((client) => loadSettingsData(client, this.plugin.vaultPath));
|
||||
|
||||
if (result.models.ok) {
|
||||
this.namingModels = result.models.data;
|
||||
this.namingModelsStatus = result.models.status;
|
||||
this.models = result.models.data;
|
||||
this.modelsStatus = result.models.status;
|
||||
} else {
|
||||
failedCount += 1;
|
||||
this.namingModelsStatus = result.models.status;
|
||||
this.modelsStatus = result.models.status;
|
||||
}
|
||||
|
||||
if (result.hooks.ok) {
|
||||
|
|
@ -212,12 +249,12 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
} catch (error) {
|
||||
failedCount = 3;
|
||||
const message = errorMessage(error);
|
||||
this.namingModelsStatus = `Could not load models: ${message}`;
|
||||
this.modelsStatus = `Could not load models: ${message}`;
|
||||
this.hooksStatus = `Could not load hooks: ${message}`;
|
||||
this.archivedThreadsStatus = `Could not load archived threads: ${message}`;
|
||||
} finally {
|
||||
this.settingsDataLoading = false;
|
||||
this.namingModelsLoading = false;
|
||||
this.modelsLoading = false;
|
||||
this.archivedThreadsLoading = false;
|
||||
this.hooksLoading = false;
|
||||
if (failedCount > 0) {
|
||||
|
|
@ -302,20 +339,24 @@ export class CodexPanelSettingTab extends PluginSettingTab {
|
|||
return withAppServerSession(this.plugin.settings.codexPath, this.plugin.vaultPath, operation);
|
||||
}
|
||||
|
||||
private namingModelOptions(): Model[] {
|
||||
return sortedAvailableModels(this.namingModels);
|
||||
private modelOptions(): Model[] {
|
||||
return sortedAvailableModels(this.models);
|
||||
}
|
||||
|
||||
private namingEffortOptions(): ReasoningEffort[] {
|
||||
const model = this.selectedNamingModel();
|
||||
private effortOptions(modelIdOrName: string | null): ReasoningEffort[] {
|
||||
const model = this.selectedModel(modelIdOrName);
|
||||
return model ? supportedEffortsForModel(model) : REASONING_EFFORTS;
|
||||
}
|
||||
|
||||
private namingEffortSupported(effort: ReasoningEffort | null): boolean {
|
||||
return !effort || this.namingEffortOptions().includes(effort);
|
||||
return !effort || this.effortOptions(this.plugin.settings.threadNamingModel).includes(effort);
|
||||
}
|
||||
|
||||
private selectedNamingModel(): Model | null {
|
||||
return findModelByIdOrName(this.namingModels, this.plugin.settings.threadNamingModel);
|
||||
private rewriteSelectionEffortSupported(effort: ReasoningEffort | null): boolean {
|
||||
return !effort || this.effortOptions(this.plugin.settings.rewriteSelectionModel).includes(effort);
|
||||
}
|
||||
|
||||
private selectedModel(modelIdOrName: string | null): Model | null {
|
||||
return findModelByIdOrName(this.models, modelIdOrName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,16 +30,8 @@ describe("editor rewrite output", () => {
|
|||
});
|
||||
|
||||
describe("editor rewrite prompt", () => {
|
||||
it("omits note context in selection-only mode", () => {
|
||||
const prompt = buildRewritePrompt(session({ contextMode: "selection" }));
|
||||
|
||||
expect(prompt).toContain("Selected text:");
|
||||
expect(prompt).toContain("Rewrite this sentence.");
|
||||
expect(prompt).not.toContain("Current note context:");
|
||||
});
|
||||
|
||||
it("includes note context in note mode", () => {
|
||||
const prompt = buildRewritePrompt(session({ contextMode: "note" }));
|
||||
it("always includes note context with the selected text", () => {
|
||||
const prompt = buildRewritePrompt(session());
|
||||
|
||||
expect(prompt).toContain("Context mode: Selection + note context");
|
||||
expect(prompt).toContain("Current note context:");
|
||||
|
|
@ -84,7 +76,6 @@ function session(overrides: Partial<RewriteSession> = {}): RewriteSession {
|
|||
},
|
||||
originalText: "Rewrite this sentence.",
|
||||
noteText: "# Heading\n\nRewrite this sentence.\n\nNext paragraph.",
|
||||
contextMode: "note",
|
||||
instruction: "Make it clearer.",
|
||||
status: "editing-prompt",
|
||||
streamText: "",
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ describe("settings tab", () => {
|
|||
"Codex executable",
|
||||
"Send shortcut",
|
||||
"Thread naming model",
|
||||
"Rewrite selection model",
|
||||
"Hook status",
|
||||
"Archived thread list",
|
||||
]);
|
||||
|
|
@ -285,6 +286,8 @@ function newSettingsTab(options: { saveSettings?: () => Promise<void>; sendShort
|
|||
codexPath: "codex",
|
||||
threadNamingModel: null,
|
||||
threadNamingEffort: null,
|
||||
rewriteSelectionModel: null,
|
||||
rewriteSelectionEffort: null,
|
||||
sendShortcut: options.sendShortcut ?? "enter",
|
||||
},
|
||||
vaultPath: "/vault",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ describe("settings", () => {
|
|||
codexPath: "/usr/local/bin/codex",
|
||||
threadNamingModel: "gpt-5.4-mini",
|
||||
threadNamingEffort: "low",
|
||||
rewriteSelectionModel: "gpt-5.4-mini",
|
||||
rewriteSelectionEffort: "minimal",
|
||||
sendShortcut: "mod-enter",
|
||||
model: "gpt-5.5",
|
||||
sandboxMode: "workspace-write",
|
||||
|
|
@ -30,6 +32,8 @@ describe("settings", () => {
|
|||
codexPath: "/usr/local/bin/codex",
|
||||
threadNamingModel: "gpt-5.4-mini",
|
||||
threadNamingEffort: "low",
|
||||
rewriteSelectionModel: "gpt-5.4-mini",
|
||||
rewriteSelectionEffort: "minimal",
|
||||
sendShortcut: "mod-enter",
|
||||
};
|
||||
|
||||
|
|
@ -46,6 +50,8 @@ describe("settings", () => {
|
|||
codexPath: "/usr/local/bin/codex",
|
||||
threadNamingModel: "gpt-5.4-mini",
|
||||
threadNamingEffort: "low",
|
||||
rewriteSelectionModel: "gpt-5.4-mini",
|
||||
rewriteSelectionEffort: "minimal",
|
||||
sendShortcut: "mod-enter",
|
||||
});
|
||||
expect(settingsMatchNormalizedData({ ...settings }, settings)).toBe(true);
|
||||
|
|
@ -64,6 +70,17 @@ describe("settings", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("normalizes current rewrite selection runtime settings", () => {
|
||||
expect(normalizeSettings({ rewriteSelectionModel: " gpt-5.4-mini ", rewriteSelectionEffort: "minimal" })).toMatchObject({
|
||||
rewriteSelectionModel: "gpt-5.4-mini",
|
||||
rewriteSelectionEffort: "minimal",
|
||||
});
|
||||
expect(normalizeSettings({ rewriteSelectionModel: 1, rewriteSelectionEffort: "invalid" })).toMatchObject({
|
||||
rewriteSelectionModel: DEFAULT_SETTINGS.rewriteSelectionModel,
|
||||
rewriteSelectionEffort: DEFAULT_SETTINGS.rewriteSelectionEffort,
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes the send shortcut", () => {
|
||||
expect(normalizeSettings({ sendShortcut: "mod-enter" }).sendShortcut).toBe("mod-enter");
|
||||
expect(normalizeSettings({ sendShortcut: "invalid" }).sendShortcut).toBe(DEFAULT_SETTINGS.sendShortcut);
|
||||
|
|
|
|||
Loading…
Reference in a new issue