mirror of
https://github.com/PawanOsman/ChatGPT.git
synced 2026-07-18 08:05:57 +02:00
Implement per-tool timeout configuration in settings
- Added support for per-tool timeout overrides in seconds, allowing users to customize tool execution limits. - Introduced `setToolTimeoutOverrides` function to apply user-defined timeout settings. - Updated the `FeatureConfig` interface and default settings to include `toolTimeoutsSec`. - Enhanced the settings UI to display and manage tool timeout values, ensuring a better user experience for configuring tool behavior.
This commit is contained in:
@@ -28,6 +28,8 @@ export {
|
||||
disposeShellSession,
|
||||
toolTimeoutMs,
|
||||
withToolTimeout,
|
||||
setToolTimeoutOverrides,
|
||||
DEFAULT_TOOL_TIMEOUTS_SEC,
|
||||
type TodoItem,
|
||||
} from "./shared";
|
||||
|
||||
|
||||
@@ -52,6 +52,26 @@ export const DEFAULT_TOOL_TIMEOUT_MS = 60_000;
|
||||
/** Tools that manage their own lifetime (user wait / nested agent). */
|
||||
export const NO_TOOL_TIMEOUT = new Set(["Task", "AskQuestion"]);
|
||||
|
||||
/** Built-in defaults in seconds (for settings UI). */
|
||||
export const DEFAULT_TOOL_TIMEOUTS_SEC: Record<string, number> = Object.fromEntries(
|
||||
Object.entries(TOOL_TIMEOUT_MS).map(([k, v]) => [k, Math.round(v / 1000)]),
|
||||
);
|
||||
|
||||
/** User overrides from settings (tool name → seconds). Empty/missing = built-in default. */
|
||||
let toolTimeoutOverridesSec: Record<string, number> = {};
|
||||
|
||||
/** Apply settings overrides (seconds). Call whenever feature config loads/changes. */
|
||||
export function setToolTimeoutOverrides(sec: Record<string, number> | undefined): void {
|
||||
const next: Record<string, number> = {};
|
||||
if (sec) {
|
||||
for (const [k, v] of Object.entries(sec)) {
|
||||
const n = Number(v);
|
||||
if (Number.isFinite(n) && n > 0) next[k] = Math.floor(n);
|
||||
}
|
||||
}
|
||||
toolTimeoutOverridesSec = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Race a tool promise against a hard timeout. On timeout rejects with an Error
|
||||
* whose message starts with "timeout:" so the loop can surface it cleanly.
|
||||
@@ -84,9 +104,11 @@ export function withToolTimeout<T>(p: Promise<T>, ms: number, label: string): Pr
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolve the hard timeout for a tool name (0 = none). */
|
||||
/** Resolve the hard timeout for a tool name (0 = none). Honors settings overrides. */
|
||||
export function toolTimeoutMs(name: string): number {
|
||||
if (NO_TOOL_TIMEOUT.has(name)) return 0;
|
||||
const overrideSec = toolTimeoutOverridesSec[name];
|
||||
if (overrideSec != null && overrideSec > 0) return overrideSec * 1000;
|
||||
return TOOL_TIMEOUT_MS[name] ?? DEFAULT_TOOL_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SidebarProvider } from './ui/sidebarProvider';
|
||||
import { registerInlineReview } from './ui/inlineReview';
|
||||
import { SettingsPanel } from './ui/settingsPanel';
|
||||
import { FeatureStore } from './stores/featureStore';
|
||||
import { setToolTimeoutOverrides } from './agent/tools/shared';
|
||||
import { mcpManager } from './integrations/mcpClient';
|
||||
import { setIndexStorageDir } from './agent/semanticIndex';
|
||||
import { setDocsStorageDir, setDocSourcesProvider } from './agent/docsIndex';
|
||||
@@ -32,6 +33,9 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
const settingsManager = new SettingsManager(context);
|
||||
const featureStore = new FeatureStore(context);
|
||||
const syncToolTimeouts = () => setToolTimeoutOverrides(featureStore.get().toolTimeoutsSec);
|
||||
syncToolTimeouts();
|
||||
context.subscriptions.push(featureStore.onDidChange(syncToolTimeouts));
|
||||
initOAuth(context);
|
||||
initUsage(context);
|
||||
// Prefetch the provider-grouped model list so every UI (settings, pickers)
|
||||
|
||||
@@ -232,6 +232,8 @@ export interface FeatureConfig {
|
||||
maxTabCount: number;
|
||||
/** Max agent steps per run before pausing (0 = default 50). */
|
||||
maxAgentSteps: number;
|
||||
/** Per-tool hard timeout overrides in seconds (empty = built-in defaults). */
|
||||
toolTimeoutsSec: Record<string, number>;
|
||||
/** Automatically continue when the step limit is reached. */
|
||||
autoContinue: boolean;
|
||||
/** Play a sound when the agent finishes responding. */
|
||||
@@ -281,6 +283,7 @@ const DEFAULTS: FeatureConfig = {
|
||||
submitWithCtrlEnter: false,
|
||||
maxTabCount: 0,
|
||||
maxAgentSteps: 50,
|
||||
toolTimeoutsSec: {},
|
||||
autoContinue: false,
|
||||
completionSound: false,
|
||||
webSearchEnabled: true,
|
||||
|
||||
@@ -67,11 +67,38 @@ const NAV: { id: Section; label: string; icon: IconName; sep?: boolean }[] = [
|
||||
{ id: "about", label: "About", icon: "book" },
|
||||
];
|
||||
|
||||
/** Built-in tool hard timeouts (seconds). Keep in sync with src/agent/tools/shared.ts. */
|
||||
const TOOL_TIMEOUT_DEFAULTS: { name: string; sec: number }[] = [
|
||||
{ name: "Shell", sec: 120 },
|
||||
{ name: "AwaitShell", sec: 150 },
|
||||
{ name: "Grep", sec: 30 },
|
||||
{ name: "Glob", sec: 30 },
|
||||
{ name: "FileSearch", sec: 20 },
|
||||
{ name: "SemanticSearch", sec: 45 },
|
||||
{ name: "SearchDocs", sec: 30 },
|
||||
{ name: "ListDir", sec: 15 },
|
||||
{ name: "Read", sec: 30 },
|
||||
{ name: "ReadLints", sec: 20 },
|
||||
{ name: "WebSearch", sec: 25 },
|
||||
{ name: "WebFetch", sec: 30 },
|
||||
{ name: "StrReplace", sec: 30 },
|
||||
{ name: "Write", sec: 30 },
|
||||
{ name: "Delete", sec: 15 },
|
||||
{ name: "EditNotebook", sec: 30 },
|
||||
{ name: "CallMcpTool", sec: 60 },
|
||||
{ name: "FetchMcpResource", sec: 45 },
|
||||
{ name: "ListMcpResources", sec: 20 },
|
||||
{ name: "TodoWrite", sec: 5 },
|
||||
{ name: "TodoRead", sec: 5 },
|
||||
{ name: "WritePlan", sec: 15 },
|
||||
{ name: "SwitchMode", sec: 5 },
|
||||
];
|
||||
|
||||
/** Search terms per section so the nav filter finds settings inside pages too. */
|
||||
const SECTION_KEYWORDS: Partial<Record<Section, string>> = {
|
||||
general: "editor settings keyboard shortcuts notifications privacy chat titles auto judge model completion sound reset",
|
||||
usage: "tokens quota limits plan usage oauth account rate limit",
|
||||
agents: "text size submit ctrl enter max tab count web search fetch context conversation",
|
||||
agents: "text size submit ctrl enter max tab count web search fetch context conversation tool timeout shell grep",
|
||||
models: "enable disable model catalog reasoning effort thinking context",
|
||||
providers: "api key openai anthropic google openrouter oauth custom base url connect",
|
||||
behavior: "workspace context file reading terminal tools auto edits approval allow deny ask review policy allowlist denylist commands mcp web",
|
||||
@@ -824,6 +851,31 @@ export function App() {
|
||||
</Row>
|
||||
</Group>
|
||||
|
||||
<div className="section-label">Tool Timeouts</div>
|
||||
<p className="panel-hint">Hard timeout per tool (seconds). Empty = built-in default. Prevents a hung tool from blocking the agent forever.</p>
|
||||
<Group>
|
||||
{TOOL_TIMEOUT_DEFAULTS.map(({ name, sec }) => {
|
||||
const overrides = features.toolTimeoutsSec || {};
|
||||
const val = overrides[name];
|
||||
return (
|
||||
<Row key={name} title={name} desc={`Default ${sec}s`}>
|
||||
<NumInput
|
||||
value={val != null && val > 0 ? val : null}
|
||||
step="1"
|
||||
min="1"
|
||||
placeholder={String(sec)}
|
||||
onChange={(v) => {
|
||||
const next = { ...(features.toolTimeoutsSec || {}) };
|
||||
if (v == null || v <= 0) delete next[name];
|
||||
else next[name] = Math.floor(v);
|
||||
setFeatures({ toolTimeoutsSec: next });
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
|
||||
<div className="section-label">Subagents</div>
|
||||
<Group>
|
||||
<Row title="Subagent Model" desc="Default model for subagents launched via the Task tool.">
|
||||
|
||||
@@ -260,6 +260,8 @@ export interface FeatureConfig {
|
||||
submitWithCtrlEnter: boolean;
|
||||
maxTabCount: number;
|
||||
maxAgentSteps: number;
|
||||
/** Per-tool hard timeout overrides in seconds (empty = built-in defaults). */
|
||||
toolTimeoutsSec: Record<string, number>;
|
||||
autoContinue: boolean;
|
||||
completionSound: boolean;
|
||||
webSearchEnabled: boolean;
|
||||
@@ -346,6 +348,7 @@ export const EMPTY_FEATURES: FeatureConfig = {
|
||||
submitWithCtrlEnter: false,
|
||||
maxTabCount: 0,
|
||||
maxAgentSteps: 50,
|
||||
toolTimeoutsSec: {},
|
||||
autoContinue: false,
|
||||
completionSound: false,
|
||||
webSearchEnabled: true,
|
||||
|
||||
Reference in New Issue
Block a user