Skip to content

[N1a] Hosted provider tools: webSearch(), codeInterpreter(), fileSearch(), hostedTool() on OpenAI - #318

Merged
LinuxDevil merged 5 commits into
mainfrom
lou-n1a-hosted-tools
Oct 2, 2026
Merged

LinuxDevil merged 5 commits into
mainfrom
lou-n1a-hosted-tools

Conversation

@LinuxDevil

@LinuxDevil LinuxDevil commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Closes #211

What

Hosted provider tools: webSearch(), codeInterpreter(), fileSearch() and hostedTool(name, aiSdkTool) (new src/tools/hosted.ts, exported from the root) go in createAgent({ tools }) next to local tools; the provider runs them inside the request.

  • Plumbing: createAgent() separates hosted tools (array and record form; in the record form the key must be the tool's name) before registerTools(); ExecuteOptions.hostedTools / GenerateOptions.hostedTools are set on every model call; a name clash is LOUSHO_CONFIG_INVALID worded like the ToolRegistry error. Sub-agents get only their own hosted tools; approval resumes keep them; the agent fingerprint includes them (drift on resume).
  • Support check: new optional LLMProvider.supportsHostedTool(type); AgentExecutor refuses before the first model call with new LOUSHO_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; OpenAIProvider maps to openai.tools.webSearch() / .codeInterpreter() / .fileSearch() (searchContextSize, userLocation with type: 'approximate', filters.allowedDomains, container, vectorStoreIds, maxNumResults); maxUses / blockedDomains dropped with one warning. An @ai-sdk/openai without the factory is LOUSHO_HOSTED_TOOL_UNSUPPORTED.
  • Versions: OpenAI hosted tools need ai 6 + @ai-sdk/openai 3 or ai 7 + @ai-sdk/openai 4; hostedTool() needs ai 6 or 7; nothing on ai 4.
  • Compat layer: provider tool objects merged into tools unchanged; providerExecuted calls are filtered from toolCalls, the stream, its toolCalls promise and finishChunks(); they become HostedToolCalls (GenerateResult.hostedToolCalls, from result.content; stream chunks hosted-tool-call / hosted-tool-result, the result held until the url source parts after it arrive).
  • Executor and events: tool.start / tool.done / tool.error with optional executedBy: 'provider' (streamed as chunks arrive; after a non-streamed call in call order, before its text); no permission rule, guardrail, approval, hook or onToolCall runs. A step whose only calls the provider ran is a final reply even when the provider says tool-calls. Assistant message metadata.hostedToolCalls (JSON-safe, results capped at 20,000 chars of JSON, sources); not sent back to the provider.
  • Usage: RunUsage.hostedToolCalls and run.done usage per tool name (delegated and remote usage roll up); costUsd stays tokens only.
  • Traces: the chat span gets lousho.hosted_tool_calls (no execute_tool span, so it cannot be mistaken for a tool the SDK ran); lousho traces shows "provider ran web_search". The AI SDK UI stream marks the parts providerExecuted: true.
  • Testing: mockModel turns take hostedToolCalls (and MockHostedToolCall), requests record hostedTools; 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() in src/execution/permissions.ts, applied in prepareGenerateRequest(), the mode read before every call): 'plan' sends only webSearch() and fileSearch() (they read) and leaves codeInterpreter() (runs code) and every hostedTool() (unknown) out of the request; 'default', 'acceptEdits' and 'dontAsk' send them all (a hosted call never asks). No permission.decision entry for a tool left out (no call was made). Tests in src/execution/hostedToolCalls.test.ts: the filter, plan mode end to end (read-only only; code interpreter alone sends no hostedTools), each other mode sends all, session.setPermissionMode() across turns, and a function mode switched mid-run.

Docs (for the docs site / G9)

  • New page 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_UNSUPPORTED under "Providers and peers" (plus its table row entry).
  • Edited without heading changes: 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.md docs table: one row for Hosted tools.
  • CHANGELOG [Unreleased] / Added; llms.txt / llms-full.txt regenerated.

Verification (merged head 59e2a12, Windows, Node 26; head 454d4a8 only adds the #320 CHANGELOG split, after which docs:llms:check was re-run: ok)

  • npx tsc --noEmit: ok · npm run lint: ok (0 warnings) · npm run build and create-lousho-agent build: ok · npm run test:types: ok
  • npm run docs:verify-snippets -- --skip-build: all 236 snippets type-check, 8 run · npm run docs:llms:check: ok
  • npm 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 threshold
  • Agent Forge: typecheck ok, typecheck:server ok, tests 119 passed, server tests 133 passed
  • npm 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)
  • Peer matrix locally (npm install --no-save per ci.yml, then npm ci to restore; lockfile unchanged): ai 6.0.300 + @ai-sdk/openai 3.0.124 + anthropic 3: tsc ok, vitest run src/providers src/execution 1040 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/openai factories; 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

LinuxDevil and others added 2 commits October 2, 2026 22:11
…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>
LinuxDevil and others added 3 commits October 2, 2026 23:43
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>
@LinuxDevil
LinuxDevil merged commit 8458b4d into main Oct 2, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[N1a] Hosted provider tools: webSearch(), codeInterpreter(), fileSearch() on OpenAI

1 participant