Skip to content

Add per-tool timeout and response size to REST tools - #1378

Merged
helpcodeai merged 4 commits into
mainfrom
fix/per-tool-timeout-size
Nov 1, 2026
Merged

helpcodeai merged 4 commits into
mainfrom
fix/per-tool-timeout-size

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

Summary

On 31 Oct, in one Cloud workspace, gemini_generate_image (model gemini-3-pro-image, 2K/4K) failed 22 of 138 calls. The logs show 18 × "API response is larger than 10 MB" and 11 × ECONNABORTED at exactly 30 s. Successful calls took 14 to 48 s. Every failure is a picture that Google had already generated and billed.

There are two causes in the REST engine:

  • timeout: 30000 is hard-coded for every tool, and a 2K/4K generation regularly takes longer.
  • maxContentLength: toolMaxResponseBytes() (10 MB) applies to the raw upstream body. That body still holds the base64 image, which responseMapping.images only takes out later. The second check in DynamicMcpTools.renderResult already measures the text after extraction (the image is replaced by a short placeholder), so it lets an extracted image through and is left unchanged.

This PR lets a REST tool set its own timeout and body limit, both clamped, and sets them on the slow generation tools in the catalog.

Changes

  • New connectors/engines/request-limits.util.ts:
    • endpointMapping.timeoutMs is clamped to 1 000 to 180 000 ms. Without it, the timeout stays 30 000.
    • endpointMapping.maxResponseBytes is capped at 50 MB, or at TOOL_MAX_RESPONSE_BYTES if an admin set that higher. A tool may set a lower value. Without it, the limit stays TOOL_MAX_RESPONSE_BYTES.
    • Invalid values (0, negative, non-numeric) fall back to the defaults, so neither an adapter nor a tool a user writes can switch the limits off.
  • rest.engine.ts passes both values to the axios config.
  • Retries: a POST, PATCH or DELETE was already never retried after a timeout (MAYBE_SENT_CODES). In addition, a call that times out with a raised timeoutMs is no longer retried for any method. Otherwise a GET with 180 s could wait up to 4 × 180 s. Calls that never left (ECONNREFUSED, EAI_AGAIN, EPROTO) are still retried.
  • engine-types.ts: both keys added to RestEndpointMapping. The adapter validator and catalog spec do not reject unknown mapping keys, so they needed no change.
  • Catalog:
    • gemini_generate_image, gemini_edit_image, openai_generate_image, openai_edit_image: 120 s and 40 MB, because their images come back inline as base64.
    • gemini_generate_text, gemini_generate_content, gemini_analyze_file_url, openai_create_response: 120 s only.
    • kie.ai is unchanged: its create-task tools are async and return a taskId straight away.
  • docs/tool-definition.md: new section "Slow or large responses (timeoutMs, maxResponseBytes)".

Behaviour is the same on Cloud and self-hosted: no isCloud() branch, and the env var keeps its meaning. Tools without these keys behave exactly as before.

Type

  • Bug fix
  • New feature
  • Documentation
  • Refactoring
  • Performance improvement
  • Other (describe below)

Testing

  • Added/updated unit tests
  • Tested manually (describe steps below)
  • No tests needed (explain why)

New specs:

  • request-limits.util.spec.ts: defaults, clamping, string values, and a global limit above 50 MB.
  • rest.engine.spec.ts › "per-tool timeout and response size": the values reach the axios config, clamping works, absent keys keep 30 s and the global cap, a raised-timeout call is not retried after a timeout, and a call that never left is still retried.
  • Static assertions in gemini.live.spec.ts and openai.live.spec.ts: the catalog values reach axios, and other tools keep 30 s / 10 MB.

Commands, results as run. Backend commands ran in packages/backend; the last three ran from the repository root:

  • npx jest src/connectors/engines/rest.engine.spec.ts src/connectors/engines/request-limits.util.spec.ts src/adapters/intl/gemini.live.spec.ts src/adapters/intl/openai.live.spec.ts: 4 suites passed, 208 passed, 6 skipped (live).
  • npx jest src/adapters: 671 suites passed. cloudflare.live.spec.ts and supabase.live.spec.ts failed 5 tests while a second Jest run was going in parallel. Re-run alone, both pass (27 passed, 14 skipped). Neither adapter is touched.
  • npx jest src/common/response-size-limit.spec.ts src/connectors/engines src/mcp-server/dynamic-mcp-tools: 42 suites passed. Five suites (xml-body, response-error.util, response-size-limit.engines, rest.engine.binary, rest.engine.redirect) failed under the same parallel load. Re-run alone: 5 suites, 52 tests passed.
  • npx eslint on the 7 changed TS files: clean.
  • npx tsc --noEmit -p tsconfig.json: clean.
  • node scripts/validate-adapters.mjs --warn: exit 0. Output is identical to origin/main (diffed), so there are no new warnings.
  • node scripts/regenerate-catalog.mjs: no diff to catalog.ts.
  • node scripts/adapter-count.mjs --check: OK.

Notes:

  • Installed connectors keep the endpointMapping they were installed with. The new values reach an existing Gemini or OpenAI connector only through a full catalog resync, which is a structural change.
  • responseMapping.images still caps each image at 4 MiB decoded by default. A 4K picture above that now arrives as a placeholder that says why, together with the text and usage, instead of a 10 MB error. Raising images.maxBytes (hard max 8 MiB) is left out on purpose, because of client-side image size limits.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation (if applicable)
  • All existing tests pass (npm test): backend catalog and engine specs listed above; full backend npm test not run
  • This PR has a descriptive title

Related Issues

Daily check 2026-11-01, cloud.md candidate 2

REST calls had a fixed 30 s timeout and the global 10 MB body cap, which
fail Gemini 2K/4K image generation after the picture is already billed:
the body carries the base64 image before responseMapping.images takes
it out, and generation can take longer than 30 s.

- endpointMapping.timeoutMs (clamped to 1-180 s, default 30 s) and
  endpointMapping.maxResponseBytes (at most 50 MB, default
  TOOL_MAX_RESPONSE_BYTES) for REST tools.
- A call that ran past a raised timeout is never retried.
- Gemini and OpenAI generation tools get 120 s; their image tools also
  get 40 MB for the inline base64 pictures.
- Document both keys in docs/tool-definition.md.
@helpcodeai
helpcodeai merged commit f718ccd into main Nov 1, 2026
1 check passed
@helpcodeai
helpcodeai deleted the fix/per-tool-timeout-size branch November 1, 2026 09:06
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 1, 2026
@helpcodeai
helpcodeai requested a review from D3nisty as a code owner November 1, 2026 09:47
@helpcodeai
helpcodeai enabled auto-merge (squash) November 1, 2026 09:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant