[N1a] Hosted provider tools: webSearch(), codeInterpreter(), fileSearch(), hostedTool() on OpenAI - #318
Merged
Conversation
…ch(), hostedTool() on OpenAI
Hosted tools go in createAgent({ tools }) next to local tools; createAgent()
separates them and AgentExecutor sends them with every model call
(ExecuteOptions/GenerateOptions.hostedTools). OpenAI maps them to
openai.tools.* on ai 6 (@ai-sdk/openai 3) and ai 7 (@ai-sdk/openai 4);
hostedTool() passes any AI SDK provider tool through on ai 6/7. Unsupported
pairings fail with LOUSHO_HOSTED_TOOL_UNSUPPORTED.
Provider-executed calls never become local tool calls: the compat layer maps
them (generate and stream) to HostedToolCall, events carry
executedBy: 'provider', the assistant message keeps metadata.hostedToolCalls,
usage counts them per tool name, the chat span names them, and a step with
only hosted calls is a final reply. mockModel scripts hosted turns; cassettes
keep them. Docs: new docs/hosted-tools.md.
Closes #211
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
9 tasks
# Conflicts: # CHANGELOG.md
A hosted tool runs inside the provider's request, so no permission mode can refuse a call. Plan mode now leaves codeInterpreter() and hostedTool() out of the model request (webSearch() and fileSearch() only read and stay); the mode is read before every model call. The other modes send every hosted tool. Docs for both features say what each mode does; README links docs/hosted-tools.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
This was referenced Oct 2, 2026
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.
Closes #211
What
Hosted provider tools:
webSearch(),codeInterpreter(),fileSearch()andhostedTool(name, aiSdkTool)(newsrc/tools/hosted.ts, exported from the root) go increateAgent({ tools })next to local tools; the provider runs them inside the request.createAgent()separates hosted tools (array and record form; in the record form the key must be the tool's name) beforeregisterTools();ExecuteOptions.hostedTools/GenerateOptions.hostedToolsare set on every model call; a name clash isLOUSHO_CONFIG_INVALIDworded like the ToolRegistry error. Sub-agents get only their own hosted tools; approval resumes keep them; the agent fingerprint includes them (drift on resume).LLMProvider.supportsHostedTool(type); AgentExecutor refuses before the first model call with newLOUSHO_HOSTED_TOOL_UNSUPPORTED(absent method = unsupported).withRetry/withFallback/ record-replay forward it.AiSdkProvider.hostedToolsFor()(protected): base =hostedTool()pass-through on ai 6/7, everything refused on ai 4;OpenAIProvidermaps toopenai.tools.webSearch()/.codeInterpreter()/.fileSearch()(searchContextSize,userLocationwithtype: 'approximate',filters.allowedDomains,container,vectorStoreIds,maxNumResults);maxUses/blockedDomainsdropped with one warning. An@ai-sdk/openaiwithout the factory isLOUSHO_HOSTED_TOOL_UNSUPPORTED.ai6 +@ai-sdk/openai3 orai7 +@ai-sdk/openai4;hostedTool()needsai6 or 7; nothing onai4.toolsunchanged;providerExecutedcalls are filtered fromtoolCalls, the stream, itstoolCallspromise andfinishChunks(); they becomeHostedToolCalls (GenerateResult.hostedToolCalls, fromresult.content; stream chunkshosted-tool-call/hosted-tool-result, the result held until the urlsourceparts after it arrive).tool.start/tool.done/tool.errorwith optionalexecutedBy: 'provider'(streamed as chunks arrive; after a non-streamed call in call order, before its text); no permission rule, guardrail, approval, hook oronToolCallruns. A step whose only calls the provider ran is a final reply even when the provider saystool-calls. Assistant messagemetadata.hostedToolCalls(JSON-safe, results capped at 20,000 chars of JSON, sources); not sent back to the provider.RunUsage.hostedToolCallsandrun.doneusage per tool name (delegated and remote usage roll up);costUsdstays tokens only.chatspan getslousho.hosted_tool_calls(noexecute_toolspan, so it cannot be mistaken for a tool the SDK ran);lousho tracesshows "provider ran web_search". The AI SDK UI stream marks the partsproviderExecuted: true.mockModelturns takehostedToolCalls(andMockHostedToolCall), requests recordhostedTools; cassettes keep hosted calls and chunks.Permission modes (#215), merged from main since this PR opened: a hosted call runs inside the provider's request, so no mode can refuse it per call. Instead the mode decides which hosted tools a model call sends (
hostedToolsInMode()insrc/execution/permissions.ts, applied inprepareGenerateRequest(), the mode read before every call):'plan'sends onlywebSearch()andfileSearch()(they read) and leavescodeInterpreter()(runs code) and everyhostedTool()(unknown) out of the request;'default','acceptEdits'and'dontAsk'send them all (a hosted call never asks). Nopermission.decisionentry for a tool left out (no call was made). Tests insrc/execution/hostedToolCalls.test.ts: the filter, plan mode end to end (read-only only; code interpreter alone sends nohostedTools), each other mode sends all,session.setPermissionMode()across turns, and a function mode switched mid-run.Docs (for the docs site / G9)
docs/hosted-tools.md("Hosted provider tools"), headings: The helpers; Which provider runs which; Any AI SDK provider tool: hostedTool(); Events; Transcript and replay; Usage and cost; Approvals and permissions; Testing; Not covered yet.docs/errors.md: new### LOUSHO_HOSTED_TOOL_UNSUPPORTEDunder "Providers and peers" (plus its table row entry).docs/tools.md(one link line),docs/models-and-cost.md(one bullet),docs/permission-modes.md(one bullet under "Which tools are read-only" on hosted tools).docs/hosted-tools.md"Approvals and permissions" has a per-mode table.README.mddocs table: one row for Hosted tools.[Unreleased]/ Added;llms.txt/llms-full.txtregenerated.Verification (merged head 59e2a12, Windows, Node 26; head 454d4a8 only adds the #320 CHANGELOG split, after which
docs:llms:checkwas re-run: ok)npx tsc --noEmit: ok ·npm run lint: ok (0 warnings) ·npm run buildand create-lousho-agent build: ok ·npm run test:types: oknpm run docs:verify-snippets -- --skip-build: all 236 snippets type-check, 8 run ·npm run docs:llms:check: oknpm run test:coverage -- --coverage.reportOnFailure: 258 files / 3866 tests passed, 1 failed =http.test.ts"rejects near the configured timeout" (known load flake; alone: 23/23 passed)npm run fallow: exit 0, no issues, 0 above thresholdnpm run pack-smoke: all checks passed (main raised the unpacked-size budget to 16 MiB in [N4] Permission modes: plan, acceptEdits and dontAsk, switchable mid-session #315; pack-smoke: unpacked size budget (14 MiB) is nearly exhausted on main; N1a exceeds it #317 closed in favour of pack-smoke: the tarball size cap is reached (main at 14.63 of 14.68 MB) #316)npm install --no-saveper ci.yml, thennpm cito restore; lockfile unchanged): ai 6.0.300 + @ai-sdk/openai 3.0.124 + anthropic 3: tsc ok,vitest run src/providers src/execution1040 passed / 18 skipped. ai 7.0.127 + @ai-sdk/openai 4.0.83 + anthropic 4: tsc ok, test:types ok, same tests 1042 passed / 16 skipped. (Earlier run before the permission-mode merge also exercised the real@ai-sdk/openaifactories; see history.)Live test: none (ticket says none; no model calls made). Spend: 0.
Follow-up for N1b: cassette request fingerprints do not include
hostedTools(a replay does not notice a changed hosted tool set).🤖 Generated with Claude Code