Skip to content

feat(desktop): support adaptive tool timeouts#409

Open
karry-0803 wants to merge 5 commits into
TouchAI-org:mainfrom
karry-0803:feat/llm-adaptive-tool-timeout-clean
Open

feat(desktop): support adaptive tool timeouts#409
karry-0803 wants to merge 5 commits into
TouchAI-org:mainfrom
karry-0803:feat/llm-adaptive-tool-timeout-clean

Conversation

@karry-0803

Copy link
Copy Markdown
Contributor

Summary

This change allows the LLM to suggest tool call timeout values dynamically instead of relying only on static defaults.

User-facing impact:

  • tool execution becomes more adaptive for long-running or short-running tasks
  • tools can optionally receive _meta.timeoutMs as execution metadata
  • requested timeout values are clamped to safe bounds to avoid runaway execution
  • existing default timeout behavior is preserved when the model does not specify a timeout

Implementation summary:

  • expose optional _meta.timeoutMs in tool schemas before tools are provided to the model
  • parse timeout metadata separately from business arguments in the executor
  • apply bounded timeout selection in MCP execution and built-in Bash execution
  • preserve existing tool defaults when no timeout is provided
  • strip invalid _meta values before forwarding arguments downstream
  • align runtime validation with the schema’s integer timeout contract

Related issue or RFC

AI assistance disclosure

  • Tool(s) used:
    • Trae / GPT-based coding assistant
  • Scope of assistance:
    • helped analyze the issue
    • implemented the timeout propagation changes
    • helped prepare PR summary and screenshot guidance
  • Human review or rewrite performed:
    • I manually reviewed the affected files, verified the behavior in the desktop UI, and checked the final diff before opening this PR
  • Architecture or boundary impact:
    • yes, this touches AgentService, MCP timeout handling, tool schema exposure, and built-in tool execution boundaries

Testing evidence

  • pnpm type:check
    • passed
  • pnpm test:pr
    • not run locally
    • blocker: not completed before opening this PR, so CI should be used as the full validation source before merge
  • pnpm test:coverage:rust
    • not run locally
    • blocker: cargo-tarpaulin is not installed locally, so CI coverage evidence should be used before merge
  • pnpm test:e2e
    • not run locally
    • blocker: not completed before opening this PR; CI should be treated as the first complete proof if required by maintainers

Did you follow TDD (test-first) for feature and fix work?

  • No. This change was implemented first and then validated with targeted type checking plus manual UI verification.
pnpm type:check
pnpm test:pr
pnpm test:coverage:rust
pnpm test:e2e

Risk notes

  • AgentService, runtime, MCP, or schema impact:
    • touches tool schema exposure, executor argument parsing, MCP timeout propagation, and built-in Bash timeout handling
    • bounded to optional _meta.timeoutMs
    • defaults are preserved when timeout is omitted
    • safe timeout clamp is enforced with a minimum of 1000ms and maximum of 600000ms
  • database baseline or migration impact:
    • none
  • release or packaging impact:
    • none expected

Screenshots or recordings

  1. Desktop UI: Bash tool call executed successfully in conversation view
test1
  1. Desktop UI: Bash tool log details in Settings -> Built-in Tools -> Bash
test2
  1. Code evidence: _meta.timeoutMs exposed in tool schema
test3
  1. Code evidence: requested timeout is clamped to safe bounds before execution
test4

Checklist

  • The PR title follows Conventional Commits and is valid for squash merge.
  • This PR is either ready for review or explicitly marked as a Draft PR.
  • I did not use [WIP] or similar title prefixes.
  • If AI materially assisted this PR, I disclosed the tools and scope and I personally reviewed every affected change.
  • I can explain the why, what, and how of this change without relying on an AI tool.
  • If this touches AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.
  • If this changes architecture or adds a new cross-boundary abstraction, there is an accepted RFC.
  • I ran pnpm test:pr for this code PR, or this is a docs-only change.
  • If I changed Rust behavior or tests, I reviewed pnpm test:coverage:rust or relied on CI coverage evidence.
  • If I changed desktop startup/window/search/popup/settings/E2E paths, I ran pnpm test:e2e locally or documented why CI is the first valid proof.
  • I added tests or explained why tests are not appropriate.
  • I updated docs when behavior changed.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added per-tool timeout customization. Tool executions can now request specific timeout values, with automatic validation and enforcement of 1-second to 10-minute bounds.

