Delegate research upgrade - #26
Merged
Merged
Conversation
callGenerateContentOnce had no timeout at all -- a hung/dropped connection (as opposed to a clean 429/503 HTTP status) would just leave the caller waiting indefinitely with no error to react to. Add an AbortController timeout and mark network-level failures (abort, fetch TypeError) as `transient` so the existing model-cascade + resumable-checkpoint logic in delegate.js treats them the same way it already treats 429/503, instead of only recognizing HTTP status codes as retryable.
Mirrors the existing 429/503 cascade behavior for the new `err.transient` flag from the timeout/network-error handling above -- a dropped connection to model A should try model B same as a 503 would, and should NOT record a cooldown (no per-model quota signal in a network failure, same reasoning already applied to 503).
isTransientGeminiError gates the resumable/resume_run_id messaging shown to the caller on a failed step -- without this, a timeout or dropped connection (now marked err.transient in client.js) would tell the caller "this doesn't look transient, resuming will likely reproduce the same failure," which is backwards for exactly the kind of blip that motivated the checkpoint system in the first place.
Closes gap #1 (no web access in the investigation loop) WITHOUT adding a new MCP-facing tool: web_fetch reuses the exact same fetchUrl/htmlToText helpers the standalone web_fetch and Delegate_web_fetch tools already use, just exposed as one more function in Gemini's own tool-calling loop instead of a separate server.tool(). Google Search grounding is added as a native Gemini tool (not a function -- Gemini calls it internally, no execute() needed on our side) so the loop can actually discover URLs/facts it doesn't already have, not just fetch a URL it was handed. Combined with a function- declarations tool in the same request is a newer Gemini capability ("multi-tool use") that may not be supported on every model in the fallback cascade -- see the fallback-on-error handling below for why this degrades instead of hard-failing if a given model rejects the combination.
The execute() here is intentionally the same shape as the standalone web_fetch tool and Delegate_web_fetch (fetchUrl + conditional htmlToText), just truncated tighter for server-side loop context (WEB_FETCH_MAX_CHARS) since this text is consumed by Gemini's next turn, not returned to the calling model.
…unctions web_fetch (previous commit) only reads a URL the loop already has -- it can't discover one. Google Search grounding is a built-in Gemini tool (no execute() -- Gemini runs it itself and returns grounded text), so adding it here is what actually closes "no general web search" rather than just "can re-fetch a known page inside the loop too." Combining a grounding tool with custom functionDeclarations in one request is real but newer Gemini behavior, not guaranteed across every model in the fallback cascade. TOOLS_WITH_SEARCH is tried first each run; searchToolDisabledThisRun latches off after the first same-step 400 suggesting the combination isn't accepted, so the rest of the run doesn't keep re-paying for a doomed retry every single step.
…cation Two additions: (1) tell the model web_fetch/search exist now, since an unchanged preamble would leave a model that's seen prior GitHub/Notion/CF- only framing less likely to reach for them; (2) explicit instruction to actively cross-check claims between sources rather than reporting each source's status independently -- addresses gap #3 (no cross-source synthesis), which came up concretely in practice: a GitHub PR looking "open" and unclaimed was contradicted by an external tracking spreadsheet showing the same bounty already paid out elsewhere, and nothing prompted checking for that kind of contradiction before this.
Deliberately NOT persisted in the checkpoint (unlike repeatCounts/ consecutiveAllRepeatSteps) -- it's a same-run fast-fail heuristic, not correctness-affecting state; worst case a resumed run pays for one extra same-step retry before re-latching, which is cheap and simpler than adding another checkpoint field for it.
…jection The withholdTools branches (final step, stuck-loop force) are unaffected -- they already pass undefined regardless of search. Only the normal case (tools enabled) now tries TOOLS_WITH_SEARCH first and drops to FUNCTION_DECLARATIONS-only, in the SAME step, if the model rejects the combination with a 400 -- avoiding burning a whole failed-step/checkpoint cycle over what's a request-shape mismatch, not a real failure.
This description is what the CALLING model (Claude) reads to decide when to use this tool -- distinct from delegate.js's FUNCTIONS descriptions, which only Gemini's own internal loop sees (see delegate.js's file header for why those two are never edited as if they were the same text). Without this update, Claude would have no way to know the tool now does live web research + explicit cross-source verification, and would keep reaching for manual web_search + delegate_gemini combinations instead of one call.
Every other connector's tunables (GITHUB_MIN_REQUEST_INTERVAL_MS, NOTION_MAX_RETRIES, MEM0_RETRY_BASE_MS, etc.) live in config.js as named exports, not inline in their client.js -- this had been added directly in gemini/client.js in the prior commit, which broke that pattern. Moving it here for anyone scanning config.js for the full list of overridable knobs.
…tead of defining locally
…tions when combining google_search with function declarations); update comments to reflect delegate_research rename/split
…PREAMBLE to drop web references and explain the security boundary with research.js
…y (no search-tool combination/fallback needed anymore)
…-step loop backing delegate_research's wide mode. No access to GitHub/Notion/Cloudflare/Context7/Mem0 -- see file header for the security rationale.
…de schema (precision url+question / wide task), wire in research.js's runResearch for wide mode; update delegate_gemini's description to drop web-access claims (delegate.js no longer has web access)
…rch dual-mode tool (precision url+question path unchanged; wide task path delegates to research.js's runResearch, with mutual-exclusivity validation)
…er has web access (moved to research.js/delegate_research), so drop the "AND the live web" / web_fetch+Google Search claims and point callers at delegate_research for web tasks
… and reflect that delegate_gemini is now internal-systems-only (GitHub/Notion/Cloudflare) while delegate_research covers web (precision + wide research mode)
…delegate_research, and note it's now shared by both precision mode (tools.js) and wide mode (research.js)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.