100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
import { getPreferenceValues } from "@vicinae/api";
|
|
import { DEFAULT_HOST, DEFAULT_MODEL } from "./constants";
|
|
|
|
/**
|
|
* Preferences, read and normalised.
|
|
*
|
|
* Declared locally rather than relying on the `vicinae-env.d.ts` that the
|
|
* `vici` toolchain generates, so this file typechecks and unit-tests without a
|
|
* codegen step. The manifest in `package.json` remains the source of truth; if
|
|
* a preference is added there, mirror it here.
|
|
*/
|
|
|
|
export interface LamPreferences {
|
|
apiKey?: string;
|
|
model?: string;
|
|
ollamaHost?: string;
|
|
systemPrompt?: string;
|
|
webSearchEnabled?: boolean;
|
|
thinking?: string;
|
|
showToolCalls?: boolean;
|
|
showReasoning?: boolean;
|
|
resumeTimeout?: string;
|
|
}
|
|
|
|
/** Preferences with every optional value filled in, so callers never branch on undefined. */
|
|
export interface ResolvedPreferences {
|
|
apiKey: string;
|
|
model: string;
|
|
host: string;
|
|
systemPrompt: string;
|
|
webEnabled: boolean;
|
|
/** Undefined when reasoning is off, which is what the API client expects. */
|
|
think?: string;
|
|
/** Whether the transcript lists the tools Lam called. */
|
|
showToolCalls: boolean;
|
|
/** Whether the transcript includes the model's thinking trace. */
|
|
showReasoning: boolean;
|
|
/** Idle time after which opening the command starts a fresh chat. Undefined means never. */
|
|
resumeTimeoutMs?: number;
|
|
}
|
|
|
|
const MINUTE = 60_000;
|
|
const HOUR = 60 * MINUTE;
|
|
const DAY = 24 * HOUR;
|
|
|
|
/** Keep in sync with the `resumeTimeout` dropdown in `package.json`. */
|
|
const RESUME_TIMEOUTS: Record<string, number> = {
|
|
"15m": 15 * MINUTE,
|
|
"1h": HOUR,
|
|
"6h": 6 * HOUR,
|
|
"1d": DAY,
|
|
};
|
|
|
|
/** `undefined` for "never" and for anything unrecognised, which both mean "always reopen the last chat". */
|
|
export function resolveResumeTimeout(value: string | undefined): number | undefined {
|
|
return RESUME_TIMEOUTS[(value ?? "").trim()];
|
|
}
|
|
|
|
/** Human-readable form of {@link ResolvedPreferences.resumeTimeoutMs}, for the setup screen. */
|
|
export function describeResumeTimeout(milliseconds: number | undefined): string {
|
|
if (milliseconds === undefined) return "Never — always reopen the last chat";
|
|
|
|
const minutes = Math.round(milliseconds / MINUTE);
|
|
if (minutes < 60) return `After ${minutes} minutes`;
|
|
|
|
const hours = Math.round(minutes / 60);
|
|
if (hours < 24) return `After ${hours} hour${hours === 1 ? "" : "s"}`;
|
|
|
|
const days = Math.round(hours / 24);
|
|
return `After ${days} day${days === 1 ? "" : "s"}`;
|
|
}
|
|
|
|
export function resolvePreferences(raw: LamPreferences): ResolvedPreferences {
|
|
const thinking = (raw.thinking ?? "").trim();
|
|
|
|
return {
|
|
apiKey: (raw.apiKey ?? "").trim(),
|
|
model: (raw.model ?? "").trim() || DEFAULT_MODEL,
|
|
host: (raw.ollamaHost ?? "").trim() || DEFAULT_HOST,
|
|
systemPrompt: (raw.systemPrompt ?? "").trim(),
|
|
// Defaults to on: the preference is a checkbox the user has to actively untick.
|
|
webEnabled: raw.webSearchEnabled ?? true,
|
|
think: thinking && thinking !== "off" ? thinking : undefined,
|
|
showToolCalls: raw.showToolCalls ?? true,
|
|
showReasoning: raw.showReasoning ?? true,
|
|
// Falls back to the manifest's declared default, so a missing preference
|
|
// behaves the same whether Vicinae substitutes it or not.
|
|
resumeTimeoutMs: resolveResumeTimeout(raw.resumeTimeout ?? "1h"),
|
|
};
|
|
}
|
|
|
|
export function getLamPreferences(): ResolvedPreferences {
|
|
return resolvePreferences(getPreferenceValues<LamPreferences>());
|
|
}
|
|
|
|
/** True when the extension cannot work until the user supplies a key. */
|
|
export function needsSetup(preferences: ResolvedPreferences): boolean {
|
|
return preferences.apiKey.length === 0;
|
|
}
|