Walkthrough

This PR adds per-call timeout override capability for tool execution. Agents can now request specific timeouts via _meta.timeoutMs in tool arguments, which are validated, bounded between 1s–10 minutes, and applied to both built-in and MCP tools.

Changes

Agent-directed tool call timeout control

Layer / File(s) Summary
Timeout validation utility
apps/desktop/src/utils/timeouts.ts
New clampTimeoutMs function validates optional requested timeout, falls back to configured default if invalid, and floors/clamps the result within 1s–10 minutes.
Tool schema augmentation
apps/desktop/src/services/AgentService/catalog/tools.ts
Tool definitions augmented with _meta object in input schema containing timeoutMs property with min/max constraints; all tools now mapped through the schema wrapper.
Executor timeout extraction and routing
apps/desktop/src/services/AgentService/execution/executor.ts
Executor parses _meta.timeoutMs from tool arguments, validates it as a finite integer, removes _meta from sanitized arguments, and passes both the validated timeout and sanitized args to tool execution paths.
Built-in tool timeout application
apps/desktop/src/services/BuiltInToolService/types.ts, apps/desktop/src/services/BuiltInToolService/service.ts, apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts
Execution context and options receive requestedTimeoutMs; bash tool computes effective clamped timeout and applies it to native subprocess execution.
MCP tool timeout application
apps/desktop/src/services/AgentService/infrastructure/mcp/McpManager.ts
MCP executor accepts requestedTimeoutMs in options, computes effective clamped timeout, and uses it in the tool-call vs timeout race instead of always using server-configured default.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A timeout's tale unfolds with care,
The model picks what's fair and square,
From one to ten minutes it may roam,
Yet never strays too far from home,
And /meta whispers: "Let us be!"—clipped clean before the tool runs free. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat(desktop): support adaptive tool timeouts' follows Conventional Commits standards, uses English, and clearly matches the main change in the changeset.
Description check ✅ Passed The PR description is comprehensive and covers all required sections including Summary, Related issue, AI assistance disclosure, Testing evidence with blockers clearly stated, Risk notes, Screenshots, and a detailed Checklist.
Linked Issues check ✅ Passed The code changes fully implement the requirements from issue #47: exposing timeout as controllable _meta.timeoutMs, allowing model-requested timeouts with safe bounds (1s-10min clamp), and preserving defaults when omitted.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing adaptive tool timeouts as required by issue #47. No extraneous modifications to unrelated functionality were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added area:mcp MCP integration changes area:agent-service AgentService and conversation runtime changes labels Jun 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/services/AgentService/execution/executor.ts`:
- Around line 350-383: The sanitization logic in parseRequestedTimeoutMs uses a
conditional shallow copy which is more complex and can return the original
object; always create a shallow copy and remove the meta key to avoid reference
mutations. Replace the conditional copy with a single copy of toolArgs into
sanitizedToolArgs and always delete sanitizedToolArgs[TOOL_TIMEOUT_META_KEY];
keep the rest of the validation logic for timeoutMs unchanged so
parseRequestedTimeoutMs and usage of TOOL_TIMEOUT_META_KEY remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e29b5843-a224-4513-b59e-fc2775f440b3

📥 Commits

Reviewing files that changed from the base of the PR and between b6c7907 and f6f5185.

📒 Files selected for processing (7)
  • apps/desktop/src/services/AgentService/catalog/tools.ts
  • apps/desktop/src/services/AgentService/execution/executor.ts
  • apps/desktop/src/services/AgentService/infrastructure/mcp/McpManager.ts
  • apps/desktop/src/services/BuiltInToolService/service.ts
  • apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts
  • apps/desktop/src/services/BuiltInToolService/types.ts
  • apps/desktop/src/utils/timeouts.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL (rust)
  • GitHub Check: Desktop E2E Smoke (Windows)
