feat(app): add tool call detail levels (#2031)

* feat(app): compact tool call sequences

* feat(app): summarize compact tool activity

* feat(app): add tool call detail levels

* fix(app): stabilize compact tool groups

* fix(app): address compact tool review feedback
This commit is contained in:
Matt Cowger
2026-07-14 18:55:06 +08:00
committed by GitHub
parent 3b078240a8
commit 81113d1582
16 changed files with 1488 additions and 13 deletions
+40 -4
View File
@@ -12,10 +12,16 @@ export type SendBehavior = "interrupt" | "queue";
export type ReleaseChannel = "stable" | "beta";
export type ServiceUrlBehavior = "ask" | "in-app" | "external";
export type WorkspaceTitleSource = "title" | "branch";
export type ToolCallDetailLevel = "overview" | "concise" | "detailed";
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
const VALID_SERVICE_URL_BEHAVIORS = new Set<ServiceUrlBehavior>(["ask", "in-app", "external"]);
const VALID_WORKSPACE_TITLE_SOURCES = new Set<WorkspaceTitleSource>(["title", "branch"]);
const VALID_TOOL_CALL_DETAIL_LEVELS = new Set<ToolCallDetailLevel>([
"overview",
"concise",
"detailed",
]);
export const DEFAULT_TERMINAL_SCROLLBACK_LINES = 10_000;
export const MIN_TERMINAL_SCROLLBACK_LINES = 0;
export const MAX_TERMINAL_SCROLLBACK_LINES = 1_000_000;
@@ -40,6 +46,7 @@ export interface AppSettings {
syntaxTheme: SyntaxThemeId; // default "one"
workspaceTitleSource: WorkspaceTitleSource;
autoExpandReasoning: boolean;
toolCallDetailLevel: ToolCallDetailLevel;
}
export interface Settings extends AppSettings {
@@ -47,6 +54,8 @@ export interface Settings extends AppSettings {
releaseChannel: ReleaseChannel;
}
type StoredAppSettings = Partial<AppSettings> & { compactToolCalls?: unknown };
export const DEFAULT_CLIENT_SETTINGS: AppSettings = {
theme: "auto",
language: "system",
@@ -60,6 +69,7 @@ export const DEFAULT_CLIENT_SETTINGS: AppSettings = {
syntaxTheme: "one",
workspaceTitleSource: "title",
autoExpandReasoning: false,
toolCallDetailLevel: "detailed",
};
export const DEFAULT_APP_SETTINGS: Settings = {
@@ -92,9 +102,10 @@ export async function saveAppSettings(input: {
updates: Partial<AppSettings>;
deps: SettingsDeps;
}): Promise<void> {
const current =
const storedCurrent =
input.queryClient.getQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY) ??
(await loadAppSettingsFromStorage(input.deps));
const current = normalizeAppSettings(storedCurrent);
const next = { ...current, ...input.updates };
input.queryClient.setQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY, next);
await input.deps.storage.setItem(APP_SETTINGS_KEY, JSON.stringify(next));
@@ -104,8 +115,7 @@ export async function loadAppSettingsFromStorage(deps: SettingsDeps): Promise<Ap
try {
const stored = await deps.storage.getItem(APP_SETTINGS_KEY);
if (stored) {
const parsed = JSON.parse(stored) as Partial<AppSettings>;
return { ...DEFAULT_CLIENT_SETTINGS, ...pickAppSettings(parsed) };
return normalizeAppSettings(JSON.parse(stored));
}
const legacyStored = await deps.storage.getItem(LEGACY_SETTINGS_KEY);
@@ -153,7 +163,29 @@ export async function loadSettingsFromStorage(deps: SettingsDeps): Promise<Setti
};
}
function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
export function normalizeAppSettings(value: unknown): AppSettings {
const stored =
typeof value === "object" && value !== null && !Array.isArray(value)
? (value as StoredAppSettings)
: {};
return { ...DEFAULT_CLIENT_SETTINGS, ...pickAppSettings(stored) };
}
function parseToolCallDetailLevel(stored: StoredAppSettings): ToolCallDetailLevel | null {
if (
typeof stored.toolCallDetailLevel === "string" &&
VALID_TOOL_CALL_DETAIL_LEVELS.has(stored.toolCallDetailLevel)
) {
return stored.toolCallDetailLevel;
}
if (typeof stored.compactToolCalls === "boolean") {
// COMPAT(compactToolCalls): migrated in v0.1.105, remove after 2027-01-12.
return stored.compactToolCalls ? "overview" : "detailed";
}
return null;
}
function pickAppSettings(stored: StoredAppSettings): Partial<AppSettings> {
const result: Partial<AppSettings> = {};
if (typeof stored.theme === "string" && VALID_THEMES.has(stored.theme)) {
result.theme = stored.theme;
@@ -209,6 +241,10 @@ function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
if (typeof stored.autoExpandReasoning === "boolean") {
result.autoExpandReasoning = stored.autoExpandReasoning;
}
const toolCallDetailLevel = parseToolCallDetailLevel(stored);
if (toolCallDetailLevel !== null) {
result.toolCallDetailLevel = toolCallDetailLevel;
}
return result;
}