🔇 Additional comments (8)
apps/desktop/src/utils/timeouts.ts (1)

1-13: LGTM!

apps/desktop/src/services/AgentService/catalog/tools.ts (2)

14-26: LGTM!


28-39: LGTM!

Also applies to: 57-63

apps/desktop/src/services/AgentService/execution/executor.ts (1)

777-779: LGTM!

Also applies to: 783-783, 793-793, 802-802, 808-808

apps/desktop/src/services/BuiltInToolService/types.ts (1)

45-45: LGTM!

apps/desktop/src/services/BuiltInToolService/service.ts (1)

52-52: LGTM!

Also applies to: 261-261

apps/desktop/src/services/BuiltInToolService/tools/bash/index.ts (1)

157-164: LGTM!

Also applies to: 171-171

apps/desktop/src/services/AgentService/infrastructure/mcp/McpManager.ts (1)

442-442: LGTM!

Also applies to: 460-466, 472-472

Comment on lines +350 to +383
function parseRequestedTimeoutMs(toolArgs: Record<string, unknown>): {
requestedTimeoutMs?: number;
sanitizedToolArgs: Record<string, unknown>;
} {
const meta = toolArgs[TOOL_TIMEOUT_META_KEY];
const sanitizedToolArgs = TOOL_TIMEOUT_META_KEY in toolArgs ? { ...toolArgs } : toolArgs;
if (TOOL_TIMEOUT_META_KEY in sanitizedToolArgs) {
delete sanitizedToolArgs[TOOL_TIMEOUT_META_KEY];
}

if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {
return {
requestedTimeoutMs: undefined,
sanitizedToolArgs,
};
}

const timeoutMs = (meta as Record<string, unknown>).timeoutMs;
if (
typeof timeoutMs !== 'number' ||
!Number.isFinite(timeoutMs) ||
!Number.isInteger(timeoutMs)
) {
return {
requestedTimeoutMs: undefined,
sanitizedToolArgs,
};
}

return {
requestedTimeoutMs: timeoutMs,
sanitizedToolArgs,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider simplifying the sanitization copy logic.

The conditional shallow copy at line 355 works correctly but is more complex than necessary. The code only creates a copy when _meta exists, then deletes it. A simpler and safer approach would be to always copy and always delete:

const sanitizedToolArgs = { ...toolArgs };
delete sanitizedToolArgs[TOOL_TIMEOUT_META_KEY];

This is clearer (one code path instead of two) and safer (always returns a new object, preventing potential reference-based mutations).

♻️ Proposed refactor
 function parseRequestedTimeoutMs(toolArgs: Record<string, unknown>): {
     requestedTimeoutMs?: number;
     sanitizedToolArgs: Record<string, unknown>;
 } {
     const meta = toolArgs[TOOL_TIMEOUT_META_KEY];
-    const sanitizedToolArgs = TOOL_TIMEOUT_META_KEY in toolArgs ? { ...toolArgs } : toolArgs;
-    if (TOOL_TIMEOUT_META_KEY in sanitizedToolArgs) {
-        delete sanitizedToolArgs[TOOL_TIMEOUT_META_KEY];
-    }
+    const sanitizedToolArgs = { ...toolArgs };
+    delete sanitizedToolArgs[TOOL_TIMEOUT_META_KEY];

     if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {
         return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/services/AgentService/execution/executor.ts` around lines
350 - 383, The sanitization logic in parseRequestedTimeoutMs uses a conditional
shallow copy which is more complex and can return the original object; always
create a shallow copy and remove the meta key to avoid reference mutations.
Replace the conditional copy with a single copy of toolArgs into
sanitizedToolArgs and always delete sanitizedToolArgs[TOOL_TIMEOUT_META_KEY];
keep the rest of the validation logic for timeoutMs unchanged so
parseRequestedTimeoutMs and usage of TOOL_TIMEOUT_META_KEY remain consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent-service AgentService and conversation runtime changes area:mcp MCP integration changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: let the model choose tool call timeouts

1 participant